@flowingspring/dsh-voco 0.2.2 → 0.2.4

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/lib/client.js CHANGED
@@ -7,8 +7,8 @@ window.__ModuleLoader__.load({
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let react = require("react");
9
9
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
10
- //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\codex-voice\dsh-voco\packages\ui-voice\src\client\VoiceControl.module.css.mjs
11
- const css$3 = ".PapC8q_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}.PapC8q_button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.PapC8q_active{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover-solid)}.PapC8q_button[data-state=speaking]{animation:1.2s ease-in-out infinite PapC8q_voice-pulse}@keyframes PapC8q_voice-pulse{50%{opacity:.55;transform:scale(.94)}}@media (prefers-reduced-motion:reduce){.PapC8q_button[data-state=speaking]{animation:none}}";
10
+ //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceControl.module.css.mjs
11
+ const css$3 = ".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_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
12
  const tagId$3 = "@flowingspring/dsh-voco/VoiceControl.module.css";
13
13
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
14
14
  const tag = document.createElement("style");
@@ -18,16 +18,17 @@ window.__ModuleLoader__.load({
18
18
  document.head.appendChild(tag);
19
19
  }
20
20
  var VoiceControl_module_css_default = {
21
- "voice-pulse": "PapC8q_voice-pulse",
22
- "button": "PapC8q_button",
23
- "active": "PapC8q_active"
21
+ "active": "TFZsua_active",
22
+ "voice-pulse": "TFZsua_voice-pulse",
23
+ "button": "TFZsua_button"
24
24
  };
25
25
  //#endregion
26
26
  //#region ../ui-voice/src/client/VoiceControl.tsx
27
27
  /** Start a fresh Voice Session from this Session's location or control the active connection. */
28
- function VoiceControl({ sessionId, useVoice, startVoice, retryVoice, stopVoice, t }) {
28
+ function VoiceControl({ sessionId, useVoice, startVoice, retryVoice, setVoiceMuted, t }) {
29
29
  const state = useVoice((snapshot) => snapshot.state);
30
30
  const activeSessionId = useVoice((snapshot) => snapshot.sessionId);
31
+ const inputMuted = useVoice((snapshot) => snapshot.inputMuted);
31
32
  const toggle = async () => {
32
33
  if (state === "error" && activeSessionId !== void 0) {
33
34
  try {
@@ -36,20 +37,23 @@ window.__ModuleLoader__.load({
36
37
  return;
37
38
  }
38
39
  if (state !== "off" && state !== "error") {
39
- await stopVoice();
40
+ setVoiceMuted(!inputMuted);
40
41
  return;
41
42
  }
42
43
  try {
43
44
  await startVoice(sessionId);
44
45
  } catch {}
45
46
  };
46
- const label = state === "off" ? t("control.start") : state === "error" ? t("control.retry") : t("control.stop");
47
+ const label = state === "off" ? t("control.start") : state === "error" ? t("control.retry") : inputMuted ? t("control.unmute") : t("control.mute");
48
+ const active = state !== "off" && state !== "error";
47
49
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
48
50
  type: "button",
49
- className: `${VoiceControl_module_css_default.button} ${state !== "off" ? VoiceControl_module_css_default.active : ""}`,
51
+ className: `${VoiceControl_module_css_default.button} ${active ? VoiceControl_module_css_default.active : ""}`,
50
52
  "aria-label": label,
53
+ "aria-pressed": active ? inputMuted : void 0,
51
54
  title: label,
52
55
  "data-state": state,
56
+ "data-muted": inputMuted,
53
57
  onClick: () => {
54
58
  toggle();
55
59
  },
@@ -58,28 +62,38 @@ window.__ModuleLoader__.load({
58
62
  width: "16",
59
63
  height: "16",
60
64
  "aria-hidden": true,
61
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
62
- x: "5",
63
- y: "2",
64
- width: "6",
65
- height: "8",
66
- rx: "3",
67
- fill: "none",
68
- stroke: "currentColor",
69
- strokeWidth: "1.4"
70
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
71
- d: "M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5",
72
- fill: "none",
73
- stroke: "currentColor",
74
- strokeWidth: "1.4",
75
- strokeLinecap: "round"
76
- })]
65
+ children: [
66
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
67
+ x: "5",
68
+ y: "2",
69
+ width: "6",
70
+ height: "8",
71
+ rx: "3",
72
+ fill: "none",
73
+ stroke: "currentColor",
74
+ strokeWidth: "1.4"
75
+ }),
76
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
77
+ d: "M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2M5.5 14h5",
78
+ fill: "none",
79
+ stroke: "currentColor",
80
+ strokeWidth: "1.4",
81
+ strokeLinecap: "round"
82
+ }),
83
+ inputMuted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
84
+ d: "M2.5 2.5l11 11",
85
+ fill: "none",
86
+ stroke: "currentColor",
87
+ strokeWidth: "1.8",
88
+ strokeLinecap: "round"
89
+ })
90
+ ]
77
91
  })
78
92
  });
79
93
  }
80
94
  //#endregion
