feat: 优化聊天代理功能,增加时间成本计算;调整对话状态管理,确保消息流处理更流畅;更新样式以提升用户体验。
This commit is contained in:
parent
a92cc54675
commit
578682abfa
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user