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

This commit is contained in:
Wenjie Zhang 2025-10-15 02:37:42 +08:00
parent a92cc54675
commit 578682abfa
4 changed files with 92 additions and 82 deletions

View File

@ -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

View File

@ -2,20 +2,20 @@
/* color palette from <https://github.com/vuejs/theme> */
/* 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;

View File

@ -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;
}

View File

@ -1,6 +1,7 @@
<template>
<div class="refs" v-if="showRefs">
<div class="tags">
<!-- 反馈 -->
<span
class="item btn"
:class="{ 'disabled': feedbackState.hasSubmitted }"
@ -19,37 +20,22 @@
<DislikeFilled v-if="feedbackState.rating === 'dislike'" />
<DislikeOutlined v-else />
</span>
<!-- 模型名称 -->
<span v-if="showKey('model') && getModelName(msg)" class="item" @click="console.log(msg)">
<BulbOutlined /> {{ getModelName(msg) }}
</span>
<!-- 复制 -->
<span
v-if="showKey('copy')"
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined />
</span>
<!-- 重试 -->
<span
v-if="showKey('regenerate')"
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
<span
v-if="isLatestMessage && showKey('subGraph') && hasSubGraphData(msg)"
class="item btn"
@click="openGlobalRefs('graph')"
>
<DeploymentUnitOutlined /> 关系图
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined />
</span>
<span
class="item btn"
v-if="isLatestMessage && showKey('webSearch') && msg.refs?.web_search.results.length > 0"
@click="openGlobalRefs('webSearch')"
>
<GlobalOutlined /> 网页搜索 {{ msg.refs.web_search?.results.length }}
</span>
<span
class="item btn"
v-if="isLatestMessage && showKey('knowledgeBase') && hasKnowledgeBaseData(msg)"
@click="openGlobalRefs('knowledgeBase')"
>
<FileTextOutlined /> 知识库
</span>
</div>
</div>
</div>
<!-- Dislike reason modal -->
@ -77,10 +63,7 @@ import { ref, computed, reactive, onMounted } from 'vue'
import { useClipboard } from '@vueuse/core'
import { message } from 'ant-design-vue'
import {
GlobalOutlined,
FileTextOutlined,
CopyOutlined,
DeploymentUnitOutlined,
BulbOutlined,
ReloadOutlined,
LikeOutlined,
@ -152,25 +135,6 @@ const showRefs = computed(() => {
return (msg.value.role=='received' || msg.value.role=='assistant') && msg.value.status=='finished';
})
// refs
const openGlobalRefs = (type) => {
emit('openRefs', {
type,
refs: msg.value.refs
})
}
const hasSubGraphData = (msg) => {
return msg.refs &&
msg.refs.graph_base &&
msg.refs.graph_base.results.nodes.length > 0;
}
const hasKnowledgeBaseData = (msg) => {
return msg.refs &&
msg.refs.knowledge_base &&
msg.refs.knowledge_base.results.length > 0;
}
//
const regenerateMessage = () => {