From 613c2f981ff281726c4640f8c7b2c7f6dbc8b8b9 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Tue, 19 May 2026 15:56:34 +0800 Subject: [PATCH] fix(ai): ignore IME composition enter --- .../src/components/editor/AiAssistant.vue | 8 ++++- apps/desktop/src/lib/aiPromptKeyboard.ts | 17 ++++++++++ packages/app-tests/aiPromptKeyboard.test.ts | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 apps/desktop/src/lib/aiPromptKeyboard.ts create mode 100644 packages/app-tests/aiPromptKeyboard.test.ts diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue index bb1de9b38..10ccfa65e 100644 --- a/apps/desktop/src/components/editor/AiAssistant.vue +++ b/apps/desktop/src/components/editor/AiAssistant.vue @@ -66,6 +66,7 @@ import { useDatabaseOptions } from "@/composables/useDatabaseOptions"; import { resolveDefaultDatabase } from "@/lib/defaultDatabase"; import { isSchemaAware } from "@/lib/databaseCapabilities"; import { formatAiTableMention, parseAiTableMentions, type AiTableMention } from "@/lib/aiTableMentions"; +import { isAiPromptImeCompositionEvent, shouldSubmitAiPromptOnKeydown } from "@/lib/aiPromptKeyboard"; const { t } = useI18n(); const settings = useSettingsStore(); @@ -104,6 +105,7 @@ const conversationId = ref(""); const conversations = ref([]); const showConversationList = ref(false); const promptTextareaRef = ref(null); +const promptCompositionActive = ref(false); interface AiMentionCandidate { schema?: string; @@ -430,6 +432,8 @@ function insertMention(candidate: AiMentionCandidate) { } function onPromptKeydown(event: KeyboardEvent) { + if (isAiPromptImeCompositionEvent(event, promptCompositionActive.value)) return; + if (mentionOpen.value) { if (event.key === "ArrowDown") { event.preventDefault(); @@ -456,7 +460,7 @@ function onPromptKeydown(event: KeyboardEvent) { } } - if (event.key === "Enter" && !event.shiftKey && !event.isComposing) { + if (shouldSubmitAiPromptOnKeydown(event, promptCompositionActive.value)) { event.preventDefault(); send(); } @@ -916,6 +920,8 @@ const messageRenderer = createAiMessageRenderer({ markdown: formatInlineText }); @input="refreshMentionState" @click="refreshMentionState" @keyup="refreshMentionState" + @compositionstart="promptCompositionActive = true" + @compositionend="promptCompositionActive = false" @keydown="onPromptKeydown" />
diff --git a/apps/desktop/src/lib/aiPromptKeyboard.ts b/apps/desktop/src/lib/aiPromptKeyboard.ts new file mode 100644 index 000000000..8c3bf2ee1 --- /dev/null +++ b/apps/desktop/src/lib/aiPromptKeyboard.ts @@ -0,0 +1,17 @@ +export interface AiPromptKeydownLikeEvent { + key: string; + shiftKey?: boolean; + isComposing?: boolean; + keyCode?: number; +} + +export function isAiPromptImeCompositionEvent(event: AiPromptKeydownLikeEvent, compositionActive = false): boolean { + return compositionActive || !!event.isComposing || event.keyCode === 229 || event.key === "Process"; +} + +export function shouldSubmitAiPromptOnKeydown(event: AiPromptKeydownLikeEvent, compositionActive = false): boolean { + if (event.key !== "Enter") return false; + if (event.shiftKey) return false; + if (isAiPromptImeCompositionEvent(event, compositionActive)) return false; + return true; +} diff --git a/packages/app-tests/aiPromptKeyboard.test.ts b/packages/app-tests/aiPromptKeyboard.test.ts new file mode 100644 index 000000000..9ac516246 --- /dev/null +++ b/packages/app-tests/aiPromptKeyboard.test.ts @@ -0,0 +1,31 @@ +import { strict as assert } from "node:assert"; +import test from "node:test"; +import { + isAiPromptImeCompositionEvent, + shouldSubmitAiPromptOnKeydown, +} from "../../apps/desktop/src/lib/aiPromptKeyboard.ts"; + +test("submits AI prompt on plain Enter", () => { + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Enter" }), true); +}); + +test("does not submit AI prompt while entering a newline", () => { + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Enter", shiftKey: true }), false); +}); + +test("does not submit AI prompt while IME composition is active", () => { + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Enter", isComposing: true }), false); + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Enter" }, true), false); +}); + +test("does not submit AI prompt for IME composition key events", () => { + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Enter", keyCode: 229 }), false); + assert.equal(shouldSubmitAiPromptOnKeydown({ key: "Process" }), false); +}); + +test("detects AI prompt IME composition key events before mention handling", () => { + assert.equal(isAiPromptImeCompositionEvent({ key: "Enter", isComposing: true }), true); + assert.equal(isAiPromptImeCompositionEvent({ key: "Enter", keyCode: 229 }), true); + assert.equal(isAiPromptImeCompositionEvent({ key: "Process" }), true); + assert.equal(isAiPromptImeCompositionEvent({ key: "Enter" }), false); +});