fix(ai): add inline proposal confirmation buttons
* fix(ai): add quick proposal confirmation buttons (all locales) When the assistant proposes an action, render Yes/No buttons below the message so the user does not need to type a short reply — fixing the issue where short replies caused the LLM to re-ask instead of executing. - aiProposalDetect.ts: pure detectors for proposal sentences/short replies - AiAssistant.vue: inline Yes/No buttons on last assistant proposal message - ai.ts: system-prompt rule to skip re-confirmation on short affirmatives - i18n: 3 keys added to all 7 locales (en/zh-CN/zh-TW/ja/es/it/pt-BR) Tests: 55/55. TypeCheck: passed. * fix(ai): add i18n keys and UI changes for proposal confirmation
This commit is contained in:
1 parent
21ec91a3d5
commit
bbd3ccf864
11 files changed
+375
No files matched your search
@@ -35,6 +35,7 @@ import { parseExplainResult, type ParsedExplainPlan } from "@/lib/explainPlan";
|
||||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { formatAiTableMention, parseAiTableMentions, type AiTableMention } from "@/lib/aiTableMentions";
|
||||
import { isAiPromptImeCompositionEvent, shouldSubmitAiPromptOnKeydown } from "@/lib/aiPromptKeyboard";
|
||||
import { looksLikeActionProposal, containsChinese } from "@/lib/aiProposalDetect";
|
||||
|
||||
const { t } = useI18n();
|
||||
const settings = useSettingsStore();
|
||||
@@ -221,6 +222,37 @@ const isWaitingForFirstDelta = computed(() => {
|
||||
return isGenerating.value && last?.role === "assistant" && !last.content && !last.reasoning;
|
||||
});
|
||||
|
||||
/**
|
||||
* The last assistant message whose final line looks like an action
|
||||
* proposal question. Used to render an inline "Yes / No" confirmation bar
|
||||
* so the user can answer without typing. `null` while the assistant is
|
||||
* still generating or when no such message exists.
|
||||
*/
|
||||
const proposalConfirmMessage = computed<ChatMessage | null>(() => {
|
||||
if (isGenerating.value) return null;
|
||||
for (let i = messages.value.length - 1; i >= 0; i--) {
|
||||
const msg = messages.value[i];
|
||||
if (msg.kind === "contextSummary") continue;
|
||||
if (msg.role !== "assistant") return null;
|
||||
if (!msg.content) return null;
|
||||
return looksLikeActionProposal(msg.content) ? msg : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
function sendProposalReply(positive: boolean) {
|
||||
// Disable while a stream is in flight or no proposal is currently active.
|
||||
if (isGenerating.value) return;
|
||||
const target = proposalConfirmMessage.value;
|
||||
if (!target) return;
|
||||
const isZh = containsChinese(target.content || "");
|
||||
const replyZh = positive ? "请执行上面你刚提议的操作,不要再反问确认。" : "不用执行上面提到的操作,继续当前对话。";
|
||||
const replyEn = positive ? "Execute the action you just proposed above; do not ask for confirmation again." : "Do not execute the action mentioned above; continue the current conversation.";
|
||||
prompt.value = isZh ? replyZh : replyEn;
|
||||
// Use the existing send pipeline so the message is added to history, persisted, etc.
|
||||
send();
|
||||
}
|
||||
|
||||
const activePlaceholder = computed(() => `${t(`ai.placeholders.${activeAction.value}`)} ${t("ai.tableMentionPlaceholderHint")}`);
|
||||
const activeModeHint = computed(() => t(`ai.modeHints.${assistantMode.value}`));
|
||||
const assistantModeItems = computed(() => [
|
||||
@@ -1106,6 +1138,16 @@ const messageRenderer = computed(() => {
|
||||
<pre class="ai-code-block whitespace-pre-wrap break-words p-3 text-xs leading-relaxed text-zinc-900 dark:text-zinc-100"><code v-html="seg.html"></code></pre>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="msg === proposalConfirmMessage" class="mt-2 flex gap-2" :title="t('ai.proposalConfirmTitle')">
|
||||
<Button size="sm" variant="default" class="h-7 gap-1 text-[11px]" @click="sendProposalReply(true)">
|
||||
<Check class="h-3 w-3" />
|
||||
{{ t("ai.proposalConfirmYes") }}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" class="h-7 gap-1 text-[11px]" @click="sendProposalReply(false)">
|
||||
<X class="h-3 w-3" />
|
||||
{{ t("ai.proposalConfirmNo") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in new issue
Block a user