fix: 修复 AI 回复无法送达发送者 + 重复消息 + action角色泄露 + OS环境支持

广播逻辑重构:
- AI 回复 (stream_start/response/stream_segments/multi_message/stream_end) 改用 broadcastToUser 发送给所有客户端
- 用户消息回显保持 broadcastToUserExcept 排除发送者

消息去重与角色修复:
- CacheMessage(user) 移至回复生成后,避免本轮 LLM 调用出现重复用户消息
- action 角色消息在 DB 存储时映射为 assistant,DeepSeek 等模型不支持自定义角色
- stream_end defer 机制确保错误路径也会终止客户端思考指示器

OS 完整环境支持:
- host 包重构为 HostBackend 接口 + Direct/WSL/Docker 三种后端
- 新增 os_exec/os_file/os_system 工具供 AI 在完整 Linux 环境中自由操作

其他:
- 视觉模型注入 + 图片预处理后清空 Images 避免传给 Chat 模型
- 图片 URL 相对路径→绝对 URL 转换
- DevTools 链路追踪页面 + 重启修复
- 记忆搜索模糊匹配增强
- 后台思考定时调度支持
- 管理后台页面 (模型配置/用户管理等)
- docs/api 更新广播机制说明

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 12:46:17 +08:00
parent aac64ed8b7
commit 91c9ee4b2d
49 changed files with 5032 additions and 299 deletions
+12 -1
View File
@@ -170,7 +170,7 @@ ws://<gateway>/ws/chat?token=<jwt>&session_id=<optional>&client_id=<optional>&de
"type": "message|voice_input|ping|history",
"session_id": "string (可选)",
"mode": "text|voice_msg|voice_assistant",
"content": "string (message 类型必填)",
"content": "string (纯图片消息可留空,文字+图片时填写提问内容)",
"audio_data": "string (voice_input 类型必填, base64)",
"attachments": [
{
@@ -250,6 +250,10 @@ ws://<gateway>/ws/chat?token=<jwt>&session_id=<optional>&client_id=<optional>&de
| `device_update` | IoT 设备状态更新 |
| `background_thinking` | 后台思考状态变更 |
> **广播机制**:服务端推送分为两类:
> - **用户消息回显**`type: "response"`, `role: "user"`):通过 `SendToUserExcept` 广播,排除发送者自身(发送者本地已渲染),仅同步到同用户的其他设备。
> - **AI 回复消息**`stream_start`、`stream_end`、`response`/`review`、`multi_message`、`stream_segments` 等 `role: "assistant"` 的消息):通过 `SendToUser` 广播给所有设备,包括发送者。
> **消息类型分类 (`msg_type`)**:所有 `response`、`multi_message`、`history_response`、`stream_chunk`、`thinking`、`tool_progress`、`system_info` 类型的服务端消息中,`msg_type` 字段均由后端自动分类填充,前端只需直接读取 `msg_type` 并据此渲染,无需解析消息内容来猜测类型。
>
> `msg_type` 可选值:
@@ -482,6 +486,13 @@ Content-Type: `multipart/form-data`。字段 `file`。最大 20MB。
错误: 400 `{"error":"文件大小超过限制 (最大 20MB)","errorType":"file_too_large"}`, 400 `{"error":"不支持的文件类型: ...","errorType":"unsupported_type"}`
> **文件在 AI 对话中的传递链路**:客户端上传文件后获得的 `url` 为相对路径(如 `/api/v1/files/{id}/download`)。当用户消息携带 `attachments` 时:
> 1. **Gateway** 在转发前将相对路径补全为绝对 URL`http://127.0.0.1:{port}/api/v1/files/{id}/download`
> 2. **AI-Core** 的 LLM 适配器在调用外部模型 API 前,将非 `data:` 的图片 URL 下载并转为 base64 data URL
> 3. 最终以多模态格式(`[{type: "text", text: "..."}, {type: "image_url", image_url: {url: "data:..."}}]`)传递给 LLM
>
> 即文件存储层对外部 LLM API 透明,无需暴露内网文件服务。
### GET /files — 列表
`?page=1&limit=20`