Skip to content

Commit 578682a

Browse files
committed
feat: 优化聊天代理功能,增加时间成本计算;调整对话状态管理,确保消息流处理更流畅;更新样式以提升用户体验。
1 parent a92cc54 commit 578682a

File tree

4 files changed

+92
-82
lines changed

4 files changed

+92
-82
lines changed

server/routers/chat_router.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ async def chat_agent(
123123
db: Session = Depends(get_db),
124124
):
125125
"""使用特定智能体进行对话(需要登录)"""
126+
start_time = asyncio.get_event_loop().time()
126127

127128
logger.info(f"agent_id: {agent_id}, query: {query}, config: {config}, meta: {meta}")
128129

@@ -319,6 +320,7 @@ async def stream_messages():
319320
yield make_chunk(message="检测到敏感内容,已中断输出", status="error")
320321
return
321322

323+
meta["time_cost"] = asyncio.get_event_loop().time() - start_time
322324
yield make_chunk(status="finished", meta=meta)
323325

324326
# After streaming finished, save all messages from LangGraph state

web/src/assets/css/base.css

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,42 @@
22
/* color palette from <https://github.com/vuejs/theme> */
33
/* https://material-foundation.github.io/material-theme-builder/ */
44
:root {
5-
--main-1000: #01151f; /* 更深沉的深蓝黑 */
6-
--main-900: #023944; /* 深海蓝 */
7-
--main-800: #035065; /* 深蓝绿 */
8-
--main-700: #046a82; /* 增加明度的蓝青色 */
9-
--main-600: #24839a; /* 冷色调中略带灰 */
10-
--main-500: #3996ae; /* 平衡且主色调友好 */
11-
--main-400: #5faec2; /* 更加柔和的中亮蓝 */
12-
--main-300: #82c3d6; /* 明亮通透,视觉清爽 */
13-
--main-200: #a3d8e8; /* 比原先更清澈淡蓝 */
14-
--main-100: #c4eaf5; /* 接近原色,但稍微柔和 */
15-
--main-50: #e1f6fb; /* 极浅天蓝,适合背景过渡 */
16-
--main-40: #eaf3f5; /* 淡灰蓝调 */
17-
--main-30: #f2fbfd; /* 几近白的蓝调提示 */
18-
--main-20: #f5f9fa; /* 比原配色更均匀些 */
5+
--main-1000: #01151f;
6+
--main-900: #023944;
7+
--main-800: #035065;
8+
--main-700: #046a82;
9+
--main-600: #24839a;
10+
--main-500: #3996ae;
11+
--main-400: #5faec2;
12+
--main-300: #82c3d6;
13+
--main-200: #a3d8e8;
14+
--main-100: #c4eaf5;
15+
--main-50: #e1f6fb;
16+
--main-40: #eaf3f5;
17+
--main-30: #f7fbfd;
18+
--main-20: #f6f9fa;
1919
--main-10: #fafcfd;
2020
--main-5: #fcfefe;
2121
--main-1: #fefefe;
2222
--main-0: #ffffff;
2323

2424

2525
--gray-10000: #000000; /* 保持纯黑 */
26-
--gray-2000: #0d0d0d; /* 更精致的暗灰,用于极深背景 */
27-
--gray-1000: #161616; /* 深灰黑,适用于主文本 */
28-
--gray-900: #1f1f1f;
29-
--gray-800: #333333; /* 标准深灰,适合背景 */
30-
--gray-700: #4d4d4d;
31-
--gray-600: #707070; /* 调和中灰,适合次要文本 */
32-
--gray-500: #999999; /* 更柔和的中灰色 */
33-
--gray-400: #bfbfbf;
34-
--gray-300: #d9d9d9; /* 修正了原300/200重复 */
35-
--gray-200: #e6e6e6; /* 稍微加深一点,便于分层 */
36-
--gray-150: #f0f0f0;
37-
--gray-100: #f2f2f2;
38-
--gray-50: #f7f7f7;
39-
--gray-25: #fafafa;
40-
--gray-10: #fcfcfc;
26+
--gray-2000: #0c0d0d; /* 更精致的暗灰,用于极深背景 */
27+
--gray-1000: #151616; /* 深灰黑,适用于主文本 */
28+
--gray-900: #1e1f1f;
29+
--gray-800: #323333; /* 标准深灰,适合背景 */
30+
--gray-700: #4c4d4d;
31+
--gray-600: #697070; /* 调和中灰,适合次要文本 */
32+
--gray-500: #979999; /* 更柔和的中灰色 */
33+
--gray-400: #bdbfbf;
34+
--gray-300: #d7d9d9; /* 修正了原300/200重复 */
35+
--gray-200: #e4e6e6; /* 稍微加深一点,便于分层 */
36+
--gray-150: #eef0f0;
37+
--gray-100: #eff2f2;
38+
--gray-50: #f5f7f7;
39+
--gray-25: #f8fafa;
40+
--gray-10: #fafcfc;
4141
--gray-0: #ffffff;
4242

4343
--main-color: #016179;

web/src/components/AgentChatComponent.vue

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,26 @@ const onGoingConvMessages = computed(() => {
244244
245245
const conversations = computed(() => {
246246
const historyConvs = MessageProcessor.convertServerHistoryToMessages(currentThreadMessages.value);
247-
if (onGoingConvMessages.value.length > 0) {
247+
const threadState = currentThreadState.value;
248+
249+
// 如果有进行中的消息且线程状态显示正在流式处理,添加进行中的对话
250+
if (onGoingConvMessages.value.length > 0 && threadState?.isStreaming) {
248251
const onGoingConv = {
249252
messages: onGoingConvMessages.value,
250253
status: 'streaming'
251254
};
252255
return [...historyConvs, onGoingConv];
253256
}
257+
258+
// 即使流式结束,如果历史记录为空但还有消息没有完全同步,也保持显示
259+
if (historyConvs.length === 0 && onGoingConvMessages.value.length > 0 && !threadState?.isStreaming) {
260+
const finalConv = {
261+
messages: onGoingConvMessages.value,
262+
status: 'finished'
263+
};
264+
return [finalConv];
265+
}
266+
254267
return historyConvs;
255268
});
256269
@@ -322,7 +335,7 @@ const cleanupThreadState = (threadId) => {
322335
};
323336
324337
// ==================== STREAM HANDLING LOGIC ====================
325-
const resetOnGoingConv = (threadId = null) => {
338+
const resetOnGoingConv = (threadId = null, preserveMessages = false) => {
326339
if (threadId) {
327340
// 清理指定线程的状态
328341
const threadState = getThreadState(threadId);
@@ -331,7 +344,17 @@ const resetOnGoingConv = (threadId = null) => {
331344
threadState.streamAbortController.abort();
332345
threadState.streamAbortController = null;
333346
}
334-
threadState.onGoingConv = { msgChunks: {} };
347+
// 如果指定要保留消息,则延迟清空
348+
if (preserveMessages) {
349+
// 延迟清空消息,给历史记录加载足够时间
350+
setTimeout(() => {
351+
if (threadState.onGoingConv) {
352+
threadState.onGoingConv = { msgChunks: {} };
353+
}
354+
}, 100);
355+
} else {
356+
threadState.onGoingConv = { msgChunks: {} };
357+
}
335358
}
336359
} else {
337360
// 清理当前线程或所有线程的状态
@@ -343,7 +366,15 @@ const resetOnGoingConv = (threadId = null) => {
343366
threadState.streamAbortController.abort();
344367
threadState.streamAbortController = null;
345368
}
346-
threadState.onGoingConv = { msgChunks: {} };
369+
if (preserveMessages) {
370+
setTimeout(() => {
371+
if (threadState.onGoingConv) {
372+
threadState.onGoingConv = { msgChunks: {} };
373+
}
374+
}, 100);
375+
} else {
376+
threadState.onGoingConv = { msgChunks: {} };
377+
}
347378
}
348379
} else {
349380
// 如果没有当前线程,清理所有线程状态
@@ -390,13 +421,26 @@ const _processStreamChunk = (chunk, threadId) => {
390421
resetOnGoingConv(threadId);
391422
return true;
392423
case 'finished':
393-
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId });
394-
resetOnGoingConv(threadId);
424+
// 先标记流式结束,但保持消息显示直到历史记录加载完成
425+
if (threadState) {
426+
threadState.isStreaming = false;
427+
}
428+
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
429+
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId })
430+
.finally(() => {
431+
// 历史记录加载完成后,安全地清空当前进行中的对话
432+
resetOnGoingConv(threadId, true);
433+
});
395434
return true;
396435
case 'interrupted':
397436
// 中断状态,刷新消息历史
398-
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId });
399-
resetOnGoingConv(threadId);
437+
if (threadState) {
438+
threadState.isStreaming = false;
439+
}
440+
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId })
441+
.finally(() => {
442+
resetOnGoingConv(threadId, true);
443+
});
400444
return true;
401445
}
402446