81
- //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\codex-voice\dsh-voco\packages\ui-voice\src\client\VoiceHistoryAction.module.css.mjs
82
- const css$2 = ".hhHVeG_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}.hhHVeG_trigger:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.hhHVeG_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}.hhHVeG_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}.hhHVeG_close{width:28px;height:28px;color:inherit;cursor:pointer;background:0 0;border:0;border-radius:7px;font-size:20px;line-height:1}.hhHVeG_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.hhHVeG_list{padding:8px;overflow:auto}.hhHVeG_empty{color:var(--dsw-alias-label-tertiary);text-align:center;margin:0;padding:24px 12px;font-size:13px}.hhHVeG_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}.hhHVeG_row:hover,.hhHVeG_row[data-active=true]{background:var(--dsw-alias-interactive-bg-hover)}.hhHVeG_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}.hhHVeG_wave i{background:currentColor;border-radius:2px;width:2px}.hhHVeG_wave i:first-child{height:6px}.hhHVeG_wave i:nth-child(2){height:11px}.hhHVeG_wave i:nth-child(3){height:8px}.hhHVeG_rowText{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.hhHVeG_title{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.hhHVeG_meta{color:var(--dsw-alias-label-tertiary);font-size:11px}";
95
+ //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceHistoryAction.module.css.mjs
96
+ const css$2 = "._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}";
83
97
  const tagId$2 = "@flowingspring/dsh-voco/VoiceHistoryAction.module.css";
84
98
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
85
99
  const tag = document.createElement("style");
@@ -89,17 +103,17 @@ window.__ModuleLoader__.load({
89
103
  document.head.appendChild(tag);
90
104
  }
91
105
  var VoiceHistoryAction_module_css_default = {
92
- "title": "hhHVeG_title",
93
- "panel": "hhHVeG_panel",
94
- "row": "hhHVeG_row",
95
- "trigger": "hhHVeG_trigger",
96
- "wave": "hhHVeG_wave",
97
- "header": "hhHVeG_header",
98
- "empty": "hhHVeG_empty",
99
- "rowText": "hhHVeG_rowText",
100
- "close": "hhHVeG_close",
101
- "list": "hhHVeG_list",
102
- "meta": "hhHVeG_meta"
106
+ "header": "_3t8AFq_header",
107
+ "close": "_3t8AFq_close",
108
+ "empty": "_3t8AFq_empty",
109
+ "wave": "_3t8AFq_wave",
110
+ "rowText": "_3t8AFq_rowText",
111
+ "meta": "_3t8AFq_meta",
112
+ "panel": "_3t8AFq_panel",
113
+ "title": "_3t8AFq_title",
114
+ "row": "_3t8AFq_row",
115
+ "list": "_3t8AFq_list",
116
+ "trigger": "_3t8AFq_trigger"
103
117
  };
104
118
  //#endregion
105
119
  //#region ../ui-voice/src/client/VoiceHistoryAction.tsx
@@ -200,8 +214,8 @@ window.__ModuleLoader__.load({
200
214
  })] });
201
215
  }
202
216
  //#endregion
203
- //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\codex-voice\dsh-voco\packages\ui-voice\src\client\VoiceNodeViews.module.css.mjs
204
- const css$1 = ".KSbXkW_utterance{flex-direction:column;align-items:flex-start;gap:6px;min-width:0;display:flex}.KSbXkW_utterance[data-role=user]{align-items:flex-end}.KSbXkW_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}.KSbXkW_utterance[data-role=user] .KSbXkW_bubble{background:var(--dsw-specific-bubble);border-color:#0000}.KSbXkW_utterance[data-state=streaming] .KSbXkW_bubble{color:var(--dsw-alias-label-secondary)}.KSbXkW_meta,.KSbXkW_voiceBadge{align-items:center;display:inline-flex}.KSbXkW_meta{color:var(--dsw-alias-label-tertiary);gap:8px;padding:0 5px;font-size:10px;line-height:14px}.KSbXkW_voiceBadge{color:var(--dsw-alias-state-business-primary);letter-spacing:.04em;gap:4px;font-weight:600}.KSbXkW_miniWave{align-items:center;gap:1px;height:10px;display:inline-flex}.KSbXkW_miniWave i{background:currentColor;border-radius:1px;width:1.5px;height:4px}.KSbXkW_miniWave i:nth-child(2){height:8px}.KSbXkW_miniWave i:nth-child(3){height:6px}.KSbXkW_taskCard{box-sizing:border-box;width:fit-content;min-width:0;max-width:min(525px,100%)}.KSbXkW_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}.KSbXkW_taskSummary:hover{background:var(--dsw-alias-bg-layer-1)}.KSbXkW_taskDot{background:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:7px;height:7px}.KSbXkW_taskCard[data-status=completed] .KSbXkW_taskDot{background:var(--dsw-alias-state-success-primary)}.KSbXkW_taskCard[data-status=failed] .KSbXkW_taskDot{background:var(--dsw-alias-state-error-primary)}.KSbXkW_taskCard[data-status=cancelled] .KSbXkW_taskDot,.KSbXkW_taskCard[data-status=interrupted] .KSbXkW_taskDot{background:var(--dsw-alias-state-warn-primary)}.KSbXkW_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}.KSbXkW_taskStatus{color:var(--dsw-alias-label-tertiary);flex:none;font-size:11px;line-height:16px}.KSbXkW_taskStatus:before{content:\"·\";margin-right:7px}.KSbXkW_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)}.KSbXkW_taskCard[data-expanded=true] .KSbXkW_taskChevron{transform:rotate(45deg)}.KSbXkW_taskInput,.KSbXkW_taskUpdate{overflow-wrap:anywhere;margin:0;font-size:13px;line-height:20px}.KSbXkW_taskInput{color:var(--dsw-alias-label-primary)}.KSbXkW_taskUpdate{color:var(--dsw-alias-label-secondary);margin-top:4px}.KSbXkW_taskDetails{border-left:1px solid var(--dsw-alias-border-l2);margin:9px 0 0 3px;padding:2px 0 2px 16px}.KSbXkW_taskActions{align-items:center;gap:14px;margin-top:7px;display:flex}.KSbXkW_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}.KSbXkW_taskLink:hover:not(:disabled){text-underline-position:from-font;text-decoration:underline}.KSbXkW_taskLink:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.KSbXkW_taskCancel{color:var(--dsw-alias-state-error-primary)}";
217
+ //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceNodeViews.module.css.mjs
218
+ const css$1 = ".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)}";
205
219
  const tagId$1 = "@flowingspring/dsh-voco/VoiceNodeViews.module.css";
