优化消息展示

This commit is contained in:
Wenjie Zhang 2025-04-02 22:20:56 +08:00
parent ff61e4cb48
commit 988426a5f5
3 changed files with 206 additions and 89 deletions

View File

@ -137,7 +137,7 @@ def chat_agent(agent_name: str,
meta.update({
"query": query,
"agent_name": agent_name,
"server_model_name": config["model"] ,
"server_model_name": config.get("model", agent_name) ,
"thread_id": config.get("thread_id"),
})
@ -175,7 +175,7 @@ def chat_agent(agent_name: str,
def stream_messages():
content = ""
yield make_chunk(status="init")
yield make_chunk(status="init", meta=meta)
for msg, metadata in agent.stream_messages(messages, config_schema=runnable_config):
if isinstance(msg, AIMessageChunk) and msg.content != "<tool_call>":
content += msg.content

View File

@ -32,6 +32,7 @@
:message="message"
:key="index"
:is-processing="isProcessing"
:debug-mode="state.debug_mode"
:show-refs="showMsgRefs(message)"
@retry="retryMessage(message)"
>
@ -130,6 +131,7 @@ const props = defineProps({
// UI
const state = ref(props.state);
const waitingServerResponse = ref(false);
const showMsgRefs = (msg) => {
if (msg.isLast) {
return ['copy', 'regenerate']
@ -159,12 +161,16 @@ const expandedToolCalls = ref(new Set()); // 展开的工具调用集合
// ==================== ====================
// TODO:
// TODO:
const scrollToBottom = async () => {
await nextTick();
if (!messagesContainer.value) return;
const container = messagesContainer.value;
//
const containerBox = messagesContainer.value;
const container = document.querySelector('.chat');
if (!container) return;
const scrollOptions = { top: container.scrollHeight, behavior: 'smooth' };
//
@ -334,6 +340,7 @@ const sendMessageWithText = async (text) => {
content: msg.content
}));
waitingServerResponse.value = true;
//
const requestData = {
query: userMessage,
@ -372,6 +379,7 @@ const sendMessageWithText = async (text) => {
};
}
} finally {
waitingServerResponse.value = false;
isProcessing.value = false;
await scrollToBottom();
}
@ -379,53 +387,146 @@ const sendMessageWithText = async (text) => {
//
const handleStreamResponse = async (response) => {
const reader = response.body.getReader();
//
// const assistantMsg = {
// role: 'assistant',
// content: '',
// status: 'init',
// toolCalls: {},
// toolCallIds: {}
// };
try {
await scrollToBottom();
// //
// messages.value.push(assistantMsg);
await scrollToBottom();
// API
if ('TransformStream' in window && 'ReadableStream' in window) {
const jsonStream = new TransformStream({
start(controller) {
this.buffer = '';
this.decoder = new TextDecoder();
},
transform(chunk, controller) {
this.buffer += this.decoder.decode(chunk, { stream: true });
while (true) {
const { done, value } = await reader.read();
if (done) break;
let position;
while ((position = this.buffer.indexOf('\n')) !== -1) {
const line = this.buffer.substring(0, position).trim();
this.buffer = this.buffer.substring(position + 1);
const text = new TextDecoder().decode(value);
const lines = text.split('\n').filter(line => line.trim());
if (line) {
try {
controller.enqueue(JSON.parse(line));
} catch (e) {
// JSON
}
}
}
},
flush(controller) {
if (this.buffer.trim()) {
try {
controller.enqueue(JSON.parse(this.buffer.trim()));
} catch (e) {
console.warn('最终缓冲区内容无法解析:', this.buffer);
}
}
}
});
for (const line of lines) {
try {
const data = JSON.parse(line);
if (data.debug_mode) {
console.log("debug_mode", data);
//
const transformedStream = response.body.pipeThrough(jsonStream);
const reader = transformedStream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
// valueJSON
if (value) {
if (value.debug_mode) {
console.log("debug_mode", value);
}
//
if (value.status === 'init') {
await handleInit(value);
} else if (value.status === 'finished') {
await handleFinished(value);
} else {
await handleMessageById(value);
}
await scrollToBottom();
}
}
} else {
// 使
const reader = response.body.getReader();
let buffer = '';
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || ''; //
for (const line of lines) {
if (line.trim()) {
try {
const data = JSON.parse(line.trim());
if (data.debug_mode) {
console.log("debug_mode", data);
}
if (data.status === 'init') {
await handleInit(data);
} else if (data.status === 'finished') {
await handleFinished(data);
} else {
await handleMessageById(data);
}
} catch (e) {
console.debug('解析JSON出错:', e.message);
}
}
}
//
handleMetadata(data);
await scrollToBottom();
}
// ID
if (data.msg?.id) {
await handleMessageById(data);
//
if (buffer.trim()) {
try {
const data = JSON.parse(buffer.trim());
if (data.status === 'init') {
await handleInit(data);
} else if (data.status === 'finished') {
await handleFinished(data);
} else {
await handleMessageById(data);
}
} catch (e) {
console.warn('最终缓冲区内容无法解析:', buffer);
}
//
if (data.status === 'finished') {
await handleFinished(data);
}
} catch (error) {
console.error('解析响应错误:', error);
}
}
} catch (error) {
console.error('流式处理出错:', error);
isProcessing.value = false;
}
};
const handleInit = async (data) => {
waitingServerResponse.value = false;
console.log("handleInit", data);
const initMsg = {
role: 'assistant',
content: '',
status: 'init',
toolCalls: {},
toolCallIds: {},
request_id: data.request_id
}
messages.value.push(initMsg);
await scrollToBottom();
}
//
const handleFinished = async (data) => {
//
@ -463,14 +564,14 @@ const handleMessageById = async (data) => {
await appendToolMessageToExistingAssistant(data);
} else {
//
const loadingAssistantIndex = messages.value.findIndex(m => m.role === 'assistant' && m.status === 'loading');
const loadingAssistantIndex = messages.value.findIndex(m => m.role === 'assistant' && m.status === 'init');
if (loadingAssistantIndex !== -1) {
//
messages.value[loadingAssistantIndex].id = msgId;
messageMap.value.set(msgId, loadingAssistantIndex);
console.log("更新现有助手消息", messages.value[loadingAssistantIndex]);
await updateExistingMessage(data, loadingAssistantIndex);
} else {
//
await createAssistantMessage(data);
}
}
@ -489,18 +590,32 @@ const createAssistantMessage = async (data) => {
const step = data.metadata?.langgraph_step;
const requestId = data.metadata?.request_id || data.request_id;
//
const newMsg = {
id: msgId,
role: 'assistant',
content: msgContent,
run_id: runId,
step: step,
status: 'processing',
toolCalls: {},
toolCallIds: {},
request_id: requestId
};
let currentMsg = null;
// assistant status init request_id request_id id
const lastMsg = messages.value[messages.value.length - 1];
const lastMsgIsInit = lastMsg.role === 'assistant'
&& lastMsg.status === 'init'
&& lastMsg.request_id === requestId
&& !lastMsg.id;
if (lastMsgIsInit) {
currentMsg = lastMsg;
} else {
//
currentMsg = {
role: 'assistant',
status: 'init',
toolCalls: {},
toolCallIds: {},
request_id: requestId
};
messages.value.push(currentMsg);
}
currentMsg.id = msgId;
currentMsg.content = msgContent;
currentMsg.run_id = runId;
currentMsg.step = step;
//
const toolCalls = data.msg.additional_kwargs?.tool_calls;
@ -509,14 +624,13 @@ const createAssistantMessage = async (data) => {
for (const toolCall of toolCalls) {
const toolCallId = toolCall.id;
const toolIndex = toolCall.index || 0;
newMsg.toolCallIds[toolCallId] = toolIndex;
newMsg.toolCalls[toolIndex] = toolCall;
currentMsg.toolCallIds[toolCallId] = toolIndex;
currentMsg.toolCalls[toolIndex] = toolCall;
toolCallMap.value.set(toolCallId, msgId);
}
}
//
messages.value.push(newMsg);
const newIndex = messages.value.length - 1;
messageMap.value.set(msgId, newIndex);
@ -633,41 +747,41 @@ watch(messages, () => {
//
onMounted(async () => {
try {
console.log("组件挂载");
// console.log("");
//
await fetchAgents();
//
console.log("路由参数:", props.agentId);
console.log("智能体列表:", Object.keys(agents.value));
// console.log(":", props.agentId);
// console.log(":", Object.keys(agents.value));
// - 使 Vue Router
// 使 setTimeout
setTimeout(async () => {
await loadAgentData();
console.log("初始化后消息数量:", messages.value.length);
// console.log(":", messages.value.length);
}, 10);
} catch (error) {
console.error("组件挂载出错:", error);
}
});
//
const handleMetadata = (data) => {
// ID
if (data.metadata?.run_id && !currentRunId.value) {
currentRunId.value = data.metadata.run_id;
}
// //
// const handleMetadata = (data) => {
// // ID
// if (data.metadata?.run_id && !currentRunId.value) {
// currentRunId.value = data.metadata.run_id;
// }
//
if (data.metadata?.langgraph_step !== undefined) {
const step = data.metadata.langgraph_step;
messageStepMap.value[step] = {
type: data.msg?.type || 'unknown',
timestamp: new Date().toISOString()
};
}
};
// //
// if (data.metadata?.langgraph_step !== undefined) {
// const step = data.metadata.langgraph_step;
// messageStepMap.value[step] = {
// type: data.msg?.type || 'unknown',
// timestamp: new Date().toISOString()
// };
// }
// };
// script setup toggleToolCall
const toggleToolCall = (toolCallId) => {
@ -690,14 +804,14 @@ const loadAgentData = async () => {
if (props.agentId && agents.value && agents.value[props.agentId]) {
// agentId
currentAgent.value = agents.value[props.agentId];
console.log("设置当前智能体", currentAgent.value.name);
// console.log("", currentAgent.value.name);
} else if (!props.agentId) {
//
const storagePrefix = 'agent-multi';
const savedAgent = localStorage.getItem(`${storagePrefix}-current-agent`);
if (savedAgent && agents.value && agents.value[savedAgent]) {
currentAgent.value = agents.value[savedAgent];
console.log("从存储中恢复智能体", currentAgent.value.name);
// console.log("", currentAgent.value.name);
}
}
@ -706,7 +820,7 @@ const loadAgentData = async () => {
//
if (messages.value && messages.value.length > 0) {
console.log("处理消息历史:", messages.value.length);
// console.log(":", messages.value.length);
messages.value = prepareMessageHistory(messages.value);
}
} catch (error) {
@ -727,20 +841,20 @@ const loadState = () => {
return;
}
console.log("loadState with prefix:", storagePrefix);
// console.log("loadState with prefix:", storagePrefix);
//
const savedMessages = localStorage.getItem(`${storagePrefix}-messages`);
if (savedMessages) {
try {
const parsedMessages = JSON.parse(savedMessages);
console.log(`加载消息历史 (${storagePrefix}):`, parsedMessages ? parsedMessages.length : 0);
// console.log(` (${storagePrefix}):`, parsedMessages ? parsedMessages.length : 0);
if (Array.isArray(parsedMessages)) {
messages.value = parsedMessages;
}
//
console.log(`消息历史加载后数量:`, messages.value.length);
// console.log(`:`, messages.value.length);
} catch (e) {
console.error('解析消息历史出错:', e);
}
@ -750,7 +864,7 @@ const loadState = () => {
const savedThreadId = localStorage.getItem(`${storagePrefix}-thread-id`);
if (savedThreadId) {
currentRunId.value = savedThreadId;
console.log(`加载线程ID (${storagePrefix}):`, currentRunId.value);
// console.log(`线ID (${storagePrefix}):`, currentRunId.value);
}
} catch (error) {
console.error('从localStorage加载状态出错:', error);
@ -760,7 +874,7 @@ const loadState = () => {
// agentId
watch(() => props.agentId, async (newAgentId, oldAgentId) => {
try {
console.log("智能体ID变化", oldAgentId, "->", newAgentId);
// console.log("ID", oldAgentId, "->", newAgentId);
//
if (newAgentId !== oldAgentId) {
@ -782,7 +896,7 @@ const saveState = () => {
try {
//
if (!currentAgent.value) {
console.log("当前没有选中智能体,跳过保存");
console.warn("当前没有选中智能体,跳过保存");
return;
}
@ -928,6 +1042,7 @@ watch([currentAgent, messages, currentRunId], () => {
display: flex;
flex-direction: column;
.tool-calls-container {
width: 100%;
margin-top: 10px;

View File

@ -6,6 +6,9 @@
<!-- 助手消息 -->
<div v-else-if="message.role === 'assistant' || message.role === 'received'" class="assistant-message">
<!-- 推理过程 (ChatComponent特有) -->
<p v-if="debugMode">
{{ message.status }}
</p>
<div v-if="message.reasoning_content" class="reasoning-box">
<a-collapse v-model:activeKey="reasoningActiveKey" :bordered="false">
<template #expandIcon="{ isActive }">
@ -77,11 +80,6 @@ const props = defineProps({
type: Object,
required: true
},
// HTML
contentHtml: {
type: String,
default: ''
},
//
isProcessing: {
type: Boolean,
@ -96,7 +94,11 @@ const props = defineProps({
showRefs: {
type: [Array, Boolean],
default: () => false
}
},
debugMode: {
type: Boolean,
default: false
},
});
const statusDefination = {