web/src/components/RefsComponent.vue

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<div class="refs" v-if="showRefs">
33
<div class="tags">
4+
<!-- 反馈 -->
45
<span
56
class="item btn"
67
:class="{ 'disabled': feedbackState.hasSubmitted }"
@@ -19,37 +20,22 @@
1920
<DislikeFilled v-if="feedbackState.rating === 'dislike'" />
2021
<DislikeOutlined v-else />
2122
</span>
23+
<!-- 模型名称 -->
2224
<span v-if="showKey('model') && getModelName(msg)" class="item" @click="console.log(msg)">
2325
<BulbOutlined /> {{ getModelName(msg) }}
2426
</span>
27+
<!-- 复制 -->
2528
<span
2629
v-if="showKey('copy')"
27-
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
28-
<span
29-
v-if="showKey('regenerate')"
30-
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
31-
<span
32-
v-if="isLatestMessage && showKey('subGraph') && hasSubGraphData(msg)"
33-
class="item btn"
34-
@click="openGlobalRefs('graph')"
35-
>
36-
<DeploymentUnitOutlined /> 关系图
37-
</span>
38-
<span
39-
class="item btn"
40-
v-if="isLatestMessage && showKey('webSearch') && msg.refs?.web_search.results.length > 0"
41-
@click="openGlobalRefs('webSearch')"
42-
>
43-
<GlobalOutlined /> 网页搜索 {{ msg.refs.web_search?.results.length }}
30+
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined />
4431
</span>
32+
33+
<!-- 重试 -->
4534
<span
46-
class="item btn"
47-
v-if="isLatestMessage && showKey('knowledgeBase') && hasKnowledgeBaseData(msg)"
48-
@click="openGlobalRefs('knowledgeBase')"
49-
>
50-
<FileTextOutlined /> 知识库
35+
v-if="showKey('regenerate')"
36+
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined />
5137
</span>
52-
</div>
38+
</div>
5339
</div>
5440

