From a62fe929c1917f4177f51ba0b260716fe600cc1a Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Sun, 12 Apr 2026 17:16:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=81=E5=BC=8F=E8=BE=93=E5=87=BA?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20-=20=E6=80=9D=E8=80=83=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E6=B5=81=E5=BC=8F=E6=98=BE=E7=A4=BA=E5=90=8E=E6=8A=98=E5=8F=A0?= =?UTF-8?q?=EF=BC=8C=E5=9B=9E=E7=AD=94=E5=86=85=E5=AE=B9=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main_v2.py | 64 +++++++++++++----- templates/index.html | 153 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 170 insertions(+), 47 deletions(-) diff --git a/main_v2.py b/main_v2.py index 8242760..649d4c5 100644 --- a/main_v2.py +++ b/main_v2.py @@ -638,39 +638,71 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str): # 获取对话历史 history = conv_service.get_conversation_history(conversation_id, limit=agent_config['agent'].get('max_history', 20)) - # 调用LLM + # 使用流式调用LLM try: - if agent_config['agent'].get('enable_thinking') and enable_thinking: - await websocket.send_json({ - "type": "thinking_start", - "conversation_id": conversation_id - }) + thinking_buffer = "" + content_buffer = "" + in_thinking = False - response, thinking_content = await llm_service.chat( + # 流式生成 + async for chunk in llm_service.chat_stream( messages=history, provider_config=agent_config['provider'], agent_config=agent_config['agent'], enable_thinking=enable_thinking - ) + ): + chunk_type = chunk.get('type', 'content') + chunk_text = chunk.get('text', '') + + if chunk_type == 'thinking': + if not in_thinking: + in_thinking = True + await websocket.send_json({ + "type": "thinking_start", + "conversation_id": conversation_id + }) + thinking_buffer += chunk_text + await websocket.send_json({ + "type": "thinking_stream", + "conversation_id": conversation_id, + "text": chunk_text + }) + elif chunk_type == 'content': + if in_thinking: + in_thinking = False + await websocket.send_json({ + "type": "thinking_end", + "conversation_id": conversation_id, + "content": thinking_buffer # 发送完整思考内容用于折叠 + }) + content_buffer += chunk_text + await websocket.send_json({ + "type": "content_stream", + "conversation_id": conversation_id, + "text": chunk_text + }) - if thinking_content: + # 如果思考没结束就结束了,发送思考结束 + if in_thinking: await websocket.send_json({ - "type": "thinking_content", + "type": "thinking_end", "conversation_id": conversation_id, - "content": thinking_content + "content": thinking_buffer }) + # 发送完成通知 await websocket.send_json({ - "type": "thinking_end", + "type": "stream_end", "conversation_id": conversation_id }) + # 保存AI回复 assistant_msg = conv_service.add_message( conversation_id=conversation.id, role='assistant', - content=response, + content=content_buffer, source='web', - thinking_content=thinking_content, + thinking_content=thinking_buffer if thinking_buffer else None, agent_id=current_agent_id, model_used=agent_config['provider'].get('default_model') ) @@ -681,8 +713,8 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str): "message": { "id": assistant_msg.id, "role": "assistant", - "content": response, - "thinking_content": thinking_content, + "content": content_buffer, + "thinking_content": thinking_buffer if thinking_buffer else None, "source": "web", "agent_id": current_agent_id, "agent_name": agent_config['agent'].get('display_name'), diff --git a/templates/index.html b/templates/index.html index 484cf39..ff083a7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -349,7 +349,6 @@ switch (data.type) { case 'history': displayHistory(data.messages); - // 更新Agent选择框为该对话使用的Agent if (data.agent_id) { currentAgentId = data.agent_id; updateAgentSelect(data.agent_id); @@ -368,14 +367,23 @@ currentAgentId = data.agent_id; console.log('Agent已切换:', data.agent_name); break; + // 流式输出处理 case 'thinking_start': - showThinkingIndicator(); + createStreamingMessage('thinking'); break; - case 'thinking_content': - updateThinkingContent(data.content); + case 'thinking_stream': + appendStreamText('thinking', data.text); break; case 'thinking_end': - hideThinkingIndicator(); + collapseThinking(data.content); + createStreamingMessage('content'); + break; + case 'content_stream': + appendStreamText('content', data.text); + break; + case 'stream_end': + finishStreaming(); + document.getElementById('sendBtn').disabled = false; break; case 'user_message': appendMessage('user', data.message.content, data.message.source); @@ -385,46 +393,129 @@ } break; case 'assistant_message': - appendMessage('assistant', data.message.content, data.message.source, data.message.thinking_content, data.message.agent_name); - document.getElementById('sendBtn').disabled = false; + // 流式完成后,更新最终消息(添加Agent信息等) + updateFinalMessage(data.message); break; case 'error': - alert(data.message); + showError(data.message); document.getElementById('sendBtn').disabled = false; break; } } - // 显示思考指示器 - function showThinkingIndicator() { + // 创建流式消息容器 + function createStreamingMessage(type) { const container = document.getElementById('messagesContainer'); - const thinking = document.createElement('div'); - thinking.id = 'thinkingIndicator'; - thinking.className = 'message assistant'; - thinking.innerHTML = ` -
- - `; - container.appendChild(thinking); + container.querySelector('.welcome')?.remove(); + + const existingStream = document.getElementById('streaming-message'); + if (existingStream && type === 'content') return; // 回答区域已存在 + + const div = document.createElement('div'); + div.id = 'streaming-message'; + div.className = 'message assistant'; + + if (type === 'thinking') { + div.innerHTML = ` + + + `; + } else { + div.innerHTML = ` + + + `; + } + container.appendChild(div); container.scrollTop = container.scrollHeight; } - // 更新思考内容 - function updateThinkingContent(content) { - const indicator = document.getElementById('thinkingIndicator'); - if (indicator) { - indicator.querySelector('.message-content').innerHTML = ` -