voice-page-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,596 @@
1
+ import { defineComponent, ref, onMounted, onBeforeUnmount, h, inject, getCurrentInstance } from 'vue-demi';
2
+
3
+ // src/controller.ts
4
+ var DEFAULT_WAKE_WORD = "\u5E03\u4E01\u5E03\u4E01";
5
+ var DEFAULT_OPTIONS = {
6
+ wakeWord: [DEFAULT_WAKE_WORD],
7
+ enableHomophoneMatch: true,
8
+ wakeCooldownMs: 1400,
9
+ commandInitialTimeoutMs: 12e3,
10
+ commandSilenceTimeoutMs: 2600,
11
+ commandMaxWindowMs: 22e3,
12
+ recognitionLang: "zh-CN",
13
+ showAgentWhenWake: true,
14
+ autoStart: false
15
+ };
16
+ var HOMOPHONE_VARIANTS = {
17
+ \u5E03: ["\u8865", "\u4E0D", "\u6B65", "\u90E8"],
18
+ \u4E01: ["\u53EE", "\u9489", "\u76EF", "\u9876"],
19
+ \u5C0F: ["\u6653", "\u7B11", "\u6821", "\u7B71"],
20
+ \u73ED: ["\u822C", "\u6591", "\u534A"]
21
+ };
22
+ function normalizeText(text) {
23
+ return (text || "").toLowerCase().replace(/[\s,,.。!!??;;::、"'`~\-_/\\]/g, "");
24
+ }
25
+ function escapeRegExp(value) {
26
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
27
+ }
28
+ function levenshteinDistance(source, target) {
29
+ const a = Array.from(source);
30
+ const b = Array.from(target);
31
+ if (a.length === 0) return b.length;
32
+ if (b.length === 0) return a.length;
33
+ const dp = Array.from(
34
+ { length: a.length + 1 },
35
+ () => Array.from({ length: b.length + 1 }, () => 0)
36
+ );
37
+ for (let i = 0; i <= a.length; i += 1) dp[i][0] = i;
38
+ for (let j = 0; j <= b.length; j += 1) dp[0][j] = j;
39
+ for (let i = 1; i <= a.length; i += 1) {
40
+ for (let j = 1; j <= b.length; j += 1) {
41
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
42
+ dp[i][j] = Math.min(
43
+ dp[i - 1][j] + 1,
44
+ dp[i][j - 1] + 1,
45
+ dp[i - 1][j - 1] + cost
46
+ );
47
+ }
48
+ }
49
+ return dp[a.length][b.length];
50
+ }
51
+ function makeHomophoneRegex(words) {
52
+ const fuzzyWords = words.map((word) => {
53
+ const chars = Array.from(word);
54
+ return chars.map((char) => {
55
+ const variants = HOMOPHONE_VARIANTS[char] || [];
56
+ const options = [char, ...variants].map((item) => escapeRegExp(item));
57
+ return `(?:${options.join("|")})`;
58
+ }).join("");
59
+ });
60
+ return new RegExp(fuzzyWords.join("|"));
61
+ }
62
+ function buildWakeRegex(words) {
63
+ const items = words.map((word) => normalizeText(word)).filter(Boolean).sort((a, b) => b.length - a.length).map((word) => escapeRegExp(word));
64
+ const body = items.length ? items.join("|") : escapeRegExp(normalizeText(DEFAULT_WAKE_WORD));
65
+ return {
66
+ single: new RegExp(`(${body})`),
67
+ global: new RegExp(`(${body})`, "g")
68
+ };
69
+ }
70
+ function resolveOptions(options) {
71
+ if (!(options == null ? void 0 : options.pageAgent) || typeof options.pageAgent !== "object") {
72
+ throw new Error("voice-page-agent: options.pageAgent is required");
73
+ }
74
+ const wakeWordInput = options.wakeWord;
75
+ const wakeWord = Array.isArray(wakeWordInput) ? wakeWordInput.filter(Boolean) : [wakeWordInput || DEFAULT_WAKE_WORD];
76
+ return {
77
+ ...DEFAULT_OPTIONS,
78
+ ...options,
79
+ pageAgent: options.pageAgent,
80
+ wakeWord
81
+ };
82
+ }
83
+ var VoicePageAgentController = class {
84
+ constructor(options) {
85
+ this.listeners = /* @__PURE__ */ new Set();
86
+ this.state = {
87
+ status: "off",
88
+ message: "\u8BED\u97F3\u52A9\u624B\u672A\u5F00\u542F",
89
+ supported: false,
90
+ enabled: false,
91
+ micPermissionGranted: false
92
+ };
93
+ this.recognition = null;
94
+ this.initAgentPromise = null;
95
+ this.voiceEnabled = false;
96
+ this.disposed = false;
97
+ this.awaitingCommand = false;
98
+ this.commandFinalText = "";
99
+ this.commandInterimText = "";
100
+ this.commandTimer = null;
101
+ this.commandDeadlineTimer = null;
102
+ this.restartBusy = false;
103
+ this.lastWakeAt = 0;
104
+ this.options = resolveOptions(options);
105
+ this.wakeRegex = buildWakeRegex(this.options.wakeWord);
106
+ this.wakeHomophoneRegex = this.options.enableHomophoneMatch ? makeHomophoneRegex(this.options.wakeWord) : null;
107
+ this.patchState({
108
+ supported: this.hasSpeechSupport(),
109
+ status: this.hasSpeechSupport() ? "off" : "unsupported",
110
+ 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"
111
+ });
112
+ if (typeof window !== "undefined") {
113
+ const saved = window.localStorage.getItem("voice-page-agent-enabled");
114
+ if (saved === "1" || this.options.autoStart) {
115
+ void this.startWake();
116
+ }
117
+ void this.syncPermissionState();
118
+ }
119
+ }
120
+ get snapshot() {
121
+ return { ...this.state };
122
+ }
123
+ onStateChange(listener) {
124
+ this.listeners.add(listener);
125
+ listener(this.snapshot);
126
+ return () => {
127
+ this.listeners.delete(listener);
128
+ };
129
+ }
130
+ async openAgent() {
131
+ return this.ensureAgent();
132
+ }
133
+ async startWake() {
134
+ if (this.disposed) return;
135
+ if (!this.hasSpeechSupport()) {
136
+ this.patchState({
137
+ status: "unsupported",
138
+ supported: false,
139
+ message: "\u5F53\u524D\u6D4F\u89C8\u5668\u4E0D\u652F\u6301\u8BED\u97F3\u8BC6\u522B\uFF0C\u5EFA\u8BAE\u4F7F\u7528 Chrome/Edge"
140
+ });
141
+ return;
142
+ }
143
+ if (this.voiceEnabled) {
144
+ this.patchState({
145
+ status: "waking",
146
+ enabled: true,
147
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
148
+ });
149
+ return;
150
+ }
151
+ const granted = await this.requestMicrophonePermission();
152
+ if (!granted) {
153
+ this.patchState({
154
+ status: "error",
155
+ enabled: false,
156
+ micPermissionGranted: false,
157
+ message: "\u9EA6\u514B\u98CE\u6388\u6743\u5931\u8D25\uFF0C\u8BF7\u5141\u8BB8\u6D4F\u89C8\u5668\u4F7F\u7528\u9EA6\u514B\u98CE"
158
+ });
159
+ return;
160
+ }
161
+ const recognition = this.recognition || this.buildRecognition();
162
+ if (!recognition) {
163
+ this.patchState({
164
+ status: "error",
165
+ enabled: false,
166
+ message: "\u8BED\u97F3\u8BC6\u522B\u521D\u59CB\u5316\u5931\u8D25"
167
+ });
168
+ return;
169
+ }
170
+ this.recognition = recognition;
171
+ this.voiceEnabled = true;
172
+ this.patchState({
173
+ status: "waking",
174
+ enabled: true,
175
+ micPermissionGranted: true,
176
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
177
+ });
178
+ window.localStorage.setItem("voice-page-agent-enabled", "1");
179
+ try {
180
+ recognition.start();
181
+ } catch {
182
+ }
183
+ }
184
+ stopWake() {
185
+ this.voiceEnabled = false;
186
+ this.awaitingCommand = false;
187
+ this.commandFinalText = "";
188
+ this.commandInterimText = "";
189
+ this.clearCommandTimer();
190
+ this.clearCommandDeadlineTimer();
191
+ this.patchState({
192
+ status: "off",
193
+ enabled: false,
194
+ message: "\u8BED\u97F3\u52A9\u624B\u5DF2\u5173\u95ED"
195
+ });
196
+ if (typeof window !== "undefined") {
197
+ window.localStorage.setItem("voice-page-agent-enabled", "0");
198
+ }
199
+ if (this.recognition) {
200
+ this.recognition.stop();
201
+ }
202
+ }
203
+ async runCommand(commandText) {
204
+ await this.executeVoiceCommand(commandText);
205
+ }
206
+ dispose() {
207
+ this.disposed = true;
208
+ this.stopWake();
209
+ if (this.recognition) {
210
+ this.recognition.abort();
211
+ this.recognition = null;
212
+ }
213
+ this.listeners.clear();
214
+ }
215
+ hasSpeechSupport() {
216
+ if (typeof window === "undefined") return false;
217
+ const runtimeWindow = window;
218
+ return Boolean(runtimeWindow.SpeechRecognition || runtimeWindow.webkitSpeechRecognition);
219
+ }
220
+ patchState(next) {
221
+ this.state = { ...this.state, ...next };
222
+ const snapshot = this.snapshot;
223
+ this.listeners.forEach((listener) => listener(snapshot));
224
+ }
225
+ clearCommandTimer() {
226
+ if (this.commandTimer !== null) {
227
+ window.clearTimeout(this.commandTimer);
228
+ this.commandTimer = null;
229
+ }
230
+ }
231
+ clearCommandDeadlineTimer() {
232
+ if (this.commandDeadlineTimer !== null) {
233
+ window.clearTimeout(this.commandDeadlineTimer);
234
+ this.commandDeadlineTimer = null;
235
+ }
236
+ }
237
+ composeCommandText() {
238
+ return `${this.commandFinalText} ${this.commandInterimText}`.trim();
239
+ }
240
+ scheduleFlushCommand(ms) {
241
+ this.clearCommandTimer();
242
+ this.commandTimer = window.setTimeout(() => {
243
+ this.flushVoiceCommand();
244
+ }, ms);
245
+ }
246
+ scheduleCommandDeadline() {
247
+ this.clearCommandDeadlineTimer();
248
+ this.commandDeadlineTimer = window.setTimeout(() => {
249
+ this.flushVoiceCommand();
250
+ }, this.options.commandMaxWindowMs);
251
+ }
252
+ flushVoiceCommand() {
253
+ this.clearCommandTimer();
254
+ this.clearCommandDeadlineTimer();
255
+ const command = this.sanitizeVoiceCommand(this.composeCommandText());
256
+ this.awaitingCommand = false;
257
+ this.commandFinalText = "";
258
+ this.commandInterimText = "";
259
+ void this.executeVoiceCommand(command);
260
+ }
261
+ isWakePhraseHit(text) {
262
+ var _a;
263
+ const normalized = normalizeText(text);
264
+ if (!normalized) return false;
265
+ if (this.options.wakeWord.some((word) => normalized.includes(normalizeText(word)))) {
266
+ return true;
267
+ }
268
+ if (this.options.enableHomophoneMatch && ((_a = this.wakeHomophoneRegex) == null ? void 0 : _a.test(normalized))) {
269
+ return true;
270
+ }
271
+ const candidates = this.options.wakeWord.map((word) => normalizeText(word));
272
+ for (const candidate of candidates) {
273
+ if (Math.abs(candidate.length - normalized.length) > 1) continue;
274
+ if (levenshteinDistance(normalized, candidate) <= 1) {
275
+ return true;
276
+ }
277
+ }
278
+ return false;
279
+ }
280
+ extractCommandAfterWake(text) {
281
+ const source = (text || "").trim();
282
+ if (!source) return "";
283
+ const match = source.match(this.wakeRegex.single);
284
+ if (!match || typeof match.index !== "number") return "";
285
+ const rest = source.slice(match.index + match[0].length);
286
+ return rest.replace(/^[\s,,.。!!??;;::、]+/, "").trim();
287
+ }
288
+ sanitizeVoiceCommand(text) {
289
+ return (text || "").replace(this.wakeRegex.global, "").replace(/^[\s,,.。!!??;;::、]+/, "").trim();
290
+ }
291
+ async requestMicrophonePermission() {
292
+ try {
293
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
294
+ stream.getTracks().forEach((track) => track.stop());
295
+ return true;
296
+ } catch {
297
+ return false;
298
+ }
299
+ }
300
+ async syncPermissionState() {
301
+ var _a;
302
+ if (!("permissions" in navigator) || !((_a = navigator.permissions) == null ? void 0 : _a.query)) return;
303
+ try {
304
+ const result = await navigator.permissions.query({
305
+ name: "microphone"
306
+ });
307
+ this.patchState({ micPermissionGranted: result.state === "granted" });
308
+ result.onchange = () => {
309
+ this.patchState({ micPermissionGranted: result.state === "granted" });
310
+ };
311
+ } catch {
312
+ }
313
+ }
314
+ async ensureAgent() {
315
+ if (typeof window === "undefined") return null;
316
+ const runtimeWindow = window;
317
+ if (runtimeWindow.pageAgent) {
318
+ runtimeWindow.pageAgent.panel.show();
319
+ runtimeWindow.pageAgent.panel.expand();
320
+ return runtimeWindow.pageAgent;
321
+ }
322
+ if (this.initAgentPromise) return this.initAgentPromise;
323
+ this.initAgentPromise = (async () => {
324
+ const mod = await import('page-agent');
325
+ const Agent = mod.PageAgent;
326
+ const agent = new Agent(this.options.pageAgent);
327
+ runtimeWindow.pageAgent = agent;
328
+ agent.panel.show();
329
+ agent.panel.expand();
330
+ return agent;
331
+ })().catch((err) => {
332
+ this.patchState({
333
+ status: "error",
334
+ message: err instanceof Error ? err.message : "Page Agent \u521D\u59CB\u5316\u5931\u8D25"
335
+ });
336
+ return null;
337
+ }).finally(() => {
338
+ this.initAgentPromise = null;
339
+ });
340
+ return this.initAgentPromise;
341
+ }
342
+ async executeVoiceCommand(rawCommand) {
343
+ const command = this.sanitizeVoiceCommand(rawCommand);
344
+ if (!command) {
345
+ this.patchState({
346
+ status: "waking",
347
+ message: `\u672A\u8BC6\u522B\u5230\u6307\u4EE4\uFF0C\u8BF7\u518D\u8BF4\u4E00\u6B21\u201C${this.options.wakeWord[0]}\u201D`
348
+ });
349
+ return;
350
+ }
351
+ this.patchState({
352
+ status: "processing",
353
+ message: `\u5DF2\u8BC6\u522B\uFF1A${command}`
354
+ });
355
+ const agent = await this.ensureAgent();
356
+ if (!agent) return;
357
+ if (agent.status === "running") {
358
+ this.patchState({
359
+ status: "waking",
360
+ message: "\u52A9\u624B\u6B63\u5728\u6267\u884C\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BF4"
361
+ });
362
+ return;
363
+ }
364
+ try {
365
+ await agent.execute(command);
366
+ this.patchState({
367
+ status: "waking",
368
+ message: `\u4EFB\u52A1\u5DF2\u63D0\u4EA4\u5B8C\u6210\uFF0C\u7EE7\u7EED\u7B49\u5F85\u201C${this.options.wakeWord[0]}\u201D`
369
+ });
370
+ } catch (err) {
371
+ this.patchState({
372
+ status: "error",
373
+ message: err instanceof Error ? err.message : "\u8BED\u97F3\u4EFB\u52A1\u6267\u884C\u5931\u8D25"
374
+ });
375
+ }
376
+ }
377
+ buildRecognition() {
378
+ const runtimeWindow = window;
379
+ const Ctor = runtimeWindow.SpeechRecognition || runtimeWindow.webkitSpeechRecognition;
380
+ if (!Ctor) return null;
381
+ const recognition = new Ctor();
382
+ recognition.lang = this.options.recognitionLang;
383
+ recognition.continuous = true;
384
+ recognition.interimResults = true;
385
+ recognition.maxAlternatives = 3;
386
+ recognition.onstart = () => {
387
+ if (!this.voiceEnabled) return;
388
+ this.patchState({
389
+ status: "waking",
390
+ message: `\u8BED\u97F3\u5DF2\u5F00\u542F\uFF0C\u8BF7\u8BF4\u201C${this.options.wakeWord[0]}\u201D\u5524\u9192\u52A9\u624B`
391
+ });
392
+ };
393
+ recognition.onresult = (event) => {
394
+ var _a;
395
+ for (let i = event.resultIndex; i < event.results.length; i += 1) {
396
+ const result = event.results[i];
397
+ const isFinal = Boolean(result == null ? void 0 : result.isFinal);
398
+ const transcript = String(((_a = result[0]) == null ? void 0 : _a.transcript) || "").trim();
399
+ if (!transcript) continue;
400
+ if (!this.awaitingCommand) {
401
+ if (!this.isWakePhraseHit(transcript)) continue;
402
+ const now = Date.now();
403
+ if (now - this.lastWakeAt < this.options.wakeCooldownMs) continue;
404
+ this.lastWakeAt = now;
405
+ this.awaitingCommand = true;
406
+ this.commandFinalText = "";
407
+ this.commandInterimText = "";
408
+ this.patchState({
409
+ status: "listening_command",
410
+ message: "\u5DF2\u5524\u9192\uFF0C\u8BF7\u8BF4\u51FA\u4F60\u7684\u6307\u4EE4\uFF08\u53EF\u8FDE\u7EED\u8BF4\u66F4\u957F\u5185\u5BB9\uFF09"
411
+ });
412
+ if (this.options.showAgentWhenWake) {
413
+ void this.ensureAgent();
414
+ }
415
+ const inlineCommand = isFinal ? this.extractCommandAfterWake(transcript) : "";
416
+ if (inlineCommand) {
417
+ this.commandFinalText = inlineCommand;
418
+ this.scheduleFlushCommand(this.options.commandSilenceTimeoutMs);
419
+ } else {
420
+ this.scheduleFlushCommand(this.options.commandInitialTimeoutMs);
421
+ }
422
+ this.scheduleCommandDeadline();
423
+ continue;
424
+ }
425
+ if (isFinal) {
426
+ this.commandFinalText = `${this.commandFinalText} ${transcript}`.trim();
427
+ this.commandInterimText = "";
428
+ } else {
429
+ this.commandInterimText = transcript;
430
+ }
431
+ this.patchState({
432
+ status: "listening_command",
433
+ message: `\u6B63\u5728\u8BC6\u522B\uFF1A${this.composeCommandText() || "\u8BF7\u7EE7\u7EED\u8BF4\u8BDD..."}`
434
+ });
435
+ this.scheduleFlushCommand(this.options.commandSilenceTimeoutMs);
436
+ }
437
+ };
438
+ recognition.onerror = (event) => {
439
+ const code = String(event.error || "");
440
+ if (code === "no-speech" || code === "aborted") return;
441
+ if (code === "not-allowed" || code === "service-not-allowed") {
442
+ this.voiceEnabled = false;
443
+ this.patchState({
444
+ status: "error",
445
+ enabled: false,
446
+ micPermissionGranted: false,
447
+ message: "\u9EA6\u514B\u98CE\u6743\u9650\u88AB\u62D2\u7EDD\uFF0C\u8BF7\u5728\u6D4F\u89C8\u5668\u8BBE\u7F6E\u4E2D\u5141\u8BB8\u9EA6\u514B\u98CE"
448
+ });
449
+ window.localStorage.setItem("voice-page-agent-enabled", "0");
450
+ return;
451
+ }
452
+ this.patchState({
453
+ status: "error",
454
+ message: `\u8BED\u97F3\u8BC6\u522B\u5F02\u5E38\uFF1A${code || "\u672A\u77E5\u9519\u8BEF"}`
455
+ });
456
+ };
457
+ recognition.onend = () => {
458
+ if (!this.voiceEnabled || this.disposed) return;
459
+ if (this.restartBusy) return;
460
+ this.restartBusy = true;
461
+ window.setTimeout(() => {
462
+ this.restartBusy = false;
463
+ if (!this.voiceEnabled || this.disposed) return;
464
+ try {
465
+ recognition.start();
466
+ } catch {
467
+ }
468
+ }, 320);
469
+ };
470
+ return recognition;
471
+ }
472
+ };
473
+ function createVoicePageAgent(options) {
474
+ return new VoicePageAgentController(options);
475
+ }
476
+ var VOICE_PAGE_AGENT_KEY = "VOICE_PAGE_AGENT_INSTANCE";
477
+ var defaultController = null;
478
+ function attachToApp(target, controller) {
479
+ var _a, _b, _c, _d, _e;
480
+ const anyTarget = target;
481
+ if ("config" in anyTarget && ((_a = anyTarget.config) == null ? void 0 : _a.globalProperties)) {
482
+ anyTarget.config.globalProperties.$voicePageAgent = controller;
483
+ (_b = anyTarget.provide) == null ? void 0 : _b.call(anyTarget, VOICE_PAGE_AGENT_KEY, controller);
484
+ (_c = anyTarget.component) == null ? void 0 : _c.call(anyTarget, "VoicePageAgentButton", VoicePageAgentButton);
485
+ return;
486
+ }
487
+ anyTarget.prototype.$voicePageAgent = controller;
488
+ (_d = anyTarget.component) == null ? void 0 : _d.call(anyTarget, "VoicePageAgentButton", VoicePageAgentButton);
489
+ (_e = anyTarget.mixin) == null ? void 0 : _e.call(anyTarget, {
490
+ provide() {
491
+ return {
492
+ [VOICE_PAGE_AGENT_KEY]: controller
493
+ };
494
+ }
495
+ });
496
+ }
497
+ function useVoicePageAgent() {
498
+ const injected = inject(VOICE_PAGE_AGENT_KEY, null);
499
+ if (injected) return injected;
500
+ const instance = getCurrentInstance();
501
+ const proxy = instance == null ? void 0 : instance.proxy;
502
+ if (proxy == null ? void 0 : proxy.$voicePageAgent) return proxy.$voicePageAgent;
503
+ if (defaultController) return defaultController;
504
+ throw new Error("voice-page-agent: controller not found, please install plugin first.");
505
+ }
506
+ var VoicePageAgentButton = defineComponent({
507
+ name: "VoicePageAgentButton",
508
+ props: {
509
+ showStatus: {
510
+ type: Boolean,
511
+ default: true
512
+ },
513
+ startText: {
514
+ type: String,
515
+ default: "\u5F00\u542F\u8BED\u97F3\u5524\u9192"
516
+ },
517
+ wakeOnText: {
518
+ type: String,
519
+ default: "\u8BED\u97F3\u5524\u9192\u4E2D"
520
+ },
521
+ openText: {
522
+ type: String,
523
+ default: "\u7F51\u9875\u52A9\u624B"
524
+ }
525
+ },
526
+ setup(props) {
527
+ const controller = useVoicePageAgent();
528
+ const state = ref(controller.snapshot);
529
+ let off = null;
530
+ onMounted(() => {
531
+ off = controller.onStateChange((next) => {
532
+ state.value = next;
533
+ });
534
+ });
535
+ onBeforeUnmount(() => {
536
+ off == null ? void 0 : off();
537
+ off = null;
538
+ });
539
+ const handleWakeClick = () => {
540
+ if (state.value.enabled) {
541
+ controller.stopWake();
542
+ } else {
543
+ void controller.startWake();
544
+ }
545
+ };
546
+ const handleOpenClick = () => {
547
+ void controller.openAgent();
548
+ };
549
+ return () => h("div", { class: "voice-page-agent-root" }, [
550
+ h("div", { class: "voice-page-agent-actions" }, [
551
+ state.value.supported && !state.value.micPermissionGranted ? h(
552
+ "button",
553
+ {
554
+ type: "button",
555
+ class: "voice-page-agent-btn",
556
+ onClick: handleWakeClick
557
+ },
558
+ state.value.enabled ? props.wakeOnText : props.startText
559
+ ) : null,
560
+ h(
561
+ "button",
562
+ {
563
+ type: "button",
564
+ class: "voice-page-agent-btn",
565
+ onClick: handleOpenClick
566
+ },
567
+ props.openText
568
+ )
569
+ ]),
570
+ props.showStatus ? h("p", { class: "voice-page-agent-status" }, state.value.message) : null
571
+ ]);
572
+ }
573
+ });
574
+ function createVoicePageAgentPlugin(options) {
575
+ const controller = createVoicePageAgent(options);
576
+ defaultController = controller;
577
+ return {
578
+ install(app) {
579
+ attachToApp(app, controller);
580
+ },
581
+ controller
582
+ };
583
+ }
584
+ var VoicePageAgentVuePlugin = {
585
+ install(app, options) {
586
+ if (!options) {
587
+ throw new Error("voice-page-agent: install options is required.");
588
+ }
589
+ const plugin = createVoicePageAgentPlugin(options);
590
+ VoicePageAgentVuePlugin.controller = plugin.controller;
591
+ attachToApp(app, plugin.controller);
592
+ }
593
+ };
594
+ var plugin_default = VoicePageAgentVuePlugin;
595
+
596
+ export { VoicePageAgentButton, VoicePageAgentController, plugin_default as VoicePageAgentPlugin, createVoicePageAgent, createVoicePageAgentPlugin, useVoicePageAgent };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "voice-page-agent",
3
+ "version": "0.1.0",
4
+ "description": "Voice wake plugin for page-agent with Vue2/Vue3 compatibility.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepack": "npm run build"
25
+ },
26
+ "keywords": [
27
+ "voice",
28
+ "wake-word",
29
+ "page-agent",
30
+ "vue2",
31
+ "vue3"
32
+ ],
33
+ "peerDependencies": {
34
+ "page-agent": ">=0.2.0",
35
+ "vue": "^2.6.14 || ^3.2.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "page-agent": {
39
+ "optional": false
40
+ }
41
+ },
42
+ "dependencies": {
43
+ "vue-demi": "^0.14.10"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.10.1",
47
+ "tsup": "^8.4.0",
48
+ "typescript": "^5.7.2"
49
+ }
50
+ }