voice-page-agent 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,56 @@ npm i @vue/composition-api
29
29
 
30
30
  If you do not specify a tag, `npm i voice-page-agent` installs the `latest` tag.
31
31
 
32
+ ### Runtime Script Mode (No Vue Plugin Required)
33
+
34
+ If you want "import one js and use immediately", use runtime entry:
35
+
36
+ ```html
37
+ <script src="https://unpkg.com/voice-page-agent/dist/voice-page-agent.runtime.iife.js"></script>
38
+ <script>
39
+ window.VoicePageAgentRuntime.mount({
40
+ pageAgent: {
41
+ baseURL: "https://your-api.example.com",
42
+ model: "qwen3.5-plus",
43
+ apiKey: "NA",
44
+ language: "zh-CN",
45
+ enablePanel: true,
46
+ enableMask: false
47
+ },
48
+ wakeWord: "布丁布丁",
49
+ buttonText: {
50
+ openText: "网页助手"
51
+ }
52
+ });
53
+ </script>
54
+ ```
55
+
56
+ Auto-mount is also supported:
57
+
58
+ ```html
59
+ <script
60
+ src="https://unpkg.com/voice-page-agent/dist/voice-page-agent.runtime.iife.js"
61
+ data-vpa='{"pageAgent":{"baseURL":"https://your-api.example.com","model":"qwen3.5-plus","apiKey":"NA","language":"zh-CN","enablePanel":true,"enableMask":false},"wakeWord":"布丁布丁"}'
62
+ ></script>
63
+ ```
64
+
65
+ Module usage (without Vue plugin):
66
+
67
+ ```ts
68
+ import { mount } from "voice-page-agent/runtime";
69
+
70
+ const runtime = mount({
71
+ pageAgent: {
72
+ baseURL: "https://your-api.example.com",
73
+ model: "qwen3.5-plus",
74
+ apiKey: "NA",
75
+ language: "zh-CN",
76
+ },
77
+ });
78
+
79
+ runtime.toggleAgentPanel();
80
+ ```
81
+
32
82
  ## Option Format
33
83
 
34
84
  Use this shape:
@@ -212,12 +262,26 @@ It renders:
212
262
  - `onStateChange(listener): () => void`
213
263
  - `snapshot` (current state)
214
264
 
265
+ `voice-page-agent/runtime`:
266
+ - `mount(options): VoicePageAgentRuntimeInstance`
267
+ - `unmount(): void`
268
+ - `getInstance(): VoicePageAgentRuntimeInstance | null`
269
+
270
+ `VoicePageAgentRuntimeInstance`:
271
+ - `startWake(): Promise<void>`
272
+ - `stopWake(): void`
273
+ - `openAgent(): Promise<RuntimePageAgent | null>`
274
+ - `toggleAgentPanel(): Promise<void>`
275
+ - `runCommand(text: string): Promise<void>`
276
+ - `destroy(): void`
277
+
215
278
  ## Local Examples
216
279
 
217
280
  This repo includes runnable examples:
218
281
 
219
282
  - `examples/vue3`
220
283
  - `examples/vue2`
284
+ - `examples/runtime`
221
285
 
222
286
  Run Vue3 demo:
223
287
 
