From 2248ddce155bc2c5da8c23b6293397db96364349 Mon Sep 17 00:00:00 2001 From: Aska Date: Sun, 6 Sep 2026 17:43:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E7=94=A8=E6=88=B7=E4=BE=A7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C=E8=A1=A5=E9=BD=90=E2=80=94=E2=80=94=E6=80=9D?= =?UTF-8?q?=E7=BB=B4=E9=93=BE/=E5=B7=A5=E5=85=B7=E5=8D=A1=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=BA=90=20+=20=E5=9B=BE=E7=89=87=E9=99=84=E4=BB=B6?= =?UTF-8?q?=20+=20=E8=AF=AD=E9=9F=B3=E4=B8=8A=E8=A1=8C=20+=20home/vision/v?= =?UTF-8?q?oice=20=E7=9C=9F=E8=A1=A8=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 打通 thinking/tool_call 运行时数据源:openai 发 thinking_*(reasoning_content)/tool_call_* (delta.tool_calls 累积重新组装 Args 非 JSON 串);anthropic_stream 发 thinking_*(content_block thinking start/delta/stop)。ws_bridge 已映射 thinking.*/tool_call.*,前端渲染活。 - 前端消息模型 + 附件:ChatMessage 加 image?;ChatInput 加附件按钮(file→base64)、预览/移除、 image-aware onSend(text,image);VisionMessageBody 升级图像卡+识别 chip;VoiceMessageBody 升级通话控制卡(live2D预留/波形/静音/挂断);HomeMessageBody 读真实 deviceUpdates(去掉 sampleDevices 猜)。 - 激活 voice_input:ChatShell onVoice 不再 no-op → sendUp voice_input + 切 voice mode。 - addTool 改按工具名 upsert(修复 tool_call.start/end 重复卡);ws_mode_switch 应用 agent 驱动 mode;addUserMessage/beginAssistant 带 image/mode;chatStream/useChat.send 带 image。 - 新增 image/phone 图标。 已知限制(标注):图片仅前端收集(后端 chatRequest 无 image 字段未接真推理);流式工具卡无真实 工具名(StreamEvent 只带 ID,回退 tool_);mode 由 agent 驱动但后端未逐消息下发 mode (mode_switch 回显+默认当前 mode 兜底);voice 为 UI+voice_input 上行(真实通话后置)。 go build/vet/test + 前端 tsc 绿。 --- frontend/web/src/components/ChatInput.tsx | 117 +++++++++---- frontend/web/src/components/Icon.tsx | 8 +- frontend/web/src/components/chat.tsx | 19 ++- .../web/src/components/messages/index.tsx | 125 +++++++++++--- frontend/web/src/lib/api.ts | 13 +- frontend/web/src/lib/types.ts | 5 + frontend/web/src/lib/useChat.ts | 22 ++- frontend/web/src/lib/ws.ts | 11 +- frontend/web/src/modes/ChatShell.tsx | 14 +- frontend/web/src/store/useAppStore.ts | 16 +- internal/runtime/model/anthropic_stream.go | 18 +- internal/runtime/model/anthropic_test.go | 57 +++++++ internal/runtime/model/openai.go | 161 ++++++++++++++++-- internal/runtime/model/openai_test.go | 92 ++++++++++ 14 files changed, 576 insertions(+), 102 deletions(-) diff --git a/frontend/web/src/components/ChatInput.tsx b/frontend/web/src/components/ChatInput.tsx index 62ebefe..95250e8 100644 --- a/frontend/web/src/components/ChatInput.tsx +++ b/frontend/web/src/components/ChatInput.tsx @@ -3,7 +3,7 @@ import { Button } from "./primitives"; import { Icon } from "./Icon"; export interface ChatInputProps { - onSend: (text: string) => void; + onSend: (text: string, image?: string) => void; sending: boolean; onVoice: () => void; placeholder?: string; @@ -16,7 +16,9 @@ export function ChatInput({ placeholder = "给昔涟发消息…", }: ChatInputProps): React.ReactNode { const [value, setValue] = useState(""); + const [pendingImage, setPendingImage] = useState(undefined); const taRef = useRef(null); + const fileRef = useRef(null); // auto-grow the textarea up to a max height useEffect(() => { @@ -26,45 +28,92 @@ export function ChatInput({ el.style.height = Math.min(el.scrollHeight, 140) + "px"; }, [value]); + const onPickFile = (e: React.ChangeEvent) => { + const f = e.target.files?.[0]; + if (!f) return; + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result === "string") setPendingImage(reader.result); + }; + reader.readAsDataURL(f); + e.target.value = ""; + }; + + const canSend = value.trim().length > 0 || Boolean(pendingImage); + const submit = () => { - const t = value.trim(); - if (!t || sending) return; - onSend(t); + if (sending || !canSend) return; + onSend(value.trim(), pendingImage); setValue(""); + setPendingImage(undefined); }; return (
-
-