5541
<!-- Dislike reason modal -->
@@ -77,10 +63,7 @@ import { ref, computed, reactive, onMounted } from 'vue'
7763
import { useClipboard } from '@vueuse/core'
7864
import { message } from 'ant-design-vue'
7965
import {
80-
GlobalOutlined,
81-
FileTextOutlined,
8266
CopyOutlined,
83-
DeploymentUnitOutlined,
8467
BulbOutlined,
8568
ReloadOutlined,
8669
LikeOutlined,
@@ -152,25 +135,6 @@ const showRefs = computed(() => {
152135
return (msg.value.role=='received' || msg.value.role=='assistant') && msg.value.status=='finished';
153136
})
154137
155-
// 打开全局refs抽屉
156-
const openGlobalRefs = (type) => {
157-
emit('openRefs', {
158-
type,
159-
refs: msg.value.refs
160-
})
161-
}
162-
163-
const hasSubGraphData = (msg) => {
164-
return msg.refs &&
165-
msg.refs.graph_base &&
166-
msg.refs.graph_base.results.nodes.length > 0;
167-
}
168-
169-
const hasKnowledgeBaseData = (msg) => {
170-
return msg.refs &&
171-
msg.refs.knowledge_base &&
172-
msg.refs.knowledge_base.results.length > 0;
173-
}
174138
175139
// 添加重新生成方法
176140
const regenerateMessage = () => {

0 commit comments

Comments
 (0)