@flowingspring/dsh-voco 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/lib/client.js +25 -194
- package/lib/client.js.map +1 -1
- package/lib/plugins/voice-assistant.js +88 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ The plugin uses `XingChenAGI/XingChenASR-V3.2-Ultra` for cloud speech recognitio
|
|
|
37
37
|
- Speaks a short contextual acknowledgement before starting delegated Agent work.
|
|
38
38
|
- Keeps full task reports in the task UI while speaking a concise, purpose-written result.
|
|
39
39
|
- Supports interruption, browser navigation, reconnects, and restored conversation history.
|
|
40
|
+
- Pure voice sessions get a concise title from the first meaningful voice request, with the transcription as a fallback when the title model is unavailable.
|
|
40
41
|
- Ships the server and browser surfaces as one public npm package.
|
|
41
42
|
|
|
42
43
|
## Configuration
|
package/README.zh.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -8,21 +8,21 @@ window.__ModuleLoader__.load({
|
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
10
10
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceControl.module.css.mjs
|
|
11
|
-
const css$
|
|
12
|
-
const tagId$
|
|
13
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$
|
|
11
|
+
const css$3 = ".TFZsua_controls{align-items:center;gap:2px;display:inline-flex}.TFZsua_button{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:8px;justify-content:center;align-items:center;padding:0;display:inline-flex}.TFZsua_button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.TFZsua_active{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover-solid)}.TFZsua_button[data-muted=true]{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, currentColor 12%, transparent)}.TFZsua_interruptButton,.TFZsua_interruptButton:hover{color:var(--dsw-alias-state-error-primary)}.TFZsua_button[data-muted=true]:hover{background:color-mix(in srgb, currentColor 18%, transparent)}.TFZsua_button[data-state=speaking][data-muted=false]{animation:1.2s ease-in-out infinite TFZsua_voice-pulse}@keyframes TFZsua_voice-pulse{50%{opacity:.55;transform:scale(.94)}}@media (prefers-reduced-motion:reduce){.TFZsua_button[data-state=speaking][data-muted=false]{animation:none}}";
|
|
12
|
+
const tagId$3 = "@flowingspring/dsh-voco/VoiceControl.module.css";
|
|
13
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
14
14
|
const tag = document.createElement("style");
|
|
15
15
|
tag.dataset.plugin = "@flowingspring/dsh-voco";
|
|
16
|
-
tag.dataset.pluginCss = tagId$
|
|
17
|
-
tag.textContent = css$
|
|
16
|
+
tag.dataset.pluginCss = tagId$3;
|
|
17
|
+
tag.textContent = css$3;
|
|
18
18
|
document.head.appendChild(tag);
|
|
19
19
|
}
|
|
20
20
|
var VoiceControl_module_css_default = {
|
|
21
21
|
"interruptButton": "TFZsua_interruptButton",
|
|
22
|
-
"button": "TFZsua_button",
|
|
23
|
-
"controls": "TFZsua_controls",
|
|
24
22
|
"active": "TFZsua_active",
|
|
25
|
-
"voice-pulse": "TFZsua_voice-pulse"
|
|
23
|
+
"voice-pulse": "TFZsua_voice-pulse",
|
|
24
|
+
"button": "TFZsua_button",
|
|
25
|
+
"controls": "TFZsua_controls"
|
|
26
26
|
};
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region ../ui-voice/src/client/VoiceControl.tsx
|
|
@@ -130,128 +130,6 @@ window.__ModuleLoader__.load({
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
//#endregion
|
|
133
|
-
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceHistoryAction.module.css.mjs
|
|
134
|
-
const css$3 = "._3t8AFq_trigger{width:100%;min-height:36px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:8px;justify-content:center;align-items:center;gap:9px;display:flex}._3t8AFq_trigger:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._3t8AFq_panel{z-index:60;border:1px solid var(--dsw-alias-border-l2);width:min(320px,100vw - 32px);max-height:min(480px,100vh - 96px);color:var(--dsw-alias-label-primary);background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);border-radius:14px;flex-direction:column;display:flex;position:fixed;bottom:64px;left:16px;overflow:hidden}._3t8AFq_header{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;min-height:48px;padding:0 12px 0 16px;display:flex}._3t8AFq_close{width:28px;height:28px;color:inherit;cursor:pointer;background:0 0;border:0;border-radius:7px;font-size:20px;line-height:1}._3t8AFq_close:hover{background:var(--dsw-alias-interactive-bg-hover)}._3t8AFq_list{padding:8px;overflow:auto}._3t8AFq_empty{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;padding:24px 12px;font-size:13px}._3t8AFq_row{width:100%;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:9px;align-items:center;gap:10px;padding:10px;display:flex}._3t8AFq_row:hover,._3t8AFq_row[data-active=true]{background:var(--dsw-alias-interactive-bg-hover)}._3t8AFq_wave{width:24px;height:24px;color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, currentColor 10%, transparent);border-radius:50%;flex:none;justify-content:center;align-items:center;gap:2px;display:flex}._3t8AFq_wave i{background:currentColor;border-radius:2px;width:2px}._3t8AFq_wave i:first-child{height:6px}._3t8AFq_wave i:nth-child(2){height:11px}._3t8AFq_wave i:nth-child(3){height:8px}._3t8AFq_rowText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}._3t8AFq_title{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}._3t8AFq_meta{color:var(--dsw-alias-label-tertiary);font-size:11px}";
|
|
135
|
-
const tagId$3 = "@flowingspring/dsh-voco/VoiceHistoryAction.module.css";
|
|
136
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
137
|
-
const tag = document.createElement("style");
|
|
138
|
-
tag.dataset.plugin = "@flowingspring/dsh-voco";
|
|
139
|
-
tag.dataset.pluginCss = tagId$3;
|
|
140
|
-
tag.textContent = css$3;
|
|
141
|
-
document.head.appendChild(tag);
|
|
142
|
-
}
|
|
143
|
-
var VoiceHistoryAction_module_css_default = {
|
|
144
|
-
"trigger": "_3t8AFq_trigger",
|
|
145
|
-
"panel": "_3t8AFq_panel",
|
|
146
|
-
"title": "_3t8AFq_title",
|
|
147
|
-
"meta": "_3t8AFq_meta",
|
|
148
|
-
"wave": "_3t8AFq_wave",
|
|
149
|
-
"rowText": "_3t8AFq_rowText",
|
|
150
|
-
"row": "_3t8AFq_row",
|
|
151
|
-
"header": "_3t8AFq_header",
|
|
152
|
-
"list": "_3t8AFq_list",
|
|
153
|
-
"empty": "_3t8AFq_empty",
|
|
154
|
-
"close": "_3t8AFq_close"
|
|
155
|
-
};
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region ../ui-voice/src/client/VoiceHistoryAction.tsx
|
|
158
|
-
/** Render the plugin-owned Voice history entry and its Session navigation panel. */
|
|
159
|
-
function VoiceHistoryAction({ wide, useSessions, useVoice, useVoiceHistory, openSession, t }) {
|
|
160
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
161
|
-
const sessions = useSessions((state) => state);
|
|
162
|
-
const entries = useVoiceHistory((state) => state.entries);
|
|
163
|
-
const activeSessionId = useVoice((state) => state.sessionId);
|
|
164
|
-
const available = entries.flatMap((entry) => {
|
|
165
|
-
const session = sessions.byId[entry.sessionId];
|
|
166
|
-
return session === void 0 ? [] : [{
|
|
167
|
-
entry,
|
|
168
|
-
session
|
|
169
|
-
}];
|
|
170
|
-
});
|
|
171
|
-
(0, react.useEffect)(() => {
|
|
172
|
-
if (!open) return;
|
|
173
|
-
const close = (event) => {
|
|
174
|
-
if (event.key === "Escape") setOpen(false);
|
|
175
|
-
};
|
|
176
|
-
document.addEventListener("keydown", close);
|
|
177
|
-
return () => {
|
|
178
|
-
document.removeEventListener("keydown", close);
|
|
179
|
-
};
|
|
180
|
-
}, [open]);
|
|
181
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
182
|
-
type: "button",
|
|
183
|
-
className: VoiceHistoryAction_module_css_default.trigger,
|
|
184
|
-
"aria-label": t("history.open"),
|
|
185
|
-
"aria-haspopup": "dialog",
|
|
186
|
-
"aria-expanded": open,
|
|
187
|
-
onClick: () => {
|
|
188
|
-
setOpen((value) => !value);
|
|
189
|
-
},
|
|
190
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
191
|
-
viewBox: "0 0 16 16",
|
|
192
|
-
width: "16",
|
|
193
|
-
height: "16",
|
|
194
|
-
"aria-hidden": true,
|
|
195
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
196
|
-
d: "M3 8a5 5 0 1 0 1.5-3.55M3 2.7v2.8h2.8M8 5.2V8l2 1.2",
|
|
197
|
-
fill: "none",
|
|
198
|
-
stroke: "currentColor",
|
|
199
|
-
strokeWidth: "1.35",
|
|
200
|
-
strokeLinecap: "round",
|
|
201
|
-
strokeLinejoin: "round"
|
|
202
|
-
})
|
|
203
|
-
}), wide && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("history.label") })]
|
|
204
|
-
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
205
|
-
className: VoiceHistoryAction_module_css_default.panel,
|
|
206
|
-
role: "dialog",
|
|
207
|
-
"aria-label": t("history.title"),
|
|
208
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
209
|
-
className: VoiceHistoryAction_module_css_default.header,
|
|
210
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("history.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
211
|
-
type: "button",
|
|
212
|
-
className: VoiceHistoryAction_module_css_default.close,
|
|
213
|
-
"aria-label": t("history.close"),
|
|
214
|
-
onClick: () => {
|
|
215
|
-
setOpen(false);
|
|
216
|
-
},
|
|
217
|
-
children: "×"
|
|
218
|
-
})]
|
|
219
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
220
|
-
className: VoiceHistoryAction_module_css_default.list,
|
|
221
|
-
children: [available.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
222
|
-
className: VoiceHistoryAction_module_css_default.empty,
|
|
223
|
-
children: t("history.empty")
|
|
224
|
-
}), available.map(({ entry, session }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
225
|
-
type: "button",
|
|
226
|
-
className: VoiceHistoryAction_module_css_default.row,
|
|
227
|
-
"data-active": entry.sessionId === activeSessionId || void 0,
|
|
228
|
-
onClick: () => {
|
|
229
|
-
openSession(entry.sessionId);
|
|
230
|
-
setOpen(false);
|
|
231
|
-
},
|
|
232
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
233
|
-
className: VoiceHistoryAction_module_css_default.wave,
|
|
234
|
-
"aria-hidden": true,
|
|
235
|
-
children: [
|
|
236
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
237
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
238
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
|
|
239
|
-
]
|
|
240
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
241
|
-
className: VoiceHistoryAction_module_css_default.rowText,
|
|
242
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
243
|
-
className: VoiceHistoryAction_module_css_default.title,
|
|
244
|
-
children: session.displayTitle
|
|
245
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
246
|
-
className: VoiceHistoryAction_module_css_default.meta,
|
|
247
|
-
children: entry.sessionId === activeSessionId ? t("history.active") : t("history.saved")
|
|
248
|
-
})]
|
|
249
|
-
})]
|
|
250
|
-
}, entry.sessionId))]
|
|
251
|
-
})]
|
|
252
|
-
})] });
|
|
253
|
-
}
|
|
254
|
-
//#endregion
|
|
255
133
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceNodeViews.module.css.mjs
|
|
256
134
|
const css$2 = ".Kt2smW_utterance{flex-direction:column;align-items:flex-start;gap:6px;min-width:0;display:flex}.Kt2smW_utterance[data-role=user]{align-items:flex-end}.Kt2smW_bubble{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);max-width:min(525px,82%);color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;white-space:pre-wrap;border-radius:18px;padding:10px 16px;font-size:16px;line-height:24px}.Kt2smW_utterance[data-role=user] .Kt2smW_bubble{background:var(--dsw-specific-bubble);border-color:#0000}.Kt2smW_utterance[data-state=streaming] .Kt2smW_bubble{color:var(--dsw-alias-label-secondary)}.Kt2smW_meta,.Kt2smW_voiceBadge{align-items:center;display:inline-flex}.Kt2smW_meta{color:var(--dsw-alias-label-tertiary);gap:8px;padding:0 5px;font-size:10px;line-height:14px}.Kt2smW_voiceBadge{color:var(--dsw-alias-state-business-primary);letter-spacing:.04em;gap:4px;font-weight:600}.Kt2smW_miniWave{align-items:center;gap:1px;height:10px;display:inline-flex}.Kt2smW_miniWave i{background:currentColor;border-radius:1px;width:1.5px;height:4px}.Kt2smW_miniWave i:nth-child(2){height:8px}.Kt2smW_miniWave i:nth-child(3){height:6px}.Kt2smW_taskCard{box-sizing:border-box;width:fit-content;min-width:0;max-width:min(525px,100%)}.Kt2smW_taskSummary{min-width:0;max-width:100%;color:inherit;cursor:pointer;font:inherit;background:0 0;border:0;border-radius:6px;align-items:center;gap:7px;margin:-3px -5px;padding:3px 5px;display:inline-flex}.Kt2smW_taskSummary:hover{background:var(--dsw-alias-bg-layer-1)}.Kt2smW_taskDot{background:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:7px;height:7px}.Kt2smW_taskCard[data-status=completed] .Kt2smW_taskDot{background:var(--dsw-alias-state-success-primary)}.Kt2smW_taskCard[data-status=failed] .Kt2smW_taskDot{background:var(--dsw-alias-state-error-primary)}.Kt2smW_taskCard[data-status=cancelled] .Kt2smW_taskDot,.Kt2smW_taskCard[data-status=interrupted] .Kt2smW_taskDot{background:var(--dsw-alias-state-warn-primary)}.Kt2smW_taskTitle{min-width:0;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:600;line-height:20px;overflow:hidden}.Kt2smW_taskStatus{color:var(--dsw-alias-label-tertiary);flex:none;font-size:11px;line-height:16px}.Kt2smW_taskStatus:before{content:\"·\";margin-right:7px}.Kt2smW_taskChevron{border-right:1px solid var(--dsw-alias-label-tertiary);border-bottom:1px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin:0 2px 0 1px;transition:transform .12s;transform:rotate(-45deg)}.Kt2smW_taskCard[data-expanded=true] .Kt2smW_taskChevron{transform:rotate(45deg)}.Kt2smW_taskInput,.Kt2smW_taskUpdate{overflow-wrap:anywhere;margin:0;font-size:13px;line-height:20px}.Kt2smW_taskInput{color:var(--dsw-alias-label-primary)}.Kt2smW_taskUpdate{color:var(--dsw-alias-label-secondary);margin-top:4px}.Kt2smW_taskDetails{border-left:1px solid var(--dsw-alias-border-l2);margin:9px 0 0 3px;padding:2px 0 2px 16px}.Kt2smW_taskActions{align-items:center;gap:14px;margin-top:7px;display:flex}.Kt2smW_taskLink{color:var(--dsw-alias-state-business-primary);cursor:pointer;font:inherit;background:0 0;border:0;padding:0;font-size:12px;line-height:18px}.Kt2smW_taskLink:hover:not(:disabled){text-underline-position:from-font;text-decoration:underline}.Kt2smW_taskLink:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.Kt2smW_taskCancel{color:var(--dsw-alias-state-error-primary)}";
|
|
257
135
|
const tagId$2 = "@flowingspring/dsh-voco/VoiceNodeViews.module.css";
|
|
@@ -263,23 +141,23 @@ window.__ModuleLoader__.load({
|
|
|
263
141
|
document.head.appendChild(tag);
|
|
264
142
|
}
|
|
265
143
|
var VoiceNodeViews_module_css_default = {
|
|
266
|
-
"taskDetails": "Kt2smW_taskDetails",
|
|
267
|
-
"taskTitle": "Kt2smW_taskTitle",
|
|
268
|
-
"miniWave": "Kt2smW_miniWave",
|
|
269
|
-
"meta": "Kt2smW_meta",
|
|
270
|
-
"taskSummary": "Kt2smW_taskSummary",
|
|
271
144
|
"taskLink": "Kt2smW_taskLink",
|
|
145
|
+
"taskCard": "Kt2smW_taskCard",
|
|
146
|
+
"utterance": "Kt2smW_utterance",
|
|
272
147
|
"taskDot": "Kt2smW_taskDot",
|
|
273
148
|
"bubble": "Kt2smW_bubble",
|
|
274
|
-
"taskCard": "Kt2smW_taskCard",
|
|
275
|
-
"taskStatus": "Kt2smW_taskStatus",
|
|
276
149
|
"voiceBadge": "Kt2smW_voiceBadge",
|
|
277
|
-
"taskCancel": "Kt2smW_taskCancel",
|
|
278
|
-
"taskChevron": "Kt2smW_taskChevron",
|
|
279
|
-
"taskInput": "Kt2smW_taskInput",
|
|
280
150
|
"taskActions": "Kt2smW_taskActions",
|
|
151
|
+
"taskChevron": "Kt2smW_taskChevron",
|
|
152
|
+
"taskSummary": "Kt2smW_taskSummary",
|
|
153
|
+
"miniWave": "Kt2smW_miniWave",
|
|
154
|
+
"meta": "Kt2smW_meta",
|
|
155
|
+
"taskDetails": "Kt2smW_taskDetails",
|
|
281
156
|
"taskUpdate": "Kt2smW_taskUpdate",
|
|
282
|
-
"
|
|
157
|
+
"taskCancel": "Kt2smW_taskCancel",
|
|
158
|
+
"taskInput": "Kt2smW_taskInput",
|
|
159
|
+
"taskStatus": "Kt2smW_taskStatus",
|
|
160
|
+
"taskTitle": "Kt2smW_taskTitle"
|
|
283
161
|
};
|
|
284
162
|
//#endregion
|
|
285
163
|
//#region ../ui-voice/src/client/VoiceNodeViews.tsx
|
|
@@ -397,7 +275,7 @@ window.__ModuleLoader__.load({
|
|
|
397
275
|
}
|
|
398
276
|
//#endregion
|
|
399
277
|
//#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceOverlay.module.css.mjs
|
|
400
|
-
const css$1 = ".GPPB2G_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-overlay);max-width:min(
|
|
278
|
+
const css$1 = ".GPPB2G_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-overlay);max-width:min(260px,100vw - 36px);min-height:36px;box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);pointer-events:auto;border-radius:12px;align-items:center;gap:8px;padding:4px 6px 4px 12px;font-size:12px;line-height:18px;display:flex;position:absolute;bottom:18px;right:18px}.GPPB2G_wave{width:18px;height:18px;color:var(--dsw-alias-state-business-primary);flex:none;justify-content:center;align-items:center;gap:2px;display:inline-flex}.GPPB2G_wave i{background:currentColor;border-radius:1px;width:2px;height:5px;animation:.9s ease-in-out infinite alternate GPPB2G_voice-wave}.GPPB2G_wave i:nth-child(2){height:11px;animation-delay:-300ms}.GPPB2G_wave i:nth-child(3){height:8px;animation-delay:-600ms}.GPPB2G_wave i:nth-child(4){height:4px;animation-delay:-150ms}.GPPB2G_root[data-state=connecting] .GPPB2G_wave,.GPPB2G_root[data-state=error] .GPPB2G_wave{opacity:.55}.GPPB2G_root[data-state=error] .GPPB2G_wave{color:var(--dsw-alias-state-error-primary)}.GPPB2G_status{text-overflow:ellipsis;white-space:nowrap;flex:0 auto;min-width:0;overflow:hidden}.GPPB2G_root[data-muted=true] .GPPB2G_wave{color:var(--dsw-alias-state-error-primary);opacity:.65}.GPPB2G_root[data-muted=true] .GPPB2G_status{color:var(--dsw-alias-state-error-primary);font-weight:600}.GPPB2G_root[data-muted=true] .GPPB2G_wave i{animation:none}.GPPB2G_stop{min-height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:7px;flex:none;padding:0 8px}.GPPB2G_stop:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.GPPB2G_stop{color:var(--dsw-alias-state-error-primary)}@keyframes GPPB2G_voice-wave{to{transform:scaleY(.55)}}@media (prefers-reduced-motion:reduce){.GPPB2G_wave i{animation:none}}";
|
|
401
279
|
const tagId$1 = "@flowingspring/dsh-voco/VoiceOverlay.module.css";
|
|
402
280
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
403
281
|
const tag = document.createElement("style");
|
|
@@ -407,12 +285,11 @@ window.__ModuleLoader__.load({
|
|
|
407
285
|
document.head.appendChild(tag);
|
|
408
286
|
}
|
|
409
287
|
var VoiceOverlay_module_css_default = {
|
|
410
|
-
"stop": "GPPB2G_stop",
|
|
411
288
|
"root": "GPPB2G_root",
|
|
412
|
-
"
|
|
413
|
-
"link": "GPPB2G_link",
|
|
289
|
+
"status": "GPPB2G_status",
|
|
414
290
|
"wave": "GPPB2G_wave",
|
|
415
|
-
"
|
|
291
|
+
"voice-wave": "GPPB2G_voice-wave",
|
|
292
|
+
"stop": "GPPB2G_stop"
|
|
416
293
|
};
|
|
417
294
|
//#endregion
|
|
418
295
|
//#region ../ui-voice/src/client/VoiceOverlay.tsx
|
|
@@ -423,9 +300,8 @@ window.__ModuleLoader__.load({
|
|
|
423
300
|
error: "overlay.error"
|
|
424
301
|
};
|
|
425
302
|
/** Keep the active Voice Session controllable while another Session is selected. */
|
|
426
|
-
function VoiceOverlay({ useVoice,
|
|
303
|
+
function VoiceOverlay({ useVoice, stopVoice, t }) {
|
|
427
304
|
const voice = useVoice((snapshot) => snapshot);
|
|
428
|
-
const navigable = useSessions((sessions) => voice.sessionId !== void 0 && sessions.ids.includes(voice.sessionId));
|
|
429
305
|
if (voice.state === "off") return null;
|
|
430
306
|
const statusKey = voice.inputMuted && voice.state !== "error" ? "overlay.muted" : STATE_KEYS[voice.state];
|
|
431
307
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
@@ -449,15 +325,6 @@ window.__ModuleLoader__.load({
|
|
|
449
325
|
className: VoiceOverlay_module_css_default.status,
|
|
450
326
|
children: t(statusKey)
|
|
451
327
|
}),
|
|
452
|
-
voice.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
453
|
-
type: "button",
|
|
454
|
-
className: VoiceOverlay_module_css_default.link,
|
|
455
|
-
disabled: !navigable,
|
|
456
|
-
onClick: () => {
|
|
457
|
-
openVoiceSession(voice.sessionId);
|
|
458
|
-
},
|
|
459
|
-
children: t("overlay.return")
|
|
460
|
-
}),
|
|
461
328
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
462
329
|
type: "button",
|
|
463
330
|
className: VoiceOverlay_module_css_default.stop,
|
|
@@ -1490,18 +1357,10 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1490
1357
|
"control.unmute": "解除静音",
|
|
1491
1358
|
"overlay.connecting": "正在连接语音",
|
|
1492
1359
|
"overlay.listening": "正在聆听",
|
|
1493
|
-
"overlay.speaking": "
|
|
1360
|
+
"overlay.speaking": "正在回复",
|
|
1494
1361
|
"overlay.error": "语音连接失败",
|
|
1495
1362
|
"overlay.muted": "已静音",
|
|
1496
|
-
"overlay.return": "返回语音对话",
|
|
1497
1363
|
"overlay.stop": "结束",
|
|
1498
|
-
"history.open": "打开语音历史",
|
|
1499
|
-
"history.label": "语音历史",
|
|
1500
|
-
"history.title": "语音对话",
|
|
1501
|
-
"history.close": "关闭语音历史",
|
|
1502
|
-
"history.empty": "还没有语音对话",
|
|
1503
|
-
"history.active": "通话中",
|
|
1504
|
-
"history.saved": "已保存",
|
|
1505
1364
|
"utterance.user.pending": "正在聆听…",
|
|
1506
1365
|
"utterance.assistant.pending": "正在生成语音回复…",
|
|
1507
1366
|
"utterance.interrupted": "已打断",
|
|
@@ -1532,15 +1391,7 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1532
1391
|
"overlay.speaking": "Playing response",
|
|
1533
1392
|
"overlay.error": "Voice connection failed",
|
|
1534
1393
|
"overlay.muted": "Muted",
|
|
1535
|
-
"overlay.return": "Return to voice conversation",
|
|
1536
1394
|
"overlay.stop": "End",
|
|
1537
|
-
"history.open": "Open voice history",
|
|
1538
|
-
"history.label": "Voice history",
|
|
1539
|
-
"history.title": "Voice conversations",
|
|
1540
|
-
"history.close": "Close voice history",
|
|
1541
|
-
"history.empty": "No voice conversations yet",
|
|
1542
|
-
"history.active": "Active",
|
|
1543
|
-
"history.saved": "Saved",
|
|
1544
1395
|
"utterance.user.pending": "Listening…",
|
|
1545
1396
|
"utterance.assistant.pending": "Generating voice response…",
|
|
1546
1397
|
"utterance.interrupted": "Interrupted",
|
|
@@ -1610,9 +1461,6 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1610
1461
|
});
|
|
1611
1462
|
const voiceOverlayInjected = () => ({
|
|
1612
1463
|
hooks: { voice: controller },
|
|
1613
|
-
openVoiceSession: (id) => {
|
|
1614
|
-
ctx.sessions.open(id);
|
|
1615
|
-
},
|
|
1616
1464
|
stopVoice
|
|
1617
1465
|
});
|
|
1618
1466
|
const voiceSessionMarkersInjected = () => ({ hooks: { voiceHistory: history.snapshot } });
|
|
@@ -1626,16 +1474,6 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1626
1474
|
controller.cancelTask(id);
|
|
1627
1475
|
}
|
|
1628
1476
|
});
|
|
1629
|
-
const historyInjected = () => ({
|
|
1630
|
-
hooks: {
|
|
1631
|
-
voice: controller,
|
|
1632
|
-
voiceHistory: history.snapshot
|
|
1633
|
-
},
|
|
1634
|
-
openSession: (id) => {
|
|
1635
|
-
history.record(id);
|
|
1636
|
-
ctx.sessions.open(id);
|
|
1637
|
-
}
|
|
1638
|
-
});
|
|
1639
1477
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
1640
1478
|
zh,
|
|
1641
1479
|
en
|
|
@@ -1672,13 +1510,6 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
|
|
|
1672
1510
|
locale: NS,
|
|
1673
1511
|
inject: voiceSessionMarkersInjected
|
|
1674
1512
|
}, VoiceSessionMarkers));
|
|
1675
|
-
ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
|
|
1676
|
-
name: "sidebar.footer.action",
|
|
1677
|
-
id: "voice-history",
|
|
1678
|
-
order: 10,
|
|
1679
|
-
locale: NS,
|
|
1680
|
-
inject: historyInjected
|
|
1681
|
-
}, VoiceHistoryAction));
|
|
1682
1513
|
ctx.slots.inject("conversation.chat.node", () => ctx.slots.register({
|
|
1683
1514
|
name: "conversation.chat.node",
|
|
1684
1515
|
key: "voice-utterance",
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["css","useState","css","css","useState","css","css","createSnapshotStore"],"sources":["../../../packages/ui-voice/src/client/VoiceControl.tsx","../../../packages/ui-voice/src/client/VoiceHistoryAction.tsx","../../../packages/ui-voice/src/client/VoiceNodeViews.tsx","../../../packages/ui-voice/src/client/VoiceOverlay.tsx","../../../packages/ui-voice/src/client/VoiceSessionMarkers.tsx","../../../packages/ui-voice/src/client/voice-controller.ts","../../../packages/ui-voice/src/client/voice-history.ts","../../../packages/ui-voice/src/client/voice-text-submit.ts","../../../packages/ui-voice/src/client/voice-definitions.ts","../../../packages/ui-voice/src/client/locales.ts","../../../packages/ui-voice/src/client/index.ts"],"sourcesContent":["import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport css from './VoiceControl.module.css'\r\n\r\n/** Business callbacks and the root-owned transport snapshot. */\r\nexport interface VoiceControlInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n /** Start a fresh Voice conversation located from the current Session. */\n readonly startVoice: (sourceSessionId: SessionId) => Promise<void>\n readonly retryVoice: () => Promise<void>\n readonly interruptResponse: () => void\n readonly setVoiceMuted: (muted: boolean) => void\n}\n\r\nexport type VoiceControlProps =\r\n PropsRuntime<'conversation.input.right'> & InjectFace<VoiceControlInjected> & PropsLocale<'voice'>\r\n\r\n/** Start a fresh Voice Session from this Session's location or control the active connection. */\r\nexport function VoiceControl({\n sessionId, useVoice, startVoice, retryVoice, setVoiceMuted, t,\n interruptResponse,\n}: VoiceControlProps) {\n const state = useVoice(snapshot => snapshot.state)\n const activeSessionId = useVoice(snapshot => snapshot.sessionId)\n const inputMuted = useVoice(snapshot => snapshot.inputMuted)\n\r\n const toggle = async (): Promise<void> => {\r\n if (state === 'error' && activeSessionId !== undefined) {\r\n try { await retryVoice() } catch { /* controller owns the visible error state */ }\r\n return\r\n }\r\n if (state !== 'off' && state !== 'error') {\n setVoiceMuted(!inputMuted)\n return\n }\r\n try {\r\n await startVoice(sessionId)\r\n } catch { /* controller owns the visible error state */ }\r\n }\r\n\r\n const label = state === 'off'\n ? t('control.start')\n : state === 'error'\n ? t('control.retry')\n : inputMuted\n ? t('control.unmute')\n : t('control.mute')\n const active = state !== 'off' && state !== 'error'\n const interrupt = (): void => {\n try { interruptResponse() } catch { /* controller owns the visible connection state */ }\n }\n return (\n <span className={css.controls}>\n {state === 'speaking' && (\n <button\n type=\"button\"\n className={`${css.button} ${css.interruptButton}`}\n aria-label={t('control.interrupt')}\n title={t('control.interrupt')}\n data-state={state}\n onClick={interrupt}\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n <rect x=\"5.25\" y=\"5.25\" width=\"5.5\" height=\"5.5\" rx=\"1\" fill=\"currentColor\" />\n </svg>\n </button>\n )}\n <button\n type=\"button\"\n className={`${css.button} ${active ? css.active : ''}`}\n aria-label={label}\n aria-pressed={active ? inputMuted : undefined}\n title={label}\n data-state={state}\n data-muted={inputMuted}\n onClick={() => { void toggle() }}\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\n <rect x=\"5\" y=\"2\" width=\"6\" height=\"8\" rx=\"3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n <path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n {inputMuted && (\n <path d=\"M2.5 2.5l11 11\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" />\n )}\n </svg>\n </button>\n </span>\n )\n}\n","import { useEffect, useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceHistorySnapshot } from './voice-history.ts'\r\nimport css from './VoiceHistoryAction.module.css'\r\n\r\n/** History state and navigation owned by the Voice UI plugin. */\r\nexport interface VoiceHistoryActionInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n voiceHistory: { getSnapshot(): VoiceHistorySnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n}\r\n\r\nexport type VoiceHistoryActionProps =\r\n PropsRuntime<'sidebar.footer.action'> & InjectFace<VoiceHistoryActionInjected> & PropsLocale<'voice'>\r\n\r\n/** Render the plugin-owned Voice history entry and its Session navigation panel. */\r\nexport function VoiceHistoryAction({\r\n wide, useSessions, useVoice, useVoiceHistory, openSession, t,\r\n}: VoiceHistoryActionProps) {\r\n const [open, setOpen] = useState(false)\r\n const sessions = useSessions(state => state)\r\n const entries = useVoiceHistory(state => state.entries)\r\n const activeSessionId = useVoice(state => state.sessionId)\r\n const available = entries.flatMap((entry) => {\r\n const session = sessions.byId[entry.sessionId]\r\n return session === undefined ? [] : [{ entry, session }]\r\n })\r\n\r\n useEffect(() => {\r\n if (!open) return\r\n const close = (event: KeyboardEvent): void => { if (event.key === 'Escape') setOpen(false) }\r\n document.addEventListener('keydown', close)\r\n return () => { document.removeEventListener('keydown', close) }\r\n }, [open])\r\n\r\n return (\r\n <>\r\n <button\r\n type=\"button\"\r\n className={css.trigger}\r\n aria-label={t('history.open')}\r\n aria-haspopup=\"dialog\"\r\n aria-expanded={open}\r\n onClick={() => { setOpen(value => !value) }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <path d=\"M3 8a5 5 0 1 0 1.5-3.55M3 2.7v2.8h2.8M8 5.2V8l2 1.2\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.35\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\r\n </svg>\r\n {wide && <span>{t('history.label')}</span>}\r\n </button>\r\n {open && (\r\n <section className={css.panel} role=\"dialog\" aria-label={t('history.title')}>\r\n <header className={css.header}>\r\n <strong>{t('history.title')}</strong>\r\n <button type=\"button\" className={css.close} aria-label={t('history.close')} onClick={() => { setOpen(false) }}>×</button>\r\n </header>\r\n <div className={css.list}>\r\n {available.length === 0 && <p className={css.empty}>{t('history.empty')}</p>}\r\n {available.map(({ entry, session }) => (\r\n <button\r\n key={entry.sessionId}\r\n type=\"button\"\r\n className={css.row}\r\n data-active={entry.sessionId === activeSessionId || undefined}\r\n onClick={() => {\r\n openSession(entry.sessionId)\r\n setOpen(false)\r\n }}\r\n >\r\n <span className={css.wave} aria-hidden><i /><i /><i /></span>\r\n <span className={css.rowText}>\r\n <span className={css.title}>{session.displayTitle}</span>\r\n <span className={css.meta}>{entry.sessionId === activeSessionId ? t('history.active') : t('history.saved')}</span>\r\n </span>\r\n </button>\r\n ))}\r\n </div>\r\n </section>\r\n )}\r\n </>\r\n )\r\n}\r\n","import { useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceDelegationChatData } from './voice-definitions.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceNodeViews.module.css'\r\n\r\n/** Live transcript source injected into the Voice utterance renderer. */\r\nexport interface VoiceUtteranceInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n}\r\n\r\n/** Navigation action injected into the task-card renderer. */\r\nexport interface VoiceDelegationInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n readonly cancelTask: (id: string) => void\r\n}\r\n\r\nexport type VoiceUtteranceViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-utterance'>\r\n & InjectFace<VoiceUtteranceInjected>\r\n & PropsLocale<'voice'>\r\nexport type VoiceDelegationViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-delegation'>\r\n & InjectFace<VoiceDelegationInjected>\r\n & PropsLocale<'voice'>\r\n\r\n/** Render a durable Voice utterance while replacing its unsettled text with live WebSocket deltas. */\r\nexport function VoiceUtteranceView({ node, sessionId, useVoice, t }: VoiceUtteranceViewProps) {\r\n const liveText = useVoice(snapshot => snapshot.sessionId === sessionId\r\n ? snapshot.textById[String(node.data.utteranceId)]?.text\r\n : undefined)\r\n const text = node.data.text ?? liveText ?? (node.data.role === 'user'\r\n ? t('utterance.user.pending')\r\n : t('utterance.assistant.pending'))\r\n return (\r\n <article\r\n className={css.utterance}\r\n data-voice-utterance\r\n data-role={node.data.role}\r\n data-state={node.data.state}\r\n >\r\n <div className={css.bubble}>{text}</div>\r\n <div className={css.meta}>\r\n <span className={css.voiceBadge}>\r\n <span className={css.miniWave} aria-hidden><i /><i /><i /></span>\r\n {t('badge.voice')}\r\n </span>\r\n {node.data.state === 'interrupted' && <span>{t('utterance.interrupted')}</span>}\r\n </div>\r\n </article>\r\n )\r\n}\r\n\r\nconst STATUS_KEYS: Record<VoiceDelegationChatData['status'], VoiceKey> = {\r\n accepted: 'task.status.accepted',\r\n queued: 'task.status.queued',\r\n running: 'task.status.running',\r\n 'waiting-user': 'task.status.waiting-user',\r\n completed: 'task.status.completed',\r\n failed: 'task.status.failed',\r\n cancelled: 'task.status.cancelled',\r\n interrupted: 'task.status.interrupted',\r\n}\r\n\r\n/** Render the task link from a Voice Session to its bound background Agent Session. */\r\nexport function VoiceDelegationView({ node, sessionId, useSessions, useVoice, openSession, cancelTask, t }: VoiceDelegationViewProps) {\r\n const [expanded, setExpanded] = useState(false)\r\n const navigable = useSessions(sessions => sessions.ids.includes(node.data.taskSessionId))\r\n const connected = useVoice(voice => voice.sessionId === sessionId && voice.state !== 'off' && voice.state !== 'error')\r\n const cancellable = connected && !isTerminalTaskStatus(node.data.status)\r\n return (\r\n <article\r\n className={css.taskCard}\r\n data-voice-delegation\r\n data-status={node.data.status}\r\n data-expanded={expanded}\r\n >\r\n <button\r\n type=\"button\"\r\n className={css.taskSummary}\r\n aria-expanded={expanded}\r\n aria-label={expanded ? t('task.collapse') : t('task.expand')}\r\n onClick={() => { setExpanded(value => !value) }}\r\n >\r\n <span className={css.taskDot} aria-hidden />\r\n <span className={css.taskTitle}>{t('task.title')}</span>\r\n <span className={css.taskStatus}>{t(STATUS_KEYS[node.data.status])}</span>\r\n <span className={css.taskChevron} aria-hidden />\r\n </button>\r\n {expanded && (\r\n <div className={css.taskDetails}>\r\n <p className={css.taskInput}>{node.data.input}</p>\r\n {node.data.update !== undefined && <p className={css.taskUpdate}>{node.data.update}</p>}\r\n <div className={css.taskActions}>\r\n {cancellable && (\r\n <button type=\"button\" className={`${css.taskLink} ${css.taskCancel}`} onClick={() => { cancelTask(String(node.data.taskId)) }}>\r\n {t('task.cancel')}\r\n </button>\r\n )}\r\n <button\r\n type=\"button\"\r\n className={css.taskLink}\r\n disabled={!navigable}\r\n onClick={() => { openSession(node.data.taskSessionId) }}\r\n >\r\n {t('task.open')}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </article>\r\n )\r\n}\r\n\r\nfunction isTerminalTaskStatus(status: VoiceDelegationChatData['status']): boolean {\r\n return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'interrupted'\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot, VoiceClientState } from './voice-controller.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceOverlay.module.css'\r\n\r\n/** Root-level voice connection actions. */\r\nexport interface VoiceOverlayInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openVoiceSession: (id: SessionId) => void\r\n readonly stopVoice: () => Promise<void>\r\n}\r\n\r\nexport type VoiceOverlayProps =\r\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceOverlayInjected> & PropsLocale<'voice'>\r\n\r\nconst STATE_KEYS: Record<Exclude<VoiceClientState, 'off'>, VoiceKey> = {\r\n connecting: 'overlay.connecting',\r\n listening: 'overlay.listening',\r\n speaking: 'overlay.speaking',\r\n error: 'overlay.error',\r\n}\r\n\r\n/** Keep the active Voice Session controllable while another Session is selected. */\r\nexport function VoiceOverlay({ useVoice, useSessions, openVoiceSession, stopVoice, t }: VoiceOverlayProps) {\n const voice = useVoice(snapshot => snapshot)\n const navigable = useSessions(sessions => voice.sessionId !== undefined && sessions.ids.includes(voice.sessionId))\n if (voice.state === 'off') return null\n const statusKey = voice.inputMuted && voice.state !== 'error' ? 'overlay.muted' : STATE_KEYS[voice.state]\n return (\n <aside className={css.root} data-voice-overlay data-state={voice.state} data-muted={voice.inputMuted} aria-live=\"polite\">\n <span className={css.wave} aria-hidden>\r\n <i /><i /><i /><i />\r\n </span>\r\n <span className={css.status}>{t(statusKey)}</span>\n {voice.sessionId !== undefined && (\r\n <button\r\n type=\"button\"\r\n className={css.link}\r\n disabled={!navigable}\r\n onClick={() => { openVoiceSession(voice.sessionId as SessionId) }}\r\n >\r\n {t('overlay.return')}\r\n </button>\r\n )}\r\n <button type=\"button\" className={css.stop} onClick={() => { void stopVoice() }}>\r\n {t('overlay.stop')}\r\n </button>\r\n </aside>\r\n )\r\n}\r\n","import { useEffect } from 'react'\nimport type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { VoiceHistorySnapshot } from './voice-history.ts'\nimport css from './VoiceSessionMarkers.module.css'\n\n/** Root overlay that annotates the host-owned session rows with Voice markers. */\nexport interface VoiceSessionMarkersInjected {\n hooks: {\n voiceHistory: { getSnapshot(): VoiceHistorySnapshot; subscribe(listener: () => void): () => void }\n }\n}\n\nexport type VoiceSessionMarkersProps =\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceSessionMarkersInjected>\n\n/**\n * The workspace package currently owns the session-row renderer and does not\n * expose a row-level slot. This small, read-only decorator keeps the marker in\n * sync with that renderer while leaving row interactions and layout intact.\n */\nexport function VoiceSessionMarkers({ useSessions, useVoiceHistory }: VoiceSessionMarkersProps) {\n const sessions = useSessions(state => state.byId)\n const history = useVoiceHistory(state => state.entries)\n\n useEffect(() => {\n const voiceTitles = new Set<SessionId>(history.map(entry => entry.sessionId))\n const sessionIds = new Set(Object.keys(sessions))\n\n let frame = 0\n const decorate = (): void => {\n frame = 0\n const rows = document.querySelectorAll<HTMLElement>('[role=\"treeitem\"].sessionRow, [role=\"treeitem\"][class*=\"sessionRow\"]')\n for (const row of rows) {\n const slot = row.querySelector<HTMLElement>('[class*=\"slot\"]')\n const marker = row.querySelector<HTMLElement>('[data-voice-session-marker]')\n const sessionId = findSessionKey(row, sessionIds)\n const isVoice = sessionId !== undefined && voiceTitles.has(sessionId as SessionId)\n if (!isVoice) {\n marker?.remove()\n continue\n }\n if (marker !== null) continue\n const target = slot ?? createMarkerSlot(row)\n target.append(createMarker())\n }\n }\n const schedule = (): void => {\n if (frame !== 0) return\n frame = requestAnimationFrame(decorate)\n }\n decorate()\n const observer = new MutationObserver(schedule)\n observer.observe(document.body, { childList: true, subtree: true, characterData: true })\n return () => {\n observer.disconnect()\n if (frame !== 0) cancelAnimationFrame(frame)\n document.querySelectorAll('[data-voice-session-marker]').forEach(node => { node.remove() })\n document.querySelectorAll('[data-voice-session-marker-slot]').forEach(node => { node.remove() })\n }\n }, [history, sessions])\n\n return null\n}\n\n/** React keeps the keyed SessionNodeItem identity on the row's owner fiber. */\nfunction findSessionKey(row: HTMLElement, sessionIds: Set<string>): string | undefined {\n const fiberKey = Object.keys(row).find(key => key.startsWith('__reactFiber$'))\n if (fiberKey === undefined) return undefined\n let fiber = (row as unknown as Record<string, unknown>)[fiberKey] as { key: string | null; return?: unknown } | undefined\n while (fiber !== undefined) {\n if (fiber.key !== null && sessionIds.has(fiber.key)) return fiber.key\n fiber = fiber.return as typeof fiber | undefined\n }\n return undefined\n}\n\nfunction createMarkerSlot(row: HTMLElement): HTMLSpanElement {\n const slot = document.createElement('span')\n slot.dataset.voiceSessionMarkerSlot = ''\n slot.className = css.slot ?? 'voice-session-marker-slot'\n const title = row.querySelector<HTMLElement>('[class*=\"title\"]')\n row.insertBefore(slot, title ?? row.firstChild ?? null)\n return slot\n}\n\nfunction createMarker(): HTMLSpanElement {\n const marker = document.createElement('span')\n marker.dataset.voiceSessionMarker = ''\n marker.className = css.marker ?? 'voice-session-marker'\n marker.setAttribute('aria-hidden', 'true')\n marker.innerHTML = '<svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M3 8h1.5M6 5v6M9 3.5v9M12 5.5v5M14 7h-1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.35\" stroke-linecap=\"round\"/></svg>'\n return marker\n}\n","import type { ObservableSnapshot, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\n/** Browser-visible lifecycle of the one active voice transport. */\r\nexport type VoiceClientState = 'off' | 'connecting' | 'listening' | 'speaking' | 'error'\r\n\r\n/** Live text retained until the matching durable utterance settles. */\r\nexport interface VoiceLiveText {\r\n readonly text: string\r\n}\r\n\r\n/** Root-owned voice transport projection shared by every UI seat. */\r\nexport interface VoiceClientSnapshot {\n readonly state: VoiceClientState\n readonly inputMuted: boolean\n readonly sessionId?: SessionId\n readonly textById: Readonly<Record<string, VoiceLiveText>>\n}\n\ninterface VoiceTransport {\n close(): Promise<void>\n cancelTask(taskId: string): void\n interruptResponse(): void\n setInputMuted(muted: boolean): void\n submitText(text: string): void\n}\n\r\ninterface VoiceTransportHandlers {\n readonly onBinary: (audio: ArrayBuffer) => void\r\n readonly onEvent: (event: unknown) => void\r\n readonly onPlaybackEnded: () => void\r\n readonly onUnexpectedClose: () => void\r\n}\n\nexport interface VoiceControllerOptions {\n /** Called once after the first non-empty durable voice turn is completed. */\n readonly onConversationStarted?: (sessionId: SessionId) => void\n}\n\r\ninterface VoiceReadyEvent {\r\n readonly type: 'ready'\r\n readonly voiceSessionId: string\r\n readonly audio: {\r\n readonly inputSampleRate: number\r\n readonly outputSampleRate: number\r\n readonly format: 'pcm_s16le' | 'audio_mpeg'\r\n }\r\n readonly interactionMode: 'speech-shell' | 'frontend-agent'\r\n}\r\n\r\nconst INITIAL_SNAPSHOT: VoiceClientSnapshot = { state: 'off', inputMuted: false, textById: {} }\n\r\n/** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */\r\nexport class VoiceController implements ObservableSnapshot<VoiceClientSnapshot> {\n private snapshot = INITIAL_SNAPSHOT\r\n private readonly listeners = new Set<() => void>()\r\n private transport: VoiceTransport | undefined\r\n private openingTransport: VoiceTransport | undefined\r\n private openingAbort: AbortController | undefined\r\n private generation = 0\r\n private frame: number | undefined\r\n private readonly pendingText: string[] = []\n private readonly onConversationStarted: ((sessionId: SessionId) => void) | undefined\n private conversationStarted = false\n\n constructor(options: VoiceControllerOptions = {}) {\n this.onConversationStarted = options.onConversationStarted\n }\n\r\n /** Return the identity-stable snapshot until a voice fact changes. */\r\n getSnapshot = (): VoiceClientSnapshot => this.snapshot\r\n\r\n /** Subscribe to state and frame-coalesced transcript changes. */\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => { this.listeners.delete(listener) }\r\n }\r\n\r\n /**\n * Open one voice transport and make it the root-owned active session.\n * @param sessionId - durable voice session to connect.\n */\n async start(sessionId: SessionId): Promise<void> {\n await this.open(sessionId, false)\n }\n\n private async open(sessionId: SessionId, inputMuted: boolean): Promise<void> {\n const generation = ++this.generation\n this.pendingText.splice(0)\n this.conversationStarted = false\n // Construct and unlock both contexts before the first await so browser\r\n // autoplay policy still treats the microphone button as the user gesture.\r\n const contexts = createAudioContexts()\r\n await this.closeTransport()\r\n if (generation !== this.generation) {\r\n await closeAudioContexts(contexts)\r\n return\r\n }\r\n const openingAbort = new AbortController()\r\n this.openingAbort = openingAbort\r\n this.publish({ state: 'connecting', inputMuted, sessionId, textById: {} })\n try {\r\n const transport = await openVoiceTransport(String(sessionId), {\r\n onBinary: () => { this.setState('speaking') },\r\n onEvent: (event) => { this.handleEvent(event) },\r\n onPlaybackEnded: () => { this.setState('listening') },\r\n onUnexpectedClose: () => { this.handleUnexpectedClose() },\r\n }, openingAbort.signal, (opening) => {\n if (generation === this.generation) {\n this.openingTransport = opening\n opening.setInputMuted(this.snapshot.inputMuted)\n }\n else void opening.close()\n }, contexts)\r\n if (generation !== this.generation) {\r\n await transport.close()\r\n return\r\n }\r\n this.openingTransport = undefined\r\n this.transport = transport\r\n for (const text of this.pendingText.splice(0)) transport.submitText(text)\r\n this.setState('listening')\r\n } catch (cause) {\r\n if (generation !== this.generation) return\r\n this.setState('error')\r\n throw cause\r\n } finally {\r\n if (this.openingAbort === openingAbort) this.openingAbort = undefined\r\n if (generation === this.generation) this.openingTransport = undefined\r\n }\r\n }\r\n\r\n /** Retry the failed active Voice Session, when one was created successfully. */\r\n async retry(): Promise<void> {\n const sessionId = this.snapshot.sessionId\n if (sessionId === undefined) throw new Error('voice retry requires an active session')\n await this.open(sessionId, this.snapshot.inputMuted)\n }\n\n /** Mute or resume microphone input without closing text submission or playback. */\n setInputMuted(muted: boolean): void {\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\n throw new Error('voice input mute requires an active connection')\n }\n if (this.snapshot.inputMuted === muted) return\n this.openingTransport?.setInputMuted(muted)\n this.transport?.setInputMuted(muted)\n this.publish({ ...this.snapshot, inputMuted: muted })\n }\n\r\n /** Cancel the exact active background task through the attached Voice transport. */\r\n cancelTask(taskId: string): void {\n if (taskId === '') throw new Error('voice task id is required')\r\n if (this.transport === undefined) throw new Error('voice task cancellation requires an active connection')\r\n this.transport.cancelTask(taskId)\n }\n\n /** Stop the active assistant response without ending the Voice Session. */\n interruptResponse(): void {\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\n throw new Error('voice response interruption requires an active connection')\n }\n const transport = this.transport ?? this.openingTransport\n if (transport === undefined) throw new Error('voice response interruption requires an active connection')\n transport.interruptResponse()\n this.setState('listening')\n }\n\r\n /** Submit composer text as a voice turn, queueing it while the transport connects. */\r\n submitText(text: string): void {\r\n const value = text.trim()\r\n if (value === '') throw new Error('voice text submission requires non-empty text')\r\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\r\n throw new Error('voice text submission requires an active connection')\r\n }\r\n if (this.transport === undefined) this.pendingText.push(value)\r\n else this.transport.submitText(value)\r\n }\r\n\r\n /** Stop capture/playback and await owned browser-resource teardown. */\r\n async stop(): Promise<void> {\r\n ++this.generation\r\n this.openingAbort?.abort()\r\n this.openingAbort = undefined\r\n this.publish(INITIAL_SNAPSHOT)\r\n this.pendingText.splice(0)\r\n await this.closeTransport()\r\n }\r\n\r\n private async closeTransport(): Promise<void> {\r\n const transports = new Set<VoiceTransport>()\r\n if (this.transport !== undefined) transports.add(this.transport)\r\n if (this.openingTransport !== undefined) transports.add(this.openingTransport)\r\n this.transport = undefined\r\n this.openingTransport = undefined\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n const settled = await Promise.allSettled([...transports].map(transport => transport.close()))\r\n const failures: unknown[] = []\r\n for (const result of settled) {\r\n if (result.status === 'rejected') failures.push(result.reason as unknown)\r\n }\r\n if (failures.length > 0) throw new AggregateError(failures, 'voice transport cleanup failed')\r\n }\r\n\r\n private handleUnexpectedClose(): void {\r\n this.openingAbort?.abort()\r\n this.setState('error')\r\n void this.closeTransport().catch((cause: unknown) => {\r\n console.error('voice transport cleanup failed:', cause)\r\n })\r\n }\r\n\r\n private setState(state: VoiceClientState): void {\r\n if (this.snapshot.state === state) return\r\n this.publish({ ...this.snapshot, state })\r\n }\r\n\r\n private setText(id: string, text: string, cadence: 'immediate' | 'animation-frame'): void {\r\n if (id === '') return\r\n const current = this.snapshot.textById[id]\r\n if (current?.text === text) return\r\n this.snapshot = {\r\n ...this.snapshot,\r\n textById: { ...this.snapshot.textById, [id]: { text } },\r\n }\r\n if (cadence === 'immediate') this.notify()\r\n else this.notifyOnFrame()\r\n }\r\n\r\n private appendText(id: string, delta: string): void {\r\n if (id === '' || delta === '') return\r\n this.setText(id, `${this.snapshot.textById[id]?.text ?? ''}${delta}`, 'animation-frame')\r\n }\r\n\r\n private handleEvent(input: unknown): void {\r\n const event = recordOf(input)\r\n if (event === undefined) return\r\n const type = stringOf(event.type)\r\n if (type === 'transcription.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'transcription.updated') {\r\n this.setText(stringOf(event.utteranceId), stringOf(event.text), 'animation-frame')\r\n return\r\n }\r\n if (type === 'transcription.completed') {\n const text = stringOf(event.text)\n this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_text.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n return\r\n }\r\n if (type === 'output_text.delta') {\r\n this.appendText(stringOf(event.utteranceId), stringOf(event.text))\r\n return\r\n }\r\n if (type === 'output_text.done') {\n const text = stringOf(event.text)\n if (text !== '') this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_audio.started') {\r\n this.setState('speaking')\r\n return\r\n }\r\n if (type === 'response.interrupted') {\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'error') this.handleUnexpectedClose()\r\n }\n\n private markConversationStarted(): void {\n if (this.conversationStarted) return\n const sessionId = this.snapshot.sessionId\n if (sessionId === undefined) return\n this.conversationStarted = true\n this.onConversationStarted?.(sessionId)\n }\n\r\n private publish(snapshot: VoiceClientSnapshot): void {\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n this.snapshot = snapshot\r\n this.notify()\r\n }\r\n\r\n private notifyOnFrame(): void {\r\n this.frame ??= requestAnimationFrame(() => {\r\n this.frame = undefined\r\n this.notify()\r\n })\r\n }\r\n\r\n private notify(): void {\r\n for (const listener of this.listeners) {\r\n try { listener() } catch (cause) { console.error('voice snapshot listener failed:', cause) }\r\n }\r\n }\r\n}\r\n\r\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\r\n return value !== null && typeof value === 'object' ? value as Record<string, unknown> : undefined\r\n}\r\n\r\nfunction stringOf(value: unknown): string {\r\n return typeof value === 'string' ? value : ''\r\n}\r\n\r\nasync function openVoiceTransport(\r\n sessionId: string,\r\n handlers: VoiceTransportHandlers,\r\n signal: AbortSignal,\r\n onOpening: (transport: VoiceTransport) => void,\r\n contexts: AudioContexts,\r\n): Promise<VoiceTransport> {\r\n const { input: inputContext, output: outputContext } = contexts\r\n const socket = new WebSocket(voiceUrl(sessionId))\r\n const workletUrl = URL.createObjectURL(new Blob([CAPTURE_WORKLET], { type: 'text/javascript' }))\r\n let stream: MediaStream | undefined\r\n let source: MediaStreamAudioSourceNode | undefined\r\n let processor: AudioWorkletNode | undefined\r\n let silent: GainNode | undefined\r\n let stopped = false\r\n let socketClosed = false\r\n let ready = false\r\n let outputDone = false\r\n let encodedPending = 0\r\n let playbackGeneration = 0\r\n let encodedQueue: Promise<void> = Promise.resolve()\r\n let inputSampleRate = 16_000\r\n let outputSampleRate = 24_000\r\n let audioFormat: VoiceReadyEvent['audio']['format'] = 'pcm_s16le'\r\n let playAt = outputContext.currentTime\n let inputMuted = false\n const chunks: number[] = []\n const playing = new Set<AudioBufferSourceNode>()\r\n\r\n const transport: VoiceTransport = {\n cancelTask: (taskId) => {\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\n },\n interruptResponse: () => {\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\n socket.send(JSON.stringify({ type: 'response.interrupt' }))\n stopPlayback()\n },\n setInputMuted: (muted) => {\n if (inputMuted === muted) return\n inputMuted = muted\n chunks.splice(0)\n for (const track of stream?.getTracks() ?? []) track.enabled = !muted\n if (muted && !stopped && socket.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify({ type: 'audio.commit' }))\n }\n },\n submitText: (text) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'text.submit', text }))\r\n },\r\n close: async () => {\r\n /* v8 ignore next -- controller ownership closes a transport once; the guard protects browser close races. */\r\n if (stopped) return\r\n stopped = true\r\n if (!socketClosed && socket.readyState === WebSocket.OPEN) {\r\n try { socket.send(JSON.stringify({ type: 'session.close' })) } catch {\r\n /* A socket closing concurrently cannot carry the final control; the server expires its reconnect grace. */\r\n }\r\n }\r\n socket.removeEventListener('message', onMessage)\r\n socket.removeEventListener('close', onClose)\r\n stopPlayback()\r\n source?.disconnect()\r\n processor?.disconnect()\r\n silent?.disconnect()\r\n processor?.port.close()\r\n for (const track of stream?.getTracks() ?? []) track.stop()\r\n socket.close()\r\n await Promise.allSettled([inputContext.close(), outputContext.close()])\r\n },\r\n }\r\n\r\n const stopPlayback = (): void => {\r\n playbackGeneration += 1\r\n encodedPending = 0\r\n encodedQueue = Promise.resolve()\r\n for (const node of playing) {\r\n try { node.stop() } catch { /* a completed source is already stopped */ }\r\n }\r\n playing.clear()\r\n playAt = outputContext.currentTime\r\n outputDone = false\r\n }\r\n const notifyPlaybackEnded = (): void => {\r\n if (outputDone && encodedPending === 0 && playing.size === 0 && socket.readyState === WebSocket.OPEN) {\r\n socket.send(JSON.stringify({ type: 'playback.ended' }))\r\n outputDone = false\r\n handlers.onPlaybackEnded()\r\n }\r\n }\r\n const onMessage = (message: MessageEvent): void => {\r\n if (!ready) return\r\n if (message.data instanceof ArrayBuffer) {\r\n if (audioFormat === 'audio_mpeg') {\r\n const generation = playbackGeneration\r\n encodedPending += 1\r\n encodedQueue = encodedQueue.catch(() => {}).then(() => scheduleEncoded(\r\n outputContext,\r\n message.data,\r\n playing,\r\n notifyPlaybackEnded,\r\n (value) => { playAt = value },\r\n () => playAt,\r\n () => generation === playbackGeneration && !stopped,\r\n )).catch(error => {\r\n if (generation === playbackGeneration && !stopped) {\r\n handlers.onEvent({ type: 'error', message: `audio decode failed: ${String(error)}` })\r\n }\r\n }).finally(() => {\r\n if (generation === playbackGeneration) {\r\n encodedPending -= 1\r\n notifyPlaybackEnded()\r\n }\r\n })\r\n } else {\r\n schedulePcm(outputContext, message.data, outputSampleRate, playing, notifyPlaybackEnded, (value) => { playAt = value }, playAt)\r\n }\r\n handlers.onBinary(message.data)\r\n return\r\n }\r\n let event: unknown\r\n try { event = JSON.parse(String(message.data)) } catch { return }\r\n const record = recordOf(event)\r\n if (record?.type === 'response.interrupted') stopPlayback()\n if (record?.type === 'output_audio.done') {\r\n outputDone = true\r\n notifyPlaybackEnded()\r\n }\r\n handlers.onEvent(event)\r\n }\r\n const onClose = (): void => {\r\n socketClosed = true\r\n handlers.onUnexpectedClose()\r\n }\r\n\r\n socket.binaryType = 'arraybuffer'\r\n onOpening(transport)\r\n try {\r\n await socketOpened(socket, signal)\r\n socket.addEventListener('message', onMessage)\r\n socket.addEventListener('close', onClose)\r\n const readyEvent = await socketReady(socket, signal)\r\n inputSampleRate = readyEvent.audio.inputSampleRate\r\n outputSampleRate = readyEvent.audio.outputSampleRate\r\n audioFormat = readyEvent.audio.format\r\n ready = true\r\n const captured = await microphone(signal)\r\n if (openingStopped(signal, stopped)) {\r\n for (const track of captured.getTracks()) track.stop()\r\n throw new Error('voice connection opening aborted')\r\n }\r\n stream = captured\n for (const track of stream.getTracks()) track.enabled = !inputMuted\n await abortable(inputContext.audioWorklet.addModule(workletUrl), signal)\r\n if (openingStopped(signal, stopped)) throw new Error('voice connection opening aborted')\r\n source = inputContext.createMediaStreamSource(stream)\r\n processor = new AudioWorkletNode(inputContext, 'dsh-pcm-capture')\r\n silent = inputContext.createGain()\r\n silent.gain.value = 0\r\n processor.port.onmessage = (event: MessageEvent<Float32Array>) => {\n if (inputMuted || socket.readyState !== WebSocket.OPEN) return\n chunks.push(...resample(event.data, inputContext.sampleRate, inputSampleRate))\r\n const frameSize = Math.max(1, Math.round(inputSampleRate / 5))\r\n while (chunks.length >= frameSize) {\r\n const part = chunks.splice(0, frameSize)\r\n const pcm = new Int16Array(part.length)\r\n for (const [index, sample] of part.entries()) {\r\n pcm[index] = Math.round(Math.max(-1, Math.min(1, sample)) * 32767)\r\n }\r\n socket.send(pcm.buffer)\r\n }\r\n }\r\n source.connect(processor)\r\n processor.connect(silent)\r\n silent.connect(inputContext.destination)\r\n } catch (cause) {\r\n await transport.close()\r\n throw cause\r\n } finally {\r\n URL.revokeObjectURL(workletUrl)\r\n }\r\n\r\n return transport\r\n}\r\n\r\ninterface AudioContexts {\r\n readonly input: AudioContext\r\n readonly output: AudioContext\r\n}\r\n\r\nfunction createAudioContexts(): AudioContexts {\r\n const contexts = { input: new AudioContext(), output: new AudioContext() }\r\n // Calling resume synchronously is required on browsers that start contexts\r\n // suspended when they are created outside a trusted user gesture.\r\n void contexts.input.resume().catch(() => {})\r\n void contexts.output.resume().catch(() => {})\r\n return contexts\r\n}\r\n\r\nasync function closeAudioContexts(contexts: AudioContexts): Promise<void> {\r\n await Promise.allSettled([contexts.input.close(), contexts.output.close()])\r\n}\r\n\r\nfunction microphone(signal: AbortSignal): Promise<MediaStream> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cancelled = true\r\n cleanup()\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n let request: Promise<MediaStream>\r\n try {\r\n request = navigator.mediaDevices.getUserMedia({\r\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\r\n })\r\n } catch (cause) {\r\n cleanup()\r\n reject(errorOf(cause))\r\n return\r\n }\r\n void request.then((value) => {\r\n cleanup()\r\n if (cancelled || signal.aborted) {\r\n for (const track of value.getTracks()) track.stop()\r\n return\r\n }\r\n resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cleanup()\r\n cancelled = true\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n void operation.then((value) => {\r\n cleanup()\r\n if (!cancelled) resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction socketOpened(socket: WebSocket, signal: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('open', opened)\r\n socket.removeEventListener('error', failed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const opened = (): void => { cleanup(); resolve() }\r\n const failed = (): void => { cleanup(); reject(new Error('voice websocket failed')) }\r\n const aborted = (): void => { cleanup(); reject(new Error('voice connection opening aborted')) }\r\n if (signal.aborted) { aborted(); return }\r\n socket.addEventListener('open', opened, { once: true })\r\n socket.addEventListener('error', failed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction socketReady(socket: WebSocket, signal: AbortSignal): Promise<VoiceReadyEvent> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('message', received)\r\n socket.removeEventListener('error', failed)\r\n socket.removeEventListener('close', closed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const settle = (operation: () => void): void => { cleanup(); operation() }\r\n const received = (message: MessageEvent): void => {\r\n try {\r\n const value: unknown = message.data instanceof ArrayBuffer\r\n ? undefined\r\n : JSON.parse(String(message.data)) as unknown\r\n settle(() => { resolve(parseReady(value)) })\r\n } catch (cause) {\r\n settle(() => { reject(errorOf(cause)) })\r\n }\r\n }\r\n const failed = (): void => { settle(() => { reject(new Error('voice websocket failed before ready')) }) }\r\n const closed = (): void => { settle(() => { reject(new Error('voice websocket closed before ready')) }) }\r\n const aborted = (): void => { settle(() => { reject(new Error('voice connection opening aborted')) }) }\r\n if (signal.aborted) { aborted(); return }\r\n if (socket.readyState !== WebSocket.OPEN) { closed(); return }\r\n socket.addEventListener('message', received)\r\n socket.addEventListener('error', failed, { once: true })\r\n socket.addEventListener('close', closed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction openingStopped(signal: AbortSignal, stopped: boolean): boolean {\r\n return signal.aborted || stopped\r\n}\r\n\r\nfunction errorOf(cause: unknown): Error {\r\n return cause instanceof Error ? cause : new Error(String(cause))\r\n}\r\n\r\nfunction parseReady(value: unknown): VoiceReadyEvent {\r\n const event = recordOf(value)\r\n if (event?.type === 'error') {\r\n const message = stringOf(event.message)\r\n throw new Error(message === '' ? 'voice setup failed before ready' : message)\r\n }\r\n if (event?.type !== 'ready') throw new Error('voice websocket expected a ready event')\r\n const voiceSessionId = stringOf(event.voiceSessionId)\r\n if (voiceSessionId === '') throw new Error('voice websocket ready event has no voiceSessionId')\r\n const audio = recordOf(event.audio)\r\n if (audio?.inputSampleRate !== 16_000\r\n || (audio.outputSampleRate !== 24_000 && audio.outputSampleRate !== 48_000)\r\n || (audio.format !== 'pcm_s16le' && audio.format !== 'audio_mpeg')) {\r\n throw new Error('voice websocket ready event has unsupported audio settings')\r\n }\r\n if (event.interactionMode !== 'speech-shell' && event.interactionMode !== 'frontend-agent') {\r\n throw new Error('voice websocket ready event has an invalid interactionMode')\r\n }\r\n return { type: 'ready', voiceSessionId, audio: {\r\n inputSampleRate: audio.inputSampleRate,\r\n outputSampleRate: audio.outputSampleRate,\r\n format: audio.format,\r\n }, interactionMode: event.interactionMode }\r\n}\r\n\r\n/**\r\n * Resolve the dedicated WebSocket URL for one durable Voice Session.\r\n * @param sessionId - durable voice session id.\r\n * @param location - browser location used for host and protocol defaults.\r\n * @returns the /voice WebSocket URL carrying the session id.\r\n */\r\nexport function voiceUrl(sessionId: string, location: Pick<Location, 'href' | 'protocol'> = window.location): URL {\r\n const url = new URL('/voice', location.href)\r\n url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'\r\n url.searchParams.set('sessionId', sessionId)\r\n return url\r\n}\r\n\r\nconst CAPTURE_WORKLET = `\r\nclass DshPcmCaptureProcessor extends AudioWorkletProcessor {\r\n process(inputs) {\r\n const channel = inputs[0] && inputs[0][0]\r\n if (channel) this.port.postMessage(channel.slice())\r\n return true\r\n }\r\n}\r\nregisterProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)\r\n`\r\n\r\n/**\r\n * Resample browser float PCM into the provider input rate.\r\n * @param input - float samples at `fromRate`.\r\n * @param fromRate - source sample rate in Hz.\r\n * @param toRate - target sample rate in Hz.\r\n * @returns linearly interpolated samples at `toRate`.\r\n */\r\nexport function resample(input: Float32Array, fromRate: number, toRate: number): number[] {\r\n if (fromRate === toRate) return [...input]\r\n const ratio = fromRate / toRate\r\n const result = new Array<number>(Math.floor(input.length / ratio))\r\n for (let index = 0; index < result.length; index += 1) {\r\n const position = index * ratio\r\n const left = Math.floor(position)\r\n const fraction = position - left\r\n const leftSample = input[left] as number\r\n const rightSample = input[Math.min(left + 1, input.length - 1)] as number\r\n result[index] = leftSample * (1 - fraction) + rightSample * fraction\r\n }\r\n return result\r\n}\r\n\r\nfunction schedulePcm(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n sampleRate: number,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n currentPlayAt: number,\r\n): void {\r\n const pcm = new Int16Array(bytes)\r\n const buffer = context.createBuffer(1, pcm.length, sampleRate)\r\n const channel = buffer.getChannelData(0)\r\n for (const [index, sample] of pcm.entries()) channel[index] = sample / 32768\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(currentPlayAt, context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n\r\nasync function scheduleEncoded(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n getPlayAt: () => number,\r\n shouldSchedule: () => boolean,\r\n): Promise<void> {\r\n const buffer = await context.decodeAudioData(bytes.slice(0))\r\n if (!shouldSchedule()) return\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(getPlayAt(), context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n","import {\r\n createSnapshotStore, type SessionId, type SnapshotStore,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\nconst STORAGE_KEY = 'dsh.voice.history.v1'\r\n\r\n/** One locally indexed durable Voice Session. */\r\nexport interface VoiceHistoryEntry {\r\n readonly sessionId: SessionId\r\n readonly lastActiveAt: number\r\n}\r\n\r\n/** Browser-persistent Voice Session index. */\r\nexport interface VoiceHistorySnapshot {\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\ninterface StoredVoiceHistory {\r\n readonly version: 1\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\nfunction parseEntry(input: unknown): VoiceHistoryEntry | undefined {\r\n if (typeof input !== 'object' || input === null) return undefined\r\n const value = input as Record<string, unknown>\r\n if (typeof value.sessionId !== 'string' || value.sessionId === '') return undefined\r\n if (typeof value.lastActiveAt !== 'number' || !Number.isFinite(value.lastActiveAt) || value.lastActiveAt < 0) {\r\n return undefined\r\n }\r\n return { sessionId: value.sessionId as SessionId, lastActiveAt: value.lastActiveAt }\r\n}\r\n\r\nfunction readHistory(storage: Storage | undefined): VoiceHistorySnapshot {\r\n if (storage === undefined) return { entries: [] }\r\n try {\r\n const raw = storage.getItem(STORAGE_KEY)\r\n if (raw === null) return { entries: [] }\r\n const parsed = JSON.parse(raw) as unknown\r\n if (typeof parsed !== 'object' || parsed === null) return { entries: [] }\r\n const value = parsed as Record<string, unknown>\r\n if (value.version !== 1 || !Array.isArray(value.entries)) return { entries: [] }\r\n const entries: VoiceHistoryEntry[] = []\r\n const seen = new Set<SessionId>()\r\n for (const candidate of value.entries) {\r\n const entry = parseEntry(candidate)\r\n if (entry === undefined || seen.has(entry.sessionId)) continue\r\n seen.add(entry.sessionId)\r\n entries.push(entry)\r\n }\r\n entries.sort((left, right) => right.lastActiveAt - left.lastActiveAt)\r\n return { entries }\r\n } catch (error: unknown) {\r\n console.error('voice history rehydration failed:', error)\r\n return { entries: [] }\r\n }\r\n}\r\n\r\n/** Own the local history index without adding Voice metadata to Host session projections. */\r\nexport class VoiceHistoryStore {\r\n readonly snapshot: SnapshotStore<VoiceHistorySnapshot>\r\n private readonly storage: Storage | undefined\r\n private readonly unsubscribe: () => void\r\n\r\n constructor(storage: Storage | undefined = typeof localStorage === 'undefined' ? undefined : localStorage) {\r\n this.storage = storage\r\n this.snapshot = createSnapshotStore(readHistory(storage))\r\n this.unsubscribe = this.snapshot.subscribe(() => { this.persist() })\r\n }\r\n\r\n /** Move one successfully opened Voice Session to the front of the index. */\r\n record(sessionId: SessionId, lastActiveAt = Date.now()): void {\r\n this.snapshot.set({\r\n entries: [\r\n { sessionId, lastActiveAt },\r\n ...this.snapshot.getSnapshot().entries.filter(entry => entry.sessionId !== sessionId),\r\n ],\r\n })\r\n }\r\n\r\n /** Stop persistence notifications. */\r\n dispose(): void {\r\n this.unsubscribe()\r\n }\r\n\r\n private persist(): void {\r\n if (this.storage === undefined) return\r\n const value: StoredVoiceHistory = { version: 1, entries: this.snapshot.getSnapshot().entries }\r\n try {\r\n this.storage.setItem(STORAGE_KEY, JSON.stringify(value))\r\n } catch (error: unknown) {\r\n console.error('voice history persistence failed:', error)\r\n }\r\n }\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\n\r\ninterface VoiceTextInputState {\r\n readonly draft: string\r\n readonly imageIds: readonly unknown[]\r\n}\r\n\r\nexport interface VoiceTextInput {\r\n readonly state: { getSnapshot(): VoiceTextInputState }\r\n setDraft(text: string): void\r\n submit(mode?: unknown): void\r\n notify(level: 'info' | 'error', text: string): void\r\n}\r\n\r\ninterface VoiceTextController {\r\n getSnapshot(): VoiceClientSnapshot\r\n submitText(text: string): void\r\n}\r\n\r\ninterface PatchedInput {\r\n readonly input: VoiceTextInput\r\n readonly originalOwnDescriptor: PropertyDescriptor | undefined\r\n readonly patchedSubmit: (mode?: unknown) => void\r\n}\r\n\r\n/** Routes the native composer through Voice while preserving its ordinary offline submission. */\r\nexport class VoiceTextSubmitBridge {\r\n private readonly patched = new Map<SessionId, PatchedInput>()\r\n\r\n constructor(\r\n private readonly voice: VoiceTextController,\r\n private readonly resolveInput: (sessionId: SessionId) => VoiceTextInput,\r\n ) {}\r\n\r\n install(sessionId: SessionId): void {\r\n if (this.patched.has(sessionId)) return\r\n const input = this.resolveInput(sessionId)\r\n const originalOwnDescriptor = Object.getOwnPropertyDescriptor(input, 'submit')\r\n const originalSubmit = input.submit.bind(input)\r\n const patchedSubmit = (mode?: unknown): void => {\r\n const voice = this.voice.getSnapshot()\r\n const state = input.state.getSnapshot()\r\n const active = voice.sessionId === sessionId\r\n && voice.state !== 'off'\r\n && voice.state !== 'error'\r\n if (!active || state.draft.trim() === '' || state.imageIds.length > 0) {\r\n originalSubmit(mode)\r\n return\r\n }\r\n try {\r\n this.voice.submitText(state.draft)\r\n input.setDraft('')\r\n } catch (cause: unknown) {\r\n input.notify('error', cause instanceof Error ? cause.message : String(cause))\r\n }\r\n }\r\n input.submit = patchedSubmit\r\n this.patched.set(sessionId, { input, originalOwnDescriptor, patchedSubmit })\r\n }\r\n\r\n dispose(): void {\r\n for (const { input, originalOwnDescriptor, patchedSubmit } of this.patched.values()) {\r\n if (input.submit !== patchedSubmit) continue\r\n if (originalOwnDescriptor === undefined) delete (input as Partial<VoiceTextInput>).submit\r\n else Object.defineProperty(input, 'submit', originalOwnDescriptor)\r\n }\r\n this.patched.clear()\r\n }\r\n}\r\n","import type {\r\n ChatConversationViewNode, ConversationNodeDefinition, SessionId,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {\r\n TaskObservation, VoiceTaskId, VoiceUtteranceId,\r\n} from '@flowingspring/dsh-voice'\n\r\n/** Renderer-ready durable Voice utterance. */\r\nexport interface VoiceUtteranceChatData {\r\n readonly utteranceId: VoiceUtteranceId\r\n readonly role: 'user' | 'assistant'\r\n readonly text?: string\r\n readonly state: 'streaming' | 'completed' | 'interrupted'\r\n}\r\n\r\n/** Renderer-ready delegated task link and its latest observation. */\r\nexport interface VoiceDelegationChatData {\r\n readonly taskId: VoiceTaskId\r\n readonly taskSessionId: SessionId\r\n readonly input: string\r\n readonly status: TaskObservation['status']\r\n readonly update?: string\r\n}\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\r\n interface ChatNodeDataMap {\r\n /** One user ASR or assistant TTS transcript produced in Voice Mode. */\r\n 'voice-utterance': VoiceUtteranceChatData\r\n /** One independent DSH task delegated by the Voice frontend Agent. */\r\n 'voice-delegation': VoiceDelegationChatData\r\n }\r\n}\r\n\r\ntype VoiceUtteranceState = VoiceUtteranceChatData\r\ntype VoiceDelegationState = VoiceDelegationChatData\r\n\r\n/** Fold one streamed utterance into a stable Chat node. */\r\nexport const voiceUtteranceDefinition: ConversationNodeDefinition<VoiceUtteranceState> = {\r\n kind: 'voice-utterance',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/utterance-start') {\r\n return { id: String(event.data.utteranceId), role: 'start' }\r\n }\r\n if (event.type === 'voice/utterance-end') {\r\n return { id: String(event.data.utteranceId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/utterance-start') {\r\n throw new Error('voice utterance start requires voice/utterance-start')\r\n }\r\n return {\r\n utteranceId: match.event.data.utteranceId,\r\n role: match.event.data.role,\r\n state: 'streaming',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/utterance-end') return context.state\r\n return {\r\n ...context.state,\r\n text: match.event.data.text,\r\n state: match.event.data.state,\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined || context.state === undefined) return null\r\n const emptyInterrupted = context.state.state === 'interrupted'\r\n && (context.state.text?.trim() ?? '') === ''\r\n return {\r\n key: context.key,\r\n kind: 'voice-utterance',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n // A live start may already be materialized before an empty noise segment\r\n // settles. Conversation nodes cannot be withdrawn after publication, so\r\n // keep its stable identity and hide it instead.\r\n visibility: emptyInterrupted ? 'hidden' : 'visible',\r\n data: context.state as VoiceUtteranceState,\r\n }\r\n },\r\n}\r\n\r\n/** Fold a delegated task and all observations into one navigation card. */\r\nexport const voiceDelegationDefinition: ConversationNodeDefinition<VoiceDelegationState> = {\r\n kind: 'voice-delegation',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/task-delegated') {\r\n return { id: String(event.data.taskId), role: 'start' }\r\n }\r\n if (event.type === 'voice/task-observation') {\r\n return { id: String(event.data.taskId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/task-delegated') {\r\n throw new Error('voice delegation start requires voice/task-delegated')\r\n }\r\n return {\r\n taskId: match.event.data.taskId,\r\n taskSessionId: match.event.data.taskSessionId,\r\n input: match.event.data.input,\r\n status: 'accepted',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/task-observation') return context.state\r\n const observation = match.event.data\r\n const update = observation.voiceMessage?.text\r\n ?? observation.announcement\r\n ?? observation.reason\r\n ?? context.state.update\r\n return {\r\n ...context.state,\r\n status: observation.status,\r\n ...(update === undefined ? {} : { update }),\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined) return null\r\n return {\r\n key: context.key,\r\n kind: 'voice-delegation',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n visibility: 'visible',\r\n data: context.state as VoiceDelegationState,\r\n }\r\n },\r\n}\r\n","/** `voice` namespace dictionaries. */\r\n\r\n/** Dictionary namespace owned by this plugin. */\r\nexport const NS = 'voice'\r\n\r\n/** Simplified Chinese dictionary (the key-set source of truth). */\r\nexport const zh = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': '开始语音对话',\n 'control.retry': '语音连接失败,点击重试',\n 'control.interrupt': '打断播放',\n 'control.mute': '静音麦克风',\n 'control.unmute': '解除静音',\n 'overlay.connecting': '正在连接语音',\r\n 'overlay.listening': '正在聆听',\r\n 'overlay.speaking': '正在播放回复',\r\n 'overlay.error': '语音连接失败',\n 'overlay.muted': '已静音',\n 'overlay.return': '返回语音对话',\r\n 'overlay.stop': '结束',\r\n 'history.open': '打开语音历史',\r\n 'history.label': '语音历史',\r\n 'history.title': '语音对话',\r\n 'history.close': '关闭语音历史',\r\n 'history.empty': '还没有语音对话',\r\n 'history.active': '通话中',\r\n 'history.saved': '已保存',\r\n 'utterance.user.pending': '正在聆听…',\r\n 'utterance.assistant.pending': '正在生成语音回复…',\r\n 'utterance.interrupted': '已打断',\r\n 'task.title': '后台处理',\r\n 'task.status.accepted': '已接受',\r\n 'task.status.queued': '排队中',\r\n 'task.status.running': '执行中',\r\n 'task.status.waiting-user': '等待确认',\r\n 'task.status.completed': '已完成',\r\n 'task.status.failed': '失败',\r\n 'task.status.cancelled': '已取消',\r\n 'task.status.interrupted': '服务中断',\r\n 'task.expand': '展开后台处理详情',\r\n 'task.collapse': '收起后台处理详情',\r\n 'task.cancel': '取消任务',\r\n 'task.open': '查看后台详情',\r\n} satisfies Record<string, string>\r\n\r\n/** Union of this namespace's dictionary keys. */\r\nexport type VoiceKey = keyof typeof zh\r\n\r\n/** English dictionary, checked complete against the zh key set. */\r\nexport const en = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': 'Start voice conversation',\n 'control.retry': 'Voice connection failed, press to retry',\n 'control.interrupt': 'Stop playback',\n 'control.mute': 'Mute microphone',\n 'control.unmute': 'Unmute microphone',\n 'overlay.connecting': 'Connecting voice',\r\n 'overlay.listening': 'Listening',\r\n 'overlay.speaking': 'Playing response',\r\n 'overlay.error': 'Voice connection failed',\n 'overlay.muted': 'Muted',\n 'overlay.return': 'Return to voice conversation',\r\n 'overlay.stop': 'End',\r\n 'history.open': 'Open voice history',\r\n 'history.label': 'Voice history',\r\n 'history.title': 'Voice conversations',\r\n 'history.close': 'Close voice history',\r\n 'history.empty': 'No voice conversations yet',\r\n 'history.active': 'Active',\r\n 'history.saved': 'Saved',\r\n 'utterance.user.pending': 'Listening…',\r\n 'utterance.assistant.pending': 'Generating voice response…',\r\n 'utterance.interrupted': 'Interrupted',\r\n 'task.title': 'Background task',\r\n 'task.status.accepted': 'Accepted',\r\n 'task.status.queued': 'Queued',\r\n 'task.status.running': 'Running',\r\n 'task.status.waiting-user': 'Waiting for you',\r\n 'task.status.completed': 'Completed',\r\n 'task.status.failed': 'Failed',\r\n 'task.status.cancelled': 'Cancelled',\r\n 'task.status.interrupted': 'Interrupted',\r\n 'task.expand': 'Show background task details',\r\n 'task.collapse': 'Hide background task details',\r\n 'task.cancel': 'Cancel task',\r\n 'task.open': 'View background details',\r\n} satisfies Record<VoiceKey, string>\r\n","/** Browser voice UI assembly. @module @flowingspring/dsh-client-ui-voice/client */\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\r\nimport { VoiceControl, type VoiceControlInjected } from './VoiceControl.tsx'\r\nimport { VoiceHistoryAction, type VoiceHistoryActionInjected } from './VoiceHistoryAction.tsx'\r\nimport {\r\n VoiceDelegationView, type VoiceDelegationInjected,\r\n VoiceUtteranceView, type VoiceUtteranceInjected,\r\n} from './VoiceNodeViews.tsx'\r\nimport { VoiceOverlay, type VoiceOverlayInjected } from './VoiceOverlay.tsx'\nimport { VoiceSessionMarkers, type VoiceSessionMarkersInjected } from './VoiceSessionMarkers.tsx'\nimport { VoiceController } from './voice-controller.ts'\r\nimport { VoiceHistoryStore } from './voice-history.ts'\r\nimport { VoiceTextSubmitBridge, type VoiceTextInput } from './voice-text-submit.ts'\r\nimport { voiceDelegationDefinition, voiceUtteranceDefinition } from './voice-definitions.ts'\r\nimport { en, NS, type VoiceKey, zh } from './locales.ts'\r\n\r\nexport type { VoiceKey } from './locales.ts'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** Browser Voice conversation copy. */\r\n voice: VoiceKey\r\n }\r\n}\r\n\r\nexport const inject = ['conversation', 'conversationEvents', 'slots', 'sessions', 'locale']\n\n/** Refresh the Host-backed Session list when the runtime exposes its baseline pull. */\nfunction refreshSessions(ctx: ClientContext): void {\n // `refresh()` exists on the concrete SessionRuntime used by dsh Web, but is\n // intentionally omitted from the narrow feature-facing ISessions contract.\n // Keep this compatibility bridge optional for older runtimes and fixtures.\n const service = ctx.sessions as unknown as { refresh?: () => Promise<void> }\n if (typeof service.refresh !== 'function') return\n void service.refresh.call(ctx.sessions).catch((cause: unknown) => {\n console.warn('voice session list refresh failed:', cause)\n })\n}\n\n/** Mount root transport controls and durable Voice conversation renderers. @param ctx - browser context. */\nexport function apply(ctx: ClientContext): void {\n const controller = new VoiceController({\n onConversationStarted: () => {\n // Voice events are appended by the Host, while the browser's session\n // list keeps its own blank-bit cache. Refresh once after the first\n // completed turn so Workspace new-session creation does not reuse the\n // now-engaged Voice Session.\n refreshSessions(ctx)\n },\n })\n const history = new VoiceHistoryStore()\r\n const textSubmit = new VoiceTextSubmitBridge(controller, (sessionId) => {\r\n const scope = ctx.sessions.scope(sessionId)\r\n if (scope === undefined) throw new Error(`voice input session \"${sessionId}\" is unavailable`)\r\n return ctx.conversation.input.for(scope) as VoiceTextInput\r\n })\r\n const stopVoice = (): Promise<void> => controller.stop()\r\n const startVoice = async (sourceSessionId: SessionId): Promise<void> => {\r\n ctx.sessions.open(sourceSessionId)\r\n textSubmit.install(sourceSessionId)\r\n await controller.start(sourceSessionId)\r\n history.record(sourceSessionId)\r\n }\r\n const voiceControlInjected = (): VoiceControlInjected => ({\r\n hooks: { voice: controller },\r\n startVoice,\r\n retryVoice: async () => {\n await controller.retry()\n const sessionId = controller.getSnapshot().sessionId\n if (sessionId !== undefined) history.record(sessionId)\n },\n interruptResponse: () => { controller.interruptResponse() },\n setVoiceMuted: (muted) => { controller.setInputMuted(muted) },\n })\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({\n hooks: { voice: controller },\r\n openVoiceSession: (id) => { ctx.sessions.open(id) },\r\n stopVoice,\r\n })\n const voiceSessionMarkersInjected = (): VoiceSessionMarkersInjected => ({\n hooks: { voiceHistory: history.snapshot },\n })\n const utteranceInjected = (): VoiceUtteranceInjected => ({ hooks: { voice: controller } })\r\n const delegationInjected = (): VoiceDelegationInjected => ({\r\n hooks: { voice: controller },\r\n openSession: (id) => { ctx.sessions.open(id) },\r\n cancelTask: (id) => { controller.cancelTask(id) },\r\n })\r\n const historyInjected = (): VoiceHistoryActionInjected => ({\r\n hooks: { voice: controller, voiceHistory: history.snapshot },\r\n openSession: (id) => {\r\n history.record(id)\r\n ctx.sessions.open(id)\r\n },\r\n })\r\n\r\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-voice: dictionaries')\r\n ctx.effect(() => async () => { await controller.stop() }, 'ui-voice: transport teardown')\r\n ctx.effect(() => () => { textSubmit.dispose() }, 'ui-voice: text submit bridge teardown')\r\n ctx.effect(() => () => { history.dispose() }, 'ui-voice: history persistence teardown')\r\n ctx.conversationEvents.register(voiceUtteranceDefinition)\r\n ctx.conversationEvents.register(voiceDelegationDefinition)\r\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\r\n name: 'conversation.input.right',\r\n id: 'voice',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceControlInjected,\r\n }, VoiceControl))\r\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'voice-session-markers',\n order: 10,\n locale: NS,\n inject: voiceSessionMarkersInjected,\n }, VoiceSessionMarkers))\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\r\n name: 'sidebar.footer.action',\r\n id: 'voice-history',\r\n order: 10,\r\n locale: NS,\r\n inject: historyInjected,\r\n }, VoiceHistoryAction))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-utterance',\r\n locale: NS,\r\n inject: utteranceInjected,\r\n }, VoiceUtteranceView))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-delegation',\r\n locale: NS,\r\n inject: delegationInjected,\r\n }, VoiceDelegationView))\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["css","css","useState","css","css","createSnapshotStore"],"sources":["../../../packages/ui-voice/src/client/VoiceControl.tsx","../../../packages/ui-voice/src/client/VoiceNodeViews.tsx","../../../packages/ui-voice/src/client/VoiceOverlay.tsx","../../../packages/ui-voice/src/client/VoiceSessionMarkers.tsx","../../../packages/ui-voice/src/client/voice-controller.ts","../../../packages/ui-voice/src/client/voice-history.ts","../../../packages/ui-voice/src/client/voice-text-submit.ts","../../../packages/ui-voice/src/client/voice-definitions.ts","../../../packages/ui-voice/src/client/locales.ts","../../../packages/ui-voice/src/client/index.ts"],"sourcesContent":["import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport css from './VoiceControl.module.css'\r\n\r\n/** Business callbacks and the root-owned transport snapshot. */\r\nexport interface VoiceControlInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n /** Start a fresh Voice conversation located from the current Session. */\n readonly startVoice: (sourceSessionId: SessionId) => Promise<void>\n readonly retryVoice: () => Promise<void>\n readonly interruptResponse: () => void\n readonly setVoiceMuted: (muted: boolean) => void\n}\n\r\nexport type VoiceControlProps =\r\n PropsRuntime<'conversation.input.right'> & InjectFace<VoiceControlInjected> & PropsLocale<'voice'>\r\n\r\n/** Start a fresh Voice Session from this Session's location or control the active connection. */\r\nexport function VoiceControl({\n sessionId, useVoice, startVoice, retryVoice, setVoiceMuted, t,\n interruptResponse,\n}: VoiceControlProps) {\n const state = useVoice(snapshot => snapshot.state)\n const activeSessionId = useVoice(snapshot => snapshot.sessionId)\n const inputMuted = useVoice(snapshot => snapshot.inputMuted)\n\r\n const toggle = async (): Promise<void> => {\r\n if (state === 'error' && activeSessionId !== undefined) {\r\n try { await retryVoice() } catch { /* controller owns the visible error state */ }\r\n return\r\n }\r\n if (state !== 'off' && state !== 'error') {\n setVoiceMuted(!inputMuted)\n return\n }\r\n try {\r\n await startVoice(sessionId)\r\n } catch { /* controller owns the visible error state */ }\r\n }\r\n\r\n const label = state === 'off'\n ? t('control.start')\n : state === 'error'\n ? t('control.retry')\n : inputMuted\n ? t('control.unmute')\n : t('control.mute')\n const active = state !== 'off' && state !== 'error'\n const interrupt = (): void => {\n try { interruptResponse() } catch { /* controller owns the visible connection state */ }\n }\n return (\n <span className={css.controls}>\n {state === 'speaking' && (\n <button\n type=\"button\"\n className={`${css.button} ${css.interruptButton}`}\n aria-label={t('control.interrupt')}\n title={t('control.interrupt')}\n data-state={state}\n onClick={interrupt}\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n <rect x=\"5.25\" y=\"5.25\" width=\"5.5\" height=\"5.5\" rx=\"1\" fill=\"currentColor\" />\n </svg>\n </button>\n )}\n <button\n type=\"button\"\n className={`${css.button} ${active ? css.active : ''}`}\n aria-label={label}\n aria-pressed={active ? inputMuted : undefined}\n title={label}\n data-state={state}\n data-muted={inputMuted}\n onClick={() => { void toggle() }}\n >\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\n <rect x=\"5\" y=\"2\" width=\"6\" height=\"8\" rx=\"3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\n <path d=\"M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\n {inputMuted && (\n <path d=\"M2.5 2.5l11 11\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" />\n )}\n </svg>\n </button>\n </span>\n )\n}\n","import { useState } from 'react'\r\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\nimport type { VoiceDelegationChatData } from './voice-definitions.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceNodeViews.module.css'\r\n\r\n/** Live transcript source injected into the Voice utterance renderer. */\r\nexport interface VoiceUtteranceInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n}\r\n\r\n/** Navigation action injected into the task-card renderer. */\r\nexport interface VoiceDelegationInjected {\r\n hooks: {\r\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\r\n }\r\n readonly openSession: (id: SessionId) => void\r\n readonly cancelTask: (id: string) => void\r\n}\r\n\r\nexport type VoiceUtteranceViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-utterance'>\r\n & InjectFace<VoiceUtteranceInjected>\r\n & PropsLocale<'voice'>\r\nexport type VoiceDelegationViewProps =\r\n PropsRuntime<'conversation.chat.node', 'voice-delegation'>\r\n & InjectFace<VoiceDelegationInjected>\r\n & PropsLocale<'voice'>\r\n\r\n/** Render a durable Voice utterance while replacing its unsettled text with live WebSocket deltas. */\r\nexport function VoiceUtteranceView({ node, sessionId, useVoice, t }: VoiceUtteranceViewProps) {\r\n const liveText = useVoice(snapshot => snapshot.sessionId === sessionId\r\n ? snapshot.textById[String(node.data.utteranceId)]?.text\r\n : undefined)\r\n const text = node.data.text ?? liveText ?? (node.data.role === 'user'\r\n ? t('utterance.user.pending')\r\n : t('utterance.assistant.pending'))\r\n return (\r\n <article\r\n className={css.utterance}\r\n data-voice-utterance\r\n data-role={node.data.role}\r\n data-state={node.data.state}\r\n >\r\n <div className={css.bubble}>{text}</div>\r\n <div className={css.meta}>\r\n <span className={css.voiceBadge}>\r\n <span className={css.miniWave} aria-hidden><i /><i /><i /></span>\r\n {t('badge.voice')}\r\n </span>\r\n {node.data.state === 'interrupted' && <span>{t('utterance.interrupted')}</span>}\r\n </div>\r\n </article>\r\n )\r\n}\r\n\r\nconst STATUS_KEYS: Record<VoiceDelegationChatData['status'], VoiceKey> = {\r\n accepted: 'task.status.accepted',\r\n queued: 'task.status.queued',\r\n running: 'task.status.running',\r\n 'waiting-user': 'task.status.waiting-user',\r\n completed: 'task.status.completed',\r\n failed: 'task.status.failed',\r\n cancelled: 'task.status.cancelled',\r\n interrupted: 'task.status.interrupted',\r\n}\r\n\r\n/** Render the task link from a Voice Session to its bound background Agent Session. */\r\nexport function VoiceDelegationView({ node, sessionId, useSessions, useVoice, openSession, cancelTask, t }: VoiceDelegationViewProps) {\r\n const [expanded, setExpanded] = useState(false)\r\n const navigable = useSessions(sessions => sessions.ids.includes(node.data.taskSessionId))\r\n const connected = useVoice(voice => voice.sessionId === sessionId && voice.state !== 'off' && voice.state !== 'error')\r\n const cancellable = connected && !isTerminalTaskStatus(node.data.status)\r\n return (\r\n <article\r\n className={css.taskCard}\r\n data-voice-delegation\r\n data-status={node.data.status}\r\n data-expanded={expanded}\r\n >\r\n <button\r\n type=\"button\"\r\n className={css.taskSummary}\r\n aria-expanded={expanded}\r\n aria-label={expanded ? t('task.collapse') : t('task.expand')}\r\n onClick={() => { setExpanded(value => !value) }}\r\n >\r\n <span className={css.taskDot} aria-hidden />\r\n <span className={css.taskTitle}>{t('task.title')}</span>\r\n <span className={css.taskStatus}>{t(STATUS_KEYS[node.data.status])}</span>\r\n <span className={css.taskChevron} aria-hidden />\r\n </button>\r\n {expanded && (\r\n <div className={css.taskDetails}>\r\n <p className={css.taskInput}>{node.data.input}</p>\r\n {node.data.update !== undefined && <p className={css.taskUpdate}>{node.data.update}</p>}\r\n <div className={css.taskActions}>\r\n {cancellable && (\r\n <button type=\"button\" className={`${css.taskLink} ${css.taskCancel}`} onClick={() => { cancelTask(String(node.data.taskId)) }}>\r\n {t('task.cancel')}\r\n </button>\r\n )}\r\n <button\r\n type=\"button\"\r\n className={css.taskLink}\r\n disabled={!navigable}\r\n onClick={() => { openSession(node.data.taskSessionId) }}\r\n >\r\n {t('task.open')}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </article>\r\n )\r\n}\r\n\r\nfunction isTerminalTaskStatus(status: VoiceDelegationChatData['status']): boolean {\r\n return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'interrupted'\r\n}\r\n","import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { VoiceClientSnapshot, VoiceClientState } from './voice-controller.ts'\r\nimport type { VoiceKey } from './locales.ts'\r\nimport css from './VoiceOverlay.module.css'\r\n\r\n/** Root-level voice connection actions. */\r\nexport interface VoiceOverlayInjected {\n hooks: {\n voice: { getSnapshot(): VoiceClientSnapshot; subscribe(listener: () => void): () => void }\n }\n readonly stopVoice: () => Promise<void>\n}\n\r\nexport type VoiceOverlayProps =\r\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceOverlayInjected> & PropsLocale<'voice'>\r\n\r\nconst STATE_KEYS: Record<Exclude<VoiceClientState, 'off'>, VoiceKey> = {\r\n connecting: 'overlay.connecting',\r\n listening: 'overlay.listening',\r\n speaking: 'overlay.speaking',\r\n error: 'overlay.error',\r\n}\r\n\r\n/** Keep the active Voice Session controllable while another Session is selected. */\r\nexport function VoiceOverlay({ useVoice, stopVoice, t }: VoiceOverlayProps) {\n const voice = useVoice(snapshot => snapshot)\n if (voice.state === 'off') return null\n const statusKey = voice.inputMuted && voice.state !== 'error' ? 'overlay.muted' : STATE_KEYS[voice.state]\n return (\n <aside className={css.root} data-voice-overlay data-state={voice.state} data-muted={voice.inputMuted} aria-live=\"polite\">\n <span className={css.wave} aria-hidden>\r\n <i /><i /><i /><i />\r\n </span>\r\n <span className={css.status}>{t(statusKey)}</span>\n <button type=\"button\" className={css.stop} onClick={() => { void stopVoice() }}>\n {t('overlay.stop')}\r\n </button>\r\n </aside>\r\n )\r\n}\r\n","import { useEffect } from 'react'\nimport type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { VoiceHistorySnapshot } from './voice-history.ts'\nimport css from './VoiceSessionMarkers.module.css'\n\n/** Root overlay that annotates the host-owned session rows with Voice markers. */\nexport interface VoiceSessionMarkersInjected {\n hooks: {\n voiceHistory: { getSnapshot(): VoiceHistorySnapshot; subscribe(listener: () => void): () => void }\n }\n}\n\nexport type VoiceSessionMarkersProps =\n PropsRuntime<'shell.overlay'> & InjectFace<VoiceSessionMarkersInjected>\n\n/**\n * The workspace package currently owns the session-row renderer and does not\n * expose a row-level slot. This small, read-only decorator keeps the marker in\n * sync with that renderer while leaving row interactions and layout intact.\n */\nexport function VoiceSessionMarkers({ useSessions, useVoiceHistory }: VoiceSessionMarkersProps) {\n const sessions = useSessions(state => state.byId)\n const history = useVoiceHistory(state => state.entries)\n\n useEffect(() => {\n const voiceTitles = new Set<SessionId>(history.map(entry => entry.sessionId))\n const sessionIds = new Set(Object.keys(sessions))\n\n let frame = 0\n const decorate = (): void => {\n frame = 0\n const rows = document.querySelectorAll<HTMLElement>('[role=\"treeitem\"].sessionRow, [role=\"treeitem\"][class*=\"sessionRow\"]')\n for (const row of rows) {\n const slot = row.querySelector<HTMLElement>('[class*=\"slot\"]')\n const marker = row.querySelector<HTMLElement>('[data-voice-session-marker]')\n const sessionId = findSessionKey(row, sessionIds)\n const isVoice = sessionId !== undefined && voiceTitles.has(sessionId as SessionId)\n if (!isVoice) {\n marker?.remove()\n continue\n }\n if (marker !== null) continue\n const target = slot ?? createMarkerSlot(row)\n target.append(createMarker())\n }\n }\n const schedule = (): void => {\n if (frame !== 0) return\n frame = requestAnimationFrame(decorate)\n }\n decorate()\n const observer = new MutationObserver(schedule)\n observer.observe(document.body, { childList: true, subtree: true, characterData: true })\n return () => {\n observer.disconnect()\n if (frame !== 0) cancelAnimationFrame(frame)\n document.querySelectorAll('[data-voice-session-marker]').forEach(node => { node.remove() })\n document.querySelectorAll('[data-voice-session-marker-slot]').forEach(node => { node.remove() })\n }\n }, [history, sessions])\n\n return null\n}\n\n/** React keeps the keyed SessionNodeItem identity on the row's owner fiber. */\nfunction findSessionKey(row: HTMLElement, sessionIds: Set<string>): string | undefined {\n const fiberKey = Object.keys(row).find(key => key.startsWith('__reactFiber$'))\n if (fiberKey === undefined) return undefined\n let fiber = (row as unknown as Record<string, unknown>)[fiberKey] as { key: string | null; return?: unknown } | undefined\n while (fiber !== undefined) {\n if (fiber.key !== null && sessionIds.has(fiber.key)) return fiber.key\n fiber = fiber.return as typeof fiber | undefined\n }\n return undefined\n}\n\nfunction createMarkerSlot(row: HTMLElement): HTMLSpanElement {\n const slot = document.createElement('span')\n slot.dataset.voiceSessionMarkerSlot = ''\n slot.className = css.slot ?? 'voice-session-marker-slot'\n const title = row.querySelector<HTMLElement>('[class*=\"title\"]')\n row.insertBefore(slot, title ?? row.firstChild ?? null)\n return slot\n}\n\nfunction createMarker(): HTMLSpanElement {\n const marker = document.createElement('span')\n marker.dataset.voiceSessionMarker = ''\n marker.className = css.marker ?? 'voice-session-marker'\n marker.setAttribute('aria-hidden', 'true')\n marker.innerHTML = '<svg viewBox=\"0 0 16 16\" aria-hidden=\"true\"><path d=\"M3 8h1.5M6 5v6M9 3.5v9M12 5.5v5M14 7h-1\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.35\" stroke-linecap=\"round\"/></svg>'\n return marker\n}\n","import type { ObservableSnapshot, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\n/** Browser-visible lifecycle of the one active voice transport. */\r\nexport type VoiceClientState = 'off' | 'connecting' | 'listening' | 'speaking' | 'error'\r\n\r\n/** Live text retained until the matching durable utterance settles. */\r\nexport interface VoiceLiveText {\r\n readonly text: string\r\n}\r\n\r\n/** Root-owned voice transport projection shared by every UI seat. */\r\nexport interface VoiceClientSnapshot {\n readonly state: VoiceClientState\n readonly inputMuted: boolean\n readonly sessionId?: SessionId\n readonly textById: Readonly<Record<string, VoiceLiveText>>\n}\n\ninterface VoiceTransport {\n close(): Promise<void>\n cancelTask(taskId: string): void\n interruptResponse(): void\n setInputMuted(muted: boolean): void\n submitText(text: string): void\n}\n\r\ninterface VoiceTransportHandlers {\n readonly onBinary: (audio: ArrayBuffer) => void\r\n readonly onEvent: (event: unknown) => void\r\n readonly onPlaybackEnded: () => void\r\n readonly onUnexpectedClose: () => void\r\n}\n\nexport interface VoiceControllerOptions {\n /** Called once after the first non-empty durable voice turn is completed. */\n readonly onConversationStarted?: (sessionId: SessionId) => void\n}\n\r\ninterface VoiceReadyEvent {\r\n readonly type: 'ready'\r\n readonly voiceSessionId: string\r\n readonly audio: {\r\n readonly inputSampleRate: number\r\n readonly outputSampleRate: number\r\n readonly format: 'pcm_s16le' | 'audio_mpeg'\r\n }\r\n readonly interactionMode: 'speech-shell' | 'frontend-agent'\r\n}\r\n\r\nconst INITIAL_SNAPSHOT: VoiceClientSnapshot = { state: 'off', inputMuted: false, textById: {} }\n\r\n/** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */\r\nexport class VoiceController implements ObservableSnapshot<VoiceClientSnapshot> {\n private snapshot = INITIAL_SNAPSHOT\r\n private readonly listeners = new Set<() => void>()\r\n private transport: VoiceTransport | undefined\r\n private openingTransport: VoiceTransport | undefined\r\n private openingAbort: AbortController | undefined\r\n private generation = 0\r\n private frame: number | undefined\r\n private readonly pendingText: string[] = []\n private readonly onConversationStarted: ((sessionId: SessionId) => void) | undefined\n private conversationStarted = false\n\n constructor(options: VoiceControllerOptions = {}) {\n this.onConversationStarted = options.onConversationStarted\n }\n\r\n /** Return the identity-stable snapshot until a voice fact changes. */\r\n getSnapshot = (): VoiceClientSnapshot => this.snapshot\r\n\r\n /** Subscribe to state and frame-coalesced transcript changes. */\r\n subscribe = (listener: () => void): (() => void) => {\r\n this.listeners.add(listener)\r\n return () => { this.listeners.delete(listener) }\r\n }\r\n\r\n /**\n * Open one voice transport and make it the root-owned active session.\n * @param sessionId - durable voice session to connect.\n */\n async start(sessionId: SessionId): Promise<void> {\n await this.open(sessionId, false)\n }\n\n private async open(sessionId: SessionId, inputMuted: boolean): Promise<void> {\n const generation = ++this.generation\n this.pendingText.splice(0)\n this.conversationStarted = false\n // Construct and unlock both contexts before the first await so browser\r\n // autoplay policy still treats the microphone button as the user gesture.\r\n const contexts = createAudioContexts()\r\n await this.closeTransport()\r\n if (generation !== this.generation) {\r\n await closeAudioContexts(contexts)\r\n return\r\n }\r\n const openingAbort = new AbortController()\r\n this.openingAbort = openingAbort\r\n this.publish({ state: 'connecting', inputMuted, sessionId, textById: {} })\n try {\r\n const transport = await openVoiceTransport(String(sessionId), {\r\n onBinary: () => { this.setState('speaking') },\r\n onEvent: (event) => { this.handleEvent(event) },\r\n onPlaybackEnded: () => { this.setState('listening') },\r\n onUnexpectedClose: () => { this.handleUnexpectedClose() },\r\n }, openingAbort.signal, (opening) => {\n if (generation === this.generation) {\n this.openingTransport = opening\n opening.setInputMuted(this.snapshot.inputMuted)\n }\n else void opening.close()\n }, contexts)\r\n if (generation !== this.generation) {\r\n await transport.close()\r\n return\r\n }\r\n this.openingTransport = undefined\r\n this.transport = transport\r\n for (const text of this.pendingText.splice(0)) transport.submitText(text)\r\n this.setState('listening')\r\n } catch (cause) {\r\n if (generation !== this.generation) return\r\n this.setState('error')\r\n throw cause\r\n } finally {\r\n if (this.openingAbort === openingAbort) this.openingAbort = undefined\r\n if (generation === this.generation) this.openingTransport = undefined\r\n }\r\n }\r\n\r\n /** Retry the failed active Voice Session, when one was created successfully. */\r\n async retry(): Promise<void> {\n const sessionId = this.snapshot.sessionId\n if (sessionId === undefined) throw new Error('voice retry requires an active session')\n await this.open(sessionId, this.snapshot.inputMuted)\n }\n\n /** Mute or resume microphone input without closing text submission or playback. */\n setInputMuted(muted: boolean): void {\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\n throw new Error('voice input mute requires an active connection')\n }\n if (this.snapshot.inputMuted === muted) return\n this.openingTransport?.setInputMuted(muted)\n this.transport?.setInputMuted(muted)\n this.publish({ ...this.snapshot, inputMuted: muted })\n }\n\r\n /** Cancel the exact active background task through the attached Voice transport. */\r\n cancelTask(taskId: string): void {\n if (taskId === '') throw new Error('voice task id is required')\r\n if (this.transport === undefined) throw new Error('voice task cancellation requires an active connection')\r\n this.transport.cancelTask(taskId)\n }\n\n /** Stop the active assistant response without ending the Voice Session. */\n interruptResponse(): void {\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\n throw new Error('voice response interruption requires an active connection')\n }\n const transport = this.transport ?? this.openingTransport\n if (transport === undefined) throw new Error('voice response interruption requires an active connection')\n transport.interruptResponse()\n this.setState('listening')\n }\n\r\n /** Submit composer text as a voice turn, queueing it while the transport connects. */\r\n submitText(text: string): void {\r\n const value = text.trim()\r\n if (value === '') throw new Error('voice text submission requires non-empty text')\r\n if (this.snapshot.state === 'off' || this.snapshot.state === 'error' || this.snapshot.sessionId === undefined) {\r\n throw new Error('voice text submission requires an active connection')\r\n }\r\n if (this.transport === undefined) this.pendingText.push(value)\r\n else this.transport.submitText(value)\r\n }\r\n\r\n /** Stop capture/playback and await owned browser-resource teardown. */\r\n async stop(): Promise<void> {\r\n ++this.generation\r\n this.openingAbort?.abort()\r\n this.openingAbort = undefined\r\n this.publish(INITIAL_SNAPSHOT)\r\n this.pendingText.splice(0)\r\n await this.closeTransport()\r\n }\r\n\r\n private async closeTransport(): Promise<void> {\r\n const transports = new Set<VoiceTransport>()\r\n if (this.transport !== undefined) transports.add(this.transport)\r\n if (this.openingTransport !== undefined) transports.add(this.openingTransport)\r\n this.transport = undefined\r\n this.openingTransport = undefined\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n const settled = await Promise.allSettled([...transports].map(transport => transport.close()))\r\n const failures: unknown[] = []\r\n for (const result of settled) {\r\n if (result.status === 'rejected') failures.push(result.reason as unknown)\r\n }\r\n if (failures.length > 0) throw new AggregateError(failures, 'voice transport cleanup failed')\r\n }\r\n\r\n private handleUnexpectedClose(): void {\r\n this.openingAbort?.abort()\r\n this.setState('error')\r\n void this.closeTransport().catch((cause: unknown) => {\r\n console.error('voice transport cleanup failed:', cause)\r\n })\r\n }\r\n\r\n private setState(state: VoiceClientState): void {\r\n if (this.snapshot.state === state) return\r\n this.publish({ ...this.snapshot, state })\r\n }\r\n\r\n private setText(id: string, text: string, cadence: 'immediate' | 'animation-frame'): void {\r\n if (id === '') return\r\n const current = this.snapshot.textById[id]\r\n if (current?.text === text) return\r\n this.snapshot = {\r\n ...this.snapshot,\r\n textById: { ...this.snapshot.textById, [id]: { text } },\r\n }\r\n if (cadence === 'immediate') this.notify()\r\n else this.notifyOnFrame()\r\n }\r\n\r\n private appendText(id: string, delta: string): void {\r\n if (id === '' || delta === '') return\r\n this.setText(id, `${this.snapshot.textById[id]?.text ?? ''}${delta}`, 'animation-frame')\r\n }\r\n\r\n private handleEvent(input: unknown): void {\r\n const event = recordOf(input)\r\n if (event === undefined) return\r\n const type = stringOf(event.type)\r\n if (type === 'transcription.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'transcription.updated') {\r\n this.setText(stringOf(event.utteranceId), stringOf(event.text), 'animation-frame')\r\n return\r\n }\r\n if (type === 'transcription.completed') {\n const text = stringOf(event.text)\n this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_text.started') {\r\n this.setText(stringOf(event.utteranceId), '', 'immediate')\r\n return\r\n }\r\n if (type === 'output_text.delta') {\r\n this.appendText(stringOf(event.utteranceId), stringOf(event.text))\r\n return\r\n }\r\n if (type === 'output_text.done') {\n const text = stringOf(event.text)\n if (text !== '') this.setText(stringOf(event.utteranceId), text, 'immediate')\n if (text.trim() !== '') this.markConversationStarted()\n return\n }\r\n if (type === 'output_audio.started') {\r\n this.setState('speaking')\r\n return\r\n }\r\n if (type === 'response.interrupted') {\r\n this.setState('listening')\r\n return\r\n }\r\n if (type === 'error') this.handleUnexpectedClose()\r\n }\n\n private markConversationStarted(): void {\n if (this.conversationStarted) return\n const sessionId = this.snapshot.sessionId\n if (sessionId === undefined) return\n this.conversationStarted = true\n this.onConversationStarted?.(sessionId)\n }\n\r\n private publish(snapshot: VoiceClientSnapshot): void {\r\n if (this.frame !== undefined) {\r\n cancelAnimationFrame(this.frame)\r\n this.frame = undefined\r\n }\r\n this.snapshot = snapshot\r\n this.notify()\r\n }\r\n\r\n private notifyOnFrame(): void {\r\n this.frame ??= requestAnimationFrame(() => {\r\n this.frame = undefined\r\n this.notify()\r\n })\r\n }\r\n\r\n private notify(): void {\r\n for (const listener of this.listeners) {\r\n try { listener() } catch (cause) { console.error('voice snapshot listener failed:', cause) }\r\n }\r\n }\r\n}\r\n\r\nfunction recordOf(value: unknown): Record<string, unknown> | undefined {\r\n return value !== null && typeof value === 'object' ? value as Record<string, unknown> : undefined\r\n}\r\n\r\nfunction stringOf(value: unknown): string {\r\n return typeof value === 'string' ? value : ''\r\n}\r\n\r\nasync function openVoiceTransport(\r\n sessionId: string,\r\n handlers: VoiceTransportHandlers,\r\n signal: AbortSignal,\r\n onOpening: (transport: VoiceTransport) => void,\r\n contexts: AudioContexts,\r\n): Promise<VoiceTransport> {\r\n const { input: inputContext, output: outputContext } = contexts\r\n const socket = new WebSocket(voiceUrl(sessionId))\r\n const workletUrl = URL.createObjectURL(new Blob([CAPTURE_WORKLET], { type: 'text/javascript' }))\r\n let stream: MediaStream | undefined\r\n let source: MediaStreamAudioSourceNode | undefined\r\n let processor: AudioWorkletNode | undefined\r\n let silent: GainNode | undefined\r\n let stopped = false\r\n let socketClosed = false\r\n let ready = false\r\n let outputDone = false\r\n let encodedPending = 0\r\n let playbackGeneration = 0\r\n let encodedQueue: Promise<void> = Promise.resolve()\r\n let inputSampleRate = 16_000\r\n let outputSampleRate = 24_000\r\n let audioFormat: VoiceReadyEvent['audio']['format'] = 'pcm_s16le'\r\n let playAt = outputContext.currentTime\n let inputMuted = false\n const chunks: number[] = []\n const playing = new Set<AudioBufferSourceNode>()\r\n\r\n const transport: VoiceTransport = {\n cancelTask: (taskId) => {\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\n },\n interruptResponse: () => {\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\n socket.send(JSON.stringify({ type: 'response.interrupt' }))\n stopPlayback()\n },\n setInputMuted: (muted) => {\n if (inputMuted === muted) return\n inputMuted = muted\n chunks.splice(0)\n for (const track of stream?.getTracks() ?? []) track.enabled = !muted\n if (muted && !stopped && socket.readyState === WebSocket.OPEN) {\n socket.send(JSON.stringify({ type: 'audio.commit' }))\n }\n },\n submitText: (text) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'text.submit', text }))\r\n },\r\n close: async () => {\r\n /* v8 ignore next -- controller ownership closes a transport once; the guard protects browser close races. */\r\n if (stopped) return\r\n stopped = true\r\n if (!socketClosed && socket.readyState === WebSocket.OPEN) {\r\n try { socket.send(JSON.stringify({ type: 'session.close' })) } catch {\r\n /* A socket closing concurrently cannot carry the final control; the server expires its reconnect grace. */\r\n }\r\n }\r\n socket.removeEventListener('message', onMessage)\r\n socket.removeEventListener('close', onClose)\r\n stopPlayback()\r\n source?.disconnect()\r\n processor?.disconnect()\r\n silent?.disconnect()\r\n processor?.port.close()\r\n for (const track of stream?.getTracks() ?? []) track.stop()\r\n socket.close()\r\n await Promise.allSettled([inputContext.close(), outputContext.close()])\r\n },\r\n }\r\n\r\n const stopPlayback = (): void => {\r\n playbackGeneration += 1\r\n encodedPending = 0\r\n encodedQueue = Promise.resolve()\r\n for (const node of playing) {\r\n try { node.stop() } catch { /* a completed source is already stopped */ }\r\n }\r\n playing.clear()\r\n playAt = outputContext.currentTime\r\n outputDone = false\r\n }\r\n const notifyPlaybackEnded = (): void => {\r\n if (outputDone && encodedPending === 0 && playing.size === 0 && socket.readyState === WebSocket.OPEN) {\r\n socket.send(JSON.stringify({ type: 'playback.ended' }))\r\n outputDone = false\r\n handlers.onPlaybackEnded()\r\n }\r\n }\r\n const onMessage = (message: MessageEvent): void => {\r\n if (!ready) return\r\n if (message.data instanceof ArrayBuffer) {\r\n if (audioFormat === 'audio_mpeg') {\r\n const generation = playbackGeneration\r\n encodedPending += 1\r\n encodedQueue = encodedQueue.catch(() => {}).then(() => scheduleEncoded(\r\n outputContext,\r\n message.data,\r\n playing,\r\n notifyPlaybackEnded,\r\n (value) => { playAt = value },\r\n () => playAt,\r\n () => generation === playbackGeneration && !stopped,\r\n )).catch(error => {\r\n if (generation === playbackGeneration && !stopped) {\r\n handlers.onEvent({ type: 'error', message: `audio decode failed: ${String(error)}` })\r\n }\r\n }).finally(() => {\r\n if (generation === playbackGeneration) {\r\n encodedPending -= 1\r\n notifyPlaybackEnded()\r\n }\r\n })\r\n } else {\r\n schedulePcm(outputContext, message.data, outputSampleRate, playing, notifyPlaybackEnded, (value) => { playAt = value }, playAt)\r\n }\r\n handlers.onBinary(message.data)\r\n return\r\n }\r\n let event: unknown\r\n try { event = JSON.parse(String(message.data)) } catch { return }\r\n const record = recordOf(event)\r\n if (record?.type === 'response.interrupted') stopPlayback()\n if (record?.type === 'output_audio.done') {\r\n outputDone = true\r\n notifyPlaybackEnded()\r\n }\r\n handlers.onEvent(event)\r\n }\r\n const onClose = (): void => {\r\n socketClosed = true\r\n handlers.onUnexpectedClose()\r\n }\r\n\r\n socket.binaryType = 'arraybuffer'\r\n onOpening(transport)\r\n try {\r\n await socketOpened(socket, signal)\r\n socket.addEventListener('message', onMessage)\r\n socket.addEventListener('close', onClose)\r\n const readyEvent = await socketReady(socket, signal)\r\n inputSampleRate = readyEvent.audio.inputSampleRate\r\n outputSampleRate = readyEvent.audio.outputSampleRate\r\n audioFormat = readyEvent.audio.format\r\n ready = true\r\n const captured = await microphone(signal)\r\n if (openingStopped(signal, stopped)) {\r\n for (const track of captured.getTracks()) track.stop()\r\n throw new Error('voice connection opening aborted')\r\n }\r\n stream = captured\n for (const track of stream.getTracks()) track.enabled = !inputMuted\n await abortable(inputContext.audioWorklet.addModule(workletUrl), signal)\r\n if (openingStopped(signal, stopped)) throw new Error('voice connection opening aborted')\r\n source = inputContext.createMediaStreamSource(stream)\r\n processor = new AudioWorkletNode(inputContext, 'dsh-pcm-capture')\r\n silent = inputContext.createGain()\r\n silent.gain.value = 0\r\n processor.port.onmessage = (event: MessageEvent<Float32Array>) => {\n if (inputMuted || socket.readyState !== WebSocket.OPEN) return\n chunks.push(...resample(event.data, inputContext.sampleRate, inputSampleRate))\r\n const frameSize = Math.max(1, Math.round(inputSampleRate / 5))\r\n while (chunks.length >= frameSize) {\r\n const part = chunks.splice(0, frameSize)\r\n const pcm = new Int16Array(part.length)\r\n for (const [index, sample] of part.entries()) {\r\n pcm[index] = Math.round(Math.max(-1, Math.min(1, sample)) * 32767)\r\n }\r\n socket.send(pcm.buffer)\r\n }\r\n }\r\n source.connect(processor)\r\n processor.connect(silent)\r\n silent.connect(inputContext.destination)\r\n } catch (cause) {\r\n await transport.close()\r\n throw cause\r\n } finally {\r\n URL.revokeObjectURL(workletUrl)\r\n }\r\n\r\n return transport\r\n}\r\n\r\ninterface AudioContexts {\r\n readonly input: AudioContext\r\n readonly output: AudioContext\r\n}\r\n\r\nfunction createAudioContexts(): AudioContexts {\r\n const contexts = { input: new AudioContext(), output: new AudioContext() }\r\n // Calling resume synchronously is required on browsers that start contexts\r\n // suspended when they are created outside a trusted user gesture.\r\n void contexts.input.resume().catch(() => {})\r\n void contexts.output.resume().catch(() => {})\r\n return contexts\r\n}\r\n\r\nasync function closeAudioContexts(contexts: AudioContexts): Promise<void> {\r\n await Promise.allSettled([contexts.input.close(), contexts.output.close()])\r\n}\r\n\r\nfunction microphone(signal: AbortSignal): Promise<MediaStream> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cancelled = true\r\n cleanup()\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n let request: Promise<MediaStream>\r\n try {\r\n request = navigator.mediaDevices.getUserMedia({\r\n audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },\r\n })\r\n } catch (cause) {\r\n cleanup()\r\n reject(errorOf(cause))\r\n return\r\n }\r\n void request.then((value) => {\r\n cleanup()\r\n if (cancelled || signal.aborted) {\r\n for (const track of value.getTracks()) track.stop()\r\n return\r\n }\r\n resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction abortable<T>(operation: Promise<T>, signal: AbortSignal): Promise<T> {\r\n if (signal.aborted) return Promise.reject(new Error('voice connection opening aborted'))\r\n return new Promise((resolve, reject) => {\r\n let cancelled = false\r\n const cleanup = (): void => { signal.removeEventListener('abort', aborted) }\r\n const aborted = (): void => {\r\n cleanup()\r\n cancelled = true\r\n reject(new Error('voice connection opening aborted'))\r\n }\r\n signal.addEventListener('abort', aborted, { once: true })\r\n void operation.then((value) => {\r\n cleanup()\r\n if (!cancelled) resolve(value)\r\n }, (cause: unknown) => {\r\n cleanup()\r\n if (!cancelled) reject(errorOf(cause))\r\n })\r\n })\r\n}\r\n\r\nfunction socketOpened(socket: WebSocket, signal: AbortSignal): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('open', opened)\r\n socket.removeEventListener('error', failed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const opened = (): void => { cleanup(); resolve() }\r\n const failed = (): void => { cleanup(); reject(new Error('voice websocket failed')) }\r\n const aborted = (): void => { cleanup(); reject(new Error('voice connection opening aborted')) }\r\n if (signal.aborted) { aborted(); return }\r\n socket.addEventListener('open', opened, { once: true })\r\n socket.addEventListener('error', failed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction socketReady(socket: WebSocket, signal: AbortSignal): Promise<VoiceReadyEvent> {\r\n return new Promise((resolve, reject) => {\r\n const cleanup = (): void => {\r\n socket.removeEventListener('message', received)\r\n socket.removeEventListener('error', failed)\r\n socket.removeEventListener('close', closed)\r\n signal.removeEventListener('abort', aborted)\r\n }\r\n const settle = (operation: () => void): void => { cleanup(); operation() }\r\n const received = (message: MessageEvent): void => {\r\n try {\r\n const value: unknown = message.data instanceof ArrayBuffer\r\n ? undefined\r\n : JSON.parse(String(message.data)) as unknown\r\n settle(() => { resolve(parseReady(value)) })\r\n } catch (cause) {\r\n settle(() => { reject(errorOf(cause)) })\r\n }\r\n }\r\n const failed = (): void => { settle(() => { reject(new Error('voice websocket failed before ready')) }) }\r\n const closed = (): void => { settle(() => { reject(new Error('voice websocket closed before ready')) }) }\r\n const aborted = (): void => { settle(() => { reject(new Error('voice connection opening aborted')) }) }\r\n if (signal.aborted) { aborted(); return }\r\n if (socket.readyState !== WebSocket.OPEN) { closed(); return }\r\n socket.addEventListener('message', received)\r\n socket.addEventListener('error', failed, { once: true })\r\n socket.addEventListener('close', closed, { once: true })\r\n signal.addEventListener('abort', aborted, { once: true })\r\n })\r\n}\r\n\r\nfunction openingStopped(signal: AbortSignal, stopped: boolean): boolean {\r\n return signal.aborted || stopped\r\n}\r\n\r\nfunction errorOf(cause: unknown): Error {\r\n return cause instanceof Error ? cause : new Error(String(cause))\r\n}\r\n\r\nfunction parseReady(value: unknown): VoiceReadyEvent {\r\n const event = recordOf(value)\r\n if (event?.type === 'error') {\r\n const message = stringOf(event.message)\r\n throw new Error(message === '' ? 'voice setup failed before ready' : message)\r\n }\r\n if (event?.type !== 'ready') throw new Error('voice websocket expected a ready event')\r\n const voiceSessionId = stringOf(event.voiceSessionId)\r\n if (voiceSessionId === '') throw new Error('voice websocket ready event has no voiceSessionId')\r\n const audio = recordOf(event.audio)\r\n if (audio?.inputSampleRate !== 16_000\r\n || (audio.outputSampleRate !== 24_000 && audio.outputSampleRate !== 48_000)\r\n || (audio.format !== 'pcm_s16le' && audio.format !== 'audio_mpeg')) {\r\n throw new Error('voice websocket ready event has unsupported audio settings')\r\n }\r\n if (event.interactionMode !== 'speech-shell' && event.interactionMode !== 'frontend-agent') {\r\n throw new Error('voice websocket ready event has an invalid interactionMode')\r\n }\r\n return { type: 'ready', voiceSessionId, audio: {\r\n inputSampleRate: audio.inputSampleRate,\r\n outputSampleRate: audio.outputSampleRate,\r\n format: audio.format,\r\n }, interactionMode: event.interactionMode }\r\n}\r\n\r\n/**\r\n * Resolve the dedicated WebSocket URL for one durable Voice Session.\r\n * @param sessionId - durable voice session id.\r\n * @param location - browser location used for host and protocol defaults.\r\n * @returns the /voice WebSocket URL carrying the session id.\r\n */\r\nexport function voiceUrl(sessionId: string, location: Pick<Location, 'href' | 'protocol'> = window.location): URL {\r\n const url = new URL('/voice', location.href)\r\n url.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'\r\n url.searchParams.set('sessionId', sessionId)\r\n return url\r\n}\r\n\r\nconst CAPTURE_WORKLET = `\r\nclass DshPcmCaptureProcessor extends AudioWorkletProcessor {\r\n process(inputs) {\r\n const channel = inputs[0] && inputs[0][0]\r\n if (channel) this.port.postMessage(channel.slice())\r\n return true\r\n }\r\n}\r\nregisterProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)\r\n`\r\n\r\n/**\r\n * Resample browser float PCM into the provider input rate.\r\n * @param input - float samples at `fromRate`.\r\n * @param fromRate - source sample rate in Hz.\r\n * @param toRate - target sample rate in Hz.\r\n * @returns linearly interpolated samples at `toRate`.\r\n */\r\nexport function resample(input: Float32Array, fromRate: number, toRate: number): number[] {\r\n if (fromRate === toRate) return [...input]\r\n const ratio = fromRate / toRate\r\n const result = new Array<number>(Math.floor(input.length / ratio))\r\n for (let index = 0; index < result.length; index += 1) {\r\n const position = index * ratio\r\n const left = Math.floor(position)\r\n const fraction = position - left\r\n const leftSample = input[left] as number\r\n const rightSample = input[Math.min(left + 1, input.length - 1)] as number\r\n result[index] = leftSample * (1 - fraction) + rightSample * fraction\r\n }\r\n return result\r\n}\r\n\r\nfunction schedulePcm(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n sampleRate: number,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n currentPlayAt: number,\r\n): void {\r\n const pcm = new Int16Array(bytes)\r\n const buffer = context.createBuffer(1, pcm.length, sampleRate)\r\n const channel = buffer.getChannelData(0)\r\n for (const [index, sample] of pcm.entries()) channel[index] = sample / 32768\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(currentPlayAt, context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n\r\nasync function scheduleEncoded(\r\n context: AudioContext,\r\n bytes: ArrayBuffer,\r\n playing: Set<AudioBufferSourceNode>,\r\n ended: () => void,\r\n setPlayAt: (value: number) => void,\r\n getPlayAt: () => number,\r\n shouldSchedule: () => boolean,\r\n): Promise<void> {\r\n const buffer = await context.decodeAudioData(bytes.slice(0))\r\n if (!shouldSchedule()) return\r\n const node = context.createBufferSource()\r\n node.buffer = buffer\r\n node.connect(context.destination)\r\n const start = Math.max(getPlayAt(), context.currentTime)\r\n setPlayAt(start + buffer.duration)\r\n playing.add(node)\r\n node.onended = () => { playing.delete(node); ended() }\r\n node.start(start)\r\n}\r\n","import {\r\n createSnapshotStore, type SessionId, type SnapshotStore,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\n\r\nconst STORAGE_KEY = 'dsh.voice.history.v1'\r\n\r\n/** One locally indexed durable Voice Session. */\r\nexport interface VoiceHistoryEntry {\r\n readonly sessionId: SessionId\r\n readonly lastActiveAt: number\r\n}\r\n\r\n/** Browser-persistent Voice Session index. */\r\nexport interface VoiceHistorySnapshot {\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\ninterface StoredVoiceHistory {\r\n readonly version: 1\r\n readonly entries: readonly VoiceHistoryEntry[]\r\n}\r\n\r\nfunction parseEntry(input: unknown): VoiceHistoryEntry | undefined {\r\n if (typeof input !== 'object' || input === null) return undefined\r\n const value = input as Record<string, unknown>\r\n if (typeof value.sessionId !== 'string' || value.sessionId === '') return undefined\r\n if (typeof value.lastActiveAt !== 'number' || !Number.isFinite(value.lastActiveAt) || value.lastActiveAt < 0) {\r\n return undefined\r\n }\r\n return { sessionId: value.sessionId as SessionId, lastActiveAt: value.lastActiveAt }\r\n}\r\n\r\nfunction readHistory(storage: Storage | undefined): VoiceHistorySnapshot {\r\n if (storage === undefined) return { entries: [] }\r\n try {\r\n const raw = storage.getItem(STORAGE_KEY)\r\n if (raw === null) return { entries: [] }\r\n const parsed = JSON.parse(raw) as unknown\r\n if (typeof parsed !== 'object' || parsed === null) return { entries: [] }\r\n const value = parsed as Record<string, unknown>\r\n if (value.version !== 1 || !Array.isArray(value.entries)) return { entries: [] }\r\n const entries: VoiceHistoryEntry[] = []\r\n const seen = new Set<SessionId>()\r\n for (const candidate of value.entries) {\r\n const entry = parseEntry(candidate)\r\n if (entry === undefined || seen.has(entry.sessionId)) continue\r\n seen.add(entry.sessionId)\r\n entries.push(entry)\r\n }\r\n entries.sort((left, right) => right.lastActiveAt - left.lastActiveAt)\r\n return { entries }\r\n } catch (error: unknown) {\r\n console.error('voice history rehydration failed:', error)\r\n return { entries: [] }\r\n }\r\n}\r\n\r\n/** Own the local history index without adding Voice metadata to Host session projections. */\r\nexport class VoiceHistoryStore {\r\n readonly snapshot: SnapshotStore<VoiceHistorySnapshot>\r\n private readonly storage: Storage | undefined\r\n private readonly unsubscribe: () => void\r\n\r\n constructor(storage: Storage | undefined = typeof localStorage === 'undefined' ? undefined : localStorage) {\r\n this.storage = storage\r\n this.snapshot = createSnapshotStore(readHistory(storage))\r\n this.unsubscribe = this.snapshot.subscribe(() => { this.persist() })\r\n }\r\n\r\n /** Move one successfully opened Voice Session to the front of the index. */\r\n record(sessionId: SessionId, lastActiveAt = Date.now()): void {\r\n this.snapshot.set({\r\n entries: [\r\n { sessionId, lastActiveAt },\r\n ...this.snapshot.getSnapshot().entries.filter(entry => entry.sessionId !== sessionId),\r\n ],\r\n })\r\n }\r\n\r\n /** Stop persistence notifications. */\r\n dispose(): void {\r\n this.unsubscribe()\r\n }\r\n\r\n private persist(): void {\r\n if (this.storage === undefined) return\r\n const value: StoredVoiceHistory = { version: 1, entries: this.snapshot.getSnapshot().entries }\r\n try {\r\n this.storage.setItem(STORAGE_KEY, JSON.stringify(value))\r\n } catch (error: unknown) {\r\n console.error('voice history persistence failed:', error)\r\n }\r\n }\r\n}\r\n","import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { VoiceClientSnapshot } from './voice-controller.ts'\r\n\r\ninterface VoiceTextInputState {\r\n readonly draft: string\r\n readonly imageIds: readonly unknown[]\r\n}\r\n\r\nexport interface VoiceTextInput {\r\n readonly state: { getSnapshot(): VoiceTextInputState }\r\n setDraft(text: string): void\r\n submit(mode?: unknown): void\r\n notify(level: 'info' | 'error', text: string): void\r\n}\r\n\r\ninterface VoiceTextController {\r\n getSnapshot(): VoiceClientSnapshot\r\n submitText(text: string): void\r\n}\r\n\r\ninterface PatchedInput {\r\n readonly input: VoiceTextInput\r\n readonly originalOwnDescriptor: PropertyDescriptor | undefined\r\n readonly patchedSubmit: (mode?: unknown) => void\r\n}\r\n\r\n/** Routes the native composer through Voice while preserving its ordinary offline submission. */\r\nexport class VoiceTextSubmitBridge {\r\n private readonly patched = new Map<SessionId, PatchedInput>()\r\n\r\n constructor(\r\n private readonly voice: VoiceTextController,\r\n private readonly resolveInput: (sessionId: SessionId) => VoiceTextInput,\r\n ) {}\r\n\r\n install(sessionId: SessionId): void {\r\n if (this.patched.has(sessionId)) return\r\n const input = this.resolveInput(sessionId)\r\n const originalOwnDescriptor = Object.getOwnPropertyDescriptor(input, 'submit')\r\n const originalSubmit = input.submit.bind(input)\r\n const patchedSubmit = (mode?: unknown): void => {\r\n const voice = this.voice.getSnapshot()\r\n const state = input.state.getSnapshot()\r\n const active = voice.sessionId === sessionId\r\n && voice.state !== 'off'\r\n && voice.state !== 'error'\r\n if (!active || state.draft.trim() === '' || state.imageIds.length > 0) {\r\n originalSubmit(mode)\r\n return\r\n }\r\n try {\r\n this.voice.submitText(state.draft)\r\n input.setDraft('')\r\n } catch (cause: unknown) {\r\n input.notify('error', cause instanceof Error ? cause.message : String(cause))\r\n }\r\n }\r\n input.submit = patchedSubmit\r\n this.patched.set(sessionId, { input, originalOwnDescriptor, patchedSubmit })\r\n }\r\n\r\n dispose(): void {\r\n for (const { input, originalOwnDescriptor, patchedSubmit } of this.patched.values()) {\r\n if (input.submit !== patchedSubmit) continue\r\n if (originalOwnDescriptor === undefined) delete (input as Partial<VoiceTextInput>).submit\r\n else Object.defineProperty(input, 'submit', originalOwnDescriptor)\r\n }\r\n this.patched.clear()\r\n }\r\n}\r\n","import type {\r\n ChatConversationViewNode, ConversationNodeDefinition, SessionId,\r\n} from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {\r\n TaskObservation, VoiceTaskId, VoiceUtteranceId,\r\n} from '@flowingspring/dsh-voice'\n\r\n/** Renderer-ready durable Voice utterance. */\r\nexport interface VoiceUtteranceChatData {\r\n readonly utteranceId: VoiceUtteranceId\r\n readonly role: 'user' | 'assistant'\r\n readonly text?: string\r\n readonly state: 'streaming' | 'completed' | 'interrupted'\r\n}\r\n\r\n/** Renderer-ready delegated task link and its latest observation. */\r\nexport interface VoiceDelegationChatData {\r\n readonly taskId: VoiceTaskId\r\n readonly taskSessionId: SessionId\r\n readonly input: string\r\n readonly status: TaskObservation['status']\r\n readonly update?: string\r\n}\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-conversation/client' {\r\n interface ChatNodeDataMap {\r\n /** One user ASR or assistant TTS transcript produced in Voice Mode. */\r\n 'voice-utterance': VoiceUtteranceChatData\r\n /** One independent DSH task delegated by the Voice frontend Agent. */\r\n 'voice-delegation': VoiceDelegationChatData\r\n }\r\n}\r\n\r\ntype VoiceUtteranceState = VoiceUtteranceChatData\r\ntype VoiceDelegationState = VoiceDelegationChatData\r\n\r\n/** Fold one streamed utterance into a stable Chat node. */\r\nexport const voiceUtteranceDefinition: ConversationNodeDefinition<VoiceUtteranceState> = {\r\n kind: 'voice-utterance',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/utterance-start') {\r\n return { id: String(event.data.utteranceId), role: 'start' }\r\n }\r\n if (event.type === 'voice/utterance-end') {\r\n return { id: String(event.data.utteranceId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/utterance-start') {\r\n throw new Error('voice utterance start requires voice/utterance-start')\r\n }\r\n return {\r\n utteranceId: match.event.data.utteranceId,\r\n role: match.event.data.role,\r\n state: 'streaming',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/utterance-end') return context.state\r\n return {\r\n ...context.state,\r\n text: match.event.data.text,\r\n state: match.event.data.state,\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined || context.state === undefined) return null\r\n const emptyInterrupted = context.state.state === 'interrupted'\r\n && (context.state.text?.trim() ?? '') === ''\r\n return {\r\n key: context.key,\r\n kind: 'voice-utterance',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n // A live start may already be materialized before an empty noise segment\r\n // settles. Conversation nodes cannot be withdrawn after publication, so\r\n // keep its stable identity and hide it instead.\r\n visibility: emptyInterrupted ? 'hidden' : 'visible',\r\n data: context.state as VoiceUtteranceState,\r\n }\r\n },\r\n}\r\n\r\n/** Fold a delegated task and all observations into one navigation card. */\r\nexport const voiceDelegationDefinition: ConversationNodeDefinition<VoiceDelegationState> = {\r\n kind: 'voice-delegation',\r\n target: 'chat',\r\n match: (event) => {\r\n if (event.type === 'voice/task-delegated') {\r\n return { id: String(event.data.taskId), role: 'start' }\r\n }\r\n if (event.type === 'voice/task-observation') {\r\n return { id: String(event.data.taskId), role: 'update' }\r\n }\r\n return null\r\n },\r\n start: (_context, match) => {\r\n if (match.event.type !== 'voice/task-delegated') {\r\n throw new Error('voice delegation start requires voice/task-delegated')\r\n }\r\n return {\r\n taskId: match.event.data.taskId,\r\n taskSessionId: match.event.data.taskSessionId,\r\n input: match.event.data.input,\r\n status: 'accepted',\r\n }\r\n },\r\n update: (context, match) => {\r\n if (match.event.type !== 'voice/task-observation') return context.state\r\n const observation = match.event.data\r\n const update = observation.voiceMessage?.text\r\n ?? observation.announcement\r\n ?? observation.reason\r\n ?? context.state.update\r\n return {\r\n ...context.state,\r\n status: observation.status,\r\n ...(update === undefined ? {} : { update }),\r\n }\r\n },\r\n buildViewNode: (context): ChatConversationViewNode | null => {\r\n if (context.start === undefined) return null\r\n return {\r\n key: context.key,\r\n kind: 'voice-delegation',\r\n id: context.id,\r\n target: 'chat',\r\n anchorSeq: context.start.event.seq,\r\n location: context.start.location,\r\n visibility: 'visible',\r\n data: context.state as VoiceDelegationState,\r\n }\r\n },\r\n}\r\n","/** `voice` namespace dictionaries. */\r\n\r\n/** Dictionary namespace owned by this plugin. */\r\nexport const NS = 'voice'\r\n\r\n/** Simplified Chinese dictionary (the key-set source of truth). */\r\nexport const zh = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': '开始语音对话',\n 'control.retry': '语音连接失败,点击重试',\n 'control.interrupt': '打断播放',\n 'control.mute': '静音麦克风',\n 'control.unmute': '解除静音',\n 'overlay.connecting': '正在连接语音',\n 'overlay.listening': '正在聆听',\n 'overlay.speaking': '正在回复',\n 'overlay.error': '语音连接失败',\n 'overlay.muted': '已静音',\n 'overlay.stop': '结束',\n 'utterance.user.pending': '正在聆听…',\r\n 'utterance.assistant.pending': '正在生成语音回复…',\r\n 'utterance.interrupted': '已打断',\r\n 'task.title': '后台处理',\r\n 'task.status.accepted': '已接受',\r\n 'task.status.queued': '排队中',\r\n 'task.status.running': '执行中',\r\n 'task.status.waiting-user': '等待确认',\r\n 'task.status.completed': '已完成',\r\n 'task.status.failed': '失败',\r\n 'task.status.cancelled': '已取消',\r\n 'task.status.interrupted': '服务中断',\r\n 'task.expand': '展开后台处理详情',\r\n 'task.collapse': '收起后台处理详情',\r\n 'task.cancel': '取消任务',\r\n 'task.open': '查看后台详情',\r\n} satisfies Record<string, string>\r\n\r\n/** Union of this namespace's dictionary keys. */\r\nexport type VoiceKey = keyof typeof zh\r\n\r\n/** English dictionary, checked complete against the zh key set. */\r\nexport const en = {\r\n 'badge.voice': 'VOICE',\r\n 'control.start': 'Start voice conversation',\n 'control.retry': 'Voice connection failed, press to retry',\n 'control.interrupt': 'Stop playback',\n 'control.mute': 'Mute microphone',\n 'control.unmute': 'Unmute microphone',\n 'overlay.connecting': 'Connecting voice',\n 'overlay.listening': 'Listening',\n 'overlay.speaking': 'Playing response',\n 'overlay.error': 'Voice connection failed',\n 'overlay.muted': 'Muted',\n 'overlay.stop': 'End',\n 'utterance.user.pending': 'Listening…',\r\n 'utterance.assistant.pending': 'Generating voice response…',\r\n 'utterance.interrupted': 'Interrupted',\r\n 'task.title': 'Background task',\r\n 'task.status.accepted': 'Accepted',\r\n 'task.status.queued': 'Queued',\r\n 'task.status.running': 'Running',\r\n 'task.status.waiting-user': 'Waiting for you',\r\n 'task.status.completed': 'Completed',\r\n 'task.status.failed': 'Failed',\r\n 'task.status.cancelled': 'Cancelled',\r\n 'task.status.interrupted': 'Interrupted',\r\n 'task.expand': 'Show background task details',\r\n 'task.collapse': 'Hide background task details',\r\n 'task.cancel': 'Cancel task',\r\n 'task.open': 'View background details',\r\n} satisfies Record<VoiceKey, string>\r\n","/** Browser voice UI assembly. @module @flowingspring/dsh-client-ui-voice/client */\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport { VoiceControl, type VoiceControlInjected } from './VoiceControl.tsx'\nimport {\n VoiceDelegationView, type VoiceDelegationInjected,\r\n VoiceUtteranceView, type VoiceUtteranceInjected,\r\n} from './VoiceNodeViews.tsx'\r\nimport { VoiceOverlay, type VoiceOverlayInjected } from './VoiceOverlay.tsx'\nimport { VoiceSessionMarkers, type VoiceSessionMarkersInjected } from './VoiceSessionMarkers.tsx'\nimport { VoiceController } from './voice-controller.ts'\r\nimport { VoiceHistoryStore } from './voice-history.ts'\r\nimport { VoiceTextSubmitBridge, type VoiceTextInput } from './voice-text-submit.ts'\r\nimport { voiceDelegationDefinition, voiceUtteranceDefinition } from './voice-definitions.ts'\r\nimport { en, NS, type VoiceKey, zh } from './locales.ts'\r\n\r\nexport type { VoiceKey } from './locales.ts'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** Browser Voice conversation copy. */\r\n voice: VoiceKey\r\n }\r\n}\r\n\r\nexport const inject = ['conversation', 'conversationEvents', 'slots', 'sessions', 'locale']\n\n/** Refresh the Host-backed Session list when the runtime exposes its baseline pull. */\nfunction refreshSessions(ctx: ClientContext): void {\n // `refresh()` exists on the concrete SessionRuntime used by dsh Web, but is\n // intentionally omitted from the narrow feature-facing ISessions contract.\n // Keep this compatibility bridge optional for older runtimes and fixtures.\n const service = ctx.sessions as unknown as { refresh?: () => Promise<void> }\n if (typeof service.refresh !== 'function') return\n void service.refresh.call(ctx.sessions).catch((cause: unknown) => {\n console.warn('voice session list refresh failed:', cause)\n })\n}\n\n/** Mount root transport controls and durable Voice conversation renderers. @param ctx - browser context. */\nexport function apply(ctx: ClientContext): void {\n const controller = new VoiceController({\n onConversationStarted: () => {\n // Voice events are appended by the Host, while the browser's session\n // list keeps its own blank-bit cache. Refresh once after the first\n // completed turn so Workspace new-session creation does not reuse the\n // now-engaged Voice Session.\n refreshSessions(ctx)\n },\n })\n const history = new VoiceHistoryStore()\r\n const textSubmit = new VoiceTextSubmitBridge(controller, (sessionId) => {\r\n const scope = ctx.sessions.scope(sessionId)\r\n if (scope === undefined) throw new Error(`voice input session \"${sessionId}\" is unavailable`)\r\n return ctx.conversation.input.for(scope) as VoiceTextInput\r\n })\r\n const stopVoice = (): Promise<void> => controller.stop()\r\n const startVoice = async (sourceSessionId: SessionId): Promise<void> => {\r\n ctx.sessions.open(sourceSessionId)\r\n textSubmit.install(sourceSessionId)\r\n await controller.start(sourceSessionId)\r\n history.record(sourceSessionId)\r\n }\r\n const voiceControlInjected = (): VoiceControlInjected => ({\r\n hooks: { voice: controller },\r\n startVoice,\r\n retryVoice: async () => {\n await controller.retry()\n const sessionId = controller.getSnapshot().sessionId\n if (sessionId !== undefined) history.record(sessionId)\n },\n interruptResponse: () => { controller.interruptResponse() },\n setVoiceMuted: (muted) => { controller.setInputMuted(muted) },\n })\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({ hooks: { voice: controller }, stopVoice })\n const voiceSessionMarkersInjected = (): VoiceSessionMarkersInjected => ({\n hooks: { voiceHistory: history.snapshot },\n })\n const utteranceInjected = (): VoiceUtteranceInjected => ({ hooks: { voice: controller } })\r\n const delegationInjected = (): VoiceDelegationInjected => ({\r\n hooks: { voice: controller },\r\n openSession: (id) => { ctx.sessions.open(id) },\r\n cancelTask: (id) => { controller.cancelTask(id) },\r\n })\r\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-voice: dictionaries')\r\n ctx.effect(() => async () => { await controller.stop() }, 'ui-voice: transport teardown')\r\n ctx.effect(() => () => { textSubmit.dispose() }, 'ui-voice: text submit bridge teardown')\r\n ctx.effect(() => () => { history.dispose() }, 'ui-voice: history persistence teardown')\r\n ctx.conversationEvents.register(voiceUtteranceDefinition)\r\n ctx.conversationEvents.register(voiceDelegationDefinition)\r\n ctx.slots.inject('conversation.input.right', () => ctx.slots.register({\r\n name: 'conversation.input.right',\r\n id: 'voice',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceControlInjected,\r\n }, VoiceControl))\r\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'voice-session-markers',\n order: 10,\n locale: NS,\n inject: voiceSessionMarkersInjected,\n }, VoiceSessionMarkers))\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-utterance',\r\n locale: NS,\r\n inject: utteranceInjected,\r\n }, VoiceUtteranceView))\r\n ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({\r\n name: 'conversation.chat.node',\r\n key: 'voice-delegation',\r\n locale: NS,\r\n inject: delegationInjected,\r\n }, VoiceDelegationView))\r\n}\r\n"],"mappings":"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"}
|
|
@@ -20,7 +20,7 @@ function installVoiceMessageTool(agentCtx, send, resolveDelegationId) {
|
|
|
20
20
|
const disposeSection = agentCtx.systemPrompt.section({
|
|
21
21
|
name: "tool:send-voice-message",
|
|
22
22
|
order: VOICE_MESSAGE_SECTION_ORDER,
|
|
23
|
-
text: "This Agent handles tasks delegated by the realtime voice assistant. While a realtime voice delegation is active, use send_voice_message to keep the voice assistant informed. The tool is already bound to the exact active delegation; never ask for, invent, or expose an internal delegation id. Report structured events with type and detail. Use progress for meaningful progress, warning or error for important conditions, question when user input is required, and result exactly once before a successful final turn ends. Put the complete factual content in detail; the Voice layer independently rewrites every reported event against the user request. Keep detail complete enough to support an accurate rewrite. The full report remains in the task UI. The voice assistant does not automatically see your transcript, tool output, or reasoning. A result is held until the turn actually succeeds. A question leaves the task waiting for a user reply. Reporting never ends your turn. Do not use this tool when no realtime voice delegation is active."
|
|
23
|
+
text: "This Agent handles tasks delegated by the realtime voice assistant. While a realtime voice delegation is active, use send_voice_message to keep the voice assistant informed. The tool is already bound to the exact active delegation; never ask for, invent, or expose an internal delegation id. Report structured events with type and detail. Use progress for meaningful progress, warning or error for important conditions, question when user input is required, and result exactly once before a successful final turn ends. Put the complete factual content in detail; the Voice layer independently rewrites every reported event against the user request. Keep detail complete enough to support an accurate rewrite. The full report remains in the task UI. The voice assistant does not automatically see your transcript, tool output, or reasoning. Treat the task as read-only unless the current user request explicitly authorizes creating, modifying, deleting, renaming, or moving files. Searching, checking, reading, and explaining do not authorize file changes; ambiguous requests for an artifact or organized explanation mean return the result in chat and ask before writing. After explicit authorization, change only the necessary scope. A result is held until the turn actually succeeds. A question leaves the task waiting for a user reply. Reporting never ends your turn. Do not use this tool when no realtime voice delegation is active."
|
|
24
24
|
});
|
|
25
25
|
let disposeTool;
|
|
26
26
|
try {
|
|
@@ -159,6 +159,13 @@ function optionalWorkspaceMemory(ctx) {
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
function optionalSessionTitle(ctx) {
|
|
163
|
+
try {
|
|
164
|
+
return ctx.get("sessionTitle");
|
|
165
|
+
} catch {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
162
169
|
function workspaceMemoryReference(memory) {
|
|
163
170
|
const sections = [];
|
|
164
171
|
if (memory.summary.trim() !== "") sections.push(`稳定摘要:\n${memory.summary.trim()}`);
|
|
@@ -193,6 +200,55 @@ const REWRITE_EVENT_INSTRUCTIONS = {
|
|
|
193
200
|
warning: "这是警告信息。简洁说明风险、影响以及用户需要知道的事项。",
|
|
194
201
|
error: "这是失败信息。明确说明没有完成、主要原因以及可行的下一步。"
|
|
195
202
|
};
|
|
203
|
+
const VOICE_TITLE_SYSTEM_PROMPT = `你是语音会话标题生成器。根据用户第一条有实际内容的语音请求,生成一个简短、准确、便于在会话列表中识别的中文标题。
|
|
204
|
+
|
|
205
|
+
只输出标题本身,不要解释,不要引号,不要 Markdown,不要句末标点,不要使用“语音会话”“新会话”等泛化名称,不超过 12 个汉字。不要加入文件大小、行数、路径等执行细节。`;
|
|
206
|
+
function voiceTitleCandidate(value) {
|
|
207
|
+
const normalized = value.replace(/```[\s\S]*?```/gu, "").replace(/^\s*(?:标题|title)\s*[::]\s*/iu, "").replace(/["'「」『』]/gu, "").split(/\r?\n/u, 1)[0].replace(/[。!?!?;;,,::]+$/u, "").replace(/\s+/gu, " ").trim();
|
|
208
|
+
return Array.from(normalized).slice(0, 12).join("");
|
|
209
|
+
}
|
|
210
|
+
function isMeaningfulVoiceTitleInput(value) {
|
|
211
|
+
const normalized = value.replace(/\s+/gu, "").trim();
|
|
212
|
+
if (Array.from(normalized).length < 4) return false;
|
|
213
|
+
return !/^(?:你好|您好|嗨|哈喽|喂|在吗|有人吗|测试|谢谢|好的)[,。!?!?、]*$/u.test(normalized);
|
|
214
|
+
}
|
|
215
|
+
async function generateVoiceSessionTitle(ctx, session, input, titleService) {
|
|
216
|
+
let candidate = "";
|
|
217
|
+
try {
|
|
218
|
+
const llm = ctx.get("llm");
|
|
219
|
+
if (llm !== void 0) {
|
|
220
|
+
const selection = ctx.agentDefaultModel.currentSelection();
|
|
221
|
+
const message = createUserMessage({
|
|
222
|
+
content: [{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: `用户第一条有效语音请求:${input}`
|
|
225
|
+
}],
|
|
226
|
+
source: {
|
|
227
|
+
kind: "plugin",
|
|
228
|
+
plugin: "voice-assistant"
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
let output = "";
|
|
232
|
+
for await (const chunk of llm.stream({
|
|
233
|
+
provider: selection.provider,
|
|
234
|
+
model: selection.model,
|
|
235
|
+
...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: selection.reasoningEffort },
|
|
236
|
+
messages: [message],
|
|
237
|
+
system: VOICE_TITLE_SYSTEM_PROMPT
|
|
238
|
+
})) if (chunk.type === "text-delta") output += chunk.text;
|
|
239
|
+
candidate = voiceTitleCandidate(output);
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
|
|
243
|
+
}
|
|
244
|
+
if (candidate === "") candidate = voiceTitleCandidate(input);
|
|
245
|
+
if (candidate === "" || titleService.get(session) !== void 0) return;
|
|
246
|
+
try {
|
|
247
|
+
titleService.rename(session, candidate);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
196
252
|
/** Install the driver. @param ctx - composed Agent and voice context. @param config - driver copy and queue bounds. */
|
|
197
253
|
function apply(ctx, config = {}) {
|
|
198
254
|
const bindings = /* @__PURE__ */ new Map();
|
|
@@ -223,6 +279,7 @@ function apply(ctx, config = {}) {
|
|
|
223
279
|
interactionMode: void 0,
|
|
224
280
|
voiceAttached: false,
|
|
225
281
|
voiceTurnMarked: false,
|
|
282
|
+
voiceTitleRequested: false,
|
|
226
283
|
active: void 0,
|
|
227
284
|
continuousTaskAgent: void 0,
|
|
228
285
|
lastTerminalTaskId: void 0,
|
|
@@ -716,13 +773,22 @@ function apply(ctx, config = {}) {
|
|
|
716
773
|
};
|
|
717
774
|
const endUtterance = (binding, utteranceId, role, state, finalText, responseId) => {
|
|
718
775
|
const utterance = beginUtterance(binding, utteranceId, role, responseId);
|
|
719
|
-
requireSourceSession(binding)
|
|
776
|
+
const sourceSession = requireSourceSession(binding);
|
|
777
|
+
sourceSession.append("voice/utterance-end", {
|
|
720
778
|
utteranceId,
|
|
721
779
|
role,
|
|
722
780
|
text: finalText ?? utterance.text,
|
|
723
781
|
state,
|
|
724
782
|
...responseId === void 0 ? {} : { responseId }
|
|
725
783
|
});
|
|
784
|
+
const utteranceText = finalText ?? utterance.text;
|
|
785
|
+
if (role === "user" && state === "completed" && isMeaningfulVoiceTitleInput(utteranceText)) {
|
|
786
|
+
const titleService = optionalSessionTitle(ctx);
|
|
787
|
+
if (titleService !== void 0 && !binding.voiceTitleRequested && titleService.get(sourceSession) === void 0) {
|
|
788
|
+
binding.voiceTitleRequested = true;
|
|
789
|
+
generateVoiceSessionTitle(ctx, sourceSession, utteranceText, titleService);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
726
792
|
binding.utterances.delete(utteranceId);
|
|
727
793
|
};
|
|
728
794
|
const interruptAssistantUtterances = (binding, responseId) => {
|
|
@@ -815,7 +881,7 @@ function apply(ctx, config = {}) {
|
|
|
815
881
|
route = await routeFrontendInput(ctx, requireSourceSession(binding).events, call.command.input, memoryReference);
|
|
816
882
|
} catch (error) {
|
|
817
883
|
ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
|
|
818
|
-
route = fallbackDelegation(call.command.input);
|
|
884
|
+
route = fallbackDelegation(call.command.input, recentConversationText(requireSourceSession(binding).events));
|
|
819
885
|
}
|
|
820
886
|
if (route.action === "delegate") {
|
|
821
887
|
const taskId = VoiceTaskId(randomUUID());
|
|
@@ -1235,7 +1301,7 @@ function speechFragments(text, flush) {
|
|
|
1235
1301
|
rest
|
|
1236
1302
|
};
|
|
1237
1303
|
}
|
|
1238
|
-
const DEFAULT_DELEGATION_ACKNOWLEDGEMENT = "
|
|
1304
|
+
const DEFAULT_DELEGATION_ACKNOWLEDGEMENT = "我先查看一下。";
|
|
1239
1305
|
const MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH = 40;
|
|
1240
1306
|
const MAX_DELEGATED_TASK_LENGTH = 1e3;
|
|
1241
1307
|
const MAX_DELEGATION_BACKGROUND_LENGTH = 2e3;
|
|
@@ -1265,12 +1331,19 @@ function delegationField(value, name, maxLength) {
|
|
|
1265
1331
|
if (normalized === "" || normalized.length > maxLength) throw new Error(`voice frontend router returned an invalid ${name}`);
|
|
1266
1332
|
return normalized;
|
|
1267
1333
|
}
|
|
1268
|
-
function
|
|
1334
|
+
function recentConversationText(events) {
|
|
1335
|
+
return events.flatMap((event) => {
|
|
1336
|
+
if (event.type !== "voice/utterance-end" || event.data.state !== "completed") return [];
|
|
1337
|
+
const text = event.data.text.trim();
|
|
1338
|
+
return text === "" ? [] : [`${event.data.role === "user" ? "用户" : "助手"}:${text}`];
|
|
1339
|
+
}).slice(-12).join("\n");
|
|
1340
|
+
}
|
|
1341
|
+
function fallbackDelegation(input, background = "") {
|
|
1269
1342
|
return {
|
|
1270
1343
|
action: "delegate",
|
|
1271
1344
|
acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT,
|
|
1272
1345
|
task: input.trim() || input,
|
|
1273
|
-
background: "没有可用的额外背景。",
|
|
1346
|
+
background: background || "没有可用的额外背景。",
|
|
1274
1347
|
userRequest: input
|
|
1275
1348
|
};
|
|
1276
1349
|
}
|
|
@@ -1303,13 +1376,9 @@ async function routeFrontendInput(ctx, events, input, workspaceMemory = "") {
|
|
|
1303
1376
|
} catch {
|
|
1304
1377
|
llm = void 0;
|
|
1305
1378
|
}
|
|
1306
|
-
|
|
1379
|
+
const recentConversation = recentConversationText(events);
|
|
1380
|
+
if (llm === void 0) return fallbackDelegation(input, recentConversation);
|
|
1307
1381
|
const selection = ctx.agentDefaultModel.currentSelection();
|
|
1308
|
-
const recentConversation = events.flatMap((event) => {
|
|
1309
|
-
if (event.type !== "voice/utterance-end" || event.data.state !== "completed") return [];
|
|
1310
|
-
const text = event.data.text.trim();
|
|
1311
|
-
return text === "" ? [] : [`${event.data.role === "user" ? "用户" : "助手"}:${text}`];
|
|
1312
|
-
}).slice(-12).join("\n");
|
|
1313
1382
|
const message = createUserMessage({
|
|
1314
1383
|
content: [{
|
|
1315
1384
|
type: "text",
|
|
@@ -1322,7 +1391,13 @@ async function routeFrontendInput(ctx, events, input, workspaceMemory = "") {
|
|
|
1322
1391
|
"选择 delegate 时,必须结合最近对话补全省略指代,输出自包含的 task、只用于消歧的 background,并原样复制 user_request。",
|
|
1323
1392
|
"task 是后台当前唯一要执行的任务;background 不能包含新的要求,也不要把旧任务写成待办。",
|
|
1324
1393
|
"同时给出一句简短自然的 acknowledgement,表示接下来要做什么。不能声称任务已经完成、已经找到结果,也不要提后台 Agent、工具或路由。",
|
|
1325
|
-
"acknowledgement 只能有一句,最多 40
|
|
1394
|
+
"acknowledgement 只能有一句,最多 40 个字符;不要固定使用“好的”或其他相同开头,要根据任务自然变化,并尽量避免重复最近对话中的句式。",
|
|
1395
|
+
"可以使用“我先核实一下”“我来检查一下”“我去确认一下”等自然表达,只有语气合适时才使用“好的”。",
|
|
1396
|
+
"任务边界与写操作规则:task 只能重述用户明确表达的目标,不得擅自增加新的交付物。",
|
|
1397
|
+
"搜索、查询、核实、阅读和分析类任务默认只读,不创建或修改工作区文件。",
|
|
1398
|
+
"“给我一个结果”“给我一个产物”“整理一下”“形成说明”等模糊表达,不代表允许写文件。",
|
|
1399
|
+
"只有用户明确要求创建、修改、删除、重命名或移动文件时,才允许执行对应操作;获得授权后也只能操作必要范围。",
|
|
1400
|
+
"无法判断用户是否要求写文件时,输出的 task 应保持只读,或要求先确认。",
|
|
1326
1401
|
"只输出一行 JSON,不要 Markdown。格式只能是:",
|
|
1327
1402
|
"{\"action\":\"chat\",\"reply\":\"...\"}",
|
|
1328
1403
|
"或:",
|
package/package.json
CHANGED