From 747b9ad4141b1623a5edfa564326448d99914402 Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 8 Sep 2026 21:07:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E8=81=8A=E5=A4=A9=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=90=BA=E5=B8=A6=E8=A1=8C=E4=B8=BA=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=20mode(A3)=20=E2=80=94=20useChat=20=E8=AF=BB=E5=BD=93=E5=89=8D?= =?UTF-8?q?=20mode=20=E7=BB=8F=20REST=20=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api.ts chatStream 新增可选 mode,空则不写键(后端回落chat) - useChat.ts 发送时带上 useModeStore 当前 mode,使非chat模式消息在Web端真正落库 - 修正 docblock(移除虚构的WS chat回退描述) --- frontend/web/src/lib/api.ts | 15 ++++++++++++--- frontend/web/src/lib/useChat.ts | 14 +++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/web/src/lib/api.ts b/frontend/web/src/lib/api.ts index c7b7d78..bbb1aea 100644 --- a/frontend/web/src/lib/api.ts +++ b/frontend/web/src/lib/api.ts @@ -248,15 +248,24 @@ export function chatSync( * `image` (a base64 data: URI or URL) is attached for the vision path; the * gateway's chat body currently has no image field, so an unconfigured backend * simply ignores it. `deviceId` mirrors the client's per-browser id onto the - * persisted message (design/16 §4.5). */ + * persisted message (design/16 §4.5). `mode` is the behavior mode this turn is + * sent in (design/01 §3); omitted when empty so the backend falls back to chat. */ export function chatStream( sessionId: string, text: string, - image?: string + image?: string, + mode?: Mode ): Promise<{ stream: true }> { return request<{ stream: true }>("/chat", { method: "POST", - body: JSON.stringify({ sessionId, text, image, stream: true, deviceId: getClientId() }), + body: JSON.stringify({ + sessionId, + text, + image, + stream: true, + deviceId: getClientId(), + ...(mode ? { mode } : {}), + }), }); } diff --git a/frontend/web/src/lib/useChat.ts b/frontend/web/src/lib/useChat.ts index f32220e..c74e3c5 100644 --- a/frontend/web/src/lib/useChat.ts +++ b/frontend/web/src/lib/useChat.ts @@ -8,13 +8,13 @@ import { now } from "./util"; /** Drives one user turn: push the user bubble, blanket a streaming assistant * bubble, trigger the backend loop over REST (`stream:true`), and let the WS - * stream render into that bubble. Falls back to the WS `chat` channel when the - * REST trigger fails so the demo still works without a wired loop. + * stream render into that bubble. If the REST trigger fails, the failure is + * surfaced inline on the bubble rather than left streaming forever. * - * The behavior mode is NOT chosen by the user at send time — the agent / backend - * hot-switches it (design/01 §3), and each message carries its own mode, so the - * bubble renders per that message's mode. Until the backend advertises a - * per-message mode, a new assistant bubble defaults to the current UI mode. */ + * The behavior mode is chosen by the agent / backend hot-switch (design/01 §3); + * each message carries the mode in effect when the turn is sent, and the bubble + * renders per that message's mode. We read the current UI mode here and pass it + * on the turn so the backend persists it alongside the message (A3). */ export function useSendChat(): { send: (text: string, image?: string) => Promise; sending: boolean; @@ -46,7 +46,7 @@ export function useSendChat(): { beginAssistant(mode, clientId); try { - await chatStream(sessionId, prompt, image); + await chatStream(sessionId, prompt, image, mode); } catch (err: unknown) { // Backend loop not wired or gateway unreachable: show the failure inline // rather than leaving a permanently-streaming bubble.