@omnivoice/page-agent 0.2.0 → 0.4.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.
@@ -1,426 +0,0 @@
1
- (function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode("/* FORK: estilos do widget central (launcher + microfone). Injetados via CSS-in-JS — sem\n request de CSS separada, sem fontes externas. Pensado para mobile (alvo de toque 48px,\n safe-area) e para não brigar com a página host (z-index alto, cor própria). */\n\n.page-agent-widget-launcher {\n position: fixed;\n z-index: 2147483630;\n width: 48px;\n height: 48px;\n padding: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 50%;\n background: #111827;\n color: #f9fafb;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);\n cursor: pointer;\n transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;\n -webkit-tap-highlight-color: transparent;\n}\n\n/* Safe-area em telas com notch/barras: o botão não fica preso na borda. */\n.page-agent-widget-launcher {\n right: calc(20px + env(safe-area-inset-right, 0px));\n bottom: calc(20px + env(safe-area-inset-bottom, 0px));\n}\n\n.page-agent-widget-launcher:hover {\n transform: translateY(-1px);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.34);\n background: #1f2937;\n}\n\n.page-agent-widget-launcher:active {\n transform: translateY(0) scale(0.97);\n}\n\n.page-agent-widget-launcher:focus-visible {\n outline: 3px solid #60a5fa;\n outline-offset: 2px;\n}\n\n.page-agent-widget-launcher svg {\n display: block;\n}\n\n/* Botão de microfone, injetado ao lado do input de tarefa do painel. */\n.page-agent-voice-mic {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 34px;\n height: 34px;\n flex: 0 0 auto;\n margin-right: 8px;\n padding: 0;\n border: 1px solid rgba(148, 163, 184, 0.4);\n border-radius: 8px;\n background: rgba(148, 163, 184, 0.12);\n color: inherit;\n cursor: pointer;\n transition: background 0.12s ease, border-color 0.12s ease, transform 0.12s ease;\n -webkit-tap-highlight-color: transparent;\n}\n\n.page-agent-voice-mic:hover {\n background: rgba(148, 163, 184, 0.22);\n border-color: rgba(148, 163, 184, 0.6);\n}\n\n.page-agent-voice-mic:active {\n transform: scale(0.96);\n}\n\n.page-agent-voice-mic:focus-visible {\n outline: 2px solid #60a5fa;\n outline-offset: 1px;\n}\n\n.page-agent-voice-mic.is-listening {\n background: rgba(239, 68, 68, 0.16);\n border-color: rgba(239, 68, 68, 0.6);\n animation: page-agent-voice-pulse 1.4s ease-in-out infinite;\n}\n\n@keyframes page-agent-voice-pulse {\n 0%,\n 100% {\n box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.35);\n }\n 50% {\n box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .page-agent-voice-mic.is-listening {\n animation: none;\n }\n}")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
2
- import { PageAgentCore } from "@omnivoice/core";
3
- import { PageController } from "@omnivoice/page-controller";
4
- import { Panel } from "@omnivoice/ui";
5
- export * from "@omnivoice/core";
6
- //#region src/voice.ts
7
- function getRecognitionCtor() {
8
- const w = globalThis;
9
- return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
10
- }
11
- var STATUS_LISTENING = "listening";
12
- var STATUS_SPEAKING = "speaking";
13
- var STATUS_IDLE = "idle";
14
- function createVoice(options = {}) {
15
- const lang = options.lang ?? "pt-BR";
16
- const rate = options.rate ?? 1;
17
- const pitch = options.pitch ?? 1;
18
- const inputEnabled = options.input ?? true;
19
- const outputEnabled = options.output ?? false;
20
- const listenTimeoutMs = options.listenTimeoutMs ?? 3e4;
21
- const Recognition = inputEnabled ? getRecognitionCtor() : null;
22
- const synth = typeof window !== "undefined" ? window.speechSynthesis : void 0;
23
- const supported = {
24
- input: Boolean(inputEnabled && Recognition),
25
- output: Boolean(outputEnabled && synth)
26
- };
27
- let status = STATUS_IDLE;
28
- const statusListeners = /* @__PURE__ */ new Set();
29
- function setStatus(next) {
30
- if (status === next) return;
31
- status = next;
32
- statusListeners.forEach((cb) => {
33
- try {
34
- cb(next);
35
- } catch {}
36
- });
37
- }
38
- let activeRecognition = null;
39
- let currentVoice = null;
40
- if (synth) {
41
- const pickVoice = () => {
42
- if (options.voiceURI) currentVoice = synth.getVoices().find((v) => v.voiceURI === options.voiceURI) ?? null;
43
- else {
44
- const voices = synth.getVoices();
45
- const prefix = lang.split("-")[0].toLowerCase();
46
- currentVoice = voices.find((v) => v.lang?.toLowerCase() === lang.toLowerCase()) ?? voices.find((v) => v.lang?.toLowerCase().startsWith(prefix)) ?? null;
47
- }
48
- };
49
- pickVoice();
50
- synth.onvoiceschanged = pickVoice;
51
- }
52
- function speak(text, opts) {
53
- if (!synth || !outputEnabled) return;
54
- const clean = (text ?? "").trim();
55
- if (!clean) return;
56
- if (opts?.interrupt !== false) synth.cancel();
57
- const utterance = new SpeechSynthesisUtterance(clean);
58
- utterance.lang = lang;
59
- utterance.rate = rate;
60
- utterance.pitch = pitch;
61
- if (currentVoice) utterance.voice = currentVoice;
62
- utterance.onend = () => setStatus(STATUS_IDLE);
63
- utterance.onerror = () => setStatus(STATUS_IDLE);
64
- setStatus(STATUS_SPEAKING);
65
- synth.speak(utterance);
66
- }
67
- function listen() {
68
- if (!Recognition) return Promise.reject(/* @__PURE__ */ new Error("Voz de entrada não suportada neste navegador"));
69
- return new Promise((resolve, reject) => {
70
- const recognition = new Recognition();
71
- activeRecognition = recognition;
72
- let finalText = "";
73
- let settled = false;
74
- let timer;
75
- const finish = (fn) => {
76
- if (settled) return;
77
- settled = true;
78
- if (timer) clearTimeout(timer);
79
- if (activeRecognition === recognition) activeRecognition = null;
80
- setStatus(STATUS_IDLE);
81
- fn();
82
- };
83
- timer = setTimeout(() => {
84
- try {
85
- recognition.stop();
86
- } catch {}
87
- finish(() => reject(/* @__PURE__ */ new Error("Tempo de escuta esgotado")));
88
- }, listenTimeoutMs);
89
- recognition.lang = lang;
90
- recognition.continuous = false;
91
- recognition.interimResults = true;
92
- recognition.maxAlternatives = 1;
93
- recognition.onstart = () => setStatus(STATUS_LISTENING);
94
- recognition.onresult = (event) => {
95
- for (let i = event.resultIndex; i < event.results.length; i++) {
96
- const result = event.results[i];
97
- if (result?.isFinal) finalText = result[0]?.transcript ?? finalText;
98
- }
99
- };
100
- recognition.onerror = (event) => {
101
- const code = event?.error;
102
- if (code === "no-speech" || code === "aborted") {
103
- finish(() => resolve(finalText.trim()));
104
- return;
105
- }
106
- finish(() => reject(/* @__PURE__ */ new Error(`Erro de reconhecimento de voz: ${code ?? "desconhecido"}`)));
107
- };
108
- recognition.onend = () => {
109
- finish(() => resolve(finalText.trim()));
110
- };
111
- try {
112
- recognition.start();
113
- } catch (error) {
114
- finish(() => reject(error instanceof Error ? error : new Error(String(error))));
115
- }
116
- });
117
- }
118
- return {
119
- supported,
120
- listen,
121
- stopListening() {
122
- try {
123
- activeRecognition?.stop();
124
- } catch {}
125
- activeRecognition = null;
126
- setStatus(STATUS_IDLE);
127
- },
128
- speak,
129
- stopSpeaking() {
130
- if (!synth) return;
131
- synth.cancel();
132
- setStatus(STATUS_IDLE);
133
- },
134
- onStatus(cb) {
135
- statusListeners.add(cb);
136
- return () => statusListeners.delete(cb);
137
- }
138
- };
139
- }
140
- //#endregion
141
- //#region src/widget.ts
142
- /**
143
- * FORK: widget central do page-agent. É o ÚNICO ponto de integração para um projeto host:
144
- * uma chamada `mountWidget(config)` centraliza agente + painel + voz + config, com defaults
145
- * sensatos, criação lazy do agente (a página não paga startup até o widget abrir) e degradação
146
- * graciosa de voz. Pensado para mobile/desktop e rede limitada/offline: arquivo único, sem
147
- * assets externos, agente criado sob demanda.
148
- *
149
- * Uso em projeto host (npm):
150
- * import { mountWidget } from "@omnivoice/page-agent";
151
- * const w = mountWidget({ model, baseURL, apiKey, voice: { input: true, output: true } });
152
- *
153
- * Uso via CDN (IIFE):
154
- * <script src=".../page-agent.demo.js" data-auto-init="false"><\/script>
155
- * <script>PageAgent.mountWidget({ model, baseURL, apiKey })<\/script>
156
- */
157
- var DEFAULTS = {
158
- language: "pt-BR",
159
- maxSteps: 30,
160
- enableMask: true,
161
- viewportExpansion: 0,
162
- promptForNextTask: true,
163
- position: "bottom-right",
164
- launcherLabel: "Abrir assistente da página",
165
- open: false
166
- };
167
- var LAUNCHER_ICON = "<svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l1.9 4.6L18.5 9l-4.6 1.9L12 15.5l-1.9-4.6L5.5 9l4.6-1.4L12 3z\"/><path d=\"M19 14l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8.8-2z\"/></svg>";
168
- var MIC_ICON = "<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"3\" width=\"6\" height=\"11\" rx=\"3\"/><path d=\"M5 11a7 7 0 0 0 14 0M12 18v3\"/></svg>";
169
- function positionCss(position) {
170
- switch (position) {
171
- case "bottom-left": return {
172
- bottom: "20px",
173
- left: "20px",
174
- right: "auto",
175
- top: "auto"
176
- };
177
- case "top-right": return {
178
- top: "20px",
179
- right: "20px",
180
- bottom: "auto",
181
- left: "auto"
182
- };
183
- case "top-left": return {
184
- top: "20px",
185
- left: "20px",
186
- bottom: "auto",
187
- right: "auto"
188
- };
189
- default: return {
190
- bottom: "20px",
191
- right: "20px",
192
- top: "auto",
193
- left: "auto"
194
- };
195
- }
196
- }
197
- function mountWidget(options = {}) {
198
- const position = options.position ?? DEFAULTS.position;
199
- const launcherLabel = options.launcherLabel ?? DEFAULTS.launcherLabel;
200
- const open = options.open ?? DEFAULTS.open;
201
- let agent = null;
202
- let voice = null;
203
- let micButton = null;
204
- let listening = false;
205
- let configPromise = null;
206
- async function ensureConfig() {
207
- if (configPromise) return configPromise;
208
- if (!options.configUrl) return;
209
- configPromise = (async () => {
210
- const res = await fetch(options.configUrl, { headers: { accept: "application/json" } });
211
- if (!res.ok) throw new Error(`mountWidget: configUrl respondeu ${res.status} — o gateway está no ar?`);
212
- const server = await res.json();
213
- for (const key of [
214
- "model",
215
- "language",
216
- "maxSteps",
217
- "promptForNextTask",
218
- "instructions",
219
- "voice"
220
- ]) {
221
- const value = server[key];
222
- if (value !== void 0 && value !== null) options[key] = value;
223
- }
224
- })();
225
- return configPromise;
226
- }
227
- function ensureVoice() {
228
- if (!voice) voice = createVoice(options.voice ?? {});
229
- return voice;
230
- }
231
- async function ensureAgent() {
232
- if (agent && !agent.disposed) return agent;
233
- await ensureConfig();
234
- const v = ensureVoice();
235
- const model = options.model;
236
- const baseURL = options.baseURL;
237
- if (!model || !baseURL) throw new Error(options.configUrl ? "mountWidget: o gateway não enviou `model` e/ou falta `baseURL` no front. Confira AGENT_MODEL no gateway e a baseURL do widget." : "mountWidget: configure `model` e `baseURL` (config central do LLM) antes de abrir o agente.");
238
- agent = new PageAgent({
239
- model,
240
- baseURL,
241
- apiKey: options.apiKey,
242
- transformRequestBody: options.transformRequestBody,
243
- customFetch: options.customFetch,
244
- maxRetries: options.maxRetries,
245
- language: options.language ?? DEFAULTS.language,
246
- maxSteps: options.maxSteps ?? DEFAULTS.maxSteps,
247
- enableMask: options.enableMask ?? DEFAULTS.enableMask,
248
- viewportExpansion: options.viewportExpansion ?? DEFAULTS.viewportExpansion,
249
- instructions: options.instructions,
250
- customTools: options.customTools,
251
- promptForNextTask: options.promptForNextTask ?? DEFAULTS.promptForNextTask
252
- });
253
- wireVoiceOutput(agent, v);
254
- injectMicButton(agent, v);
255
- return agent;
256
- }
257
- function wireVoiceOutput(a, v) {
258
- const speakOnComplete = options.voice?.speakOnComplete ?? true;
259
- const speakSteps = options.voice?.speakSteps ?? false;
260
- const onStatus = () => {
261
- const status = a.status;
262
- const result = a.lastResult;
263
- if (status === "completed" || status === "error") {
264
- if (speakOnComplete && v.supported.output) {
265
- if (status === "error") v.speak("A tarefa encontrou um problema e foi parada.");
266
- else if (result?.success) v.speak("Tarefa concluída com sucesso.");
267
- else v.speak("A tarefa terminou, mas o resultado não foi confirmado.");
268
- }
269
- return;
270
- }
271
- if (speakSteps && status === "running" && v.supported.output) {
272
- const goal = [...a.history].reverse().find((h) => h.type === "step")?.reflection?.next_goal;
273
- if (goal) v.speak(goal, { interrupt: true });
274
- }
275
- };
276
- a.addEventListener("statuschange", onStatus);
277
- a.addEventListener("activity", (event) => {
278
- const detail = event.detail;
279
- if (detail?.type === "error" && detail.message && v.supported.output) v.speak(detail.message, { interrupt: true });
280
- });
281
- }
282
- function injectMicButton(a, v) {
283
- const wrapper = a.panel.wrapper;
284
- const input = wrapper.querySelector("input");
285
- if (!input || !v.supported.input) return;
286
- if (wrapper.querySelector("[data-page-agent-voice=\"mic\"]")) return;
287
- if (micButton) micButton.remove();
288
- micButton = document.createElement("button");
289
- micButton.type = "button";
290
- micButton.className = "page-agent-voice-mic";
291
- micButton.setAttribute("data-page-agent-voice", "mic");
292
- micButton.setAttribute("aria-label", "Ditar tarefa por voz");
293
- micButton.title = "Ditar tarefa por voz";
294
- micButton.innerHTML = MIC_ICON;
295
- micButton.style.display = "inline-flex";
296
- (input.parentElement ?? wrapper).insertBefore(micButton, input);
297
- v.onStatus((s) => {
298
- micButton?.classList.toggle("is-listening", s === "listening");
299
- });
300
- micButton.addEventListener("click", async () => {
301
- if (listening) {
302
- v.stopListening();
303
- return;
304
- }
305
- listening = true;
306
- try {
307
- const transcript = await v.listen();
308
- if (transcript) {
309
- input.value = transcript;
310
- input.dispatchEvent(new KeyboardEvent("keydown", {
311
- key: "Enter",
312
- bubbles: true
313
- }));
314
- }
315
- } catch (error) {
316
- input.setAttribute("placeholder", error instanceof Error ? error.message : "Não foi possível ouvir; digite a tarefa.");
317
- setTimeout(() => input.removeAttribute("placeholder"), 6e3);
318
- } finally {
319
- listening = false;
320
- }
321
- });
322
- }
323
- const launcher = document.createElement("button");
324
- launcher.type = "button";
325
- launcher.className = "page-agent-widget-launcher";
326
- launcher.setAttribute("aria-label", launcherLabel);
327
- launcher.title = launcherLabel;
328
- launcher.innerHTML = LAUNCHER_ICON;
329
- const pos = positionCss(position);
330
- Object.assign(launcher.style, pos);
331
- let panelVisible = false;
332
- async function openPanel(silent = false) {
333
- try {
334
- (await ensureAgent()).panel.show();
335
- panelVisible = true;
336
- } catch (error) {
337
- if (silent) console.error("[page-agent-widget] não abriu o agente:", error);
338
- else throw error;
339
- }
340
- }
341
- function closePanel() {
342
- agent?.panel.hide?.();
343
- panelVisible = false;
344
- }
345
- launcher.addEventListener("click", () => {
346
- if (panelVisible) closePanel();
347
- else openPanel(true);
348
- });
349
- document.body.appendChild(launcher);
350
- if (open) openPanel(true);
351
- return {
352
- get agent() {
353
- return agent;
354
- },
355
- get voice() {
356
- return voice;
357
- },
358
- launcher,
359
- ready() {
360
- return ensureAgent();
361
- },
362
- async run(task) {
363
- return await (await ensureAgent()).execute(task);
364
- },
365
- async stop() {
366
- if (agent) await agent.stop();
367
- },
368
- show() {
369
- return openPanel(false);
370
- },
371
- hide() {
372
- closePanel();
373
- },
374
- toggle() {
375
- launcher.click();
376
- },
377
- setConfig(patch) {
378
- Object.assign(options, patch);
379
- if (agent) {
380
- agent.dispose();
381
- agent = null;
382
- }
383
- if (panelVisible) {
384
- panelVisible = false;
385
- openPanel(true);
386
- }
387
- },
388
- unmount() {
389
- voice?.stopSpeaking();
390
- voice?.stopListening();
391
- agent?.dispose();
392
- agent = null;
393
- voice = null;
394
- micButton?.remove();
395
- micButton = null;
396
- launcher.remove();
397
- }
398
- };
399
- }
400
- //#endregion
401
- //#region src/PageAgent.ts
402
- /**
403
- * Copyright (C) 2025 Alibaba Group Holding Limited
404
- * All rights reserved.
405
- */
406
- var PageAgent = class extends PageAgentCore {
407
- panel;
408
- constructor(config) {
409
- const pageController = new PageController({
410
- ...config,
411
- enableMask: config.enableMask ?? true
412
- });
413
- super({
414
- ...config,
415
- pageController
416
- });
417
- this.panel = new Panel(this, {
418
- language: config.language,
419
- promptForNextTask: config.promptForNextTask
420
- });
421
- }
422
- };
423
- //#endregion
424
- export { PageAgent, createVoice, mountWidget };
425
-
426
- //# sourceMappingURL=page-agent.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"page-agent.js","names":[],"sources":["../../src/voice.ts","../../src/widget.ts","../../src/PageAgent.ts"],"sourcesContent":["/**\n * FORK: módulo de voz do widget. Zero dependências — usa apenas a Web Speech API nativa do\n * navegador (reconhecimento + síntese), o que mantém o bundle leve e funciona offline/sem rede\n * além das chamadas do próprio agente.\n *\n * Filosofia (regras do laboratório):\n * - Degrada sem quebrar: se o navegador não tem mic nem síntese, `supported` informa e os\n * métodos viram no-op silenciosos — a UI decide esconder os botões, o agente segue funcionando.\n * - Espera por condição, nunca por relógio: `listen()` resolve quando chega o resultado final;\n * o timeout é só uma trava de segurança contra um reconhecimento que \"prende\" a UI.\n * - Nada de rede própria: o reconhecimento/síntese são do navegador (Chrome/Edge/Safari enviam\n * apenas o áudio necessário para o serviço de voz do SO; em rede limitada isso é mínimo).\n */\n\ntype VoiceStatus = 'idle' | 'listening' | 'speaking'\n\ninterface VoiceRecognitionLike {\n\tlang: string\n\tcontinuous: boolean\n\tinterimResults: boolean\n\tmaxAlternatives: number\n\tabort(): void\n\tstart(): void\n\tstop(): void\n\tonresult: ((event: { resultIndex: number; results: ArrayLike<ArrayLike<{ transcript: string }> & { isFinal: boolean }> }) => void) | null\n\tonerror: ((event: { error?: string }) => void) | null\n\tonend: (() => void) | null\n\tonstart: (() => void) | null\n}\n\nfunction getRecognitionCtor(): (new () => VoiceRecognitionLike) | null {\n\tconst w = globalThis as unknown as {\n\t\tSpeechRecognition?: new () => VoiceRecognitionLike\n\t\twebkitSpeechRecognition?: new () => VoiceRecognitionLike\n\t}\n\treturn w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null\n}\n\nexport interface VoiceOptions {\n\t/** Idioma BCP-47 do reconhecimento e da síntese. @default 'pt-BR' */\n\tlang?: string\n\t/** Habilita o reconhecimento de fala (botão de microfone). @default true */\n\tinput?: boolean\n\t/** Habilita a síntese de fala (agente fala o resultado). @default false */\n\toutput?: boolean\n\t/** URI de uma voz específica do speechSynthesis (deixa o módulo escolher quando ausente). */\n\tvoiceURI?: string\n\t/** Velocidade da síntese (0.1–10). @default 1 */\n\trate?: number\n\t/** Tom da síntese (0–2). @default 1 */\n\tpitch?: number\n\t/** Trava de segurança para `listen()`: ms até rejeitar se o reconhecimento não terminar. @default 30000 */\n\tlistenTimeoutMs?: number\n}\n\nexport interface Voice {\n\t/** O que o navegador realmente oferece. A UI usa isto para mostrar/ocultar controles. */\n\treadonly supported: { input: boolean; output: boolean }\n\t/**\n\t * Escuta uma vez e resolve com a transcrição final. Rejeita em erro real; resolve com \"\"\n\t * quando o usuário não fala nada ('no-speech') — não é falha, é \"nada foi dito\".\n\t */\n\tlisten(): Promise<string>\n\t/** Interrompe a escuta em andamento. */\n\tstopListening(): void\n\t/** Fala um texto. `interrupt` corta o que estiver sendo dito antes de começar. */\n\tspeak(text: string, opts?: { interrupt?: boolean }): void\n\t/** Para a síntese em andamento. */\n\tstopSpeaking(): void\n\t/** Observa transições de estado (a UI mostra o indicador de \"ouvindo/falando\"). */\n\tonStatus(cb: (status: VoiceStatus) => void): () => void\n}\n\nconst STATUS_LISTENING = 'listening'\nconst STATUS_SPEAKING = 'speaking'\nconst STATUS_IDLE = 'idle'\n\nexport function createVoice(options: VoiceOptions = {}): Voice {\n\tconst lang = options.lang ?? 'pt-BR'\n\tconst rate = options.rate ?? 1\n\tconst pitch = options.pitch ?? 1\n\tconst inputEnabled = options.input ?? true\n\tconst outputEnabled = options.output ?? false\n\tconst listenTimeoutMs = options.listenTimeoutMs ?? 30_000\n\n\tconst Recognition = inputEnabled ? getRecognitionCtor() : null\n\tconst synth = typeof window !== 'undefined' ? window.speechSynthesis : undefined\n\n\tconst supported: Voice['supported'] = {\n\t\tinput: Boolean(inputEnabled && Recognition),\n\t\toutput: Boolean(outputEnabled && synth),\n\t}\n\n\tlet status: VoiceStatus = STATUS_IDLE\n\tconst statusListeners = new Set<(s: VoiceStatus) => void>()\n\tfunction setStatus(next: VoiceStatus) {\n\t\tif (status === next) return\n\t\tstatus = next\n\t\tstatusListeners.forEach(cb => {\n\t\t\ttry {\n\t\t\t\tcb(next)\n\t\t\t} catch {\n\t\t\t\t// ouvinte quebrado não derruba o módulo de voz\n\t\t\t}\n\t\t})\n\t}\n\n\tlet activeRecognition: VoiceRecognitionLike | null = null\n\tlet currentVoice: SpeechSynthesisVoice | null = null\n\n\t// A lista de vozes carrega de forma assíncrona em alguns navegadores; escolhe a melhor pt-BR\n\t// (ou a língua pedida) quando ela ficar disponível.\n\tif (synth) {\n\t\tconst pickVoice = () => {\n\t\t\tif (options.voiceURI) {\n\t\t\t\tcurrentVoice = synth.getVoices().find(v => v.voiceURI === options.voiceURI) ?? null\n\t\t\t} else {\n\t\t\t\tconst voices = synth.getVoices()\n\t\t\t\tconst prefix = lang.split('-')[0].toLowerCase()\n\t\t\t\tcurrentVoice =\n\t\t\t\t\tvoices.find(v => v.lang?.toLowerCase() === lang.toLowerCase()) ??\n\t\t\t\t\tvoices.find(v => v.lang?.toLowerCase().startsWith(prefix)) ??\n\t\t\t\t\tnull\n\t\t\t}\n\t\t}\n\t\tpickVoice()\n\t\tsynth.onvoiceschanged = pickVoice\n\t}\n\n\tfunction speak(text: string, opts?: { interrupt?: boolean }) {\n\t\tif (!synth || !outputEnabled) return\n\t\tconst clean = (text ?? '').trim()\n\t\tif (!clean) return\n\t\tif (opts?.interrupt !== false) {\n\t\t\tsynth.cancel()\n\t\t}\n\t\tconst utterance = new SpeechSynthesisUtterance(clean)\n\t\tutterance.lang = lang\n\t\tutterance.rate = rate\n\t\tutterance.pitch = pitch\n\t\tif (currentVoice) utterance.voice = currentVoice\n\t\tutterance.onend = () => setStatus(STATUS_IDLE)\n\t\tutterance.onerror = () => setStatus(STATUS_IDLE)\n\t\tsetStatus(STATUS_SPEAKING)\n\t\tsynth.speak(utterance)\n\t}\n\n\tfunction listen(): Promise<string> {\n\t\tif (!Recognition) {\n\t\t\treturn Promise.reject(new Error('Voz de entrada não suportada neste navegador'))\n\t\t}\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\tconst recognition = new Recognition()\n\t\t\tactiveRecognition = recognition\n\t\t\tlet finalText = ''\n\t\t\tlet settled = false\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\n\t\t\tconst finish = (fn: () => void) => {\n\t\t\t\tif (settled) return\n\t\t\t\tsettled = true\n\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t\tif (activeRecognition === recognition) activeRecognition = null\n\t\t\t\tsetStatus(STATUS_IDLE)\n\t\t\t\tfn()\n\t\t\t}\n\n\t\t\t// Trava de segurança: nunca deixar a UI presa num reconhecimento que não termina.\n\t\t\ttimer = setTimeout(() => {\n\t\t\t\ttry {\n\t\t\t\t\trecognition.stop()\n\t\t\t\t} catch {\n\t\t\t\t\t/* já parado */\n\t\t\t\t}\n\t\t\t\tfinish(() => reject(new Error('Tempo de escuta esgotado')))\n\t\t\t}, listenTimeoutMs)\n\n\t\t\trecognition.lang = lang\n\t\t\trecognition.continuous = false\n\t\t\trecognition.interimResults = true\n\t\t\trecognition.maxAlternatives = 1\n\n\t\t\trecognition.onstart = () => setStatus(STATUS_LISTENING)\n\t\t\trecognition.onresult = event => {\n\t\t\t\tfor (let i = event.resultIndex; i < event.results.length; i++) {\n\t\t\t\t\tconst result = event.results[i]\n\t\t\t\t\tif (result?.isFinal) finalText = result[0]?.transcript ?? finalText\n\t\t\t\t}\n\t\t\t}\n\t\t\trecognition.onerror = event => {\n\t\t\t\tconst code = event?.error\n\t\t\t\t// 'no-speech' e 'aborted' não são falhas: o usuário não falou / cancelou.\n\t\t\t\tif (code === 'no-speech' || code === 'aborted') {\n\t\t\t\t\tfinish(() => resolve(finalText.trim()))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfinish(() => reject(new Error(`Erro de reconhecimento de voz: ${code ?? 'desconhecido'}`)))\n\t\t\t}\n\t\t\trecognition.onend = () => {\n\t\t\t\tfinish(() => resolve(finalText.trim()))\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\trecognition.start()\n\t\t\t} catch (error) {\n\t\t\t\tfinish(() => reject(error instanceof Error ? error : new Error(String(error))))\n\t\t\t}\n\t\t})\n\t}\n\n\treturn {\n\t\tsupported,\n\t\tlisten,\n\t\tstopListening() {\n\t\t\ttry {\n\t\t\t\tactiveRecognition?.stop()\n\t\t\t} catch {\n\t\t\t\t/* já parado */\n\t\t\t}\n\t\t\tactiveRecognition = null\n\t\t\tsetStatus(STATUS_IDLE)\n\t\t},\n\t\tspeak,\n\t\tstopSpeaking() {\n\t\t\tif (!synth) return\n\t\t\tsynth.cancel()\n\t\t\tsetStatus(STATUS_IDLE)\n\t\t},\n\t\tonStatus(cb) {\n\t\t\tstatusListeners.add(cb)\n\t\t\treturn () => statusListeners.delete(cb)\n\t\t},\n\t}\n}\n","/**\n * FORK: widget central do page-agent. É o ÚNICO ponto de integração para um projeto host:\n * uma chamada `mountWidget(config)` centraliza agente + painel + voz + config, com defaults\n * sensatos, criação lazy do agente (a página não paga startup até o widget abrir) e degradação\n * graciosa de voz. Pensado para mobile/desktop e rede limitada/offline: arquivo único, sem\n * assets externos, agente criado sob demanda.\n *\n * Uso em projeto host (npm):\n * import { mountWidget } from \"@omnivoice/page-agent\";\n * const w = mountWidget({ model, baseURL, apiKey, voice: { input: true, output: true } });\n *\n * Uso via CDN (IIFE):\n * <script src=\".../page-agent.demo.js\" data-auto-init=\"false\"></script>\n * <script>PageAgent.mountWidget({ model, baseURL, apiKey })</script>\n */\nimport { PageAgent, type PageAgentConfig } from './PageAgent'\nimport type { ExecutionResult } from '@omnivoice/core'\nimport { createVoice, type Voice, type VoiceOptions } from './voice'\nimport './widget.css'\n\nexport type { Voice, VoiceOptions }\nexport { createVoice }\n\nexport interface WidgetVoiceOptions extends VoiceOptions {\n\t/** Fala o resultado quando a tarefa termina (requer `output: true`). @default true */\n\tspeakOnComplete?: boolean\n\t/** Fala o próximo objetivo a cada passo (mais falante; útil para mãos-livres). @default false */\n\tspeakSteps?: boolean\n}\n\nexport interface WidgetOptions {\n\t// --- LLM (config central; um lugar só) ---\n\t/** Modelo. Ex.: \"gpt-4o-mini\". */\n\tmodel?: string\n\t/** Endpoint OpenAI-compatible. */\n\tbaseURL?: string\n\t/**\n\t * Chave da API. Só para uso local/sem gateway (ex.: laboratório). Em produção, prefira\n\t * `configUrl` + gateway: a chave fica no servidor e não aparece no front-end.\n\t */\n\tapiKey?: string\n\t/**\n\t * URL de uma config pública servida pelo gateway do projeto (ex.: \"/api/agent/config\").\n\t * O widget busca model/idioma/passos/voz DE LÁ — o front nunca precisa da chave. O gateway\n\t * é a autoridade: ele injeta a `apiKey` no servidor e força o modelo configurado.\n\t */\n\tconfigUrl?: string\n\t/** Customiza o corpo da request (providers específicos). */\n\ttransformRequestBody?: PageAgentConfig['transformRequestBody']\n\t/** `fetch` custom (proxy, credentials, baixa latência). */\n\tcustomFetch?: PageAgentConfig['customFetch']\n\tmaxRetries?: number\n\n\t// --- Agente ---\n\t/** Idioma da interface e do raciocínio. @default 'pt-BR' */\n\tlanguage?: PageAgentConfig['language']\n\t/** Máximo de passos por tarefa. @default 30 */\n\tmaxSteps?: number\n\t/** Instruções globais/por página. */\n\tinstructions?: PageAgentConfig['instructions']\n\t/** Ferramentas customizadas. */\n\tcustomTools?: PageAgentConfig['customTools']\n\t/** Máscula sobre a página durante a execução. @default true */\n\tenableMask?: boolean\n\t/** Expansão da viewport ao enumerar elementos. @default 0 (só o que está visível) */\n\tviewportExpansion?: number\n\t/** Pergunta pela próxima tarefa ao concluir. @default true */\n\tpromptForNextTask?: boolean\n\n\t// --- UI ---\n\t/** Posição do botão-lançador. @default 'bottom-right' */\n\tposition?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n\t/** Rótulo acessível do botão-lançador. @default 'Abrir assistente da página' */\n\tlauncherLabel?: string\n\t/** Começa com o painel aberto (em vez de só o botão). @default false */\n\topen?: boolean\n\n\t// --- Voz ---\n\tvoice?: WidgetVoiceOptions\n}\n\nexport interface Widget {\n\t/** Agente centralizado (mesma instância que o painel controla). `null` até a primeira abertura. */\n\treadonly agent: PageAgent | null\n\t/** Voz centralizada (reconhecimento + síntese). `null` até a primeira abertura. */\n\treadonly voice: Voice | null\n\t/** Posição do botão-lançador. */\n\treadonly launcher: HTMLElement\n\t/** Garante config (busca o `configUrl`) + voz + agente prontos. Idempotente; devolve o agente. */\n\tready(): Promise<PageAgent>\n\t/** Executa uma tarefa programaticamente. */\n\trun(task: string): Promise<ExecutionResult>\n\t/** Para a tarefa em andamento (o agente segue reutilizável). */\n\tstop(): Promise<void>\n\t/** Abre o painel (propaga erro de config). `hide()`/`toggle()` para fechar/alternar. */\n\tshow(): Promise<void>\n\thide(): void\n\ttoggle(): void\n\t/** Atualiza config em runtime (recria o agente quando muda algo de LLM/agente). */\n\tsetConfig(patch: Partial<WidgetOptions>): void\n\t/** Remove launcher, painel e voz da página. */\n\tunmount(): void\n}\n\nconst DEFAULTS = {\n\tlanguage: 'pt-BR' as const,\n\tmaxSteps: 30,\n\tenableMask: true,\n\tviewportExpansion: 0,\n\tpromptForNextTask: true,\n\tposition: 'bottom-right' as const,\n\tlauncherLabel: 'Abrir assistente da página',\n\topen: false,\n}\n\nconst LAUNCHER_ICON =\n\t'<svg viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M12 3l1.9 4.6L18.5 9l-4.6 1.9L12 15.5l-1.9-4.6L5.5 9l4.6-1.4L12 3z\"/><path d=\"M19 14l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8.8-2z\"/></svg>'\n\nconst MIC_ICON =\n\t'<svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"9\" y=\"3\" width=\"6\" height=\"11\" rx=\"3\"/><path d=\"M5 11a7 7 0 0 0 14 0M12 18v3\"/></svg>'\n\ntype LauncherPosition = { top?: string; bottom?: string; left?: string; right?: string }\n\nfunction positionCss(position: WidgetOptions['position']): LauncherPosition {\n\tswitch (position) {\n\t\tcase 'bottom-left':\n\t\t\treturn { bottom: '20px', left: '20px', right: 'auto', top: 'auto' }\n\t\tcase 'top-right':\n\t\t\treturn { top: '20px', right: '20px', bottom: 'auto', left: 'auto' }\n\t\tcase 'top-left':\n\t\t\treturn { top: '20px', left: '20px', bottom: 'auto', right: 'auto' }\n\t\tdefault:\n\t\t\treturn { bottom: '20px', right: '20px', top: 'auto', left: 'auto' }\n\t}\n}\n\nexport function mountWidget(options: WidgetOptions = {}): Widget {\n\t// UI do launcher é decisão do cliente (posição/rotulo/abrir já). Config de LLM/agente/voz\n\t// pode vir do gateway (configUrl) — resolvida lazy em ensureConfig(), antes de criar o agente.\n\tconst position = options.position ?? DEFAULTS.position\n\tconst launcherLabel = options.launcherLabel ?? DEFAULTS.launcherLabel\n\tconst open = options.open ?? DEFAULTS.open\n\n\tlet agent: PageAgent | null = null\n\tlet voice: Voice | null = null\n\tlet micButton: HTMLButtonElement | null = null\n\tlet listening = false\n\tlet configPromise: Promise<void> | null = null\n\n\t// ---- Config pública do gateway (a chave NUNCA vem aqui) ----\n\t// O front manda só a configUrl; modelo/idioma/passos/voz vêm do servidor do projeto. Se não\n\t// houver configUrl, a config é a passada em `options` (modo local, ex.: laboratório).\n\tasync function ensureConfig(): Promise<void> {\n\t\tif (configPromise) return configPromise\n\t\tif (!options.configUrl) return\n\t\tconfigPromise = (async () => {\n\t\t\tconst res = await fetch(options.configUrl as string, { headers: { accept: 'application/json' } })\n\t\t\tif (!res.ok) throw new Error(`mountWidget: configUrl respondeu ${res.status} — o gateway está no ar?`)\n\t\t\tconst server = (await res.json()) as Partial<WidgetOptions>\n\t\t\t// O gateway é a autoridade do LLM/agente/voz. Não deixa o server tocar em\n\t\t\t// baseURL/configUrl/UI (essas são do cliente) nem injetar segredos novos.\n\t\t\tfor (const key of ['model', 'language', 'maxSteps', 'promptForNextTask', 'instructions', 'voice'] as const) {\n\t\t\t\tconst value = (server as Record<string, unknown>)[key]\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\t;(options as Record<string, unknown>)[key] = value\n\t\t\t\t}\n\t\t\t}\n\t\t})()\n\t\treturn configPromise\n\t}\n\n\t// ---- Voz (lazy: só existe após a config resolvida, pois o gateway controla \"voz ativa\") ----\n\tfunction ensureVoice(): Voice {\n\t\tif (!voice) voice = createVoice(options.voice ?? {})\n\t\treturn voice\n\t}\n\n\t// ---- Agente (lazy: config → voz → instância) ----\n\tasync function ensureAgent(): Promise<PageAgent> {\n\t\tif (agent && !agent.disposed) return agent\n\t\tawait ensureConfig()\n\t\tconst v = ensureVoice()\n\t\t// model e baseURL são o mínimo p/ o agente existir. Com configUrl, model vem do gateway;\n\t\t// baseURL o cliente sempre sabe (é onde o gateway escuta). Falhar cedo = mensagem clara.\n\t\tconst model = options.model\n\t\tconst baseURL = options.baseURL\n\t\tif (!model || !baseURL) {\n\t\t\tthrow new Error(\n\t\t\t\toptions.configUrl\n\t\t\t\t\t? 'mountWidget: o gateway não enviou `model` e/ou falta `baseURL` no front. Confira AGENT_MODEL no gateway e a baseURL do widget.'\n\t\t\t\t\t: 'mountWidget: configure `model` e `baseURL` (config central do LLM) antes de abrir o agente.',\n\t\t\t)\n\t\t}\n\t\tagent = new PageAgent({\n\t\t\tmodel,\n\t\t\tbaseURL,\n\t\t\tapiKey: options.apiKey,\n\t\t\ttransformRequestBody: options.transformRequestBody,\n\t\t\tcustomFetch: options.customFetch,\n\t\t\tmaxRetries: options.maxRetries,\n\t\t\tlanguage: options.language ?? DEFAULTS.language,\n\t\t\tmaxSteps: options.maxSteps ?? DEFAULTS.maxSteps,\n\t\t\tenableMask: options.enableMask ?? DEFAULTS.enableMask,\n\t\t\tviewportExpansion: options.viewportExpansion ?? DEFAULTS.viewportExpansion,\n\t\t\tinstructions: options.instructions,\n\t\t\tcustomTools: options.customTools,\n\t\t\tpromptForNextTask: options.promptForNextTask ?? DEFAULTS.promptForNextTask,\n\t\t})\n\t\twireVoiceOutput(agent, v)\n\t\tinjectMicButton(agent, v)\n\t\treturn agent\n\t}\n\n\t// ---- Voz de saída (o gateway já decidiu se fala; a UI só executa) ----\n\tfunction wireVoiceOutput(a: PageAgent, v: Voice) {\n\t\t// lido AQUI (depois do ensureConfig) para refletir a decisão do gateway\n\t\tconst speakOnComplete = options.voice?.speakOnComplete ?? true\n\t\tconst speakSteps = options.voice?.speakSteps ?? false\n\t\tconst onStatus = () => {\n\t\t\tconst status = a.status\n\t\t\tconst result = a.lastResult\n\t\t\tif (status === 'completed' || status === 'error') {\n\t\t\t\tif (speakOnComplete && v.supported.output) {\n\t\t\t\t\tif (status === 'error') {\n\t\t\t\t\t\tv.speak('A tarefa encontrou um problema e foi parada.')\n\t\t\t\t\t} else if (result?.success) {\n\t\t\t\t\t\tv.speak('Tarefa concluída com sucesso.')\n\t\t\t\t\t} else {\n\t\t\t\t\t\tv.speak('A tarefa terminou, mas o resultado não foi confirmado.')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (speakSteps && status === 'running' && v.supported.output) {\n\t\t\t\tconst lastStep = [...a.history].reverse().find(h => h.type === 'step')\n\t\t\t\tconst goal = (lastStep as { reflection?: { next_goal?: string } } | undefined)?.reflection?.next_goal\n\t\t\t\tif (goal) v.speak(goal, { interrupt: true })\n\t\t\t}\n\t\t}\n\t\ta.addEventListener('statuschange', onStatus)\n\t\ta.addEventListener('activity', (event: Event) => {\n\t\t\tconst detail = (event as CustomEvent).detail as { type?: string; message?: string } | undefined\n\t\t\tif (detail?.type === 'error' && detail.message && v.supported.output) {\n\t\t\t\tv.speak(detail.message, { interrupt: true })\n\t\t\t}\n\t\t})\n\t}\n\n\t// ---- Botão de microfone (injetado no input de tarefa do painel) ----\n\tfunction injectMicButton(a: PageAgent, v: Voice) {\n\t\tconst wrapper = a.panel.wrapper\n\t\tconst input = wrapper.querySelector('input') as HTMLInputElement | null\n\t\tif (!input || !v.supported.input) return\n\t\tif (wrapper.querySelector('[data-page-agent-voice=\"mic\"]')) return\n\t\tif (micButton) micButton.remove()\n\n\t\tmicButton = document.createElement('button')\n\t\tmicButton.type = 'button'\n\t\tmicButton.className = 'page-agent-voice-mic'\n\t\tmicButton.setAttribute('data-page-agent-voice', 'mic')\n\t\tmicButton.setAttribute('aria-label', 'Ditar tarefa por voz')\n\t\tmicButton.title = 'Ditar tarefa por voz'\n\t\tmicButton.innerHTML = MIC_ICON\n\t\tmicButton.style.display = 'inline-flex'\n\n\t\t// Insere o mic junto ao input (dentro do mesmo contêiner, antes do input).\n\t\tconst host = input.parentElement ?? wrapper\n\t\thost.insertBefore(micButton, input)\n\n\t\tconst unsubscribe = v.onStatus(s => {\n\t\t\tmicButton?.classList.toggle('is-listening', s === 'listening')\n\t\t})\n\n\t\tmicButton.addEventListener('click', async () => {\n\t\t\tif (listening) {\n\t\t\t\tv.stopListening()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlistening = true\n\t\t\ttry {\n\t\t\t\tconst transcript = await v.listen()\n\t\t\t\tif (transcript) {\n\t\t\t\t\tinput.value = transcript\n\t\t\t\t\tinput.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// Sem rede/permissão de mic: degrada para digitar. O widget não quebra.\n\t\t\t\tinput.setAttribute('placeholder', error instanceof Error ? error.message : 'Não foi possível ouvir; digite a tarefa.')\n\t\t\t\tsetTimeout(() => input.removeAttribute('placeholder'), 6000)\n\t\t\t} finally {\n\t\t\t\tlistening = false\n\t\t\t}\n\t\t})\n\n\t\tvoid unsubscribe\n\t}\n\n\t// ---- Launcher (FAB) ----\n\tconst launcher = document.createElement('button')\n\tlauncher.type = 'button'\n\tlauncher.className = 'page-agent-widget-launcher'\n\tlauncher.setAttribute('aria-label', launcherLabel)\n\tlauncher.title = launcherLabel\n\tlauncher.innerHTML = LAUNCHER_ICON\n\tconst pos = positionCss(position)\n\tObject.assign(launcher.style, pos)\n\n\tlet panelVisible = false\n\t// silent=true (clique no FAB / abrir inicial): loga e deixa o botão disponível para retry.\n\t// silent=false (w.show()/w.ready()/w.run): propaga o erro para quem chamou programaticamente.\n\tasync function openPanel(silent = false) {\n\t\ttry {\n\t\t\tconst a = await ensureAgent()\n\t\t\ta.panel.show()\n\t\t\tpanelVisible = true\n\t\t} catch (error) {\n\t\t\tif (silent) console.error('[page-agent-widget] não abriu o agente:', error)\n\t\t\telse throw error\n\t\t}\n\t}\n\tfunction closePanel() {\n\t\tagent?.panel.hide?.()\n\t\tpanelVisible = false\n\t}\n\tlauncher.addEventListener('click', () => {\n\t\tif (panelVisible) {\n\t\t\tclosePanel()\n\t\t} else {\n\t\t\tvoid openPanel(true)\n\t\t}\n\t})\n\n\tdocument.body.appendChild(launcher)\n\n\tif (open) {\n\t\tvoid openPanel(true)\n\t}\n\n\t// ---- Handle ----\n\tconst widget: Widget = {\n\t\tget agent() {\n\t\t\treturn agent\n\t\t},\n\t\tget voice() {\n\t\t\treturn voice\n\t\t},\n\t\tlauncher,\n\t\tready() {\n\t\t\treturn ensureAgent()\n\t\t},\n\t\tasync run(task: string) {\n\t\t\tconst a = await ensureAgent()\n\t\t\treturn await a.execute(task)\n\t\t},\n\t\tasync stop() {\n\t\t\tif (agent) await agent.stop()\n\t\t},\n\t\tshow() {\n\t\t\treturn openPanel(false)\n\t\t},\n\t\thide() {\n\t\t\tclosePanel()\n\t\t},\n\t\ttoggle() {\n\t\t\tlauncher.click()\n\t\t},\n\t\tsetConfig(patch: Partial<WidgetOptions>) {\n\t\t\t// Config de LLM/agente muda a identidade da instância: recria o agente na próxima abertura.\n\t\t\tObject.assign(options, patch)\n\t\t\tif (agent) {\n\t\t\t\tagent.dispose()\n\t\t\t\tagent = null\n\t\t\t}\n\t\t\tif (panelVisible) {\n\t\t\t\tpanelVisible = false\n\t\t\t\tvoid openPanel(true)\n\t\t\t}\n\t\t},\n\t\tunmount() {\n\t\t\tvoice?.stopSpeaking()\n\t\t\tvoice?.stopListening()\n\t\t\tagent?.dispose()\n\t\t\tagent = null\n\t\t\tvoice = null\n\t\t\tmicButton?.remove()\n\t\t\tmicButton = null\n\t\t\tlauncher.remove()\n\t\t},\n\t}\n\n\treturn widget\n}\n","/**\n * Copyright (C) 2025 Alibaba Group Holding Limited\n * All rights reserved.\n */\nimport { type AgentConfig, PageAgentCore } from '@omnivoice/core'\nimport { PageController, type PageControllerConfig } from '@omnivoice/page-controller'\nimport { Panel, type PanelConfig } from '@omnivoice/ui'\n\nexport * from '@omnivoice/core'\nexport { mountWidget, createVoice, type Widget, type WidgetOptions, type WidgetVoiceOptions, type Voice, type VoiceOptions } from './widget'\n\nexport type PageAgentConfig = AgentConfig & PageControllerConfig & Omit<PanelConfig, 'language'>\n\nexport class PageAgent extends PageAgentCore {\n\tpanel: Panel\n\n\tconstructor(config: PageAgentConfig) {\n\t\tconst pageController = new PageController({\n\t\t\t...config,\n\t\t\tenableMask: config.enableMask ?? true,\n\t\t})\n\n\t\tsuper({ ...config, pageController })\n\n\t\tthis.panel = new Panel(this, {\n\t\t\tlanguage: config.language,\n\t\t\tpromptForNextTask: config.promptForNextTask,\n\t\t})\n\t}\n}\n"],"mappings":"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"}