From 578682abfa42debc21d5371bc5099cc2fffdea2a Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Wed, 15 Oct 2025 02:37:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=88=90=E6=9C=AC=E8=AE=A1=E7=AE=97=EF=BC=9B?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AF=B9=E8=AF=9D=E7=8A=B6=E6=80=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=B6=88=E6=81=AF=E6=B5=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=9B=B4=E6=B5=81=E7=95=85=EF=BC=9B=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=A0=B7=E5=BC=8F=E4=BB=A5=E6=8F=90=E5=8D=87=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routers/chat_router.py | 2 + web/src/assets/css/base.css | 58 +++++++++++----------- web/src/components/AgentChatComponent.vue | 60 ++++++++++++++++++++--- web/src/components/RefsComponent.vue | 54 ++++---------------- 4 files changed, 92 insertions(+), 82 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 9954459a..fe5f2c35 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -123,6 +123,7 @@ async def chat_agent( db: Session = Depends(get_db), ): """使用特定智能体进行对话(需要登录)""" + start_time = asyncio.get_event_loop().time() logger.info(f"agent_id: {agent_id}, query: {query}, config: {config}, meta: {meta}") @@ -319,6 +320,7 @@ async def chat_agent( yield make_chunk(message="检测到敏感内容,已中断输出", status="error") return + meta["time_cost"] = asyncio.get_event_loop().time() - start_time yield make_chunk(status="finished", meta=meta) # After streaming finished, save all messages from LangGraph state diff --git a/web/src/assets/css/base.css b/web/src/assets/css/base.css index 71476518..6b8d77fd 100644 --- a/web/src/assets/css/base.css +++ b/web/src/assets/css/base.css @@ -2,20 +2,20 @@ /* color palette from */ /* https://material-foundation.github.io/material-theme-builder/ */ :root { - --main-1000: #01151f; /* 更深沉的深蓝黑 */ - --main-900: #023944; /* 深海蓝 */ - --main-800: #035065; /* 深蓝绿 */ - --main-700: #046a82; /* 增加明度的蓝青色 */ - --main-600: #24839a; /* 冷色调中略带灰 */ - --main-500: #3996ae; /* 平衡且主色调友好 */ - --main-400: #5faec2; /* 更加柔和的中亮蓝 */ - --main-300: #82c3d6; /* 明亮通透,视觉清爽 */ - --main-200: #a3d8e8; /* 比原先更清澈淡蓝 */ - --main-100: #c4eaf5; /* 接近原色,但稍微柔和 */ - --main-50: #e1f6fb; /* 极浅天蓝,适合背景过渡 */ - --main-40: #eaf3f5; /* 淡灰蓝调 */ - --main-30: #f2fbfd; /* 几近白的蓝调提示 */ - --main-20: #f5f9fa; /* 比原配色更均匀些 */ + --main-1000: #01151f; + --main-900: #023944; + --main-800: #035065; + --main-700: #046a82; + --main-600: #24839a; + --main-500: #3996ae; + --main-400: #5faec2; + --main-300: #82c3d6; + --main-200: #a3d8e8; + --main-100: #c4eaf5; + --main-50: #e1f6fb; + --main-40: #eaf3f5; + --main-30: #f7fbfd; + --main-20: #f6f9fa; --main-10: #fafcfd; --main-5: #fcfefe; --main-1: #fefefe; @@ -23,21 +23,21 @@ --gray-10000: #000000; /* 保持纯黑 */ - --gray-2000: #0d0d0d; /* 更精致的暗灰,用于极深背景 */ - --gray-1000: #161616; /* 深灰黑,适用于主文本 */ - --gray-900: #1f1f1f; - --gray-800: #333333; /* 标准深灰,适合背景 */ - --gray-700: #4d4d4d; - --gray-600: #707070; /* 调和中灰,适合次要文本 */ - --gray-500: #999999; /* 更柔和的中灰色 */ - --gray-400: #bfbfbf; - --gray-300: #d9d9d9; /* 修正了原300/200重复 */ - --gray-200: #e6e6e6; /* 稍微加深一点,便于分层 */ - --gray-150: #f0f0f0; - --gray-100: #f2f2f2; - --gray-50: #f7f7f7; - --gray-25: #fafafa; - --gray-10: #fcfcfc; + --gray-2000: #0c0d0d; /* 更精致的暗灰,用于极深背景 */ + --gray-1000: #151616; /* 深灰黑,适用于主文本 */ + --gray-900: #1e1f1f; + --gray-800: #323333; /* 标准深灰,适合背景 */ + --gray-700: #4c4d4d; + --gray-600: #697070; /* 调和中灰,适合次要文本 */ + --gray-500: #979999; /* 更柔和的中灰色 */ + --gray-400: #bdbfbf; + --gray-300: #d7d9d9; /* 修正了原300/200重复 */ + --gray-200: #e4e6e6; /* 稍微加深一点,便于分层 */ + --gray-150: #eef0f0; + --gray-100: #eff2f2; + --gray-50: #f5f7f7; + --gray-25: #f8fafa; + --gray-10: #fafcfc; --gray-0: #ffffff; --main-color: #016179; diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 6622f2c9..df714f76 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -244,13 +244,26 @@ const onGoingConvMessages = computed(() => { const conversations = computed(() => { const historyConvs = MessageProcessor.convertServerHistoryToMessages(currentThreadMessages.value); - if (onGoingConvMessages.value.length > 0) { + const threadState = currentThreadState.value; + + // 如果有进行中的消息且线程状态显示正在流式处理,添加进行中的对话 + if (onGoingConvMessages.value.length > 0 && threadState?.isStreaming) { const onGoingConv = { messages: onGoingConvMessages.value, status: 'streaming' }; return [...historyConvs, onGoingConv]; } + + // 即使流式结束,如果历史记录为空但还有消息没有完全同步,也保持显示 + if (historyConvs.length === 0 && onGoingConvMessages.value.length > 0 && !threadState?.isStreaming) { + const finalConv = { + messages: onGoingConvMessages.value, + status: 'finished' + }; + return [finalConv]; + } + return historyConvs; }); @@ -322,7 +335,7 @@ const cleanupThreadState = (threadId) => { }; // ==================== STREAM HANDLING LOGIC ==================== -const resetOnGoingConv = (threadId = null) => { +const resetOnGoingConv = (threadId = null, preserveMessages = false) => { if (threadId) { // 清理指定线程的状态 const threadState = getThreadState(threadId); @@ -331,7 +344,17 @@ const resetOnGoingConv = (threadId = null) => { threadState.streamAbortController.abort(); threadState.streamAbortController = null; } - threadState.onGoingConv = { msgChunks: {} }; + // 如果指定要保留消息,则延迟清空 + if (preserveMessages) { + // 延迟清空消息,给历史记录加载足够时间 + setTimeout(() => { + if (threadState.onGoingConv) { + threadState.onGoingConv = { msgChunks: {} }; + } + }, 100); + } else { + threadState.onGoingConv = { msgChunks: {} }; + } } } else { // 清理当前线程或所有线程的状态 @@ -343,7 +366,15 @@ const resetOnGoingConv = (threadId = null) => { threadState.streamAbortController.abort(); threadState.streamAbortController = null; } - threadState.onGoingConv = { msgChunks: {} }; + if (preserveMessages) { + setTimeout(() => { + if (threadState.onGoingConv) { + threadState.onGoingConv = { msgChunks: {} }; + } + }, 100); + } else { + threadState.onGoingConv = { msgChunks: {} }; + } } } else { // 如果没有当前线程,清理所有线程状态 @@ -390,13 +421,26 @@ const _processStreamChunk = (chunk, threadId) => { resetOnGoingConv(threadId); return true; case 'finished': - fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }); - resetOnGoingConv(threadId); + // 先标记流式结束,但保持消息显示直到历史记录加载完成 + if (threadState) { + threadState.isStreaming = false; + } + // 异步加载历史记录,保持当前消息显示直到历史记录加载完成 + fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }) + .finally(() => { + // 历史记录加载完成后,安全地清空当前进行中的对话 + resetOnGoingConv(threadId, true); + }); return true; case 'interrupted': // 中断状态,刷新消息历史 - fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }); - resetOnGoingConv(threadId); + if (threadState) { + threadState.isStreaming = false; + } + fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId }) + .finally(() => { + resetOnGoingConv(threadId, true); + }); return true; } diff --git a/web/src/components/RefsComponent.vue b/web/src/components/RefsComponent.vue index 41c6bf20..bc286ad8 100644 --- a/web/src/components/RefsComponent.vue +++ b/web/src/components/RefsComponent.vue @@ -1,6 +1,7 @@