mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-30 19:33:05 +08:00
feat: Enhance chat message handling and agent completion events
- Updated chat component to reset loading and reply states when switching sessions, improving user experience. - Modified event handling to support agent completion events, ensuring accurate message state updates. - Enhanced message reconstruction logic to handle completed messages and empty content scenarios effectively. - Improved loading state management during message streaming, preventing unnecessary loading indicators for existing messages.
This commit is contained in:
@@ -68,6 +68,12 @@ watch([() => route.params], (newvalue) => {
|
||||
}
|
||||
messagesList.splice(0);
|
||||
session_id.value = newvalue[0].chatid;
|
||||
|
||||
// 切换会话时,重置状态(加载历史消息不应显示loading)
|
||||
loading.value = false;
|
||||
isReplying.value = false;
|
||||
currentAssistantMessageId.value = '';
|
||||
|
||||
checkmenuTitle(session_id.value)
|
||||
let data = {
|
||||
session_id: session_id.value,
|
||||
@@ -117,7 +123,7 @@ const getmsgList = (data, isScrollType = false, scrollHeight) => {
|
||||
|
||||
// Reconstruct agentEventStream from agent_steps stored in database
|
||||
// This allows the frontend to restore the exact conversation state including all agent reasoning steps
|
||||
const reconstructEventStreamFromSteps = (agentSteps, messageContent) => {
|
||||
const reconstructEventStreamFromSteps = (agentSteps, messageContent, isCompleted = false) => {
|
||||
const events = [];
|
||||
|
||||
// Process agent steps if they exist
|
||||
@@ -163,6 +169,14 @@ const reconstructEventStreamFromSteps = (agentSteps, messageContent) => {
|
||||
content: messageContent,
|
||||
done: true
|
||||
});
|
||||
} else if (isCompleted) {
|
||||
// 如果消息已完成但 content 为空(Agent 模式常见情况),添加一个空的 answer 事件标记完成
|
||||
// 这样可以确保 isConversationDone 返回 true,不显示 loading-indicator
|
||||
events.push({
|
||||
type: 'answer',
|
||||
content: '',
|
||||
done: true
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
@@ -181,7 +195,7 @@ const handleMsgList = async (data, isScrollType = false, newScrollHeight) => {
|
||||
if (item.agent_steps && Array.isArray(item.agent_steps) && item.agent_steps.length > 0) {
|
||||
console.log('[Message Load] Reconstructing agent steps for message:', item.id, 'steps:', item.agent_steps.length);
|
||||
item.isAgentMode = true;
|
||||
item.agentEventStream = reconstructEventStreamFromSteps(item.agent_steps, item.content);
|
||||
item.agentEventStream = reconstructEventStreamFromSteps(item.agent_steps, item.content, item.is_completed);
|
||||
// 隐藏最终答案内容,因为它已经包含在 agentEventStream 的 answer 事件中
|
||||
item.hideContent = true;
|
||||
console.log('[Message Load] Reconstructed', item.agentEventStream.length, 'events from agent steps');
|
||||
@@ -200,7 +214,10 @@ const handleMsgList = async (data, isScrollType = false, newScrollHeight) => {
|
||||
item.content = item.content.substring(index + 8);
|
||||
}
|
||||
}
|
||||
if (item.is_completed && !item.content) {
|
||||
|
||||
// 只给非Agent模式的空内容已完成消息设置默认错误消息
|
||||
// Agent模式的消息内容在agent_steps中,content为空是正常的
|
||||
if (item.is_completed && !item.content && !item.isAgentMode) {
|
||||
item.content = "抱歉,我无法回答这个问题。";
|
||||
}
|
||||
messagesList.unshift(item);
|
||||
@@ -317,8 +334,17 @@ onChunk((data) => {
|
||||
query: data.data?.query,
|
||||
request_id: data.data?.request_id
|
||||
});
|
||||
// 保持 loading 状态,等待实际内容
|
||||
|
||||
// 检查是否是继续流式传输(消息已存在)
|
||||
const existingMessage = messagesList.findLast((item) => item.id === data.id || item.request_id === data.id);
|
||||
if (!existingMessage) {
|
||||
// 新消息,设置 loading 状态
|
||||
loading.value = true;
|
||||
console.log('[Agent Query] New message, setting loading=true');
|
||||
} else {
|
||||
// 继续流式传输(刷新页面场景),不设置 loading,因为消息已经在列表中
|
||||
console.log('[Agent Query] Continuing stream for existing message, keeping current loading state');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -341,7 +367,10 @@ onChunk((data) => {
|
||||
// 判断是否是 Agent 模式的响应
|
||||
const isAgentResponse = data.response_type === 'thinking' ||
|
||||
data.response_type === 'tool_call' ||
|
||||
data.response_type === 'tool_result' ||
|
||||
data.response_type === 'references' ||
|
||||
data.response_type === 'answer' ||
|
||||
data.response_type === 'reflection' ||
|
||||
data.response_type === 'stop';
|
||||
|
||||
// Agent 模式处理(包括 stop 事件)
|
||||
@@ -426,6 +455,13 @@ const handleAgentChunk = (data) => {
|
||||
|
||||
message.isAgentMode = true;
|
||||
|
||||
// 确保在继续流式传输时(刷新页面场景),一旦接收到实际内容就关闭 loading
|
||||
// 这是一个保护措施,防止任何边缘情况导致 loading 残留
|
||||
if (loading.value && (data.response_type === 'thinking' || data.response_type === 'answer' || data.response_type === 'tool_call')) {
|
||||
console.log('[Agent Chunk] Closing loading for continued stream');
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
switch(data.response_type) {
|
||||
case 'thinking':
|
||||
{
|
||||
@@ -608,7 +644,8 @@ const handleAgentChunk = (data) => {
|
||||
if (data.done) {
|
||||
console.log('[Agent] Answer done, content length:', message.content?.length || 0);
|
||||
|
||||
// 完成
|
||||
// 完成 - 关闭所有状态
|
||||
loading.value = false;
|
||||
isReplying.value = false;
|
||||
fullContent.value = '';
|
||||
// 清空当前 assistant message ID
|
||||
@@ -661,6 +698,10 @@ const updateAssistantSession = (payload) => {
|
||||
onMounted(async () => {
|
||||
messagesList.splice(0);
|
||||
|
||||
// 初始化状态:加载历史消息时不应显示loading
|
||||
loading.value = false;
|
||||
isReplying.value = false;
|
||||
|
||||
// Load session data to get agent_config
|
||||
try {
|
||||
const sessionRes = await getSession(session_id.value);
|
||||
|
||||
@@ -65,6 +65,7 @@ func (h *AgentStreamHandler) Subscribe() {
|
||||
h.eventBus.On(event.EventAgentReflection, h.handleReflection)
|
||||
h.eventBus.On(event.EventError, h.handleError)
|
||||
h.eventBus.On(event.EventSessionTitle, h.handleSessionTitle)
|
||||
h.eventBus.On(event.EventAgentComplete, h.handleComplete)
|
||||
}
|
||||
|
||||
// handleThought handles agent thought events
|
||||
@@ -355,6 +356,58 @@ func (h *AgentStreamHandler) handleSessionTitle(ctx context.Context, evt event.E
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleComplete handles agent complete events
|
||||
func (h *AgentStreamHandler) handleComplete(ctx context.Context, evt event.Event) error {
|
||||
data, ok := evt.Data.(event.AgentCompleteData)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Update assistant message with final data
|
||||
if data.MessageID == h.assistantMessageID {
|
||||
// h.assistantMessage.Content = data.FinalAnswer
|
||||
h.assistantMessage.IsCompleted = true
|
||||
|
||||
// Update knowledge references if provided
|
||||
if len(data.KnowledgeRefs) > 0 {
|
||||
knowledgeRefs := make([]*types.SearchResult, 0, len(data.KnowledgeRefs))
|
||||
for _, ref := range data.KnowledgeRefs {
|
||||
if sr, ok := ref.(*types.SearchResult); ok {
|
||||
knowledgeRefs = append(knowledgeRefs, sr)
|
||||
}
|
||||
}
|
||||
h.assistantMessage.KnowledgeReferences = knowledgeRefs
|
||||
}
|
||||
|
||||
// Update agent steps if provided
|
||||
if data.AgentSteps != nil {
|
||||
if steps, ok := data.AgentSteps.([]types.AgentStep); ok {
|
||||
h.assistantMessage.AgentSteps = steps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send completion event to stream manager so SSE can detect completion
|
||||
if err := h.streamManager.AppendEvent(h.ctx, h.sessionID, h.assistantMessageID, interfaces.StreamEvent{
|
||||
ID: evt.ID,
|
||||
Type: types.ResponseTypeComplete,
|
||||
Content: "",
|
||||
Done: true,
|
||||
Timestamp: time.Now(),
|
||||
Data: map[string]interface{}{
|
||||
"total_steps": data.TotalSteps,
|
||||
"total_duration": data.TotalDuration,
|
||||
},
|
||||
}); err != nil {
|
||||
logger.GetLogger(h.ctx).Error("Append complete event to stream failed", "error", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func getString(m map[string]interface{}, key string) string {
|
||||
if val, ok := m[key].(string); ok {
|
||||
|
||||
@@ -58,6 +58,8 @@ const (
|
||||
ResponseTypeSessionTitle ResponseType = "session_title"
|
||||
// Agent query response type (query received and processing started)
|
||||
ResponseTypeAgentQuery ResponseType = "agent_query"
|
||||
// Complete response type (agent complete)
|
||||
ResponseTypeComplete ResponseType = "complete"
|
||||
)
|
||||
|
||||
// StreamResponse stream response
|
||||
|
||||
Reference in New Issue
Block a user