diff --git a/ai_knowledge_base_v2/apps/user-client/package-lock.json b/ai_knowledge_base_v2/apps/user-client/package-lock.json index daa1c77..05c4a63 100644 --- a/ai_knowledge_base_v2/apps/user-client/package-lock.json +++ b/ai_knowledge_base_v2/apps/user-client/package-lock.json @@ -8,7 +8,9 @@ "name": "ai-kb-user-client", "version": "0.1.0", "dependencies": { + "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^6.0.1", + "markdown-it": "^14.3.0", "vite": "^7.1.0", "vue": "^3.5.0" }, @@ -822,6 +824,28 @@ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.7", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", @@ -990,6 +1014,12 @@ "dev": true, "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -1086,6 +1116,25 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1095,6 +1144,51 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/muggle-string": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", @@ -1173,6 +1267,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", @@ -1256,6 +1359,12 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/vite": { "version": "7.3.6", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", diff --git a/ai_knowledge_base_v2/apps/user-client/package.json b/ai_knowledge_base_v2/apps/user-client/package.json index aebffdc..ac54eb3 100644 --- a/ai_knowledge_base_v2/apps/user-client/package.json +++ b/ai_knowledge_base_v2/apps/user-client/package.json @@ -9,7 +9,9 @@ "preview": "vite preview --host 127.0.0.1" }, "dependencies": { + "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^6.0.1", + "markdown-it": "^14.3.0", "vite": "^7.1.0", "vue": "^3.5.0" }, diff --git a/ai_knowledge_base_v2/apps/user-client/src/App.vue b/ai_knowledge_base_v2/apps/user-client/src/App.vue index ab29c0d..0904510 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/App.vue +++ b/ai_knowledge_base_v2/apps/user-client/src/App.vue @@ -23,6 +23,7 @@ const loading = ref(false); const booting = ref(true); const statusText = ref("连接后端中"); const bottomRef = ref(null); +const activeAbortController = ref(null); onMounted(async () => { if (!getToken()) { @@ -82,28 +83,53 @@ async function send(message: string) { const assistantMessage: UiMessage = { id: `assistant-${Date.now()}`, role: "assistant", content: "", streaming: true }; messages.value.push(userMessage, assistantMessage); loading.value = true; + activeAbortController.value = new AbortController(); await scrollToBottom(); try { await streamChat(activeSessionId.value, message, async (chunk) => { assistantMessage.content += chunk; await scrollToBottom(); - }); + }, activeAbortController.value.signal); assistantMessage.streaming = false; await refreshSessions(activeSessionId.value); + user.value = await api.profile(); } catch (error) { assistantMessage.streaming = false; - assistantMessage.content = error instanceof Error ? error.message : "AI 回复失败"; + if (error instanceof DOMException && error.name === "AbortError") { + assistantMessage.content = assistantMessage.content || "已停止生成"; + } else { + assistantMessage.content = error instanceof Error ? error.message : "AI 回复失败"; + } } finally { loading.value = false; + activeAbortController.value = null; } } async function stop() { if (!activeSessionId.value) return; + activeAbortController.value?.abort(); await api.stop(activeSessionId.value); loading.value = false; } +async function renameSession(sessionId: number, title: string) { + await api.renameSession(sessionId, title); + await refreshSessions(sessionId); +} + +async function deleteSession(sessionId: number) { + await api.deleteSession(sessionId); + sessions.value = await api.listSessions(); + if (activeSessionId.value === sessionId) { + messages.value = []; + activeSessionId.value = null; + if (sessions.value.length > 0) { + await selectSession(sessions.value[0].id); + } + } +} + async function logout() { try { await api.logout(); @@ -175,6 +201,8 @@ async function scrollToBottom() { @close="drawerOpen = false" @select="selectSession" @create="createSession" + @rename="renameSession" + @delete="deleteSession" /> diff --git a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue index a1ecdae..ce3bb27 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue +++ b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue @@ -1,16 +1,31 @@