@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 +1 -1
- package/lib/client.js +235 -48
- package/lib/client.js.map +1 -1
- package/lib/plugins/voice-local.js +23 -3
- package/lib/plugins/voice-web.js +2 -1
- package/lib/plugins/voice.js +4 -0
- package/package.json +27 -27
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
|
-
|
|
97
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
232
|
+
"taskUpdate": "Kt2smW_taskUpdate",
|
|
233
|
+
"utterance": "Kt2smW_utterance",
|
|
180
234
|
"taskInput": "Kt2smW_taskInput",
|
|
235
|
+
"taskLink": "Kt2smW_taskLink",
|
|
181
236
|
"taskChevron": "Kt2smW_taskChevron",
|
|
182
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
879
|
-
"
|
|
880
|
-
"
|
|
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
|
-
"
|
|
950
|
+
"input": "G5LlXq_input",
|
|
886
951
|
"badge": "G5LlXq_badge",
|
|
887
|
-
"
|
|
888
|
-
"
|
|
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.
|
|
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
|
},
|