206
220
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
207
221
  const tag = document.createElement("style");
@@ -211,23 +225,23 @@ window.__ModuleLoader__.load({
211
225
  document.head.appendChild(tag);
212
226
  }
213
227
  var VoiceNodeViews_module_css_default = {
214
- "taskTitle": "KSbXkW_taskTitle",
215
- "bubble": "KSbXkW_bubble",
216
- "taskInput": "KSbXkW_taskInput",
217
- "taskStatus": "KSbXkW_taskStatus",
218
- "taskDetails": "KSbXkW_taskDetails",
219
- "taskDot": "KSbXkW_taskDot",
220
- "taskActions": "KSbXkW_taskActions",
221
- "taskCard": "KSbXkW_taskCard",
222
- "taskSummary": "KSbXkW_taskSummary",
223
- "miniWave": "KSbXkW_miniWave",
224
- "utterance": "KSbXkW_utterance",
225
- "meta": "KSbXkW_meta",
226
- "taskLink": "KSbXkW_taskLink",
227
- "taskCancel": "KSbXkW_taskCancel",
228
- "voiceBadge": "KSbXkW_voiceBadge",
229
- "taskUpdate": "KSbXkW_taskUpdate",
230
- "taskChevron": "KSbXkW_taskChevron"
228
+ "taskCard": "Kt2smW_taskCard",
229
+ "taskUpdate": "Kt2smW_taskUpdate",
230
+ "taskLink": "Kt2smW_taskLink",
231
+ "taskDot": "Kt2smW_taskDot",
232
+ "taskChevron": "Kt2smW_taskChevron",
233
+ "taskDetails": "Kt2smW_taskDetails",
234
+ "taskCancel": "Kt2smW_taskCancel",
235
+ "voiceBadge": "Kt2smW_voiceBadge",
236
+ "taskInput": "Kt2smW_taskInput",
237
+ "utterance": "Kt2smW_utterance",
238
+ "taskStatus": "Kt2smW_taskStatus",
239
+ "bubble": "Kt2smW_bubble",
240
+ "meta": "Kt2smW_meta",
241
+ "miniWave": "Kt2smW_miniWave",
242
+ "taskSummary": "Kt2smW_taskSummary",
243
+ "taskTitle": "Kt2smW_taskTitle",
244
+ "taskActions": "Kt2smW_taskActions"
231
245
  };
232
246
  //#endregion
233
247
  //#region ../ui-voice/src/client/VoiceNodeViews.tsx
@@ -344,8 +358,8 @@ window.__ModuleLoader__.load({
344
358
  return status === "completed" || status === "failed" || status === "cancelled" || status === "interrupted";
345
359
  }
346
360
  //#endregion
347
- //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\codex-voice\dsh-voco\packages\ui-voice\src\client\VoiceOverlay.module.css.mjs
348
- const css = ".AAuDqa_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-overlay);max-width:min(520px,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:10px;padding:4px 6px 4px 12px;font-size:12px;line-height:18px;display:flex;position:absolute;bottom:18px;right:18px}.AAuDqa_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}.AAuDqa_wave i{background:currentColor;border-radius:1px;width:2px;height:5px;animation:.9s ease-in-out infinite alternate AAuDqa_voice-wave}.AAuDqa_wave i:nth-child(2){height:11px;animation-delay:-300ms}.AAuDqa_wave i:nth-child(3){height:8px;animation-delay:-600ms}.AAuDqa_wave i:nth-child(4){height:4px;animation-delay:-150ms}.AAuDqa_root[data-state=connecting] .AAuDqa_wave,.AAuDqa_root[data-state=error] .AAuDqa_wave{opacity:.55}.AAuDqa_root[data-state=error] .AAuDqa_wave{color:var(--dsw-alias-state-error-primary)}.AAuDqa_status{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.AAuDqa_link,.AAuDqa_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}.AAuDqa_link:hover:not(:disabled),.AAuDqa_stop:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.AAuDqa_link:disabled{cursor:default;opacity:.45}.AAuDqa_stop{color:var(--dsw-alias-state-error-primary)}@keyframes AAuDqa_voice-wave{to{transform:scaleY(.55)}}@media (prefers-reduced-motion:reduce){.AAuDqa_wave i{animation:none}}";
361
+ //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceOverlay.module.css.mjs
362
+ const css = ".GPPB2G_root{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-overlay);max-width:min(520px,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:10px;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:auto;min-width:0;overflow:hidden}.GPPB2G_muted{color:var(--dsw-alias-state-error-primary);flex:none;font-weight:600}.GPPB2G_root[data-muted=true] .GPPB2G_wave{color:var(--dsw-alias-state-error-primary);opacity:.65}.GPPB2G_root[data-muted=true] .GPPB2G_wave i{animation:none}.GPPB2G_link,.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_link:hover:not(:disabled),.GPPB2G_stop:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.GPPB2G_link:disabled{cursor:default;opacity:.45}.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}}";
349
363
  const tagId = "@flowingspring/dsh-voco/VoiceOverlay.module.css";
350
364
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
351
365
  const tag = document.createElement("style");
@@ -355,12 +369,13 @@ window.__ModuleLoader__.load({
355
369
  document.head.appendChild(tag);
356
370
  }
357
371
  var VoiceOverlay_module_css_default = {
358
- "wave": "AAuDqa_wave",
359
- "root": "AAuDqa_root",
360
- "link": "AAuDqa_link",
361
- "stop": "AAuDqa_stop",
362
- "status": "AAuDqa_status",
363
- "voice-wave": "AAuDqa_voice-wave"
372
+ "stop": "GPPB2G_stop",
373
+ "voice-wave": "GPPB2G_voice-wave",
374
+ "wave": "GPPB2G_wave",
375
+ "root": "GPPB2G_root",
376
+ "muted": "GPPB2G_muted",
377
+ "status": "GPPB2G_status",
378
+ "link": "GPPB2G_link"
364
379
  };
365
380
  //#endregion
366
381
  //#region ../ui-voice/src/client/VoiceOverlay.tsx
@@ -379,6 +394,7 @@ window.__ModuleLoader__.load({
379
394
  className: VoiceOverlay_module_css_default.root,
380
395
  "data-voice-overlay": true,
381
396
  "data-state": voice.state,
397
+ "data-muted": voice.inputMuted,
382
398
  "aria-live": "polite",
383
399
  children: [
384
400
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -395,6 +411,10 @@ window.__ModuleLoader__.load({
395
411
  className: VoiceOverlay_module_css_default.status,
396
412
  children: t(STATE_KEYS[voice.state])
397
413
  }),
414
+ voice.inputMuted && voice.state !== "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
415
+ className: VoiceOverlay_module_css_default.muted,
416
+ children: t("overlay.muted")
417
+ }),
398
418
  voice.sessionId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
399
419
  type: "button",
400
420
  className: VoiceOverlay_module_css_default.link,
@@ -419,6 +439,7 @@ window.__ModuleLoader__.load({
419
439
  //#region ../ui-voice/src/client/voice-controller.ts
420
440
  const INITIAL_SNAPSHOT = {
421
441
  state: "off",
442
+ inputMuted: false,
422
443
  textById: {}
423
444
  };
424
445
  /** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */
@@ -450,6 +471,9 @@ window.__ModuleLoader__.load({
450
471
  * @param sessionId - durable voice session to connect.
451
472
  */
452
473
  async start(sessionId) {
474
+ await this.open(sessionId, false);
475
+ }
476
+ async open(sessionId, inputMuted) {
453
477
  const generation = ++this.generation;
454
478
  this.pendingText.splice(0);
455
479
  this.conversationStarted = false;
@@ -463,6 +487,7 @@ window.__ModuleLoader__.load({
463
487
  this.openingAbort = openingAbort;
464
488
  this.publish({
465
489
  state: "connecting",
490
+ inputMuted,
466
491
  sessionId,
467
492
  textById: {}
468
493
  });
@@ -481,8 +506,10 @@ window.__ModuleLoader__.load({
481
506
  this.handleUnexpectedClose();
482
507
  }
483
508
  }, openingAbort.signal, (opening) => {
484
- if (generation === this.generation) this.openingTransport = opening;
485
- else opening.close();
509
+ if (generation === this.generation) {
510
+ this.openingTransport = opening;
511
+ opening.setInputMuted(this.snapshot.inputMuted);
512
+ } else opening.close();
486
513
  }, contexts);
487
514
  if (generation !== this.generation) {
488
515
  await transport.close();
@@ -505,7 +532,18 @@ window.__ModuleLoader__.load({
505
532
  async retry() {
506
533
  const sessionId = this.snapshot.sessionId;
507
534
  if (sessionId === void 0) throw new Error("voice retry requires an active session");
508
- await this.start(sessionId);
535
+ await this.open(sessionId, this.snapshot.inputMuted);
536
+ }
537
+ /** Mute or resume microphone input without closing text submission or playback. */
538
+ setInputMuted(muted) {
539
+ if (this.snapshot.state === "off" || this.snapshot.state === "error" || this.snapshot.sessionId === void 0) throw new Error("voice input mute requires an active connection");
540
+ if (this.snapshot.inputMuted === muted) return;
541
+ this.openingTransport?.setInputMuted(muted);
542
+ this.transport?.setInputMuted(muted);
543
+ this.publish({
544
+ ...this.snapshot,
545
+ inputMuted: muted
546
+ });
509
547
  }
510
548
  /** Cancel the exact active background task through the attached Voice transport. */
511
549
  cancelTask(taskId) {
@@ -673,6 +711,7 @@ window.__ModuleLoader__.load({
673
711
  let outputSampleRate = 24e3;
674
712
  let audioFormat = "pcm_s16le";
675
713
  let playAt = outputContext.currentTime;
714
+ let inputMuted = false;
676
715
  const chunks = [];
677
716
  const playing = /* @__PURE__ */ new Set();
678
717
  const transport = {
@@ -683,6 +722,13 @@ window.__ModuleLoader__.load({
683
722
  taskId
684
723
  }));
685
724
  },
725
+ setInputMuted: (muted) => {
726
+ if (inputMuted === muted) return;
727
+ inputMuted = muted;
728
+ chunks.splice(0);
729
+ for (const track of stream?.getTracks() ?? []) track.enabled = !muted;
730
+ if (muted && !stopped && socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "audio.commit" }));
731
+ },
686
732
  submitText: (text) => {
687
733
  if (socket.readyState !== WebSocket.OPEN) throw new Error("voice transport is not open");
688
734
  socket.send(JSON.stringify({
@@ -787,6 +833,7 @@ window.__ModuleLoader__.load({
787
833
  throw new Error("voice connection opening aborted");
788
834
  }
789
835
  stream = captured;
836
+ for (const track of stream.getTracks()) track.enabled = !inputMuted;
790
837
  await abortable(inputContext.audioWorklet.addModule(workletUrl), signal);
791
838
  if (openingStopped(signal, stopped)) throw new Error("voice connection opening aborted");
792
839
  source = inputContext.createMediaStreamSource(stream);
@@ -794,7 +841,7 @@ window.__ModuleLoader__.load({
794
841
  silent = inputContext.createGain();
795
842
  silent.gain.value = 0;
796
843
  processor.port.onmessage = (event) => {
797
- if (socket.readyState !== WebSocket.OPEN) return;
844
+ if (inputMuted || socket.readyState !== WebSocket.OPEN) return;
798
845
  chunks.push(...resample(event.data, inputContext.sampleRate, inputSampleRate));
799
846
  const frameSize = Math.max(1, Math.round(inputSampleRate / 5));
800
847
  while (chunks.length >= frameSize) {
@@ -1297,11 +1344,13 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
1297
1344
  "badge.voice": "VOICE",
1298
1345
  "control.start": "开始语音对话",
1299
1346
  "control.retry": "语音连接失败,点击重试",
1300
- "control.stop": "结束语音对话",
1347
+ "control.mute": "静音麦克风",
1348
+ "control.unmute": "解除静音",
1301
1349
  "overlay.connecting": "正在连接语音",
1302
1350
  "overlay.listening": "正在聆听",
1303
1351
  "overlay.speaking": "正在播放回复",
1304
1352
  "overlay.error": "语音连接失败",
1353
+ "overlay.muted": "麦克风已静音",
1305
1354
  "overlay.return": "返回语音对话",
1306
1355
  "overlay.stop": "结束",
1307
1356
  "history.open": "打开语音历史",
@@ -1333,11 +1382,13 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
1333
1382
  "badge.voice": "VOICE",
1334
1383
  "control.start": "Start voice conversation",
1335
1384
  "control.retry": "Voice connection failed, press to retry",
1336
- "control.stop": "End voice conversation",
1385
+ "control.mute": "Mute microphone",
1386
+ "control.unmute": "Unmute microphone",
1337
1387
  "overlay.connecting": "Connecting voice",
1338
1388
  "overlay.listening": "Listening",
1339
1389
  "overlay.speaking": "Playing response",
1340
1390
  "overlay.error": "Voice connection failed",
1391
+ "overlay.muted": "Microphone muted",
1341
1392
  "overlay.return": "Return to voice conversation",
1342
1393
  "overlay.stop": "End",
1343
1394
  "history.open": "Open voice history",
@@ -1407,7 +1458,9 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
1407
1458
  const sessionId = controller.getSnapshot().sessionId;
1408
1459
  if (sessionId !== void 0) history.record(sessionId);
1409
1460
  },
1410
- stopVoice
1461
+ setVoiceMuted: (muted) => {
1462
+ controller.setInputMuted(muted);
1463
+ }
1411
1464
  });
1412
1465
  const voiceOverlayInjected = () => ({
1413
1466
  hooks: { voice: controller },
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["css","useState","css","css","useState","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/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. */\r\n readonly startVoice: (sourceSessionId: SessionId) => Promise<void>\r\n readonly retryVoice: () => Promise<void>\r\n readonly stopVoice: () => Promise<void>\r\n}\r\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({\r\n sessionId, useVoice, startVoice, retryVoice, stopVoice, t,\r\n}: VoiceControlProps) {\r\n const state = useVoice(snapshot => snapshot.state)\r\n const activeSessionId = useVoice(snapshot => snapshot.sessionId)\r\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') {\r\n await stopVoice()\r\n return\r\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'\r\n ? t('control.start')\r\n : state === 'error'\r\n ? t('control.retry')\r\n : t('control.stop')\r\n return (\r\n <button\r\n type=\"button\"\r\n className={`${css.button} ${state !== 'off' ? css.active : ''}`}\r\n aria-label={label}\r\n title={label}\r\n data-state={state}\r\n onClick={() => { void toggle() }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\n <rect x=\"5\" y=\"2\" width=\"6\" height=\"8\" rx=\"3\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\r\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\" />\r\n </svg>\r\n </button>\r\n )\r\n}\r\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) {\r\n const voice = useVoice(snapshot => snapshot)\r\n const navigable = useSessions(sessions => voice.sessionId !== undefined && sessions.ids.includes(voice.sessionId))\r\n if (voice.state === 'off') return null\r\n return (\r\n <aside className={css.root} data-voice-overlay data-state={voice.state} aria-live=\"polite\">\r\n <span className={css.wave} aria-hidden>\r\n <i /><i /><i /><i />\r\n </span>\r\n <span className={css.status}>{t(STATE_KEYS[voice.state])}</span>\r\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 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 {\r\n readonly state: VoiceClientState\r\n readonly sessionId?: SessionId\r\n readonly textById: Readonly<Record<string, VoiceLiveText>>\r\n}\r\n\r\ninterface VoiceTransport {\r\n close(): Promise<void>\r\n cancelTask(taskId: string): void\r\n submitText(text: string): void\r\n}\r\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', textById: {} }\r\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 /**\r\n * Open one voice transport and make it the root-owned active session.\r\n * @param sessionId - durable voice session to connect.\r\n */\r\n async start(sessionId: SessionId): 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', sessionId, textById: {} })\r\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) => {\r\n if (generation === this.generation) this.openingTransport = opening\r\n else void opening.close()\r\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> {\r\n const sessionId = this.snapshot.sessionId\r\n if (sessionId === undefined) throw new Error('voice retry requires an active session')\r\n await this.start(sessionId)\r\n }\r\n\r\n /** Cancel the exact active background task through the attached Voice transport. */\r\n cancelTask(taskId: string): void {\r\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)\r\n }\r\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\r\n const chunks: number[] = []\r\n const playing = new Set<AudioBufferSourceNode>()\r\n\r\n const transport: VoiceTransport = {\r\n cancelTask: (taskId) => {\r\n if (socket.readyState !== WebSocket.OPEN) throw new Error('voice transport is not open')\r\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\r\n },\r\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\r\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>) => {\r\n if (socket.readyState !== WebSocket.OPEN) return\r\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': '开始语音对话',\r\n 'control.retry': '语音连接失败,点击重试',\r\n 'control.stop': '结束语音对话',\r\n 'overlay.connecting': '正在连接语音',\r\n 'overlay.listening': '正在聆听',\r\n 'overlay.speaking': '正在播放回复',\r\n 'overlay.error': '语音连接失败',\r\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',\r\n 'control.retry': 'Voice connection failed, press to retry',\r\n 'control.stop': 'End voice conversation',\r\n 'overlay.connecting': 'Connecting voice',\r\n 'overlay.listening': 'Listening',\r\n 'overlay.speaking': 'Playing response',\r\n 'overlay.error': 'Voice connection failed',\r\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'\r\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 () => {\r\n await controller.retry()\r\n const sessionId = controller.getSnapshot().sessionId\r\n if (sessionId !== undefined) history.record(sessionId)\r\n },\r\n stopVoice,\r\n })\r\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({\r\n hooks: { voice: controller },\r\n openVoiceSession: (id) => { ctx.sessions.open(id) },\r\n stopVoice,\r\n })\r\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({\r\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\r\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","useState","css","css","useState","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/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 setVoiceMuted: (muted: boolean) => void\n}\r\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}: 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 return (\n <button\r\n type=\"button\"\r\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() }}\r\n >\r\n <svg viewBox=\"0 0 16 16\" width=\"16\" height=\"16\" aria-hidden>\r\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>\r\n )\r\n}\r\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) {\r\n const voice = useVoice(snapshot => snapshot)\r\n const navigable = useSessions(sessions => voice.sessionId !== undefined && sessions.ids.includes(voice.sessionId))\r\n if (voice.state === 'off') return null\r\n return (\r\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(STATE_KEYS[voice.state])}</span>\n {voice.inputMuted && voice.state !== 'error' && (\n <span className={css.muted}>{t('overlay.muted')}</span>\n )}\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 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 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 {\r\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)\r\n }\r\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')\r\n socket.send(JSON.stringify({ type: 'task.cancel', taskId }))\r\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.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.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': 'Microphone 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'\r\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()\r\n const sessionId = controller.getSnapshot().sessionId\r\n if (sessionId !== undefined) history.record(sessionId)\n },\n setVoiceMuted: (muted) => { controller.setInputMuted(muted) },\n })\r\n const voiceOverlayInjected = (): VoiceOverlayInjected => ({\r\n hooks: { voice: controller },\r\n openVoiceSession: (id) => { ctx.sessions.open(id) },\r\n stopVoice,\r\n })\r\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({\r\n name: 'shell.overlay',\r\n id: 'voice-active',\r\n order: 20,\r\n locale: NS,\r\n inject: voiceOverlayInjected,\r\n }, VoiceOverlay))\r\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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"}
@@ -724,7 +724,7 @@ function apply(ctx, config = {}) {
724
724
  }, false);
725
725
  agent.steer(message);
726
726
  };
727
- const onTaskCommand = async (binding, voiceSessionId, call, taskIdOverride) => {
727
+ const onTaskCommand = async (binding, voiceSessionId, call, delegationOverride) => {
728
728
  const complete = (result) => {
729
729
  ctx.voice.completeTaskCommand(voiceSessionId, call.id, result);
730
730
  };
@@ -753,10 +753,7 @@ function apply(ctx, config = {}) {
753
753
  route = await routeFrontendInput(ctx, requireSourceSession(binding).events, call.command.input);
754
754
  } catch (error) {
755
755
  ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
756
- route = {
757
- action: "delegate",
758
- acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT
759
- };
756
+ route = fallbackDelegation(call.command.input);
760
757
  }
761
758
  if (route.action === "delegate") {
762
759
  const taskId = VoiceTaskId(randomUUID());
@@ -765,13 +762,16 @@ function apply(ctx, config = {}) {
765
762
  id: call.id,
766
763
  command: {
767
764
  type: "realtime_delegation",
768
- input: call.command.input
765
+ input: delegationPrompt(route)
769
766
  }
770
- }, taskId);
767
+ }, {
768
+ taskId,
769
+ requestText: call.command.input
770
+ });
771
771
  return;
772
772
  }
773
773
  complete({ kind: "handled" });
774
- speakFragment(binding, VoiceTaskId(randomUUID()), route.reply);
774
+ speakFragment(binding, VoiceTaskId(randomUUID()), route.action === "tool" ? executeFrontendTool(route) : route.reply);
775
775
  return;
776
776
  }
777
777
  case "realtime_delegation": {
@@ -784,7 +784,8 @@ function apply(ctx, config = {}) {
784
784
  return;
785
785
  }
786
786
  cancelRewrite(binding);
787
- const taskId = taskIdOverride ?? VoiceTaskId(randomUUID());
787
+ const taskId = delegationOverride?.taskId ?? VoiceTaskId(randomUUID());
788
+ const requestText = delegationOverride?.requestText ?? call.command.input;
788
789
  const continuous = (config.taskSessionPolicy ?? "isolated") === "continuous";
789
790
  let created;
790
791
  try {
@@ -824,7 +825,7 @@ function apply(ctx, config = {}) {
824
825
  interactionMode: "frontend-agent",
825
826
  taskSessionId: created.taskSessionId,
826
827
  messageIds: /* @__PURE__ */ new Set([message.id]),
827
- requestText: call.command.input,
828
+ requestText,
828
829
  agent: created.agent,
829
830
  ...continuous ? {} : { disposeVoiceMessage: created.disposeVoiceMessage },
830
831
  cancelling: false,
@@ -836,7 +837,7 @@ function apply(ctx, config = {}) {
836
837
  requireSourceSession(binding).append("voice/task-delegated", {
837
838
  taskId,
838
839
  taskSessionId: created.taskSessionId,
839
- input: call.command.input
840
+ input: requestText
840
841
  });
841
842
  if (contextMessage !== void 0) created.agent.inject(contextMessage);
842
843
  created.agent.followup(message);
@@ -1172,7 +1173,18 @@ function speechFragments(text, flush) {
1172
1173
  }
1173
1174
  const DEFAULT_DELEGATION_ACKNOWLEDGEMENT = "好的,我先查看一下。";
1174
1175
  const MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH = 40;
1176
+ const MAX_DELEGATED_TASK_LENGTH = 1e3;
1177
+ const MAX_DELEGATION_BACKGROUND_LENGTH = 2e3;
1175
1178
  const VOICE_LATENCY_DEBUG_PREFIX = "[DEBUG-VOICE-LATENCY]";
1179
+ const WEEKDAY_NAMES = [
1180
+ "星期日",
1181
+ "星期一",
1182
+ "星期二",
1183
+ "星期三",
1184
+ "星期四",
1185
+ "星期五",
1186
+ "星期六"
1187
+ ];
1176
1188
  function debugVoiceLatency(...values) {
1177
1189
  if (globalThis.process?.env?.DSH_VOICE_LATENCY_DEBUG !== "1") return;
1178
1190
  console.info(VOICE_LATENCY_DEBUG_PREFIX, (/* @__PURE__ */ new Date()).toISOString(), ...values);
@@ -1183,6 +1195,43 @@ function delegationAcknowledgement(value) {
1183
1195
  if (normalized === "" || normalized.length > MAX_DELEGATION_ACKNOWLEDGEMENT_LENGTH) return DEFAULT_DELEGATION_ACKNOWLEDGEMENT;
1184
1196
  return normalized;
1185
1197
  }
1198
+ function delegationField(value, name, maxLength) {
1199
+ if (typeof value !== "string") throw new Error(`voice frontend router omitted ${name}`);
1200
+ const normalized = value.trim();
1201
+ if (normalized === "" || normalized.length > maxLength) throw new Error(`voice frontend router returned an invalid ${name}`);
1202
+ return normalized;
1203
+ }
1204
+ function fallbackDelegation(input) {
1205
+ return {
1206
+ action: "delegate",
1207
+ acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT,
1208
+ task: input.trim() || input,
1209
+ background: "没有可用的额外背景。",
1210
+ userRequest: input
1211
+ };
1212
+ }
1213
+ function delegationPrompt(route) {
1214
+ return [
1215
+ "当前任务:",
1216
+ route.task,
1217
+ "",
1218
+ "前置背景:",
1219
+ route.background,
1220
+ "",
1221
+ "用户原话:",
1222
+ route.userRequest,
1223
+ "",
1224
+ "请以“当前任务”为最高优先级执行;前置背景只用于理解和消歧,不要把旧对话当成待执行任务。"
1225
+ ].join("\n");
1226
+ }
1227
+ function executeFrontendTool(route) {
1228
+ switch (route.tool) {
1229
+ case "local_datetime": {
1230
+ const now = /* @__PURE__ */ new Date();
1231
+ return `根据本机系统时间,现在是 ${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日,${WEEKDAY_NAMES[now.getDay()]},${String(now.getHours()).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}。`;
1232
+ }
1233
+ }
1234
+ }
1186
1235
  async function routeFrontendInput(ctx, events, input) {
1187
1236
  let llm;
1188
1237
  try {
@@ -1190,10 +1239,7 @@ async function routeFrontendInput(ctx, events, input) {
1190
1239
  } catch {
1191
1240
  llm = void 0;
1192
1241
  }
1193
- if (llm === void 0) return {
1194
- action: "delegate",
1195
- acknowledgement: DEFAULT_DELEGATION_ACKNOWLEDGEMENT
1196
- };
1242
+ if (llm === void 0) return fallbackDelegation(input);
1197
1243
  const selection = ctx.agentDefaultModel.currentSelection();
1198
1244
  const recentConversation = events.flatMap((event) => {
1199
1245
  if (event.type !== "voice/utterance-end" || event.data.state !== "completed") return [];
@@ -1204,15 +1250,21 @@ async function routeFrontendInput(ctx, events, input) {
1204
1250
  content: [{
1205
1251
  type: "text",
1206
1252
  text: [
1207
- "判断下面这句话应该由语音助手直接回答,还是委派给后台编码 Agent",
1208
- "普通寒暄、日常对话、无需读取本地项目或调用工具即可回答的问题,选择 chat,并直接给出自然简洁的中文回复。",
1209
- "只有需要查看或修改工作区文件、运行命令、测试、安装依赖或执行其他工具操作时,才选择 delegate。",
1210
- "选择 delegate 时,同时给出一句简短自然的 acknowledgement,表示接下来要做什么。不能声称任务已经完成、已经找到结果,也不要提后台 Agent、工具或路由。",
1253
+ "结合最近对话判断当前用户原话应该由语音前台聊天、轻量工具还是后台编码 Agent 处理。",
1254
+ "普通寒暄、日常对话、解释性知识以及无需外部事实或工具即可回答的问题,选择 chat,并直接给出自然简洁的中文回复。",
1255
+ "时间、日期等会随现实变化的事实不能猜测。查询本机当前时间、日期或星期时必须选择 tool=local_datetime。",
1256
+ "其他必须查询才能确认的动态事实,如果没有对应的前台工具,应选择 delegate,不能选择 chat 编造答案。",
1257
+ "需要查看或修改工作区文件、运行命令、测试、安装依赖或执行其他复杂工具操作时,选择 delegate。",
1258
+ "选择 delegate 时,必须结合最近对话补全省略指代,输出自包含的 task、只用于消歧的 background,并原样复制 user_request。",
1259
+ "task 是后台当前唯一要执行的任务;background 不能包含新的要求,也不要把旧任务写成待办。",
1260
+ "同时给出一句简短自然的 acknowledgement,表示接下来要做什么。不能声称任务已经完成、已经找到结果,也不要提后台 Agent、工具或路由。",
1211
1261
  "acknowledgement 只能有一句,最多 40 个字符,例如“好的,我先检查一下相关代码。”。",
1212
1262
  "只输出一行 JSON,不要 Markdown。格式只能是:",
1213
1263
  "{\"action\":\"chat\",\"reply\":\"...\"}",
1214
1264
  "或:",
1215
- "{\"action\":\"delegate\",\"acknowledgement\":\"...\"}",
1265
+ "{\"action\":\"tool\",\"tool\":\"local_datetime\"}",
1266
+ "或:",
1267
+ "{\"action\":\"delegate\",\"acknowledgement\":\"...\",\"task\":\"...\",\"background\":\"...\",\"user_request\":\"...\"}",
1216
1268
  "",
1217
1269
  "最近对话:",
1218
1270
  recentConversation || "(无)",
@@ -1239,9 +1291,16 @@ async function routeFrontendInput(ctx, events, input) {
1239
1291
  action: "chat",
1240
1292
  reply: parsed.reply.trim()
1241
1293
  };
1294
+ if (parsed.action === "tool" && parsed.tool === "local_datetime") return {
1295
+ action: "tool",
1296
+ tool: parsed.tool
1297
+ };
1242
1298
  if (parsed.action === "delegate") return {
1243
1299
  action: "delegate",
1244
- acknowledgement: delegationAcknowledgement(parsed.acknowledgement)
1300
+ acknowledgement: delegationAcknowledgement(parsed.acknowledgement),
1301
+ task: delegationField(parsed.task, "task", MAX_DELEGATED_TASK_LENGTH),
1302
+ background: delegationField(parsed.background, "background", MAX_DELEGATION_BACKGROUND_LENGTH),
1303
+ userRequest: input
1245
1304
  };
1246
1305
  throw new Error("voice frontend router returned an invalid decision");
1247
1306
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flowingspring/dsh-voco",
3
3
  "description": "Persistent voice conversations for DSH with cloud speech recognition, Edge TTS, and background Agent delegation",
4
- "version": "0.2.2",
4
+ "version": "0.2.4",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",