@@ -0,0 +1,539 @@
1
+ // src/controller.ts
2
+ var DEFAULT_WAKE_WORD = "\u5E03\u4E01\u5E03\u4E01";
3
+ var DEFAULT_OPTIONS = {
4
+ wakeWord: [DEFAULT_WAKE_WORD],
5
+ enableHomophoneMatch: true,
6
+ wakeCooldownMs: 1400,
7
+ commandInitialTimeoutMs: 12e3,
8
+ commandSilenceTimeoutMs: 2600,
9
+ commandMaxWindowMs: 22e3,
10
+ recognitionLang: "zh-CN",
11
+ showAgentWhenWake: true,
12
+ autoStart: false
13
+ };
14
+ var HOMOPHONE_VARIANTS = {
15
+ \u5E03: ["\u8865", "\u4E0D", "\u6B65", "\u90E8"],
16
+ \u4E01: ["\u53EE", "\u9489", "\u76EF", "\u9876"],
17
+ \u5C0F: ["\u6653", "\u7B11", "\u6821", "\u7B71"],
18
+ \u73ED: ["\u822C", "\u6591", "\u534A"]
19
+ };
20
+ function normalizeText(text) {
21
+ return (text || "").toLowerCase().replace(/[\s,,.。!!??;;::、"'`~\-_/\\]/g, "");
22
+ }
23
+ function escapeRegExp(value) {
24
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
+ }
26
+ function levenshteinDistance(source, target) {
27
+ const a = Array.from(source);
28
+ const b = Array.from(target);
29
+ if (a.length === 0) return b.length;
30
+ if (b.length === 0) return a.length;
31
+ const dp = Array.from(
32
+ { length: a.length + 1 },
33
+ () => Array.from({ length: b.length + 1 }, () => 0)
34
+ );
35
+ for (let i = 0; i <= a.length; i += 1) dp[i][0] = i;
36
+ for (let j = 0; j <= b.length; j += 1) dp[0][j] = j;
37
+ for (let i = 1; i <= a.length; i += 1) {
38
+ for (let j = 1; j <= b.length; j += 1) {
39
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
40
+ dp[i][j] = Math.min(
41
+ dp[i - 1][j] + 1,
42
+ dp[i][j - 1] + 1,
43
+ dp[i - 1][j - 1] + cost
44
+ );
45
+ }
46
+ }
47
+ return dp[a.length][b.length];
48
+ }
49
+ function makeHomophoneRegex(words) {
50
+ const fuzzyWords = words.map((word) => {
51
+ const chars = Array.from(word);
52
+ return chars.map((char) => {
53
+ const variants = HOMOPHONE_VARIANTS[char] || [];
54
+ const options = [char, ...variants].map((item) => escapeRegExp(item));
55
+ return `(?:${options.join("|")})`;
56
+ }).join("");
57
+ });
58
+ return new RegExp(fuzzyWords.join("|"));
59
+ }
60
+ function buildWakeRegex(words) {
61
+ const items = words.map((word) => normalizeText(word)).filter(Boolean).sort((a, b) => b.length - a.length).map((word) => escapeRegExp(word));
62
+ const body = items.length ? items.join("|") : escapeRegExp(normalizeText(DEFAULT_WAKE_WORD));
63
+ return {
64
+ single: new RegExp(`(${body})`),
65
+ global: new RegExp(`(${body})`, "g")
66
+ };
67
+ }
68
+ function resolveOptions(options) {
69
+ if (!(options == null ? void 0 : options.pageAgent) || typeof options.pageAgent !== "object") {
70
+ throw new Error("voice-page-agent: options.pageAgent is required");
71
+ }
72
+ const wakeWordInput = options.wakeWord;
73
+ const wakeWord = Array.isArray(wakeWordInput) ? wakeWordInput.filter(Boolean) : [wakeWordInput || DEFAULT_WAKE_WORD];
74
+ return {
75
+ ...DEFAULT_OPTIONS,
76
+ ...options,
77
+ pageAgent: options.pageAgent,
78
+ pageAgentCtor: options.pageAgentCtor,
79
+ wakeWord
80
+ };
81
+ }
82
+ var VoicePageAgentController = class {
83
+ constructor(options) {
84
+ this.listeners = /* @__PURE__ */ new Set();
85
+ this.state = {
86
+ status: "off",
87
+ message: "\u8BED\u97F3\u52A9\u624B\u672A\u5F00\u542F",
88
+ supported: false,
89
+ enabled: false,
90
+ micPermissionGranted: false
91
+ };
92
+ this.recognition = null;
93
+ this.initAgentPromise = null;
94
+ this.voiceEnabled = false;
95
+ this.disposed = false;
96
+ this.awaitingCommand = false;
97
+ this.commandFinalText = "";
98
+ this.commandInterimText = "";
99
+ this.commandTimer = null;
100
+ this.commandDeadlineTimer = null;
101
+ this.restartBusy = false;
102
+ this.lastWakeAt = 0;
103
+ this.options = resolveOptions(options);
104
+ this.wakeRegex = buildWakeRegex(this.options.wakeWord);
105
+ this.wakeHomophoneRegex = this.options.enableHomophoneMatch ? makeHomophoneRegex(this.options.wakeWord) : null;
106
+ this.patchState({
107
+ supported: this.hasSpeechSupport(),
108
+ status: this.hasSpeechSupport() ? "off" : "unsupported",
109
+ message: this.hasSpeechSupport() ? "\u8BED\u97F3\u52A9\u624B\u672A\u5F00\u542F" : "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u8BED\u97F3\u8BC6\u522B\uFF0C\u5EFA\u8BAE\u4F7F\u7528 Chrome/Edge"
110
+ });
111
+ if (typeof window !== "undefined") {
112
+ const saved = window.localStorage.getItem("voice-page-agent-enabled");
113
+ if (saved === "1" || this.options.autoStart) {
114
+ void this.startWake();
115
+ }
116
+ void this.syncPermissionState();
117
+ }
118
+ }
119
+ get snapshot() {
120
+ return { ...this.state };
121
+ }
122
+ onStateChange(listener) {
123
+ this.listeners.add(listener);
124
+ listener(this.snapshot);
125
+ return () => {
126
+ this.listeners.delete(listener);
127
+ };
128
+ }
129
+ async openAgent() {
130
+ return this.ensureAgent();
131
+ }
132
+ async toggleAgentPanel() {
133
+ if (typeof window === "undefined") return;
134
+ if (this.isAgentPanelVisible()) {
135
+ this.hideAgentPanel();
136
+ return;
137
+ }
138
+ await this.openAgent();
139
+ }
140
+ async startWake() {
141
+ if (this.disposed) return;
142
+ if (!this.hasSpeechSupport()) {
143
+ this.patchState({
144
+ status: "unsupported",
145
+ supported: false,
146
+ message: "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u8BED\u97F3\u8BC6\u522B\uFF0C\u5EFA\u8BAE\u4F7F\u7528 Chrome/Edge"
147
+ });
148
+ return;
149
+ }
150
+ if (this.voiceEnabled) {
151
+ this.patchState({
152
+ status: "waking",
153
+ enabled: true,
154
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
155
+ });
156
+ return;
157
+ }
158
+ const granted = await this.requestMicrophonePermission();
159
+ if (!granted) {
160
+ this.patchState({
161
+ status: "error",
162
+ enabled: false,
163
+ micPermissionGranted: false,
164
+ message: "\u9EA6\u514B\u98CE\u6388\u6743\u5931\u8D25\uFF0C\u8BF7\u5141\u8BB8\u6D4F\u89C8\u5668\u4F7F\u7528\u9EA6\u514B\u98CE"
165
+ });
166
+ return;
167
+ }
168
+ const recognition = this.recognition || this.buildRecognition();
169
+ if (!recognition) {
170
+ this.patchState({
171
+ status: "error",
172
+ enabled: false,
173
+ message: "\u8BED\u97F3\u8BC6\u522B\u521D\u59CB\u5316\u5931\u8D25"
174
+ });
175
+ return;
176
+ }
177
+ this.recognition = recognition;
178
+ this.voiceEnabled = true;
179
+ this.patchState({
180
+ status: "waking",
181
+ enabled: true,
182
+ micPermissionGranted: true,
183
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
184
+ });
185
+ window.localStorage.setItem("voice-page-agent-enabled", "1");
186
+ try {
187
+ recognition.start();
188
+ } catch {
189
+ }
190
+ }
191
+ stopWake() {
192
+ this.voiceEnabled = false;
193
+ this.awaitingCommand = false;
194
+ this.commandFinalText = "";
195
+ this.commandInterimText = "";
196
+ this.clearCommandTimer();
197
+ this.clearCommandDeadlineTimer();
198
+ this.patchState({
199
+ status: "off",
200
+ enabled: false,
201
+ message: "\u8BED\u97F3\u52A9\u624B\u5DF2\u5173\u95ED"
202
+ });
203
+ if (typeof window !== "undefined") {
204
+ window.localStorage.setItem("voice-page-agent-enabled", "0");
205
+ }
206
+ if (this.recognition) {
207
+ this.recognition.stop();
208
+ }
209
+ }
210
+ async runCommand(commandText) {
211
+ await this.executeVoiceCommand(commandText);
212
+ }
213
+ dispose() {
214
+ this.disposed = true;
215
+ this.stopWake();
216
+ if (this.recognition) {
217
+ this.recognition.abort();
218
+ this.recognition = null;
219
+ }
220
+ this.listeners.clear();
221
+ }
222
+ hasSpeechSupport() {
223
+ if (typeof window === "undefined") return false;
224
+ const runtimeWindow = window;
225
+ return Boolean(runtimeWindow.SpeechRecognition || runtimeWindow.webkitSpeechRecognition);
226
+ }
227
+ patchState(next) {
228
+ this.state = { ...this.state, ...next };
229
+ const snapshot = this.snapshot;
230
+ this.listeners.forEach((listener) => listener(snapshot));
231
+ }
232
+ clearCommandTimer() {
233
+ if (this.commandTimer !== null) {
234
+ window.clearTimeout(this.commandTimer);
235
+ this.commandTimer = null;
236
+ }
237
+ }
238
+ clearCommandDeadlineTimer() {
239
+ if (this.commandDeadlineTimer !== null) {
240
+ window.clearTimeout(this.commandDeadlineTimer);
241
+ this.commandDeadlineTimer = null;
242
+ }
243
+ }
244
+ composeCommandText() {
245
+ return `${this.commandFinalText} ${this.commandInterimText}`.trim();
246
+ }
247
+ scheduleFlushCommand(ms) {
248
+ this.clearCommandTimer();
249
+ this.commandTimer = window.setTimeout(() => {
250
+ this.flushVoiceCommand();
251
+ }, ms);
252
+ }
253
+ scheduleCommandDeadline() {
254
+ this.clearCommandDeadlineTimer();
255
+ this.commandDeadlineTimer = window.setTimeout(() => {
256
+ this.flushVoiceCommand();
257
+ }, this.options.commandMaxWindowMs);
258
+ }
259
+ flushVoiceCommand() {
260
+ this.clearCommandTimer();
261
+ this.clearCommandDeadlineTimer();
262
+ const command = this.sanitizeVoiceCommand(this.composeCommandText());
263
+ this.awaitingCommand = false;
264
+ this.commandFinalText = "";
265
+ this.commandInterimText = "";
266
+ void this.executeVoiceCommand(command);
267
+ }
268
+ isWakePhraseHit(text) {
269
+ var _a;
270
+ const normalized = normalizeText(text);
271
+ if (!normalized) return false;
272
+ if (this.options.wakeWord.some((word) => normalized.includes(normalizeText(word)))) {
273
+ return true;
274
+ }
275
+ if (this.options.enableHomophoneMatch && ((_a = this.wakeHomophoneRegex) == null ? void 0 : _a.test(normalized))) {
276
+ return true;
277
+ }
278
+ const candidates = this.options.wakeWord.map((word) => normalizeText(word));
279
+ for (const candidate of candidates) {
280
+ if (Math.abs(candidate.length - normalized.length) > 1) continue;
281
+ if (levenshteinDistance(normalized, candidate) <= 1) {
282
+ return true;
283
+ }
284
+ }
285
+ return false;
286
+ }
287
+ extractCommandAfterWake(text) {
288
+ const source = (text || "").trim();
289
+ if (!source) return "";
290
+ const match = source.match(this.wakeRegex.single);
291
+ if (!match || typeof match.index !== "number") return "";
292
+ const rest = source.slice(match.index + match[0].length);
293
+ return rest.replace(/^[\s,,.。!!??;;::、]+/, "").trim();
294
+ }
295
+ sanitizeVoiceCommand(text) {
296
+ return (text || "").replace(this.wakeRegex.global, "").replace(/^[\s,,.。!!??;;::、]+/, "").trim();
297
+ }
298
+ async requestMicrophonePermission() {
299
+ try {
300
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
301
+ stream.getTracks().forEach((track) => track.stop());
302
+ return true;
303
+ } catch {
304
+ return false;
305
+ }
306
+ }
307
+ async syncPermissionState() {
308
+ var _a;
309
+ if (!("permissions" in navigator) || !((_a = navigator.permissions) == null ? void 0 : _a.query)) return;
310
+ try {
311
+ const result = await navigator.permissions.query({
312
+ name: "microphone"
313
+ });
314
+ this.patchState({ micPermissionGranted: result.state === "granted" });
315
+ result.onchange = () => {
316
+ this.patchState({ micPermissionGranted: result.state === "granted" });
317
+ };
318
+ } catch {
319
+ }
320
+ }
321
+ async ensureAgent() {
322
+ var _a;
323
+ if (typeof window === "undefined") return null;
324
+ const runtimeWindow = window;
325
+ const existingAgent = runtimeWindow.pageAgent;
326
+ if (existingAgent) {
327
+ const panelNode = document.getElementById("page-agent-runtime_agent-panel");
328
+ const canReuse = Boolean(existingAgent.panel && (panelNode == null ? void 0 : panelNode.isConnected) && !existingAgent.disposed);
329
+ if (canReuse && existingAgent.panel) {
330
+ existingAgent.panel.show();
331
+ existingAgent.panel.expand();
332
+ return existingAgent;
333
+ }
334
+ try {
335
+ (_a = existingAgent.dispose) == null ? void 0 : _a.call(existingAgent, "RECREATE_WITH_PANEL");
336
+ } catch {
337
+ }
338
+ runtimeWindow.pageAgent = void 0;
339
+ }
340
+ if (this.initAgentPromise) return this.initAgentPromise;
341
+ this.initAgentPromise = (async () => {
342
+ const Agent = this.options.pageAgentCtor || (await import('./page-agent-GVCWTXJY.js')).PageAgent;
343
+ let agent;
344
+ try {
345
+ agent = new Agent(this.options.pageAgent);
346
+ } catch (err) {
347
+ const message = err instanceof Error ? err.message : String(err || "");
348
+ const webglUnavailable = /webgl2 is required/i.test(message) || /webgl/i.test(message);
349
+ if (!webglUnavailable) {
350
+ throw err;
351
+ }
352
+ const fallbackConfig = {
353
+ ...this.options.pageAgent,
354
+ enableMask: false
355
+ };
356
+ agent = new Agent(fallbackConfig);
357
+ }
358
+ runtimeWindow.pageAgent = agent;
359
+ if (!agent.panel) {
360
+ throw new Error("Page Agent panel is disabled. Set pageAgent.enablePanel=true.");
361
+ }
362
+ agent.panel.show();
363
+ agent.panel.expand();
364
+ return agent;
365
+ })().catch((err) => {
366
+ console.error("[voice-page-agent] ensureAgent failed:", err);
367
+ this.patchState({
368
+ status: "error",
369
+ message: this.resolveAgentInitErrorMessage(err)
370
+ });
371
+ return null;
372
+ }).finally(() => {
373
+ this.initAgentPromise = null;
374
+ });
375
+ return this.initAgentPromise;
376
+ }
377
+ isAgentPanelVisible() {
378
+ const panelNode = document.getElementById("page-agent-runtime_agent-panel");
379
+ if (!panelNode) return false;
380
+ const style = window.getComputedStyle(panelNode);
381
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0";
382
+ }
383
+ hideAgentPanel() {
384
+ var _a, _b, _c, _d;
385
+ const runtimeWindow = window;
386
+ const agent = runtimeWindow.pageAgent;
387
+ if (agent == null ? void 0 : agent.panel) {
388
+ (_b = (_a = agent.panel).collapse) == null ? void 0 : _b.call(_a);
389
+ (_d = (_c = agent.panel).hide) == null ? void 0 : _d.call(_c);
390
+ }
391
+ const panelNode = document.getElementById("page-agent-runtime_agent-panel");
392
+ if (panelNode) {
393
+ panelNode.style.display = "none";
394
+ panelNode.style.opacity = "0";
395
+ }
396
+ }
397
+ resolveAgentInitErrorMessage(error) {
398
+ const raw = error instanceof Error ? error.message : String(error || "");
399
+ if (/Cannot read properties of undefined \(reading 'indexOf'\)/i.test(raw)) {
400
+ return "Page Agent \u8FD0\u884C\u65F6\u4F9D\u8D56\u52A0\u8F7D\u5931\u8D25\uFF08\u65E7\u7248\u6784\u5EFA\u517C\u5BB9\u95EE\u9898\uFF09\uFF0C\u8BF7\u5347\u7EA7 voice-page-agent \u5E76\u91CD\u88C5\u4F9D\u8D56\u3002";
401
+ }
402
+ return raw || "Page Agent \u521D\u59CB\u5316\u5931\u8D25";
403
+ }
404
+ async executeVoiceCommand(rawCommand) {
405
+ const command = this.sanitizeVoiceCommand(rawCommand);
406
+ if (!command) {
407
+ this.patchState({
408
+ status: "waking",
409
+ message: `\u672A\u8BC6\u522B\u5230\u6307\u4EE4\uFF0C\u8BF7\u518D\u8BF4\u4E00\u6B21\u201C${this.options.wakeWord[0]}\u201D`
410
+ });
411
+ return;
412
+ }
413
+ this.patchState({
414
+ status: "processing",
415
+ message: `\u5DF2\u8BC6\u522B\uFF1A${command}`
416
+ });
417
+ const agent = await this.ensureAgent();
418
+ if (!agent) return;
419
+ if (agent.status === "running") {
420
+ this.patchState({
421
+ status: "waking",
422
+ message: "\u52A9\u624B\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BF4"
423
+ });
424
+ return;
425
+ }
426
+ try {
427
+ await agent.execute(command);
428
+ this.patchState({
429
+ status: "waking",
430
+ message: `\u4EFB\u52A1\u5DF2\u63D0\u4EA4\u5B8C\u6210\uFF0C\u7EE7\u7EED\u7B49\u5F85\u201C${this.options.wakeWord[0]}\u201D`
431
+ });
432
+ } catch (err) {
433
+ this.patchState({
434
+ status: "error",
435
+ message: err instanceof Error ? err.message : "\u8BED\u97F3\u4EFB\u52A1\u6267\u884C\u5931\u8D25"
436
+ });
437
+ }
438
+ }
439
+ buildRecognition() {
440
+ const runtimeWindow = window;
441
+ const Ctor = runtimeWindow.SpeechRecognition || runtimeWindow.webkitSpeechRecognition;
442
+ if (!Ctor) return null;
443
+ const recognition = new Ctor();
444
+ recognition.lang = this.options.recognitionLang;
445
+ recognition.continuous = true;
446
+ recognition.interimResults = true;
447
+ recognition.maxAlternatives = 3;
448
+ recognition.onstart = () => {
449
+ if (!this.voiceEnabled) return;
450
+ this.patchState({
451
+ status: "waking",
452
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
453
+ });
454
+ };
455
+ recognition.onresult = (event) => {
456
+ var _a;
457
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
458
+ const result = event.results[i];
459
+ const isFinal = Boolean(result == null ? void 0 : result.isFinal);
460
+ const transcript = String(((_a = result[0]) == null ? void 0 : _a.transcript) || "").trim();
461
+ if (!transcript) continue;
462
+ if (!this.awaitingCommand) {
463
+ if (!this.isWakePhraseHit(transcript)) continue;
464
+ const now = Date.now();
465
+ if (now - this.lastWakeAt < this.options.wakeCooldownMs) continue;
466
+ this.lastWakeAt = now;
467
+ this.awaitingCommand = true;
468
+ this.commandFinalText = "";
469
+ this.commandInterimText = "";
470
+ this.patchState({
471
+ status: "listening_command",
472
+ message: "\u5DF2\u5524\u9192\uFF0C\u8BF7\u8BF4\u51FA\u4F60\u7684\u6307\u4EE4\uFF08\u53EF\u8FDE\u7EED\u8BF4\u66F4\u957F\u5185\u5BB9\uFF09"
473
+ });
474
+ if (this.options.showAgentWhenWake) {
475
+ void this.ensureAgent();
476
+ }
477
+ const inlineCommand = isFinal ? this.extractCommandAfterWake(transcript) : "";
478
+ if (inlineCommand) {
479
+ this.commandFinalText = inlineCommand;
480
+ this.scheduleFlushCommand(this.options.commandSilenceTimeoutMs);
481
+ } else {
482
+ this.scheduleFlushCommand(this.options.commandInitialTimeoutMs);
483
+ }
484
+ this.scheduleCommandDeadline();
485
+ continue;
486
+ }
487
+ if (isFinal) {
488
+ this.commandFinalText = `${this.commandFinalText} ${transcript}`.trim();
489
+ this.commandInterimText = "";
490
+ } else {
491
+ this.commandInterimText = transcript;
492
+ }
493
+ this.patchState({
494
+ status: "listening_command",
495
+ message: `\u6B63\u5728\u8BC6\u522B\uFF1A${this.composeCommandText() || "\u8BF7\u7EE7\u7EED\u8BF4\u8BDD..."}`
496
+ });
497
+ this.scheduleFlushCommand(this.options.commandSilenceTimeoutMs);
498
+ }
499
+ };
500
+ recognition.onerror = (event) => {
501
+ const code = String(event.error || "");
502
+ if (code === "no-speech" || code === "aborted") return;
503
+ if (code === "not-allowed" || code === "service-not-allowed") {
504
+ this.voiceEnabled = false;
505
+ this.patchState({
506
+ status: "error",
507
+ enabled: false,
508
+ micPermissionGranted: false,
509
+ message: "\u9EA6\u514B\u98CE\u6743\u9650\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u8BBE\u7F6E\u4E2D\u5141\u8BB8\u9EA6\u514B\u98CE"
510
+ });
511
+ window.localStorage.setItem("voice-page-agent-enabled", "0");
512
+ return;
513
+ }
514
+ this.patchState({
515
+ status: "error",
516
+ message: `\u8BED\u97F3\u8BC6\u522B\u5F02\u5E38\uFF1A${code || "\u672A\u77E5\u9519\u8BEF"}`
517
+ });
518
+ };
519
+ recognition.onend = () => {
520
+ if (!this.voiceEnabled || this.disposed) return;
521
+ if (this.restartBusy) return;
522
+ this.restartBusy = true;
523
+ window.setTimeout(() => {
524
+ this.restartBusy = false;
525
+ if (!this.voiceEnabled || this.disposed) return;
526
+ try {
527
+ recognition.start();
528
+ } catch {
529
+ }
530
+ }, 320);
531
+ };
532
+ return recognition;
533
+ }
534
+ };
535
+ function createVoicePageAgent(options) {
536
+ return new VoicePageAgentController(options);
537
+ }
538
+
539
+ export { VoicePageAgentController, createVoicePageAgent };