feat(ask-user): 弹窗新增「不回答」选项

- 弹窗 footer 新增「✕ 不回答」按钮,只跳过当前查看的问题,
  其余问题保持待回答
- 后端 answer() 支持 dismissed 类型答案,跳过非空校验
- 模型侧工具结果返回「用户没有回答或不想回答这个问题,
  请直接输出内容向用户提问」,引导模型改为在对话中提问
This commit is contained in:
JOJO 2026-08-07 14:31:05 +08:00
parent 1cd3a742af
commit 045cc72c43
5 changed files with 42 additions and 2 deletions

View File

@ -105,10 +105,11 @@ class UserQuestionManager:
username: str,
selected_option_id: Optional[str] = None,
text: Optional[str] = None,
dismissed: bool = False,
) -> Dict[str, Any]:
clean_option_id = str(selected_option_id or "").strip()
clean_text = str(text or "").strip()
if not clean_option_id and not clean_text:
if not dismissed and not clean_option_id and not clean_text:
raise ValueError("回答不能为空")
with self._lock:
item = self._items.get(question_id)
@ -119,6 +120,13 @@ class UserQuestionManager:
if item.get("status") != "pending":
return dict(item)
# 用户主动选择不回答:标记为 dismissed工具结果会提示模型改为在对话中提问
if dismissed:
item["status"] = "answered"
item["answered_at"] = time.time()
item["answer"] = {"type": "dismissed", "text": ""}
return dict(item)
selected_option = None
if clean_option_id:
for opt in item.get("options") or []:
@ -152,6 +160,8 @@ def format_user_question_answer(item: Dict[str, Any]) -> str:
answer = item.get("answer") if isinstance(item, dict) else None
if not isinstance(answer, dict):
return "用户未回答。"
if answer.get("type") == "dismissed":
return "用户没有回答或不想回答这个问题,请直接输出内容向用户提问"
lines: List[str] = []
label = str(answer.get("selected_option_label") or "").strip()
text = str(answer.get("text") or "").strip()

View File

@ -72,6 +72,7 @@ def answer_user_question(terminal: WebTerminal, workspace: UserWorkspace, userna
username=username,
selected_option_id=data.get("selected_option_id"),
text=data.get("text"),
dismissed=bool(data.get("dismissed")),
)
except ValueError as exc:
return jsonify({"success": False, "error": str(exc)}), 400

View File

@ -520,6 +520,7 @@
@minimize="minimizeUserQuestionDialog"
@update:active-index="userQuestionActiveIndex = $event"
@submit="submitUserQuestionAnswers"
@dismiss="dismissUserQuestions"
/>
<transition name="overlay-fade">
<VersioningDialog

View File

@ -144,7 +144,8 @@ export const dialogMethods = {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
selected_option_id: answer?.selected_option_id || undefined,
text: answer?.text || ''
text: answer?.text || '',
dismissed: answer?.dismissed === true
})
});
const payload = await response.json().catch(() => ({}));
@ -172,6 +173,15 @@ export const dialogMethods = {
this.answeringUserQuestionIds = [];
}
},
// 用户点击「不回答」:只将当前查看的这个问题标记为 dismissed其余问题保持待回答。
// 模型侧会收到「用户没有回答或不想回答」的工具结果并改为在对话中直接提问。
async dismissUserQuestions(questionId) {
const id = String(questionId || '').trim();
if (!id) {
return;
}
await this.submitUserQuestionAnswers([{ question_id: id, dismissed: true }]);
},
async answerUserQuestionFromComposer(text) {
const clean = String(text || '').trim();
if (!clean || !Array.isArray(this.pendingUserQuestions) || !this.pendingUserQuestions.length) {

View File

@ -57,6 +57,15 @@
</div>
<footer class="user-question-footer">
<button
type="button"
class="user-question-btn ghost"
:disabled="submitting"
title="不回答当前这个问题AI 将改为在对话中直接提问"
@click="dismiss"
>
不回答
</button>
<div class="user-question-spacer"></div>
<div class="user-question-actions">
<button type="button" class="user-question-btn primary" :disabled="!canSubmit || submitting" @click="submit">
@ -83,6 +92,7 @@ const emit = defineEmits<{
(event: 'minimize'): void;
(event: 'update:active-index', value: number): void;
(event: 'submit', answers: Array<any>): void;
(event: 'dismiss', questionId: string): void;
}>();
const drafts = reactive<Record<string, { selected_option_id: string; text: string }>>({});
@ -142,6 +152,14 @@ function submit() {
});
emit('submit', answers);
}
//
function dismiss() {
if (submitting.value) return;
const questionId = String(currentQuestion.value?.question_id || '').trim();
if (!questionId) return;
emit('dismiss', questionId);
}
</script>
<style scoped>