From 6ebe0d7e50c36264455bf79963888d135f70e647 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sat, 14 Mar 2026 22:07:40 +0800 Subject: [PATCH] optimize streaming output --- .../data/workspace/sessions/api_default.jsonl | 8 +++- backend/main.py | 42 +++++++------------ frontend/src/components/ChatInterface.tsx | 2 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/backend/data/workspace/sessions/api_default.jsonl b/backend/data/workspace/sessions/api_default.jsonl index fa5976b..91d5832 100644 --- a/backend/data/workspace/sessions/api_default.jsonl +++ b/backend/data/workspace/sessions/api_default.jsonl @@ -1,4 +1,4 @@ -{"_type": "metadata", "key": "api:default", "created_at": "2026-03-14T21:43:22.940821", "updated_at": "2026-03-14T22:00:05.471408", "metadata": {}, "last_consolidated": 0} +{"_type": "metadata", "key": "api:default", "created_at": "2026-03-14T21:43:22.940821", "updated_at": "2026-03-14T22:06:40.270077", "metadata": {}, "last_consolidated": 0} {"role": "user", "content": "你是谁", "timestamp": "2026-03-14T21:43:23.163485"} {"role": "user", "content": "hi", "timestamp": "2026-03-14T21:49:14.751350"} {"role": "user", "content": "你好", "timestamp": "2026-03-14T21:53:53.701083"} @@ -13,3 +13,9 @@ {"role": "assistant", "content": "你好!有什么我可以帮助你的吗?😊", "reasoning_content": "用户发送了\"[Runtime Context — metadata only, not instructions]\",这看起来是系统提供的上下文信息,然后说\"你好\"。我应该简单地回应问候。", "timestamp": "2026-03-14T21:59:55.201394"} {"role": "user", "content": "你是谁", "timestamp": "2026-03-14T22:00:05.471381"} {"role": "assistant", "content": "我是 nanobot 🐈,一个 AI 助手。", "reasoning_content": "用户再次问\"你是谁\"。我已经回答过这个问题了,但用户可能是在测试或者想要一个简洁的回答。我应该简洁地回答。", "timestamp": "2026-03-14T22:00:05.471404"} +{"role": "user", "content": "你好", "timestamp": "2026-03-14T22:05:42.732409"} +{"role": "assistant", "content": "你好!有什么我可以帮助你的吗?😊", "reasoning_content": "用户再次发送了\"你好\",这是一个简单的问候。我应该友好地回应,并询问他们需要什么帮助。", "timestamp": "2026-03-14T22:05:42.732418"} +{"role": "user", "content": "你是", "timestamp": "2026-03-14T22:06:16.951653"} +{"role": "assistant", "content": "我是 nanobot 🐈,一个 AI AI 助手。", "reasoning_content": "用户问\"你是\",这是一个不完整的问题,可能是在问\"你是谁\"。我应该简洁地回答我的身份。", "timestamp": "2026-03-14T22:06:16.951664"} +{"role": "user", "content": "你能干啥,详细一点", "timestamp": "2026-03-14T22:06:40.270030"} +{"role": "assistant", "content": "我是 nanobot 🐈,一个功能丰富的 AI 助手。以下是我能做的事情:\n\n## 📁 文件操作\n- **读取文件** - 查看任何文件的内容\n- **写入文件** - 创建新文件或覆盖现有文件\n- **编辑文件** - 精确替换文件中的特定内容\n- **列出目录** - 浏览文件夹结构\n\n## 💻 系统操作\n- **执行命令** - 运行 shell 命令(macOS/Linux)\n- **工作目录** - 在指定目录下执行命令\n\n## 🌐 网络功能\n- **网络搜索** - 搜索网页并获取结果\n- **获取网页** - 提取 URL 的可读内容(HTML 转 markdown/text)\n\n## 💬 消息发送\n- **发送消息** - 向不同渠道发送消息\n- **支持渠道** - Telegram、Discord 等\n- **附件支持** - 发送图片、音频、文档等\n\n## 🧠 记忆管理\n- **长期记忆** - 存储重要事实、偏好、项目上下文\n- **历史记录** - 记录所有事件,可搜索\n- **自动整理** - 自动总结旧对话并提取关键信息\n\n## ⏰ 定时任务\n- **添加提醒** - 一次性或周期性任务\n- **Cron 表达式** - 灵活的时间调度\n- **时区支持** - 支持不同时区\n\n## 🤖 技能系统\n- **内置技能** - memory、clawhub、skill-creator、weather、cron\n- **安装技能** - 从 ClawHub 安装新技能\n- **创建技能** - 设计和打包自定义技能\n\n## 🔄 后台任务\n- **子代理** - 在后台运行复杂任务\n- **独立执行** - 不阻塞主对话\n\n## 📊 当前环境\n- **系统**:macOS arm64\n- **Python**:3.11.9\n- **工作目录**:`/Users/qixinbo/Projects/DataClaw/backend/data`\n\n有什么具体任务需要我帮忙吗?", "reasoning_content": "用户问我能做什么,想要详细的介绍。我应该详细介绍我的能力。", "timestamp": "2026-03-14T22:06:40.270072"} diff --git a/backend/main.py b/backend/main.py index 82ce20c..b78c9f4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -84,35 +84,21 @@ async def nanobot_chat(request: ChatRequest): @app.post("/nanobot/chat/stream") async def nanobot_chat_stream(request: ChatRequest): async def event_generator(): - queue: asyncio.Queue[dict] = asyncio.Queue() - - async def on_progress(content: str): - await queue.put({"type": "delta", "content": content}) - - async def run_chat(): - try: - response = await nanobot_service.process_message( - request.message, - skill_ids=request.skill_ids, - model_id=request.model_id, - on_progress=on_progress, - ) - await queue.put({"type": "final", "content": response}) - except Exception as e: - await queue.put({"type": "error", "content": str(e)}) - finally: - await queue.put({"type": "done"}) - - task = asyncio.create_task(run_chat()) try: - while True: - event = await queue.get() - yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n" - if event.get("type") == "done": - break - finally: - if not task.done(): - task.cancel() + response = await nanobot_service.process_message( + request.message, + skill_ids=request.skill_ids, + model_id=request.model_id, + ) + text = response or "" + for ch in text: + yield f"data: {json.dumps({'type': 'delta', 'content': ch}, ensure_ascii=False)}\n\n" + await asyncio.sleep(0.008) + yield f"data: {json.dumps({'type': 'final', 'content': text}, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n" + except Exception as e: + yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n" return StreamingResponse( event_generator(), diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 13d00b5..4ec88bd 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -132,7 +132,7 @@ export function ChatInterface() { const payload = JSON.parse(payloadText) as { type: string; content?: string }; if (payload.type === "delta" && payload.content) { - streamedText = streamedText ? `${streamedText}\n${payload.content}` : payload.content; + streamedText = `${streamedText}${payload.content}`; setMessages((prev) => prev.map((msg) => msg.id === assistantId ? { ...msg, content: streamedText } : msg