@flowingspring/dsh-voco 0.3.7 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh.md CHANGED
@@ -61,7 +61,7 @@ SILICONFLOW_API_KEY=sk-your-api-key
61
61
 
62
62
  1. 运行 `dsh web` 并打开 DSH Web UI。
63
63
  2. 新建或选择会话,点击麦克风并允许浏览器权限。
64
- 3. 直接说话,连续静音约 1.5 秒后提交一句话。
64
+ 3. 直接说话,连续静音约 1.5 秒后提交一句话;也可以长按麦克风约 400 毫秒按住说话,松手后提交这一段。
65
65
  4. 普通聊天由前台回答,需要项目工具的工作会委派给后台 Agent。
66
66
 
67
67
  每个语音会话持续绑定自己的后台 Agent Session。上下文达到轮换阈值后,后续任务会自动进入新的子会话,但仍与原语音会话绑定。
package/lib/client.js CHANGED
@@ -4,13 +4,13 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let react_jsx_runtime = require("react/jsx-runtime");
8
7
  let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react_dom_client = require("react-dom/client");
10
10
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
11
  let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
12
12
  //#region \0dsh-css:D:\QUAN\DevCode\Python\Project\dsh\dsh-voco\packages\ui-voice\src\client\VoiceControl.module.css.mjs
13
- const css$4 = ".TFZsua_controls{align-items:center;gap:2px;display:inline-flex}.TFZsua_button{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:8px;justify-content:center;align-items:center;padding:0;display:inline-flex}.TFZsua_button:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.TFZsua_active{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover-solid)}.TFZsua_button[data-muted=true]{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, currentColor 12%, transparent)}.TFZsua_interruptButton,.TFZsua_interruptButton:hover{color:var(--dsw-alias-state-error-primary)}.TFZsua_button[data-muted=true]:hover{background:color-mix(in srgb, currentColor 18%, transparent)}.TFZsua_button[data-state=speaking][data-muted=false]{animation:1.2s ease-in-out infinite TFZsua_voice-pulse}@keyframes TFZsua_voice-pulse{50%{opacity:.55;transform:scale(.94)}}@media (prefers-reduced-motion:reduce){.TFZsua_button[data-state=speaking][data-muted=false]{animation:none}}";
13
+ const css$4 = ".TFZsua_controls{align-items:center;gap:2px;display:inline-flex}.TFZsua_button{touch-action:manipulation;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-push-to-talk=true]{color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-interactive-bg-hover-solid);transform:scale(.94)}.TFZsua_interruptButton,.TFZsua_interruptButton:hover{color:var(--dsw-alias-state-error-primary)}.TFZsua_button[data-muted=true]:hover{background:color-mix(in srgb, currentColor 18%, transparent)}.TFZsua_button[data-state=speaking][data-muted=false]{animation:1.2s ease-in-out infinite TFZsua_voice-pulse}@keyframes TFZsua_voice-pulse{50%{opacity:.55;transform:scale(.94)}}@media (prefers-reduced-motion:reduce){.TFZsua_button[data-state=speaking][data-muted=false]{animation:none}}";
14
14
  const tagId$4 = "@flowingspring/dsh-voco/VoiceControl.module.css";
15
15
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
16
16
  const tag = document.createElement("style");
@@ -20,8 +20,8 @@ window.__ModuleLoader__.load({
20
20
  document.head.appendChild(tag);
21
21
  }
22
22
  var VoiceControl_module_css_default = {
23
- "button": "TFZsua_button",
24
23
  "controls": "TFZsua_controls",
24
+ "button": "TFZsua_button",
25
25
  "active": "TFZsua_active",
26
26
  "interruptButton": "TFZsua_interruptButton",
27
27
  "voice-pulse": "TFZsua_voice-pulse"
@@ -29,10 +29,38 @@ window.__ModuleLoader__.load({
29
29
  //#endregion
30
30
  //#region ../ui-voice/src/client/VoiceControl.tsx
31
31
  /** Start a fresh Voice Session from this Session's location or control the active connection. */
32
- function VoiceControl({ sessionId, useVoice, startVoice, retryVoice, setVoiceMuted, t, interruptResponse }) {
32
+ function VoiceControl({ sessionId, useVoice, startVoice, retryVoice, beginPushToTalk, endPushToTalk, setVoiceMuted, t, interruptResponse }) {
33
33
  const state = useVoice((snapshot) => snapshot.state);
34
34
  const activeSessionId = useVoice((snapshot) => snapshot.sessionId);
35
35
  const inputMuted = useVoice((snapshot) => snapshot.inputMuted);
36
+ const pushToTalkActive = useVoice((snapshot) => snapshot.pushToTalkActive);
37
+ const timer = (0, react.useRef)(void 0);
38
+ const pointerDown = (0, react.useRef)(false);
39
+ const longPress = (0, react.useRef)(false);
40
+ const suppressClick = (0, react.useRef)(false);
41
+ const clearTimer = () => {
42
+ if (timer.current !== void 0) {
43
+ window.clearTimeout(timer.current);
44
+ timer.current = void 0;
45
+ }
46
+ };
47
+ const releasePushToTalk = () => {
48
+ clearTimer();
49
+ pointerDown.current = false;
50
+ if (longPress.current) {
51
+ longPress.current = false;
52
+ endPushToTalk();
53
+ }
54
+ };
55
+ (0, react.useEffect)(() => {
56
+ window.addEventListener("blur", releasePushToTalk);
57
+ document.addEventListener("visibilitychange", releasePushToTalk);
58
+ return () => {
59
+ window.removeEventListener("blur", releasePushToTalk);
60
+ document.removeEventListener("visibilitychange", releasePushToTalk);
61
+ releasePushToTalk();
62
+ };
63
+ }, []);
36
64
  const toggle = async () => {
37
65
  if (state === "error" && activeSessionId !== void 0) {
38
66
  try {
@@ -55,6 +83,35 @@ window.__ModuleLoader__.load({
55
83
  interruptResponse();
56
84
  } catch {}
57
85
  };
86
+ const onPointerDown = (event) => {
87
+ if (event.pointerType === "mouse" && event.button !== 0) return;
88
+ pointerDown.current = true;
89
+ longPress.current = false;
90
+ suppressClick.current = false;
91
+ if (typeof event.currentTarget.setPointerCapture === "function") event.currentTarget.setPointerCapture(event.pointerId);
92
+ clearTimer();
93
+ timer.current = window.setTimeout(() => {
94
+ timer.current = void 0;
95
+ if (!pointerDown.current) return;
96
+ longPress.current = true;
97
+ suppressClick.current = true;
98
+ beginPushToTalk(sessionId);
99
+ }, 400);
100
+ };
101
+ const onPointerUp = (event) => {
102
+ releasePushToTalk();
103
+ if (event.currentTarget.hasPointerCapture?.(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
104
+ };
105
+ const onPointerCancel = () => {
106
+ releasePushToTalk();
107
+ };
108
+ const onClick = () => {
109
+ if (suppressClick.current) {
110
+ suppressClick.current = false;
111
+ return;
112
+ }
113
+ toggle();
114
+ };
58
115
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
59
116
  className: VoiceControl_module_css_default.controls,
60
117
  children: [state === "speaking" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -88,14 +145,17 @@ window.__ModuleLoader__.load({
88
145
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
89
146
  type: "button",
90
147
  className: `${VoiceControl_module_css_default.button} ${active ? VoiceControl_module_css_default.active : ""}`,
91
- "aria-label": label,
148
+ "aria-label": pushToTalkActive ? t("control.pushToTalk") : label,
92
149
  "aria-pressed": active ? inputMuted : void 0,
93
150
  title: label,
94
151
  "data-state": state,
95
152
  "data-muted": inputMuted,
96
- onClick: () => {
97
- toggle();
98
- },
153
+ "data-push-to-talk": pushToTalkActive,
154
+ onPointerDown,
155
+ onPointerUp,
156
+ onPointerCancel,
157
+ onLostPointerCapture: onPointerCancel,
158
+ onClick,
99
159
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
100
160
  viewBox: "0 0 16 16",
101
161
  width: "16",
@@ -163,23 +223,23 @@ window.__ModuleLoader__.load({
163
223
  document.head.appendChild(tag);
164
224
  }
165
225
  var VoiceNodeViews_module_css_default = {
166
- "taskDot": "Kt2smW_taskDot",
167
- "taskCard": "Kt2smW_taskCard",
168
- "meta": "Kt2smW_meta",
226
+ "taskSummary": "Kt2smW_taskSummary",
169
227
  "taskTitle": "Kt2smW_taskTitle",
170
- "utterance": "Kt2smW_utterance",
171
228
  "bubble": "Kt2smW_bubble",
172
- "taskDetails": "Kt2smW_taskDetails",
173
- "taskSummary": "Kt2smW_taskSummary",
174
- "miniWave": "Kt2smW_miniWave",
229
+ "meta": "Kt2smW_meta",
175
230
  "taskCancel": "Kt2smW_taskCancel",
176
- "taskUpdate": "Kt2smW_taskUpdate",
177
- "voiceBadge": "Kt2smW_voiceBadge",
178
231
  "taskStatus": "Kt2smW_taskStatus",
179
- "taskLink": "Kt2smW_taskLink",
232
+ "taskUpdate": "Kt2smW_taskUpdate",
233
+ "utterance": "Kt2smW_utterance",
180
234
  "taskInput": "Kt2smW_taskInput",
235
+ "taskLink": "Kt2smW_taskLink",
181
236
  "taskChevron": "Kt2smW_taskChevron",
182
- "taskActions": "Kt2smW_taskActions"
237
+ "miniWave": "Kt2smW_miniWave",
238
+ "taskCard": "Kt2smW_taskCard",
239
+ "taskDot": "Kt2smW_taskDot",
240
+ "taskActions": "Kt2smW_taskActions",
241
+ "taskDetails": "Kt2smW_taskDetails",
242
+ "voiceBadge": "Kt2smW_voiceBadge"
183
243
  };
184
244
  //#endregion
185
245
  //#region ../ui-voice/src/client/VoiceNodeViews.tsx
@@ -318,10 +378,10 @@ window.__ModuleLoader__.load({
318
378
  }
319
379
  var VoiceOverlay_module_css_default = {
320
380
  "root": "GPPB2G_root",
321
- "voice-wave": "GPPB2G_voice-wave",
322
381
  "wave": "GPPB2G_wave",
323
382
  "status": "GPPB2G_status",
324
- "stop": "GPPB2G_stop"
383
+ "stop": "GPPB2G_stop",
384
+ "voice-wave": "GPPB2G_voice-wave"
325
385
  };
326
386
  //#endregion
327
387
  //#region ../ui-voice/src/client/VoiceOverlay.tsx
@@ -380,19 +440,19 @@ window.__ModuleLoader__.load({
380
440
  document.head.appendChild(tag);
381
441
  }
382
442
  var VoiceSessionMarkers_module_css_default = {
383
- "child": "_0brRca_child",
384
443
  "toggle": "_0brRca_toggle",
385
- "childDot": "_0brRca_childDot",
386
- "childLabel": "_0brRca_childLabel",
387
- "marker": "_0brRca_marker",
388
- "childTime": "_0brRca_childTime",
389
- "deleteStatus": "_0brRca_deleteStatus",
390
444
  "children": "_0brRca_children",
391
- "childMain": "_0brRca_childMain",
445
+ "marker": "_0brRca_marker",
446
+ "child": "_0brRca_child",
447
+ "childLabel": "_0brRca_childLabel",
392
448
  "childActions": "_0brRca_childActions",
393
449
  "childMenuButton": "_0brRca_childMenuButton",
450
+ "deleteStatus": "_0brRca_deleteStatus",
394
451
  "deleteAction": "_0brRca_deleteAction",
452
+ "childDot": "_0brRca_childDot",
453
+ "childTime": "_0brRca_childTime",
395
454
  "deleteError": "_0brRca_deleteError",
455
+ "childMain": "_0brRca_childMain",
396
456
  "slot": "_0brRca_slot"
397
457
  };
398
458
  //#endregion
@@ -868,29 +928,29 @@ window.__ModuleLoader__.load({
868
928
  document.head.appendChild(tag);
869
929
  }
870
930
  var VocoSettingsCard_module_css_default = {
931
+ "save": "G5LlXq_save",
932
+ "chevron": "G5LlXq_chevron",
933
+ "hint": "G5LlXq_hint",
934
+ "header": "G5LlXq_header",
935
+ "body": "G5LlXq_body",
871
936
  "pending": "G5LlXq_pending",
872
- "badgeMuted": "G5LlXq_badgeMuted",
873
- "input": "G5LlXq_input",
874
- "footer": "G5LlXq_footer",
875
- "name": "G5LlXq_name",
876
- "chevronOpen": "G5LlXq_chevronOpen",
877
937
  "headText": "G5LlXq_headText",
878
- "cardOpen": "G5LlXq_cardOpen",
879
- "hint": "G5LlXq_hint",
880
- "discard": "G5LlXq_discard",
938
+ "chevronOpen": "G5LlXq_chevronOpen",
939
+ "description": "G5LlXq_description",
940
+ "readOnly": "G5LlXq_readOnly",
941
+ "label": "G5LlXq_label",
881
942
  "providerDetails": "G5LlXq_providerDetails",
882
- "save": "G5LlXq_save",
883
943
  "failed": "G5LlXq_failed",
944
+ "badgeMuted": "G5LlXq_badgeMuted",
945
+ "fieldHead": "G5LlXq_fieldHead",
946
+ "providerDetail": "G5LlXq_providerDetail",
947
+ "cardOpen": "G5LlXq_cardOpen",
948
+ "footer": "G5LlXq_footer",
884
949
  "field": "G5LlXq_field",
885
- "description": "G5LlXq_description",
950
+ "input": "G5LlXq_input",
886
951
  "badge": "G5LlXq_badge",
887
- "label": "G5LlXq_label",
888
- "providerDetail": "G5LlXq_providerDetail",
889
- "header": "G5LlXq_header",
890
- "body": "G5LlXq_body",
891
- "fieldHead": "G5LlXq_fieldHead",
892
- "readOnly": "G5LlXq_readOnly",
893
- "chevron": "G5LlXq_chevron",
952
+ "name": "G5LlXq_name",
953
+ "discard": "G5LlXq_discard",
894
954
  "card": "G5LlXq_card"
895
955
  };
896
956
  //#endregion
@@ -1012,6 +1072,7 @@ window.__ModuleLoader__.load({
1012
1072
  const INITIAL_SNAPSHOT = {
1013
1073
  state: "off",
1014
1074
  inputMuted: false,
1075
+ pushToTalkActive: false,
1015
1076
  textById: {}
1016
1077
  };
1017
1078
  /** Own the single browser microphone, provider WebSocket, playback queue, and live transcript feed. */
@@ -1026,6 +1087,9 @@ window.__ModuleLoader__.load({
1026
1087
  pendingText = [];
1027
1088
  onConversationStarted;
1028
1089
  conversationStarted = false;
1090
+ pushToTalkOrigin;
1091
+ pushToTalkPending = false;
1092
+ pushToTalkActive = false;
1029
1093
  constructor(options = {}) {
1030
1094
  this.onConversationStarted = options.onConversationStarted;
1031
1095
  }
@@ -1045,6 +1109,101 @@ window.__ModuleLoader__.load({
1045
1109
  async start(sessionId) {
1046
1110
  await this.open(sessionId, false);
1047
1111
  }
1112
+ /** Begin a temporary manually committed utterance after a long press. */
1113
+ beginPushToTalk(sessionId) {
1114
+ if (this.pushToTalkPending || this.pushToTalkActive) return;
1115
+ const state = this.snapshot.state;
1116
+ if (state === "off") {
1117
+ this.pushToTalkOrigin = "off";
1118
+ this.pushToTalkPending = true;
1119
+ this.open(sessionId, true).catch(() => {});
1120
+ return;
1121
+ }
1122
+ if (state === "error") {
1123
+ const activeSessionId = this.snapshot.sessionId;
1124
+ if (activeSessionId === void 0) return;
1125
+ this.pushToTalkOrigin = this.snapshot.inputMuted ? "muted" : "hands-free";
1126
+ this.pushToTalkPending = true;
1127
+ this.open(activeSessionId, true).catch(() => {});
1128
+ return;
1129
+ }
1130
+ this.pushToTalkOrigin = this.snapshot.inputMuted ? "muted" : "hands-free";
1131
+ this.pushToTalkPending = true;
1132
+ if (state === "connecting") this.setInputMutedWithoutCommit(true);
1133
+ this.activatePushToTalk();
1134
+ }
1135
+ /** Finish or cancel the temporary manually committed utterance. */
1136
+ endPushToTalk() {
1137
+ if (!this.pushToTalkPending && !this.pushToTalkActive) return;
1138
+ if (!this.pushToTalkActive) {
1139
+ this.pushToTalkPending = false;
1140
+ this.restorePendingInputState();
1141
+ return;
1142
+ }
1143
+ const transport = this.transport;
1144
+ if (transport === void 0) {
1145
+ this.pushToTalkActive = false;
1146
+ this.pushToTalkPending = false;
1147
+ this.publish({
1148
+ ...this.snapshot,
1149
+ pushToTalkActive: false
1150
+ });
1151
+ return;
1152
+ }
1153
+ const origin = this.pushToTalkOrigin;
1154
+ const restoreMuted = origin === "off" || origin === "muted";
1155
+ try {
1156
+ transport.commitAudio();
1157
+ transport.setInputMuted(restoreMuted, false);
1158
+ } catch {
1159
+ this.handleUnexpectedClose();
1160
+ return;
1161
+ }
1162
+ this.pushToTalkActive = false;
1163
+ this.pushToTalkPending = false;
1164
+ this.pushToTalkOrigin = void 0;
1165
+ this.publish({
1166
+ ...this.snapshot,
1167
+ inputMuted: restoreMuted,
1168
+ pushToTalkActive: false
1169
+ });
1170
+ }
1171
+ activatePushToTalk() {
1172
+ if (!this.pushToTalkPending || this.pushToTalkActive || this.transport === void 0) return;
1173
+ try {
1174
+ this.transport.setInputMuted(false, false);
1175
+ this.transport.beginManualUtterance();
1176
+ } catch {
1177
+ this.handleUnexpectedClose();
1178
+ return;
1179
+ }
1180
+ this.pushToTalkActive = true;
1181
+ this.publish({
1182
+ ...this.snapshot,
1183
+ inputMuted: false,
1184
+ pushToTalkActive: true
1185
+ });
1186
+ }
1187
+ restorePendingInputState() {
1188
+ const origin = this.pushToTalkOrigin;
1189
+ if (origin === void 0) return;
1190
+ const restoreMuted = origin === "off" || origin === "muted";
1191
+ (this.transport ?? this.openingTransport)?.setInputMuted(restoreMuted, false);
1192
+ this.pushToTalkOrigin = void 0;
1193
+ this.publish({
1194
+ ...this.snapshot,
1195
+ inputMuted: restoreMuted,
1196
+ pushToTalkActive: false
1197
+ });
1198
+ }
1199
+ setInputMutedWithoutCommit(muted) {
1200
+ this.openingTransport?.setInputMuted(muted, false);
1201
+ this.transport?.setInputMuted(muted, false);
1202
+ if (this.snapshot.inputMuted !== muted) this.publish({
1203
+ ...this.snapshot,
1204
+ inputMuted: muted
1205
+ });
1206
+ }
1048
1207
  async open(sessionId, inputMuted) {
1049
1208
  const generation = ++this.generation;
1050
1209
  this.pendingText.splice(0);
@@ -1060,6 +1219,7 @@ window.__ModuleLoader__.load({
1060
1219
  this.publish({
1061
1220
  state: "connecting",
1062
1221
  inputMuted,
1222
+ pushToTalkActive: false,
1063
1223
  sessionId,
1064
1224
  textById: {}
1065
1225
  });
@@ -1090,7 +1250,8 @@ window.__ModuleLoader__.load({
1090
1250
  this.openingTransport = void 0;
1091
1251
  this.transport = transport;
1092
1252
  for (const text of this.pendingText.splice(0)) transport.submitText(text);
1093
- this.setState("listening");
1253
+ if (this.pushToTalkPending) this.activatePushToTalk();
1254
+ if (this.snapshot.state !== "error") this.setState("listening");
1094
1255
  } catch (cause) {
1095
1256
  if (generation !== this.generation) return;
1096
1257
  this.setState("error");
@@ -1146,6 +1307,9 @@ window.__ModuleLoader__.load({
1146
1307
  this.openingAbort = void 0;
1147
1308
  this.publish(INITIAL_SNAPSHOT);
1148
1309
  this.pendingText.splice(0);
1310
+ this.pushToTalkPending = false;
1311
+ this.pushToTalkActive = false;
1312
+ this.pushToTalkOrigin = void 0;
1149
1313
  await this.closeTransport();
1150
1314
  }
1151
1315
  async closeTransport() {
@@ -1165,6 +1329,13 @@ window.__ModuleLoader__.load({
1165
1329
  }
1166
1330
  handleUnexpectedClose() {
1167
1331
  this.openingAbort?.abort();
1332
+ this.pushToTalkPending = false;
1333
+ this.pushToTalkActive = false;
1334
+ this.pushToTalkOrigin = void 0;
1335
+ if (this.snapshot.pushToTalkActive) this.publish({
1336
+ ...this.snapshot,
1337
+ pushToTalkActive: false
1338
+ });
1168
1339
  this.setState("error");
1169
1340
  this.closeTransport().catch((cause) => {
1170
1341
  console.error("voice transport cleanup failed:", cause);
@@ -1295,6 +1466,14 @@ window.__ModuleLoader__.load({
1295
1466
  const chunks = [];
1296
1467
  const playing = /* @__PURE__ */ new Set();
1297
1468
  const transport = {
1469
+ beginManualUtterance: () => {
1470
+ if (socket.readyState !== WebSocket.OPEN) throw new Error("voice transport is not open");
1471
+ socket.send(JSON.stringify({ type: "audio.push-to-talk.start" }));
1472
+ },
1473
+ commitAudio: () => {
1474
+ if (socket.readyState !== WebSocket.OPEN) throw new Error("voice transport is not open");
1475
+ socket.send(JSON.stringify({ type: "audio.commit" }));
1476
+ },
1298
1477
  cancelTask: (taskId) => {
1299
1478
  if (socket.readyState !== WebSocket.OPEN) throw new Error("voice transport is not open");
1300
1479
  socket.send(JSON.stringify({
@@ -1307,12 +1486,12 @@ window.__ModuleLoader__.load({
1307
1486
  socket.send(JSON.stringify({ type: "response.interrupt" }));
1308
1487
  stopPlayback();
1309
1488
  },
1310
- setInputMuted: (muted) => {
1489
+ setInputMuted: (muted, commit = true) => {
1311
1490
  if (inputMuted === muted) return;
1312
1491
  inputMuted = muted;
1313
1492
  chunks.splice(0);
1314
1493
  for (const track of stream?.getTracks() ?? []) track.enabled = !muted;
1315
- if (muted && !stopped && socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "audio.commit" }));
1494
+ if (muted && commit && !stopped && socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: "audio.commit" }));
1316
1495
  },
1317
1496
  submitText: (text) => {
1318
1497
  if (socket.readyState !== WebSocket.OPEN) throw new Error("voice transport is not open");
@@ -1992,6 +2171,7 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
1992
2171
  "control.interrupt": "打断播放",
1993
2172
  "control.mute": "静音麦克风",
1994
2173
  "control.unmute": "解除静音",
2174
+ "control.pushToTalk": "按住说话中,松开发送",
1995
2175
  "overlay.connecting": "正在连接语音",
1996
2176
  "overlay.listening": "正在聆听",
1997
2177
  "overlay.speaking": "正在回复",
@@ -2041,6 +2221,7 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
2041
2221
  "control.interrupt": "Stop playback",
2042
2222
  "control.mute": "Mute microphone",
2043
2223
  "control.unmute": "Unmute microphone",
2224
+ "control.pushToTalk": "Push to talk, release to send",
2044
2225
  "overlay.connecting": "Connecting voice",
2045
2226
  "overlay.listening": "Listening",
2046
2227
  "overlay.speaking": "Playing response",
@@ -2265,6 +2446,12 @@ registerProcessor('dsh-pcm-capture', DshPcmCaptureProcessor)
2265
2446
  const sessionId = controller.getSnapshot().sessionId;
2266
2447
  if (sessionId !== void 0) history.record(sessionId);
2267
2448
  },
2449
+ beginPushToTalk: (sessionId) => {
2450
+ controller.beginPushToTalk(sessionId);
2451
+ },
2452
+ endPushToTalk: () => {
2453
+ controller.endPushToTalk();
2454
+ },
2268
2455
  interruptResponse: () => {
2269
2456
  controller.interruptResponse();
2270
2457
  },