@pinecall/web 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.cjs ADDED
@@ -0,0 +1,4351 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/widget/VoiceWidget.tsx
7
+
8
+ // src/core/VoiceSession.ts
9
+ var INITIAL_STATE = {
10
+ status: "idle",
11
+ error: null,
12
+ isMuted: false,
13
+ phase: "idle",
14
+ userSpeaking: false,
15
+ agentSpeaking: false,
16
+ duration: 0,
17
+ messages: [],
18
+ toolCalls: [],
19
+ idleWarning: null
20
+ };
21
+ var VoiceSession = class extends EventTarget {
22
+ constructor(opts) {
23
+ super();
24
+ this.opts = opts;
25
+ }
26
+ opts;
27
+ state = { ...INITIAL_STATE };
28
+ listeners = /* @__PURE__ */ new Set();
29
+ pc = null;
30
+ stream = null;
31
+ audio = null;
32
+ dc = null;
33
+ timer = null;
34
+ ping = null;
35
+ startedAt = 0;
36
+ botWords = {};
37
+ /** Read-only snapshot of current state (stable reference until next mutation). */
38
+ getState() {
39
+ return this.state;
40
+ }
41
+ /** Subscribe to ANY state change (for React useSyncExternalStore). */
42
+ subscribe(listener) {
43
+ this.listeners.add(listener);
44
+ return () => {
45
+ this.listeners.delete(listener);
46
+ };
47
+ }
48
+ setState(patch) {
49
+ const prev = this.state;
50
+ this.state = { ...prev, ...patch };
51
+ for (const l3 of this.listeners) l3();
52
+ if (patch.status !== void 0 && patch.status !== prev.status) {
53
+ this.dispatchEvent(
54
+ new CustomEvent("status", { detail: { status: this.state.status } })
55
+ );
56
+ }
57
+ if (patch.phase !== void 0 && patch.phase !== prev.phase) {
58
+ this.dispatchEvent(
59
+ new CustomEvent("phase", { detail: { phase: this.state.phase } })
60
+ );
61
+ }
62
+ if (patch.error !== void 0 && patch.error !== null && patch.error !== prev.error) {
63
+ this.dispatchEvent(
64
+ new CustomEvent("error", { detail: { error: this.state.error } })
65
+ );
66
+ }
67
+ this.dispatchEvent(
68
+ new CustomEvent("change", { detail: { state: this.state } })
69
+ );
70
+ }
71
+ setMessages(updater) {
72
+ const next = updater(this.state.messages);
73
+ this.setState({ messages: next });
74
+ const last = next[next.length - 1];
75
+ if (last) {
76
+ this.dispatchEvent(
77
+ new CustomEvent("message", { detail: { message: last } })
78
+ );
79
+ }
80
+ }
81
+ cleanup() {
82
+ if (this.ping) {
83
+ clearInterval(this.ping);
84
+ this.ping = null;
85
+ }
86
+ if (this.timer) {
87
+ clearInterval(this.timer);
88
+ this.timer = null;
89
+ }
90
+ if (this.pc) {
91
+ this.pc.close();
92
+ this.pc = null;
93
+ }
94
+ this.dc = null;
95
+ if (this.stream) {
96
+ this.stream.getTracks().forEach((t2) => t2.stop());
97
+ this.stream = null;
98
+ }
99
+ if (this.audio) {
100
+ this.audio.pause();
101
+ this.audio.srcObject = null;
102
+ this.audio = null;
103
+ }
104
+ this.botWords = {};
105
+ this.setState({
106
+ isMuted: false,
107
+ phase: "idle",
108
+ userSpeaking: false,
109
+ agentSpeaking: false,
110
+ idleWarning: null
111
+ });
112
+ }
113
+ async connect() {
114
+ if (this.pc) return;
115
+ try {
116
+ this.setState({
117
+ status: "connecting",
118
+ error: null,
119
+ duration: 0,
120
+ messages: []
121
+ });
122
+ this.botWords = {};
123
+ const base = (this.opts.server ?? "https://voice.pinecall.io").replace(
124
+ /\/$/,
125
+ ""
126
+ );
127
+ let token;
128
+ let voiceServer;
129
+ if (this.opts.tokenProvider) {
130
+ const t2 = await this.opts.tokenProvider();
131
+ token = t2.token;
132
+ voiceServer = t2.server;
133
+ } else {
134
+ const tRes = await fetch(
135
+ `${base}/webrtc/token?agent_id=${encodeURIComponent(this.opts.agent)}`
136
+ );
137
+ if (!tRes.ok) throw new Error(`Token: ${tRes.status}`);
138
+ const t2 = await tRes.json();
139
+ token = t2.token;
140
+ voiceServer = t2.server;
141
+ }
142
+ if (!voiceServer) throw new Error("Token response missing server URL");
143
+ let ice = [{ urls: "stun:stun.l.google.com:19302" }];
144
+ try {
145
+ const r = await fetch(`${voiceServer}/webrtc/ice-servers`);
146
+ if (r.ok) {
147
+ const d2 = await r.json();
148
+ ice = d2.iceServers || d2.ice_servers || ice;
149
+ }
150
+ } catch {
151
+ }
152
+ this.stream = await navigator.mediaDevices.getUserMedia({
153
+ audio: {
154
+ echoCancellation: true,
155
+ noiseSuppression: true,
156
+ autoGainControl: true
157
+ },
158
+ video: false
159
+ });
160
+ const pc = new RTCPeerConnection({ iceServers: ice });
161
+ this.pc = pc;
162
+ this.stream.getTracks().forEach((t2) => pc.addTrack(t2, this.stream));
163
+ pc.ontrack = (e) => {
164
+ if (!this.audio) {
165
+ this.audio = new Audio();
166
+ this.audio.autoplay = true;
167
+ }
168
+ this.audio.srcObject = e.streams[0];
169
+ };
170
+ const dc = pc.createDataChannel("events", { ordered: true });
171
+ this.dc = dc;
172
+ dc.onopen = () => {
173
+ this.ping = setInterval(() => {
174
+ if (dc.readyState === "open") dc.send("ping");
175
+ }, 1e3);
176
+ };
177
+ dc.onmessage = (msg) => this.handleDataChannelMessage(msg);
178
+ pc.onconnectionstatechange = () => {
179
+ if (pc.connectionState === "connected") {
180
+ this.setState({ status: "connected", phase: "listening" });
181
+ this.startedAt = Date.now();
182
+ this.timer = setInterval(() => {
183
+ this.setState({
184
+ duration: Math.floor((Date.now() - this.startedAt) / 1e3)
185
+ });
186
+ }, 1e3);
187
+ } else if (pc.connectionState === "disconnected" || pc.connectionState === "failed") {
188
+ this.cleanup();
189
+ this.setState({ status: "idle" });
190
+ }
191
+ };
192
+ const offer = await pc.createOffer({
193
+ offerToReceiveAudio: true,
194
+ offerToReceiveVideo: false
195
+ });
196
+ await pc.setLocalDescription(offer);
197
+ await new Promise((resolve) => {
198
+ if (pc.iceGatheringState === "complete") return resolve();
199
+ const t2 = setTimeout(resolve, 2e3);
200
+ pc.onicegatheringstatechange = () => {
201
+ if (pc.iceGatheringState === "complete") {
202
+ clearTimeout(t2);
203
+ resolve();
204
+ }
205
+ };
206
+ });
207
+ const offerBody = {
208
+ sdp: pc.localDescription.sdp,
209
+ type: pc.localDescription.type,
210
+ token
211
+ };
212
+ if (this.opts.config && Object.keys(this.opts.config).length > 0) {
213
+ offerBody.config = this.opts.config;
214
+ }
215
+ if (this.opts.metadata && Object.keys(this.opts.metadata).length > 0) {
216
+ offerBody.metadata = this.opts.metadata;
217
+ }
218
+ const res = await fetch(`${voiceServer}/webrtc/offer`, {
219
+ method: "POST",
220
+ headers: { "Content-Type": "application/json" },
221
+ body: JSON.stringify(offerBody)
222
+ });
223
+ if (!res.ok) throw new Error(`Offer: ${res.status}`);
224
+ const answer = await res.json();
225
+ await pc.setRemoteDescription({ type: answer.type, sdp: answer.sdp });
226
+ } catch (err) {
227
+ this.setState({
228
+ error: err instanceof Error ? err.message : String(err),
229
+ status: "error"
230
+ });
231
+ this.cleanup();
232
+ }
233
+ }
234
+ handleDataChannelMessage(msg) {
235
+ let d2;
236
+ try {
237
+ d2 = JSON.parse(msg.data);
238
+ } catch {
239
+ return;
240
+ }
241
+ switch (d2.event) {
242
+ // ── User speech (STT) ──
243
+ case "speech.started":
244
+ this.setState({ userSpeaking: true, idleWarning: null });
245
+ break;
246
+ case "speech.ended":
247
+ this.setState({ userSpeaking: false });
248
+ break;
249
+ case "user.speaking":
250
+ if (d2.text) {
251
+ this.setMessages((prev) => {
252
+ const idx = prev.findLastIndex(
253
+ (m2) => m2.role === "user" && m2.isInterim
254
+ );
255
+ if (idx >= 0) {
256
+ return prev.map(
257
+ (m2, i) => i === idx ? { ...m2, text: d2.text } : m2
258
+ );
259
+ }
260
+ return [
261
+ ...prev,
262
+ {
263
+ id: Date.now(),
264
+ role: "user",
265
+ text: d2.text,
266
+ isInterim: true
267
+ }
268
+ ];
269
+ });
270
+ }
271
+ this.setState({ phase: "listening", userSpeaking: true });
272
+ break;
273
+ case "user.message":
274
+ if (d2.text) {
275
+ this.setMessages((prev) => {
276
+ const idx = prev.findLastIndex(
277
+ (m2) => m2.role === "user" && m2.isInterim
278
+ );
279
+ if (idx >= 0) {
280
+ return prev.map(
281
+ (m2, i) => i === idx ? { ...m2, text: d2.text, isInterim: false } : m2
282
+ );
283
+ }
284
+ return [
285
+ ...prev,
286
+ {
287
+ id: Date.now(),
288
+ role: "user",
289
+ text: d2.text,
290
+ isInterim: false
291
+ }
292
+ ];
293
+ });
294
+ }
295
+ this.setState({ userSpeaking: false, phase: "thinking" });
296
+ break;
297
+ // ── Turn detection ──
298
+ case "turn.pause":
299
+ this.setState({ phase: "pause" });
300
+ break;
301
+ case "turn.end":
302
+ this.setState({ phase: "thinking", userSpeaking: false });
303
+ break;
304
+ case "turn.resumed":
305
+ this.setState({ phase: "listening" });
306
+ break;
307
+ // ── Bot speech (TTS word-by-word) ──
308
+ case "bot.speaking":
309
+ if (d2.message_id) {
310
+ this.botWords[d2.message_id] = [];
311
+ }
312
+ break;
313
+ case "bot.word":
314
+ if (d2.message_id && d2.word) {
315
+ const ref = this.botWords;
316
+ if (!ref[d2.message_id]) ref[d2.message_id] = [];
317
+ const idx = d2.word_index ?? ref[d2.message_id].length;
318
+ ref[d2.message_id][idx] = d2.word;
319
+ const newText = ref[d2.message_id].filter(Boolean).join(" ");
320
+ this.setMessages((prev) => {
321
+ const mi = prev.findIndex((m2) => m2.messageId === d2.message_id);
322
+ if (mi >= 0)
323
+ return prev.map(
324
+ (m2, i) => i === mi ? { ...m2, text: newText } : m2
325
+ );
326
+ return [
327
+ ...prev,
328
+ {
329
+ id: Date.now(),
330
+ role: "bot",
331
+ text: newText,
332
+ messageId: d2.message_id,
333
+ speaking: true
334
+ }
335
+ ];
336
+ });
337
+ this.setState({ agentSpeaking: true, phase: "speaking" });
338
+ }
339
+ break;
340
+ case "bot.finished":
341
+ if (d2.message_id) {
342
+ this.setMessages((prev) => {
343
+ const msg2 = prev.find((m2) => m2.messageId === d2.message_id);
344
+ if (msg2 && !msg2.text && !d2.text) {
345
+ return prev.filter((m2) => m2.messageId !== d2.message_id);
346
+ }
347
+ return prev.map(
348
+ (m2) => m2.messageId === d2.message_id ? { ...m2, speaking: false, ...d2.text ? { text: d2.text } : {} } : m2
349
+ );
350
+ });
351
+ }
352
+ this.setState({ agentSpeaking: false, phase: "listening" });
353
+ break;
354
+ case "bot.interrupted":
355
+ if (d2.message_id) {
356
+ this.setMessages(
357
+ (prev) => prev.map(
358
+ (m2) => m2.messageId === d2.message_id ? { ...m2, speaking: false, interrupted: true } : m2
359
+ )
360
+ );
361
+ }
362
+ this.setState({ agentSpeaking: false, phase: "listening" });
363
+ break;
364
+ // ── Audio metrics ──
365
+ case "audio.metrics":
366
+ if (d2.source === "user" && d2.is_speech !== void 0) {
367
+ this.setState({ userSpeaking: d2.is_speech });
368
+ }
369
+ break;
370
+ // ── Session limits ──
371
+ case "session.idle_warning":
372
+ this.setState({ idleWarning: d2.remaining_seconds ?? 0 });
373
+ break;
374
+ case "session.timeout":
375
+ this.disconnect();
376
+ break;
377
+ // ── Tool events (server-side LLM) ──
378
+ case "llm.tool_call": {
379
+ if (d2.tool_calls?.length) {
380
+ this.setMessages((prev) => [
381
+ ...prev,
382
+ ...d2.tool_calls.map((tc) => ({
383
+ id: Date.now() + Math.random(),
384
+ role: "system",
385
+ text: `\u{1F527} Using ${tc.name}\u2026`,
386
+ toolCallId: tc.id
387
+ }))
388
+ ]);
389
+ const tracked = this.opts.trackedTools;
390
+ const newEntries = d2.tool_calls.filter((tc) => !tracked || tracked.includes(tc.name)).map((tc) => {
391
+ let args = {};
392
+ try {
393
+ args = typeof tc.arguments === "string" ? JSON.parse(tc.arguments) : tc.arguments ?? {};
394
+ } catch {
395
+ }
396
+ return {
397
+ toolCallId: tc.id,
398
+ name: tc.name,
399
+ arguments: args,
400
+ timestamp: Date.now()
401
+ };
402
+ });
403
+ if (newEntries.length) {
404
+ this.setState({
405
+ toolCalls: [...this.state.toolCalls, ...newEntries]
406
+ });
407
+ }
408
+ }
409
+ break;
410
+ }
411
+ case "llm.tool_result": {
412
+ if (d2.tool_call_id) {
413
+ const sysMsg = this.state.messages.find(
414
+ (m2) => m2.toolCallId === d2.tool_call_id
415
+ );
416
+ const toolName = (d2.name || sysMsg?.text?.match(/Using (\S+)/)?.[1] || "Tool").replace(/…$/, "");
417
+ this.setMessages(
418
+ (prev2) => prev2.map(
419
+ (m2) => m2.toolCallId === d2.tool_call_id ? { ...m2, text: `\u2713 ${toolName}` } : m2
420
+ )
421
+ );
422
+ const prev = this.state.toolCalls;
423
+ const idx = prev.findIndex(
424
+ (t2) => t2.toolCallId === d2.tool_call_id
425
+ );
426
+ if (idx >= 0) {
427
+ let parsed = d2.result;
428
+ if (typeof parsed === "string") {
429
+ try {
430
+ parsed = JSON.parse(parsed);
431
+ } catch {
432
+ }
433
+ }
434
+ const updated = prev.map(
435
+ (t2, i) => i === idx ? { ...t2, result: parsed } : t2
436
+ );
437
+ this.setState({ toolCalls: updated });
438
+ }
439
+ }
440
+ break;
441
+ }
442
+ }
443
+ this.dispatchEvent(new CustomEvent("event", { detail: d2 }));
444
+ }
445
+ disconnect() {
446
+ this.cleanup();
447
+ this.setState({ status: "idle" });
448
+ }
449
+ toggleMute() {
450
+ this.setMuted(!this.state.isMuted);
451
+ }
452
+ setMuted(muted) {
453
+ const stream = this.stream;
454
+ if (!stream) return;
455
+ stream.getAudioTracks().forEach((t2) => {
456
+ t2.enabled = !muted;
457
+ });
458
+ const dc = this.dc;
459
+ if (dc && dc.readyState === "open") {
460
+ dc.send(JSON.stringify({ action: muted ? "mute" : "unmute" }));
461
+ }
462
+ this.setState({ isMuted: muted });
463
+ }
464
+ /**
465
+ * Send a configuration update via DataChannel during an active call.
466
+ * Use this for mid-call language/voice/STT switching.
467
+ *
468
+ * @example
469
+ * ```ts
470
+ * session.configure({ voice: "coral", stt: "deepgram", language: "es" });
471
+ * ```
472
+ */
473
+ configure(config) {
474
+ const dc = this.dc;
475
+ if (dc && dc.readyState === "open") {
476
+ dc.send(JSON.stringify({ action: "configure", ...config }));
477
+ }
478
+ }
479
+ /**
480
+ * Inject text into the conversation as if the user spoke it.
481
+ *
482
+ * Use this for click-based interactions in tool UIs (e.g., selecting a
483
+ * calendar slot). The server routes the text to the LLM, producing the
484
+ * same effect as the user speaking.
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * session.sendText("I'd like the 10:00 AM slot");
489
+ * ```
490
+ */
491
+ sendText(text) {
492
+ const dc = this.dc;
493
+ if (dc && dc.readyState === "open") {
494
+ dc.send(JSON.stringify({ action: "inject_text", text }));
495
+ }
496
+ }
497
+ /**
498
+ * Remove a tool UI entry from state.
499
+ *
500
+ * Call this after the user interacts with a tool UI (e.g., selects a slot)
501
+ * to dismiss the rendered component from the transcript.
502
+ */
503
+ dismissTool(toolCallId) {
504
+ this.setState({
505
+ toolCalls: this.state.toolCalls.filter(
506
+ (t2) => t2.toolCallId !== toolCallId
507
+ )
508
+ });
509
+ }
510
+ /**
511
+ * Set or clear a keyed context block in the LLM system prompt.
512
+ *
513
+ * Use this to inject dynamic UI state (form data, selections, etc.)
514
+ * into the agent's prompt so it can see what the user is doing on screen.
515
+ * Each key is a named section — setting the same key replaces its value.
516
+ * Pass `null` to remove a context key.
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * // Inject form state so the agent sees what's filled
521
+ * session.setContext("contact_form", JSON.stringify({
522
+ * name: "John",
523
+ * email: "john@example.com",
524
+ * phone: "",
525
+ * }));
526
+ *
527
+ * // Clear when form is submitted
528
+ * session.setContext("contact_form", null);
529
+ * ```
530
+ */
531
+ setContext(key, value) {
532
+ const dc = this.dc;
533
+ if (dc && dc.readyState === "open") {
534
+ dc.send(JSON.stringify({ action: "set_context", key, value }));
535
+ }
536
+ }
537
+ /**
538
+ * Update session options before the next `connect()` call.
539
+ * Has no effect on an already-connected session — use `configure()` for that.
540
+ */
541
+ updateOptions(patch) {
542
+ if (patch.config !== void 0) {
543
+ this.opts = { ...this.opts, config: patch.config };
544
+ }
545
+ if (patch.metadata !== void 0) {
546
+ this.opts = { ...this.opts, metadata: patch.metadata };
547
+ }
548
+ }
549
+ /** Tear down the session and clear subscribers. After this, do not reuse. */
550
+ destroy() {
551
+ this.cleanup();
552
+ this.setState({ status: "idle" });
553
+ this.listeners.clear();
554
+ }
555
+ };
556
+
557
+ // src/widget/useVoiceSession.ts
558
+ function useVoiceSession(opts) {
559
+ const [session] = react.useState(() => new VoiceSession(opts));
560
+ const state = react.useSyncExternalStore(
561
+ react.useCallback((cb) => session.subscribe(cb), [session]),
562
+ () => session.getState(),
563
+ () => session.getState()
564
+ );
565
+ react.useEffect(() => {
566
+ return () => {
567
+ session.destroy();
568
+ };
569
+ }, [session]);
570
+ const connect = react.useCallback(() => session.connect(), [session]);
571
+ const disconnect = react.useCallback(() => session.disconnect(), [session]);
572
+ const toggleMute = react.useCallback(() => session.toggleMute(), [session]);
573
+ const setMuted = react.useCallback(
574
+ (muted) => session.setMuted(muted),
575
+ [session]
576
+ );
577
+ const configure = react.useCallback(
578
+ (config) => session.configure(config),
579
+ [session]
580
+ );
581
+ const updateOptions = react.useCallback(
582
+ (patch) => session.updateOptions(patch),
583
+ [session]
584
+ );
585
+ const sendText = react.useCallback(
586
+ (text) => session.sendText(text),
587
+ [session]
588
+ );
589
+ const dismissTool = react.useCallback(
590
+ (toolCallId) => session.dismissTool(toolCallId),
591
+ [session]
592
+ );
593
+ const setContext = react.useCallback(
594
+ (key, value) => session.setContext(key, value),
595
+ [session]
596
+ );
597
+ return {
598
+ ...state,
599
+ connect,
600
+ disconnect,
601
+ toggleMute,
602
+ setMuted,
603
+ configure,
604
+ updateOptions,
605
+ sendText,
606
+ dismissTool,
607
+ setContext
608
+ };
609
+ }
610
+
611
+ // src/widget/styles.ts
612
+ var WIDGET_CSS = (
613
+ /* css */
614
+ `
615
+ /* \u2500\u2500 Container + CSS custom-property defaults \u2500\u2500 */
616
+ .vw-wrap {
617
+ /* \u2500\u2500 Orb idle gradient (RGB triplets) \u2500\u2500 */
618
+ --vw-orb-from: 255, 255, 255;
619
+ --vw-orb-mid: 240, 238, 231;
620
+ --vw-orb-to: 184, 181, 168;
621
+
622
+ /* \u2500\u2500 State colors (RGB triplets) \u2500\u2500 */
623
+ --vw-color-connecting: 245, 158, 11;
624
+ --vw-color-active: 76, 175, 80;
625
+ --vw-color-user-speaking: 52, 211, 153;
626
+ --vw-color-speaking: 248, 113, 113;
627
+ --vw-color-thinking: 139, 92, 246;
628
+ --vw-color-warning: 255, 160, 0;
629
+ --vw-color-accent: 124, 58, 237;
630
+ --vw-ring-color: 216, 65, 44;
631
+
632
+ /* \u2500\u2500 Panel / bubble (full CSS values) \u2500\u2500 */
633
+ --vw-panel-bg: rgba(16, 14, 20, .92);
634
+ --vw-panel-border: rgba(255, 255, 255, .08);
635
+ --vw-bubble-bot-bg: rgba(18, 16, 22, .9);
636
+ --vw-bubble-bot-color: #e8e4f0;
637
+ --vw-bubble-user-color: #e0d4f7;
638
+ --vw-label-bg: #181818;
639
+ --vw-label-color: #fff;
640
+
641
+ position: fixed;
642
+ right: 28px;
643
+ bottom: 28px;
644
+ z-index: 100;
645
+ overflow: visible;
646
+ }
647
+
648
+ /* \u2500\u2500 Orb \u2500\u2500 */
649
+ .vw-orb {
650
+ width: 64px;
651
+ height: 64px;
652
+ border-radius: 999px;
653
+ background:
654
+ radial-gradient(circle at 30% 30%,
655
+ rgb(var(--vw-orb-from)),
656
+ rgb(var(--vw-orb-mid)) 35%,
657
+ rgba(var(--vw-orb-to), .85) 70%,
658
+ rgb(var(--vw-orb-to)));
659
+ box-shadow:
660
+ 0 1px 0 rgba(255, 255, 255, .9) inset,
661
+ 0 -10px 24px rgba(var(--vw-ring-color), .15) inset,
662
+ 0 14px 40px -10px rgba(0, 0, 0, .25),
663
+ 0 0 0 1px rgba(0, 0, 0, .06);
664
+ cursor: pointer;
665
+ position: relative;
666
+ display: flex;
667
+ align-items: center;
668
+ justify-content: center;
669
+ transition: transform .2s ease;
670
+ }
671
+
672
+ .vw-orb:hover { transform: scale(1.04); }
673
+
674
+ .vw-orb::after {
675
+ content: "";
676
+ position: absolute;
677
+ inset: -8px;
678
+ border-radius: 999px;
679
+ border: 1px solid rgba(var(--vw-ring-color), .2);
680
+ animation: vw-breathe 3.2s ease-in-out infinite;
681
+ }
682
+
683
+ .vw-orb::before {
684
+ content: "";
685
+ position: absolute;
686
+ inset: -16px;
687
+ border-radius: 999px;
688
+ border: 1px solid rgba(var(--vw-ring-color), .08);
689
+ animation: vw-breathe 3.2s ease-in-out .5s infinite;
690
+ }
691
+
692
+ @keyframes vw-breathe {
693
+ 0%, 100% { transform: scale(1); opacity: .9; }
694
+ 50% { transform: scale(1.08); opacity: .3; }
695
+ }
696
+
697
+ /* \u2500\u2500 Label tooltip \u2500\u2500 */
698
+ .vw-label {
699
+ position: absolute;
700
+ right: 76px;
701
+ top: 50%;
702
+ transform: translateY(-50%);
703
+ background: var(--vw-label-bg);
704
+ color: var(--vw-label-color);
705
+ padding: 8px 12px;
706
+ border-radius: 999px;
707
+ font-size: 12px;
708
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
709
+ white-space: nowrap;
710
+ opacity: 0;
711
+ pointer-events: none;
712
+ transition: opacity .2s, transform .2s;
713
+ font-variant-numeric: tabular-nums;
714
+ }
715
+
716
+ .vw-wrap:hover .vw-label { opacity: 1; transform: translateY(-50%) translateX(-2px); }
717
+ .vw-wrap.is-live .vw-label { display: none; }
718
+
719
+ /* \u2500\u2500 State: connecting \u2500\u2500 */
720
+ .vw-orb.connecting {
721
+ background: radial-gradient(circle at 30% 30%, #fff,
722
+ rgba(var(--vw-color-connecting), .35) 35%,
723
+ rgba(var(--vw-color-connecting), .7) 70%,
724
+ rgba(var(--vw-color-connecting), 1));
725
+ animation: vw-breathe 1s ease-in-out infinite;
726
+ }
727
+ .vw-orb.connecting::after,
728
+ .vw-orb.connecting::before { border-color: rgba(var(--vw-color-connecting), .25); }
729
+
730
+ /* \u2500\u2500 State: active \u2500\u2500 */
731
+ .vw-orb.active {
732
+ background: radial-gradient(circle at 30% 30%, #fff,
733
+ rgba(var(--vw-color-active), .15) 35%,
734
+ rgba(var(--vw-color-active), .3) 70%,
735
+ rgba(var(--vw-color-active), .5));
736
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .9) inset,
737
+ 0 14px 40px -10px rgba(0, 0, 0, .2),
738
+ 0 0 20px rgba(var(--vw-color-active), .15);
739
+ }
740
+ .vw-orb.active::after,
741
+ .vw-orb.active::before { border-color: rgba(var(--vw-color-active), .2); }
742
+
743
+ /* \u2500\u2500 State: user speaking \u2500\u2500 */
744
+ .vw-orb.user-speaking {
745
+ background: radial-gradient(circle at 30% 30%, #fff,
746
+ rgba(var(--vw-color-user-speaking), .35) 35%,
747
+ rgba(var(--vw-color-user-speaking), .7) 70%,
748
+ rgba(var(--vw-color-user-speaking), 1));
749
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .9) inset,
750
+ 0 14px 40px -10px rgba(0, 0, 0, .2),
751
+ 0 0 30px rgba(var(--vw-color-user-speaking), .3);
752
+ }
753
+ .vw-orb.user-speaking::after,
754
+ .vw-orb.user-speaking::before { border-color: rgba(var(--vw-color-user-speaking), .3); }
755
+
756
+ /* \u2500\u2500 State: agent speaking \u2500\u2500 */
757
+ .vw-orb.speaking {
758
+ background: radial-gradient(circle at 30% 30%, #fff,
759
+ rgba(var(--vw-color-speaking), .35) 35%,
760
+ rgba(var(--vw-color-speaking), .7) 70%,
761
+ rgba(var(--vw-color-speaking), 1));
762
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .9) inset,
763
+ 0 14px 40px -10px rgba(0, 0, 0, .2),
764
+ 0 0 30px rgba(var(--vw-color-speaking), .3);
765
+ animation: vw-speak-pulse .8s ease-in-out infinite;
766
+ }
767
+ .vw-orb.speaking::after,
768
+ .vw-orb.speaking::before { border-color: rgba(var(--vw-color-speaking), .3); }
769
+
770
+ @keyframes vw-speak-pulse {
771
+ 0%, 100% { transform: scale(1); }
772
+ 50% { transform: scale(1.06); }
773
+ }
774
+
775
+ /* \u2500\u2500 State: thinking \u2500\u2500 */
776
+ .vw-orb.thinking {
777
+ background: radial-gradient(circle at 30% 30%, #fff,
778
+ rgba(var(--vw-color-thinking), .35) 35%,
779
+ rgba(var(--vw-color-thinking), .7) 70%,
780
+ rgba(var(--vw-color-thinking), 1));
781
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .9) inset,
782
+ 0 14px 40px -10px rgba(0, 0, 0, .2),
783
+ 0 0 24px rgba(var(--vw-color-thinking), .25);
784
+ animation: vw-think-pulse 1.6s ease-in-out infinite;
785
+ }
786
+ .vw-orb.thinking::after,
787
+ .vw-orb.thinking::before { border-color: rgba(var(--vw-color-thinking), .25); }
788
+
789
+ @keyframes vw-think-pulse {
790
+ 0%, 100% { transform: scale(1); opacity: 1; }
791
+ 50% { transform: scale(1.03); opacity: .85; }
792
+ }
793
+
794
+ /* \u2500\u2500 State: idle warning \u2500\u2500 */
795
+ .vw-orb.idle-warning {
796
+ background: radial-gradient(circle at 30% 30%, #fff,
797
+ rgba(var(--vw-color-warning), .35) 35%,
798
+ rgba(var(--vw-color-warning), .7) 70%,
799
+ rgba(var(--vw-color-warning), 1));
800
+ box-shadow: 0 1px 0 rgba(255, 255, 255, .9) inset,
801
+ 0 14px 40px -10px rgba(0, 0, 0, .2),
802
+ 0 0 30px rgba(var(--vw-color-warning), .35);
803
+ animation: vw-warning-blink .6s ease-in-out infinite;
804
+ }
805
+ .vw-orb.idle-warning::after,
806
+ .vw-orb.idle-warning::before { border-color: rgba(var(--vw-color-warning), .4); }
807
+
808
+ @keyframes vw-warning-blink {
809
+ 0%, 100% { opacity: 1; transform: scale(1); }
810
+ 50% { opacity: .55; transform: scale(.96); }
811
+ }
812
+
813
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
814
+ SPEECH BUBBLE \u2014 single bubble floating above orb
815
+ Like Apple notification banners \u2014 floats to the left
816
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
817
+
818
+ .vw-bubble {
819
+ position: absolute;
820
+ bottom: 12px;
821
+ right: 76px;
822
+ width: max-content;
823
+ max-width: 300px;
824
+ min-width: 120px;
825
+ padding: 10px 16px;
826
+ font-size: 13.5px;
827
+ line-height: 1.5;
828
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
829
+ word-break: break-word;
830
+ animation: vw-bubble-in .2s ease-out;
831
+ pointer-events: none;
832
+ z-index: 1;
833
+ }
834
+
835
+ /* Tail pointer \u2192 points right toward the orb */
836
+ .vw-bubble::after {
837
+ content: "";
838
+ position: absolute;
839
+ top: 50%;
840
+ right: -6px;
841
+ margin-top: -6px;
842
+ width: 12px;
843
+ height: 12px;
844
+ transform: rotate(45deg);
845
+ border-radius: 0 3px 0 0;
846
+ }
847
+
848
+ @keyframes vw-bubble-in {
849
+ from { opacity: 0; transform: translateY(8px); }
850
+ to { opacity: 1; transform: translateY(0); }
851
+ }
852
+
853
+ /* User bubble \u2014 accent glass */
854
+ .vw-bubble.vw-bubble--user {
855
+ background: rgba(var(--vw-color-accent), .15);
856
+ border: 1px solid rgba(var(--vw-color-accent), .25);
857
+ color: var(--vw-bubble-user-color);
858
+ border-radius: 14px 14px 4px 14px;
859
+ backdrop-filter: blur(12px);
860
+ -webkit-backdrop-filter: blur(12px);
861
+ box-shadow: 0 8px 32px -8px rgba(var(--vw-color-accent), .2);
862
+ }
863
+ .vw-bubble.vw-bubble--user::after {
864
+ background: rgba(var(--vw-color-accent), .15);
865
+ border-top: 1px solid rgba(var(--vw-color-accent), .25);
866
+ border-right: 1px solid rgba(var(--vw-color-accent), .25);
867
+ }
868
+
869
+ .vw-bubble.vw-bubble--user.vw-interim {
870
+ opacity: .5;
871
+ border-style: dashed;
872
+ }
873
+ .vw-bubble.vw-bubble--user.vw-interim::after {
874
+ border-style: dashed;
875
+ }
876
+
877
+ /* Bot bubble \u2014 dark frosted glass */
878
+ .vw-bubble.vw-bubble--bot {
879
+ background: var(--vw-bubble-bot-bg);
880
+ border: 1px solid rgba(255, 255, 255, .1);
881
+ color: var(--vw-bubble-bot-color);
882
+ border-radius: 14px 14px 14px 4px;
883
+ backdrop-filter: blur(16px);
884
+ -webkit-backdrop-filter: blur(16px);
885
+ box-shadow: 0 8px 32px -8px rgba(0, 0, 0, .4);
886
+ }
887
+ .vw-bubble.vw-bubble--bot::after {
888
+ background: var(--vw-bubble-bot-bg);
889
+ border-top: 1px solid rgba(255, 255, 255, .1);
890
+ border-right: 1px solid rgba(255, 255, 255, .1);
891
+ }
892
+
893
+ .vw-bubble.vw-bubble--bot.vw-speaking {
894
+ border-color: rgba(var(--vw-color-speaking), .25);
895
+ box-shadow: 0 8px 32px -8px rgba(var(--vw-color-speaking), .15);
896
+ }
897
+ .vw-bubble.vw-bubble--bot.vw-speaking::after {
898
+ border-color: rgba(var(--vw-color-speaking), .25);
899
+ }
900
+
901
+ .vw-bubble.vw-bubble--bot.vw-interrupted {
902
+ opacity: .45;
903
+ text-decoration: line-through;
904
+ text-decoration-color: rgba(255, 107, 178, .4);
905
+ }
906
+
907
+ /* Typing dots */
908
+ .vw-dots { display: inline-flex; gap: 4px; align-items: center; height: 18px; }
909
+ .vw-dots span {
910
+ width: 5px; height: 5px; border-radius: 999px;
911
+ background: rgba(255, 255, 255, .35);
912
+ animation: vw-dot-bounce .6s ease-in-out infinite;
913
+ }
914
+ .vw-dots span:nth-child(2) { animation-delay: .12s; }
915
+ .vw-dots span:nth-child(3) { animation-delay: .24s; }
916
+
917
+ @keyframes vw-dot-bounce {
918
+ 0%, 100% { transform: translateY(0); opacity: .3; }
919
+ 50% { transform: translateY(-4px); opacity: 1; }
920
+ }
921
+
922
+ /* Tool indicator \u2014 shown during tool execution */
923
+ .vw-tool-indicator {
924
+ display: inline-flex;
925
+ align-items: center;
926
+ gap: 6px;
927
+ font-size: 12px;
928
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
929
+ color: rgba(var(--vw-color-thinking), 1);
930
+ }
931
+ .vw-tool-indicator .vw-tool-icon { font-size: 14px; }
932
+ .vw-tool-indicator .vw-tool-name {
933
+ opacity: .85;
934
+ font-weight: 500;
935
+ letter-spacing: .01em;
936
+ }
937
+ .vw-tool-indicator .vw-dots { height: 14px; }
938
+ .vw-tool-indicator .vw-dots span {
939
+ width: 4px; height: 4px;
940
+ background: rgba(var(--vw-color-thinking), .6);
941
+ }
942
+
943
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
944
+ TRANSCRIPT PANEL \u2014 expandable conversation view
945
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
946
+
947
+ .vw-tp-btn {
948
+ position: absolute;
949
+ right: 72px;
950
+ bottom: 16px;
951
+ width: 32px;
952
+ height: 32px;
953
+ border-radius: 999px;
954
+ border: 1px solid rgba(255, 255, 255, .1);
955
+ background: rgba(24, 24, 24, .8);
956
+ backdrop-filter: blur(8px);
957
+ -webkit-backdrop-filter: blur(8px);
958
+ color: rgba(255, 255, 255, .6);
959
+ cursor: pointer;
960
+ display: flex;
961
+ align-items: center;
962
+ justify-content: center;
963
+ transition: all .15s;
964
+ font-size: 14px;
965
+ z-index: 2;
966
+ }
967
+
968
+ .vw-tp-btn:hover {
969
+ border-color: rgba(255, 255, 255, .25);
970
+ color: #fff;
971
+ transform: scale(1.08);
972
+ }
973
+
974
+ /* Panel */
975
+ .vw-tp {
976
+ position: absolute;
977
+ right: 0;
978
+ bottom: 80px;
979
+ width: 320px;
980
+ max-height: 400px;
981
+ background: var(--vw-panel-bg);
982
+ border: 1px solid var(--vw-panel-border);
983
+ border-radius: 16px;
984
+ backdrop-filter: blur(20px);
985
+ -webkit-backdrop-filter: blur(20px);
986
+ box-shadow: 0 20px 60px -15px rgba(0, 0, 0, .5);
987
+ display: flex;
988
+ flex-direction: column;
989
+ overflow: hidden;
990
+ animation: vw-panel-in .2s ease-out;
991
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
992
+ z-index: 3;
993
+ }
994
+
995
+ @keyframes vw-panel-in {
996
+ from { opacity: 0; transform: translateY(10px) scale(.97); }
997
+ to { opacity: 1; transform: translateY(0) scale(1); }
998
+ }
999
+
1000
+ /* Panel header */
1001
+ .vw-tp-head {
1002
+ display: flex;
1003
+ align-items: center;
1004
+ justify-content: space-between;
1005
+ padding: 12px 16px;
1006
+ border-bottom: 1px solid rgba(255, 255, 255, .06);
1007
+ }
1008
+
1009
+ .vw-tp-title {
1010
+ font-size: 11px;
1011
+ font-weight: 600;
1012
+ letter-spacing: .06em;
1013
+ text-transform: uppercase;
1014
+ color: rgba(255, 255, 255, .5);
1015
+ }
1016
+
1017
+ .vw-tp-close {
1018
+ width: 22px; height: 22px;
1019
+ border-radius: 999px;
1020
+ border: 1px solid rgba(255, 255, 255, .1);
1021
+ background: transparent;
1022
+ color: rgba(255, 255, 255, .4);
1023
+ cursor: pointer;
1024
+ display: flex;
1025
+ align-items: center;
1026
+ justify-content: center;
1027
+ font-size: 11px;
1028
+ transition: all .15s;
1029
+ }
1030
+ .vw-tp-close:hover { border-color: rgba(255, 255, 255, .3); color: #fff; }
1031
+
1032
+ /* Panel body \u2014 scrollable */
1033
+ .vw-tp-body {
1034
+ flex: 1;
1035
+ overflow-y: auto;
1036
+ padding: 12px 14px;
1037
+ display: flex;
1038
+ flex-direction: column;
1039
+ gap: 8px;
1040
+ scrollbar-width: thin;
1041
+ scrollbar-color: rgba(255,255,255,.08) transparent;
1042
+ }
1043
+ .vw-tp-body::-webkit-scrollbar { width: 4px; }
1044
+ .vw-tp-body::-webkit-scrollbar-track { background: transparent; }
1045
+ .vw-tp-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.1); border-radius: 4px; }
1046
+
1047
+ /* Message in panel */
1048
+ .vw-tp-msg {
1049
+ max-width: 85%;
1050
+ padding: 8px 12px;
1051
+ border-radius: 12px;
1052
+ font-size: 13px;
1053
+ line-height: 1.45;
1054
+ animation: vw-bubble-in .15s ease-out;
1055
+ }
1056
+
1057
+ .vw-tp-msg--user {
1058
+ align-self: flex-end;
1059
+ background: rgba(var(--vw-color-accent), .15);
1060
+ border: 1px solid rgba(var(--vw-color-accent), .2);
1061
+ color: var(--vw-bubble-user-color);
1062
+ border-radius: 12px 12px 4px 12px;
1063
+ }
1064
+
1065
+ .vw-tp-msg--user.vw-interim {
1066
+ opacity: .5;
1067
+ border-style: dashed;
1068
+ }
1069
+
1070
+ .vw-tp-msg--bot {
1071
+ align-self: flex-start;
1072
+ background: rgba(255, 255, 255, .04);
1073
+ border: 1px solid rgba(255, 255, 255, .06);
1074
+ color: #d4d0e0;
1075
+ border-radius: 12px 12px 12px 4px;
1076
+ }
1077
+
1078
+ .vw-tp-msg--bot.vw-speaking {
1079
+ border-color: rgba(var(--vw-color-speaking), .15);
1080
+ }
1081
+
1082
+ .vw-tp-msg--bot.vw-interrupted {
1083
+ opacity: .45;
1084
+ }
1085
+
1086
+ .vw-tp-msg--system {
1087
+ align-self: center;
1088
+ max-width: 90%;
1089
+ background: rgba(255, 255, 255, .03);
1090
+ border: 1px solid rgba(255, 255, 255, .06);
1091
+ color: rgba(255, 255, 255, .4);
1092
+ border-radius: 8px;
1093
+ font-size: 11.5px;
1094
+ padding: 5px 12px;
1095
+ text-align: center;
1096
+ letter-spacing: .02em;
1097
+ }
1098
+
1099
+ /* Empty state */
1100
+ .vw-tp-empty {
1101
+ text-align: center;
1102
+ color: rgba(255, 255, 255, .25);
1103
+ font-size: 12px;
1104
+ padding: 40px 20px;
1105
+ letter-spacing: .02em;
1106
+ }
1107
+
1108
+ /* Tool UI \u2014 rendered inline in transcript */
1109
+ .vw-tp-tool {
1110
+ padding: 4px 0;
1111
+ animation: vw-bubble-in .2s ease-out;
1112
+ }
1113
+
1114
+ /* \u2500\u2500 Mobile \u2500\u2500 */
1115
+ @media (max-width: 768px) {
1116
+ .vw-wrap { right: 16px; bottom: 16px; }
1117
+ .vw-orb { width: 52px; height: 52px; }
1118
+ .vw-label { display: none; }
1119
+ .vw-bubble { bottom: 68px; max-width: 220px; font-size: 12px; }
1120
+ .vw-tp { width: 280px; max-height: 320px; bottom: 68px; }
1121
+ .vw-tp-btn { right: 60px; }
1122
+ .vw-lang-bar { bottom: 64px; }
1123
+ }
1124
+
1125
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1126
+ LANGUAGE SELECTOR \u2014 pill bar above the orb
1127
+ Hidden by default, shown on hover or during active call
1128
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
1129
+
1130
+ .vw-lang-bar {
1131
+ position: absolute;
1132
+ bottom: 76px;
1133
+ right: 0;
1134
+ display: flex;
1135
+ gap: 4px;
1136
+ padding: 4px;
1137
+ background: rgba(16, 14, 20, .75);
1138
+ backdrop-filter: blur(12px);
1139
+ -webkit-backdrop-filter: blur(12px);
1140
+ border: 1px solid rgba(255, 255, 255, .08);
1141
+ border-radius: 999px;
1142
+ z-index: 2;
1143
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1144
+
1145
+ /* Hidden by default \u2014 revealed on hover */
1146
+ opacity: 0;
1147
+ pointer-events: none;
1148
+ transform: translateY(4px);
1149
+ transition: opacity .2s ease, transform .2s ease;
1150
+ }
1151
+
1152
+ /* Show on hover (pre-call) */
1153
+ .vw-wrap:hover .vw-lang-bar {
1154
+ opacity: 1;
1155
+ pointer-events: auto;
1156
+ transform: translateY(0);
1157
+ }
1158
+
1159
+ /* Always visible during active call */
1160
+ .vw-wrap.is-live .vw-lang-bar {
1161
+ opacity: 1;
1162
+ pointer-events: auto;
1163
+ transform: translateY(0);
1164
+ }
1165
+
1166
+ .vw-lang-pill {
1167
+ display: flex;
1168
+ align-items: center;
1169
+ gap: 4px;
1170
+ padding: 5px 10px;
1171
+ border-radius: 999px;
1172
+ border: 1px solid transparent;
1173
+ background: transparent;
1174
+ color: rgba(255, 255, 255, .5);
1175
+ font-size: 11px;
1176
+ font-weight: 500;
1177
+ cursor: pointer;
1178
+ transition: all .15s ease;
1179
+ white-space: nowrap;
1180
+ letter-spacing: .02em;
1181
+ }
1182
+
1183
+ .vw-lang-pill:hover {
1184
+ color: rgba(255, 255, 255, .8);
1185
+ background: rgba(255, 255, 255, .06);
1186
+ }
1187
+
1188
+ .vw-lang-pill--active {
1189
+ background: rgba(var(--vw-color-accent), .2);
1190
+ border-color: rgba(var(--vw-color-accent), .3);
1191
+ color: #fff;
1192
+ font-weight: 600;
1193
+ }
1194
+
1195
+ .vw-lang-pill--active:hover {
1196
+ background: rgba(var(--vw-color-accent), .25);
1197
+ }
1198
+
1199
+ .vw-lang-flag {
1200
+ font-size: 14px;
1201
+ line-height: 1;
1202
+ }
1203
+
1204
+ .vw-lang-code {
1205
+ line-height: 1;
1206
+ }
1207
+ `
1208
+ );
1209
+
1210
+ // src/widget/presets.ts
1211
+ var dark = {
1212
+ orbFrom: "255, 255, 255",
1213
+ orbMid: "240, 238, 231",
1214
+ orbTo: "184, 181, 168",
1215
+ colorConnecting: "245, 158, 11",
1216
+ colorActive: "76, 175, 80",
1217
+ colorUserSpeaking: "52, 211, 153",
1218
+ colorSpeaking: "248, 113, 113",
1219
+ colorThinking: "139, 92, 246",
1220
+ colorWarning: "255, 160, 0",
1221
+ colorAccent: "124, 58, 237",
1222
+ ringColor: "216, 65, 44",
1223
+ panelBg: "rgba(16, 14, 20, .92)",
1224
+ panelBorder: "rgba(255, 255, 255, .08)",
1225
+ bubbleBotBg: "rgba(18, 16, 22, .9)",
1226
+ bubbleBotColor: "#e8e4f0",
1227
+ bubbleUserColor: "#e0d4f7",
1228
+ labelBg: "#181818",
1229
+ labelColor: "#fff"
1230
+ };
1231
+ var midnight = {
1232
+ orbFrom: "190, 210, 255",
1233
+ orbMid: "90, 120, 200",
1234
+ orbTo: "40, 55, 130",
1235
+ colorConnecting: "100, 180, 255",
1236
+ colorActive: "60, 200, 180",
1237
+ colorUserSpeaking: "80, 220, 200",
1238
+ colorSpeaking: "140, 160, 255",
1239
+ colorThinking: "100, 120, 240",
1240
+ colorWarning: "255, 180, 60",
1241
+ colorAccent: "100, 140, 255",
1242
+ ringColor: "70, 110, 210",
1243
+ panelBg: "rgba(10, 15, 35, .94)",
1244
+ panelBorder: "rgba(100, 140, 255, .12)",
1245
+ bubbleBotBg: "rgba(15, 20, 45, .92)",
1246
+ bubbleBotColor: "#c8d0f0",
1247
+ bubbleUserColor: "#b8c8ff",
1248
+ labelBg: "rgba(15, 20, 40, .92)",
1249
+ labelColor: "#c8d4ff"
1250
+ };
1251
+ var aurora = {
1252
+ orbFrom: "170, 255, 215",
1253
+ orbMid: "60, 190, 150",
1254
+ orbTo: "20, 130, 90",
1255
+ colorConnecting: "255, 200, 60",
1256
+ colorActive: "50, 220, 140",
1257
+ colorUserSpeaking: "100, 240, 180",
1258
+ colorSpeaking: "180, 100, 255",
1259
+ colorThinking: "140, 80, 220",
1260
+ colorWarning: "255, 200, 60",
1261
+ colorAccent: "50, 200, 140",
1262
+ ringColor: "40, 170, 110",
1263
+ panelBg: "rgba(8, 20, 14, .94)",
1264
+ panelBorder: "rgba(50, 200, 140, .1)",
1265
+ bubbleBotBg: "rgba(10, 25, 18, .92)",
1266
+ bubbleBotColor: "#c0ecd4",
1267
+ bubbleUserColor: "#a8e8c4",
1268
+ labelBg: "rgba(10, 24, 16, .92)",
1269
+ labelColor: "#a8e8c4"
1270
+ };
1271
+ var sunset = {
1272
+ orbFrom: "255, 215, 190",
1273
+ orbMid: "235, 140, 90",
1274
+ orbTo: "195, 70, 50",
1275
+ colorConnecting: "255, 180, 60",
1276
+ colorActive: "240, 160, 80",
1277
+ colorUserSpeaking: "255, 200, 100",
1278
+ colorSpeaking: "255, 100, 80",
1279
+ colorThinking: "240, 140, 100",
1280
+ colorWarning: "255, 180, 40",
1281
+ colorAccent: "240, 120, 60",
1282
+ ringColor: "215, 95, 45",
1283
+ panelBg: "rgba(25, 12, 8, .94)",
1284
+ panelBorder: "rgba(240, 120, 60, .1)",
1285
+ bubbleBotBg: "rgba(30, 16, 10, .92)",
1286
+ bubbleBotColor: "#f0d4c0",
1287
+ bubbleUserColor: "#ffd8b4",
1288
+ labelBg: "rgba(28, 14, 10, .92)",
1289
+ labelColor: "#f0d0b0"
1290
+ };
1291
+ var light = {
1292
+ orbFrom: "255, 255, 255",
1293
+ orbMid: "225, 230, 245",
1294
+ orbTo: "190, 200, 225",
1295
+ colorConnecting: "230, 140, 10",
1296
+ colorActive: "34, 150, 60",
1297
+ colorUserSpeaking: "20, 170, 110",
1298
+ colorSpeaking: "210, 50, 50",
1299
+ colorThinking: "100, 60, 200",
1300
+ colorWarning: "230, 140, 10",
1301
+ colorAccent: "80, 40, 200",
1302
+ ringColor: "100, 120, 200",
1303
+ panelBg: "rgba(255, 255, 255, .95)",
1304
+ panelBorder: "rgba(0, 0, 0, .08)",
1305
+ bubbleBotBg: "rgba(245, 245, 250, .95)",
1306
+ bubbleBotColor: "#2a2a3a",
1307
+ bubbleUserColor: "#3a2a5a",
1308
+ labelBg: "rgba(255, 255, 255, .92)",
1309
+ labelColor: "#333"
1310
+ };
1311
+ var PRESETS = {
1312
+ dark,
1313
+ midnight,
1314
+ aurora,
1315
+ sunset,
1316
+ light
1317
+ };
1318
+ var VoiceContext = react.createContext(null);
1319
+ function useVoice() {
1320
+ const ctx = react.useContext(VoiceContext);
1321
+ if (!ctx) throw new Error("useVoice() must be used inside <VoiceWidget>");
1322
+ return ctx;
1323
+ }
1324
+ var THEME_VAR_MAP = {
1325
+ orbFrom: "--vw-orb-from",
1326
+ orbMid: "--vw-orb-mid",
1327
+ orbTo: "--vw-orb-to",
1328
+ colorConnecting: "--vw-color-connecting",
1329
+ colorActive: "--vw-color-active",
1330
+ colorUserSpeaking: "--vw-color-user-speaking",
1331
+ colorSpeaking: "--vw-color-speaking",
1332
+ colorThinking: "--vw-color-thinking",
1333
+ colorWarning: "--vw-color-warning",
1334
+ colorAccent: "--vw-color-accent",
1335
+ ringColor: "--vw-ring-color",
1336
+ panelBg: "--vw-panel-bg",
1337
+ panelBorder: "--vw-panel-border",
1338
+ bubbleBotBg: "--vw-bubble-bot-bg",
1339
+ bubbleBotColor: "--vw-bubble-bot-color",
1340
+ bubbleUserColor: "--vw-bubble-user-color",
1341
+ labelBg: "--vw-label-bg",
1342
+ labelColor: "--vw-label-color"
1343
+ };
1344
+ function fmt(s) {
1345
+ return `${Math.floor(s / 60)}:${(s % 60).toString().padStart(2, "0")}`;
1346
+ }
1347
+ function Dots() {
1348
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-dots", children: [
1349
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1350
+ /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1351
+ /* @__PURE__ */ jsxRuntime.jsx("span", {})
1352
+ ] });
1353
+ }
1354
+ function ThinkingIndicator({ toolCalls }) {
1355
+ const pending = toolCalls.find((tc) => tc.result === void 0);
1356
+ if (pending) {
1357
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-tool-indicator", children: [
1358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-tool-icon", children: "\u{1F527}" }),
1359
+ " ",
1360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-tool-name", children: pending.name }),
1361
+ /* @__PURE__ */ jsxRuntime.jsx(Dots, {})
1362
+ ] });
1363
+ }
1364
+ return /* @__PURE__ */ jsxRuntime.jsx(Dots, {});
1365
+ }
1366
+ function VoiceWidget({
1367
+ agent,
1368
+ server,
1369
+ name = "Agent",
1370
+ label,
1371
+ config: userConfig,
1372
+ metadata,
1373
+ className,
1374
+ preset = "dark",
1375
+ theme,
1376
+ onStatusChange,
1377
+ tools,
1378
+ trackedTools: trackedToolsProp,
1379
+ languages,
1380
+ defaultLanguage,
1381
+ onLanguageChange,
1382
+ tokenProvider,
1383
+ children
1384
+ }) {
1385
+ const hasLanguages = languages && Object.keys(languages).length >= 2;
1386
+ const langKeys = hasLanguages ? Object.keys(languages) : [];
1387
+ const initialLang = defaultLanguage || (langKeys[0] ?? "");
1388
+ const [selectedLang, setSelectedLang] = react.useState(initialLang);
1389
+ const mergedConfig = react.useMemo(() => {
1390
+ const langPreset = languages?.[selectedLang];
1391
+ const langConfig = {};
1392
+ if (langPreset?.voice) langConfig.voice = langPreset.voice;
1393
+ if (langPreset?.stt) langConfig.stt = langPreset.stt;
1394
+ if (langPreset?.language) langConfig.language = langPreset.language;
1395
+ return { ...langConfig, ...userConfig };
1396
+ }, [selectedLang, languages, userConfig]);
1397
+ const trackedTools = react.useMemo(
1398
+ () => trackedToolsProp ?? (tools ? Object.keys(tools) : void 0),
1399
+ [tools, trackedToolsProp]
1400
+ );
1401
+ const session = useVoiceSession({
1402
+ server,
1403
+ agent,
1404
+ config: Object.keys(mergedConfig).length > 0 ? mergedConfig : void 0,
1405
+ metadata,
1406
+ trackedTools,
1407
+ tokenProvider
1408
+ });
1409
+ const [panelOpen, setPanelOpen] = react.useState(false);
1410
+ const themeStyle = react.useMemo(() => {
1411
+ const base = PRESETS[preset] ?? PRESETS.dark;
1412
+ const merged = { ...base, ...theme };
1413
+ const vars = {};
1414
+ for (const [key, cssVar] of Object.entries(THEME_VAR_MAP)) {
1415
+ const val = merged[key];
1416
+ if (val !== void 0) vars[cssVar] = val;
1417
+ }
1418
+ return Object.keys(vars).length > 0 ? vars : void 0;
1419
+ }, [preset, theme]);
1420
+ const scrollRef = react.useRef(null);
1421
+ react.useEffect(() => {
1422
+ if (document.getElementById("vw-styles")) return;
1423
+ const el = document.createElement("style");
1424
+ el.id = "vw-styles";
1425
+ el.textContent = WIDGET_CSS;
1426
+ document.head.appendChild(el);
1427
+ }, []);
1428
+ react.useEffect(() => {
1429
+ onStatusChange?.(session.status);
1430
+ }, [session.status, onStatusChange]);
1431
+ react.useEffect(() => {
1432
+ if (scrollRef.current) {
1433
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
1434
+ }
1435
+ }, [session.messages, session.toolCalls]);
1436
+ react.useEffect(() => {
1437
+ if (!tools) return;
1438
+ const hasRenderable = session.toolCalls.some(
1439
+ (tc) => tc.result !== void 0 && tools[tc.name]
1440
+ );
1441
+ if (hasRenderable && !panelOpen) {
1442
+ setPanelOpen(true);
1443
+ }
1444
+ }, [session.toolCalls, tools]);
1445
+ const handleLanguageChange = react.useCallback(
1446
+ (lang) => {
1447
+ if (lang === selectedLang) return;
1448
+ setSelectedLang(lang);
1449
+ const preset2 = languages?.[lang];
1450
+ if (!preset2) return;
1451
+ const cfg = {};
1452
+ if (preset2.voice) cfg.voice = preset2.voice;
1453
+ if (preset2.stt) cfg.stt = preset2.stt;
1454
+ if (preset2.language) cfg.language = preset2.language;
1455
+ if (session.status === "connected" && Object.keys(cfg).length > 0) {
1456
+ session.configure(cfg);
1457
+ } else {
1458
+ session.updateOptions({ config: { ...cfg, ...userConfig } });
1459
+ }
1460
+ onLanguageChange?.(lang, preset2);
1461
+ },
1462
+ [selectedLang, languages, session, userConfig, onLanguageChange]
1463
+ );
1464
+ const handleClick = react.useCallback(async () => {
1465
+ if (session.status === "connected") {
1466
+ session.disconnect();
1467
+ setPanelOpen(false);
1468
+ } else if (session.status === "idle" || session.status === "error") {
1469
+ await session.connect();
1470
+ }
1471
+ }, [session]);
1472
+ const isActive = session.status === "connected";
1473
+ const idleLabel = label || `Talk to ${name}`;
1474
+ const orbState = session.agentSpeaking ? "speaking" : session.userSpeaking ? "user-speaking" : session.phase === "thinking" ? "thinking" : session.idleWarning != null ? "idle-warning" : isActive ? "active" : session.status === "connecting" ? "connecting" : "";
1475
+ const statusLabel = (() => {
1476
+ if (session.status === "connecting") return "Connecting";
1477
+ if (session.status === "error") return session.error || "Connection failed";
1478
+ if (!isActive) return idleLabel;
1479
+ return `${name} \xB7 ${fmt(session.duration)}`;
1480
+ })();
1481
+ const hasPendingTools = session.toolCalls.some((tc) => tc.result === void 0);
1482
+ const activeBubble = [...session.messages].reverse().find(
1483
+ (m2) => m2.role === "user" || m2.role === "bot" && (m2.text || hasPendingTools)
1484
+ );
1485
+ const showBubble = isActive && activeBubble && !panelOpen;
1486
+ const showTranscriptBtn = isActive && session.messages.length > 0;
1487
+ const widget = /* @__PURE__ */ jsxRuntime.jsxs(
1488
+ "div",
1489
+ {
1490
+ className: `vw-wrap ${isActive ? "is-live" : ""} ${className || ""}`,
1491
+ style: themeStyle,
1492
+ children: [
1493
+ hasLanguages && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-lang-bar", children: langKeys.map((key) => {
1494
+ const lp = languages[key];
1495
+ const isSelected = key === selectedLang;
1496
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1497
+ "button",
1498
+ {
1499
+ className: `vw-lang-pill ${isSelected ? "vw-lang-pill--active" : ""}`,
1500
+ onClick: () => handleLanguageChange(key),
1501
+ "aria-label": lp.label || key,
1502
+ "aria-pressed": isSelected,
1503
+ children: [
1504
+ lp.flag && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-lang-flag", children: lp.flag }),
1505
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-lang-code", children: lp.label || key.toUpperCase() })
1506
+ ]
1507
+ },
1508
+ key
1509
+ );
1510
+ }) }),
1511
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-label", children: statusLabel }),
1512
+ showBubble && /* @__PURE__ */ jsxRuntime.jsx(
1513
+ "div",
1514
+ {
1515
+ className: `vw-bubble ${activeBubble.role === "user" ? "vw-bubble--user" : "vw-bubble--bot"} ${activeBubble.isInterim ? "vw-interim" : ""} ${activeBubble.speaking ? "vw-speaking" : ""} ${activeBubble.interrupted ? "vw-interrupted" : ""}`,
1516
+ children: activeBubble.text || /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { toolCalls: session.toolCalls })
1517
+ },
1518
+ activeBubble.id
1519
+ ),
1520
+ showTranscriptBtn && /* @__PURE__ */ jsxRuntime.jsx(
1521
+ "button",
1522
+ {
1523
+ className: "vw-tp-btn",
1524
+ onClick: () => setPanelOpen((p) => !p),
1525
+ "aria-label": panelOpen ? "Close transcript" : "Open transcript",
1526
+ children: panelOpen ? "\u2715" : "\u2630"
1527
+ }
1528
+ ),
1529
+ panelOpen && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-tp", children: [
1530
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-tp-head", children: [
1531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-tp-title", children: [
1532
+ "Transcript \xB7 ",
1533
+ fmt(session.duration)
1534
+ ] }),
1535
+ /* @__PURE__ */ jsxRuntime.jsx(
1536
+ "button",
1537
+ {
1538
+ className: "vw-tp-close",
1539
+ onClick: () => setPanelOpen(false),
1540
+ children: "\u2715"
1541
+ }
1542
+ )
1543
+ ] }),
1544
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-tp-body", ref: scrollRef, children: [
1545
+ session.messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-tp-empty", children: "Waiting for conversation\u2026" }) : session.messages.map((msg) => /* @__PURE__ */ jsxRuntime.jsx(TranscriptMsg, { msg, toolCalls: session.toolCalls }, msg.id)),
1546
+ tools && session.toolCalls.filter((tc) => tc.result !== void 0 && tools[tc.name]).map((tc) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-tp-tool", children: tools[tc.name](
1547
+ tc.result,
1548
+ {
1549
+ respond: (text) => {
1550
+ session.sendText(text);
1551
+ session.dismissTool(tc.toolCallId);
1552
+ },
1553
+ dismiss: () => session.dismissTool(tc.toolCallId)
1554
+ },
1555
+ tc
1556
+ ) }, tc.toolCallId))
1557
+ ] })
1558
+ ] }),
1559
+ /* @__PURE__ */ jsxRuntime.jsx(
1560
+ "div",
1561
+ {
1562
+ className: `vw-orb ${orbState}`,
1563
+ role: "button",
1564
+ tabIndex: 0,
1565
+ "aria-label": isActive ? "End call" : idleLabel,
1566
+ onClick: handleClick,
1567
+ onKeyDown: (e) => e.key === "Enter" && handleClick()
1568
+ }
1569
+ )
1570
+ ]
1571
+ }
1572
+ );
1573
+ return /* @__PURE__ */ jsxRuntime.jsxs(VoiceContext.Provider, { value: session, children: [
1574
+ widget,
1575
+ children
1576
+ ] });
1577
+ }
1578
+ function TranscriptMsg({ msg, toolCalls = [] }) {
1579
+ if (msg.role === "system") {
1580
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-tp-msg vw-tp-msg--system", children: msg.text });
1581
+ }
1582
+ const isUser = msg.role === "user";
1583
+ const hasPending = toolCalls.some((tc) => tc.result === void 0);
1584
+ if (!isUser && !msg.text && (!msg.speaking || hasPending)) return null;
1585
+ const cls = [
1586
+ "vw-tp-msg",
1587
+ isUser ? "vw-tp-msg--user" : "vw-tp-msg--bot",
1588
+ msg.isInterim ? "vw-interim" : "",
1589
+ msg.speaking ? "vw-speaking" : "",
1590
+ msg.interrupted ? "vw-interrupted" : ""
1591
+ ].filter(Boolean).join(" ");
1592
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, children: msg.text || /* @__PURE__ */ jsxRuntime.jsx(ThinkingIndicator, { toolCalls }) });
1593
+ }
1594
+
1595
+ // src/widget/locales.ts
1596
+ var en = {
1597
+ "hub.title": "How would you like to reach us?",
1598
+ "hub.subtitle": "Choose the option that suits you best",
1599
+ "hub.voice": "Talk to {name}",
1600
+ "hub.voiceDesc": "Live voice assistant",
1601
+ "hub.chat": "Chat with {name}",
1602
+ "hub.chatDesc": "Text chat \u2014 ask anything",
1603
+ "hub.whatsapp": "WhatsApp",
1604
+ "hub.whatsappDesc": "Chat with us instantly",
1605
+ "hub.callMe": "Call me",
1606
+ "hub.callMeDesc": "{name} calls you in seconds",
1607
+ "callMe.title": "We'll call you",
1608
+ "callMe.placeholder": "+1 (555) 123-4567",
1609
+ "callMe.submit": "Call me now",
1610
+ "callMe.formNote": "",
1611
+ "callMe.calling": "Calling...",
1612
+ "callMe.ended": "Call ended",
1613
+ "callMe.error": "Could not connect the call",
1614
+ "callMe.back": "Back"
1615
+ };
1616
+ var es = {
1617
+ "hub.title": "\xBFC\xF3mo prefieres contactarnos?",
1618
+ "hub.subtitle": "Elige la opci\xF3n que m\xE1s te convenga",
1619
+ "hub.voice": "Hablar con {name}",
1620
+ "hub.voiceDesc": "Asistente de voz en vivo",
1621
+ "hub.chat": "Chatear con {name}",
1622
+ "hub.chatDesc": "Chat de texto \u2014 pregunt\xE1 lo que quieras",
1623
+ "hub.whatsapp": "WhatsApp",
1624
+ "hub.whatsappDesc": "Chatea con nosotros al instante",
1625
+ "hub.callMe": "Te llamamos",
1626
+ "hub.callMeDesc": "{name} te llama en segundos",
1627
+ "callMe.title": "Te llamamos",
1628
+ "callMe.placeholder": "+51 987 654 321",
1629
+ "callMe.submit": "Llamarme ahora",
1630
+ "callMe.formNote": "",
1631
+ "callMe.calling": "Llamando...",
1632
+ "callMe.ended": "Llamada finalizada",
1633
+ "callMe.error": "No se pudo conectar la llamada",
1634
+ "callMe.back": "Volver"
1635
+ };
1636
+ var de = {
1637
+ "hub.title": "Wie m\xF6chten Sie uns erreichen?",
1638
+ "hub.subtitle": "W\xE4hlen Sie die passende Option",
1639
+ "hub.voice": "Mit {name} sprechen",
1640
+ "hub.voiceDesc": "Live-Sprachassistent",
1641
+ "hub.chat": "Mit {name} chatten",
1642
+ "hub.chatDesc": "Text-Chat \u2014 fragen Sie alles",
1643
+ "hub.whatsapp": "WhatsApp",
1644
+ "hub.whatsappDesc": "Chatten Sie sofort mit uns",
1645
+ "hub.callMe": "R\xFCckruf",
1646
+ "hub.callMeDesc": "{name} ruft Sie in Sekunden an",
1647
+ "callMe.title": "Wir rufen Sie an",
1648
+ "callMe.placeholder": "+49 170 1234567",
1649
+ "callMe.submit": "Jetzt anrufen",
1650
+ "callMe.formNote": "",
1651
+ "callMe.calling": "Anruf wird verbunden...",
1652
+ "callMe.ended": "Anruf beendet",
1653
+ "callMe.error": "Verbindung fehlgeschlagen",
1654
+ "callMe.back": "Zur\xFCck"
1655
+ };
1656
+ var pt = {
1657
+ "hub.title": "Como prefere nos contactar?",
1658
+ "hub.subtitle": "Escolha a op\xE7\xE3o mais conveniente",
1659
+ "hub.voice": "Falar com {name}",
1660
+ "hub.voiceDesc": "Assistente de voz ao vivo",
1661
+ "hub.chat": "Conversar com {name}",
1662
+ "hub.chatDesc": "Chat de texto \u2014 pergunte o que quiser",
1663
+ "hub.whatsapp": "WhatsApp",
1664
+ "hub.whatsappDesc": "Converse conosco instantaneamente",
1665
+ "hub.callMe": "Ligue-me",
1666
+ "hub.callMeDesc": "{name} liga para voc\xEA em segundos",
1667
+ "callMe.title": "Vamos ligar para voc\xEA",
1668
+ "callMe.placeholder": "+55 11 98765-4321",
1669
+ "callMe.submit": "Ligar agora",
1670
+ "callMe.formNote": "",
1671
+ "callMe.calling": "Ligando...",
1672
+ "callMe.ended": "Chamada encerrada",
1673
+ "callMe.error": "N\xE3o foi poss\xEDvel conectar a chamada",
1674
+ "callMe.back": "Voltar"
1675
+ };
1676
+ var locales = { en, es, de, pt };
1677
+ function t(locale, key, overrides, vars) {
1678
+ const base = locales[locale] ?? locales.en;
1679
+ let str = overrides?.[key] ?? base[key] ?? key;
1680
+ if (vars) {
1681
+ for (const [k, v2] of Object.entries(vars)) {
1682
+ str = str.replace(`{${k}}`, v2);
1683
+ }
1684
+ }
1685
+ return str;
1686
+ }
1687
+
1688
+ // src/widget/hub-styles.ts
1689
+ var HUB_CSS = (
1690
+ /* css */
1691
+ `
1692
+
1693
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1694
+ ContactHub \u2014 above-orb contact menu
1695
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
1696
+
1697
+ /* \u2500\u2500 Backdrop \u2500\u2500 */
1698
+ .vw-hub-backdrop {
1699
+ position: fixed;
1700
+ inset: 0;
1701
+ z-index: 200;
1702
+ background: rgba(0, 0, 0, 0.45);
1703
+ backdrop-filter: blur(6px);
1704
+ -webkit-backdrop-filter: blur(6px);
1705
+ display: flex;
1706
+ align-items: flex-end;
1707
+ justify-content: flex-end;
1708
+ padding: 20px;
1709
+ animation: vwHubFade .2s ease;
1710
+ }
1711
+ @keyframes vwHubFade { from { opacity: 0; } to { opacity: 1; } }
1712
+
1713
+ /* \u2500\u2500 Panel \u2500\u2500 */
1714
+ .vw-hub-panel {
1715
+ position: relative;
1716
+ width: 100%;
1717
+ max-width: 320px;
1718
+ background: var(--vw-panel-bg, rgba(16, 14, 20, .92));
1719
+ border: 1px solid var(--vw-panel-border, rgba(255, 255, 255, .08));
1720
+ border-radius: 20px;
1721
+ padding: 26px 22px 22px;
1722
+ box-shadow: 0 24px 56px -12px rgba(0, 0, 0, 0.5);
1723
+ animation: vwHubUp .25s cubic-bezier(.2,.8,.2,1);
1724
+ margin-bottom: 70px;
1725
+ }
1726
+ .vw-hub-panel--chat {
1727
+ max-width: 400px;
1728
+ padding: 0;
1729
+ overflow: hidden;
1730
+ }
1731
+ @keyframes vwHubUp {
1732
+ from { opacity: 0; transform: translateY(16px) scale(0.96); }
1733
+ to { opacity: 1; transform: translateY(0) scale(1); }
1734
+ }
1735
+
1736
+ /* Close */
1737
+ .vw-hub-close {
1738
+ position: absolute;
1739
+ top: 10px; right: 10px;
1740
+ background: none;
1741
+ border: none;
1742
+ color: rgba(255, 255, 255, .4);
1743
+ cursor: pointer;
1744
+ padding: 6px;
1745
+ border-radius: 50%;
1746
+ transition: color .2s, background .2s;
1747
+ line-height: 1;
1748
+ }
1749
+ .vw-hub-close:hover { color: #fff; background: rgba(255, 255, 255, .06); }
1750
+
1751
+ /* \u2500\u2500 Header \u2500\u2500 */
1752
+ .vw-hub-header { text-align: center; margin-bottom: 16px; }
1753
+ .vw-hub-avatar { font-size: 28px; margin-bottom: 6px; }
1754
+ .vw-hub-header h3 {
1755
+ font-size: 17px;
1756
+ color: #fff;
1757
+ margin: 0 0 3px;
1758
+ font-weight: 500;
1759
+ font-family: inherit;
1760
+ }
1761
+ .vw-hub-header p { font-size: 12px; color: rgba(255, 255, 255, .5); margin: 0; }
1762
+
1763
+ /* \u2500\u2500 Option Cards \u2500\u2500 */
1764
+ .vw-hub-options { display: flex; flex-direction: column; gap: 8px; }
1765
+
1766
+ .vw-hub-opt {
1767
+ display: flex;
1768
+ align-items: center;
1769
+ gap: 12px;
1770
+ padding: 12px 14px;
1771
+ border-radius: 12px;
1772
+ background: rgba(255, 255, 255, .03);
1773
+ border: 1px solid rgba(255, 255, 255, .06);
1774
+ color: #fff;
1775
+ text-decoration: none;
1776
+ cursor: pointer;
1777
+ transition: background .2s, border-color .2s, transform .15s;
1778
+ font-family: inherit;
1779
+ text-align: left;
1780
+ width: 100%;
1781
+ font-size: 14px;
1782
+ }
1783
+ .vw-hub-opt:hover {
1784
+ background: rgba(255, 255, 255, .06);
1785
+ border-color: rgba(255, 255, 255, .1);
1786
+ transform: translateX(2px);
1787
+ }
1788
+
1789
+ .vw-hub-icon {
1790
+ width: 36px; height: 36px;
1791
+ border-radius: 10px;
1792
+ display: grid; place-items: center;
1793
+ flex-shrink: 0;
1794
+ }
1795
+ .vw-hub-icon--voice { background: rgba(124, 58, 237, .15); color: rgba(139, 92, 246, 1); }
1796
+ .vw-hub-icon--wa { background: rgba(34, 197, 94, .15); color: rgba(34, 197, 94, 1); }
1797
+ .vw-hub-icon--call {
1798
+ background: rgba(var(--vw-accent, 124, 58, 237), .12);
1799
+ color: rgba(var(--vw-accent, 124, 58, 237), 1);
1800
+ }
1801
+ .vw-hub-icon--chat { background: rgba(59, 130, 246, .15); color: rgba(96, 165, 250, 1); }
1802
+
1803
+ .vw-hub-body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
1804
+ .vw-hub-title { font-weight: 600; font-size: 13px; }
1805
+ .vw-hub-desc { font-size: 11px; color: rgba(255, 255, 255, .45); }
1806
+ .vw-hub-arrow { color: rgba(255, 255, 255, .3); font-size: 14px; flex-shrink: 0; }
1807
+
1808
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1809
+ Call Me \u2014 form + transcript
1810
+ \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
1811
+
1812
+ /* \u2500\u2500 Form View \u2500\u2500 */
1813
+ .vw-cm { text-align: center; padding: 4px 0; }
1814
+ .vw-cm-back {
1815
+ background: none; border: none; color: rgba(255, 255, 255, .4);
1816
+ cursor: pointer; font-size: 12px; margin-bottom: 16px;
1817
+ padding: 4px 8px; border-radius: 6px;
1818
+ transition: color .2s; font-family: inherit;
1819
+ }
1820
+ .vw-cm-back:hover { color: #fff; }
1821
+
1822
+ .vw-cm h3 {
1823
+ font-size: 19px;
1824
+ color: #fff; margin: 0 0 6px; font-weight: 500;
1825
+ font-family: inherit;
1826
+ }
1827
+ .vw-cm p {
1828
+ font-size: 12.5px; color: rgba(255, 255, 255, .5);
1829
+ line-height: 1.55; margin: 0 0 18px;
1830
+ }
1831
+
1832
+ .vw-cm-phone-icon {
1833
+ width: 54px; height: 54px; border-radius: 50%;
1834
+ background: linear-gradient(135deg, rgba(var(--vw-accent, 124, 58, 237), .15), rgba(var(--vw-accent, 124, 58, 237), .06));
1835
+ border: 1.5px solid rgba(var(--vw-accent, 124, 58, 237), .3);
1836
+ display: grid; place-items: center;
1837
+ margin: 0 auto 14px;
1838
+ color: rgba(var(--vw-accent, 124, 58, 237), 1);
1839
+ position: relative;
1840
+ }
1841
+ .vw-cm-phone-icon::after {
1842
+ content: '';
1843
+ position: absolute;
1844
+ inset: -6px;
1845
+ border-radius: 50%;
1846
+ border: 1.5px solid rgba(var(--vw-accent, 124, 58, 237), .15);
1847
+ animation: vwCmIconPulse 2.5s ease-in-out infinite;
1848
+ }
1849
+ @keyframes vwCmIconPulse {
1850
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
1851
+ 50% { opacity: 0; transform: scale(1.25); }
1852
+ }
1853
+
1854
+ .vw-cm-form { display: flex; flex-direction: column; gap: 10px; }
1855
+ .vw-cm-form input {
1856
+ width: 100%; padding: 14px 16px; border-radius: 12px;
1857
+ background: rgba(255, 255, 255, .05);
1858
+ border: 1px solid rgba(255, 255, 255, .1);
1859
+ color: #fff; font-family: inherit; font-size: 16px;
1860
+ outline: none; transition: border-color .2s, box-shadow .2s; box-sizing: border-box;
1861
+ letter-spacing: 0.04em;
1862
+ }
1863
+ .vw-cm-form input:focus {
1864
+ border-color: rgba(var(--vw-accent, 124, 58, 237), .7);
1865
+ box-shadow: 0 0 0 3px rgba(var(--vw-accent, 124, 58, 237), .12);
1866
+ }
1867
+ .vw-cm-form input::placeholder { color: rgba(255, 255, 255, .25); letter-spacing: 0.02em; }
1868
+ .vw-cm-form button {
1869
+ display: flex; align-items: center; justify-content: center; gap: 8px;
1870
+ padding: 13px; border-radius: 12px;
1871
+ background: linear-gradient(135deg, rgba(var(--vw-accent, 124, 58, 237), 1), rgba(var(--vw-accent, 124, 58, 237), .8));
1872
+ color: #fff; font-family: inherit;
1873
+ font-weight: 600; font-size: 14px; border: none; cursor: pointer;
1874
+ transition: transform .2s, box-shadow .2s;
1875
+ letter-spacing: 0.01em;
1876
+ }
1877
+ .vw-cm-form button:hover:not(:disabled) {
1878
+ transform: translateY(-1px);
1879
+ box-shadow: 0 10px 28px -6px rgba(var(--vw-accent, 124, 58, 237), .5);
1880
+ }
1881
+ .vw-cm-form button:disabled { opacity: 0.4; cursor: not-allowed; }
1882
+ .vw-cm-error {
1883
+ font-size: 11px; color: rgba(248, 113, 113, 1);
1884
+ padding: 6px 0; line-height: 1.4;
1885
+ }
1886
+ .vw-cm-note {
1887
+ font-size: 10px; color: rgba(255, 255, 255, .3);
1888
+ line-height: 1.45; margin-top: 4px; opacity: 0.7;
1889
+ }
1890
+ .vw-cm-note a { color: rgba(var(--vw-accent, 124, 58, 237), 1); text-decoration: none; }
1891
+
1892
+ /* \u2500\u2500 Dialing Animation (used inside hub before call connects) \u2500\u2500 */
1893
+
1894
+ .vw-cm-dialing {
1895
+ display: flex; flex-direction: column; align-items: center;
1896
+ gap: 14px; padding: 28px 0;
1897
+ }
1898
+ .vw-cm-dialing-ring {
1899
+ width: 60px; height: 60px; border-radius: 50%;
1900
+ background: linear-gradient(135deg, rgba(var(--vw-accent, 124, 58, 237), .12), rgba(var(--vw-accent, 124, 58, 237), .04));
1901
+ border: 2px solid rgba(var(--vw-accent, 124, 58, 237), .3);
1902
+ display: grid; place-items: center;
1903
+ animation: vwCmRing 1.5s ease infinite;
1904
+ color: rgba(var(--vw-accent, 124, 58, 237), 1);
1905
+ position: relative;
1906
+ }
1907
+ .vw-cm-dialing-ring::after {
1908
+ content: '';
1909
+ position: absolute;
1910
+ inset: -8px;
1911
+ border-radius: 50%;
1912
+ border: 2px solid rgba(var(--vw-accent, 124, 58, 237), .2);
1913
+ animation: vwCmDialPulse 1.4s ease-out infinite;
1914
+ }
1915
+ .vw-cm-dialing-ring::before {
1916
+ content: '';
1917
+ position: absolute;
1918
+ inset: -16px;
1919
+ border-radius: 50%;
1920
+ border: 1.5px solid rgba(var(--vw-accent, 124, 58, 237), .1);
1921
+ animation: vwCmDialPulse 1.4s ease-out 0.3s infinite;
1922
+ }
1923
+ @keyframes vwCmRing {
1924
+ 0%, 100% { transform: scale(1); border-color: rgba(var(--vw-accent, 124, 58, 237), .3); }
1925
+ 50% { transform: scale(1.06); border-color: rgba(var(--vw-accent, 124, 58, 237), .6); }
1926
+ }
1927
+ @keyframes vwCmDialPulse {
1928
+ 0% { transform: scale(0.9); opacity: 1; }
1929
+ 100% { transform: scale(1.4); opacity: 0; }
1930
+ }
1931
+ .vw-cm-dialing-text { font-size: 13px; color: rgba(255, 255, 255, .55); font-weight: 500; }
1932
+ .vw-cm-dialing-phone { font-size: 12px; color: rgba(255, 255, 255, .35); letter-spacing: 0.06em; }
1933
+
1934
+ .vw-cm-error-view {
1935
+ display: flex; flex-direction: column; align-items: center;
1936
+ gap: 10px; padding: 24px 0;
1937
+ }
1938
+ .vw-cm-error-text { font-size: 13px; color: rgba(248, 113, 113, .9); text-align: center; line-height: 1.5; }
1939
+
1940
+ /* \u2500\u2500 Mobile \u2500\u2500 */
1941
+ @media (max-width: 640px) {
1942
+ .vw-hub-backdrop { padding: 14px; }
1943
+ .vw-hub-panel { max-width: 100%; margin-bottom: 56px; border-radius: 16px; padding: 22px 18px 18px; }
1944
+
1945
+ /* Chat: fullscreen takeover on mobile */
1946
+ .vw-hub-backdrop:has(.vw-hub-panel--chat) {
1947
+ padding: 0;
1948
+ background: var(--vw-panel-bg, rgba(16, 14, 20, .98));
1949
+ backdrop-filter: none;
1950
+ -webkit-backdrop-filter: none;
1951
+ }
1952
+ .vw-hub-panel--chat {
1953
+ max-width: 100%;
1954
+ height: 100%;
1955
+ margin-bottom: 0;
1956
+ border-radius: 0;
1957
+ border: none;
1958
+ box-shadow: none;
1959
+ }
1960
+ .vw-hub-panel--chat .vw-cv {
1961
+ height: 100%;
1962
+ max-height: 100%;
1963
+ }
1964
+ }
1965
+ `
1966
+ );
1967
+
1968
+ // src/widget/hub-chat-styles.ts
1969
+ var CHAT_VIEW_CSS = `
1970
+ /* \u2500\u2500 ChatView container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1971
+ .vw-cv {
1972
+ display: flex;
1973
+ flex-direction: column;
1974
+ height: 480px;
1975
+ max-height: 70vh;
1976
+ }
1977
+
1978
+ /* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1979
+ .vw-cv-head {
1980
+ display: flex;
1981
+ align-items: center;
1982
+ gap: 10px;
1983
+ padding: 14px 16px;
1984
+ border-bottom: 1px solid rgba(255,255,255,.06);
1985
+ }
1986
+
1987
+ .vw-cv-back {
1988
+ background: none;
1989
+ border: none;
1990
+ color: rgba(255,255,255,.5);
1991
+ cursor: pointer;
1992
+ padding: 4px;
1993
+ border-radius: 6px;
1994
+ transition: color .2s, background .2s;
1995
+ }
1996
+ .vw-cv-back:hover {
1997
+ color: #fff;
1998
+ background: rgba(255,255,255,.08);
1999
+ }
2000
+
2001
+ .vw-cv-avatar {
2002
+ width: 32px;
2003
+ height: 32px;
2004
+ border-radius: 50%;
2005
+ background: linear-gradient(135deg, rgba(var(--vw-color-accent, 197,140,95), .3), rgba(var(--vw-color-accent, 197,140,95), .1));
2006
+ display: flex;
2007
+ align-items: center;
2008
+ justify-content: center;
2009
+ font-size: 14px;
2010
+ font-weight: 600;
2011
+ color: rgb(var(--vw-color-accent, 197,140,95));
2012
+ flex-shrink: 0;
2013
+ }
2014
+
2015
+ .vw-cv-who {
2016
+ flex: 1;
2017
+ min-width: 0;
2018
+ }
2019
+ .vw-cv-name {
2020
+ display: block;
2021
+ font-size: 14px;
2022
+ font-weight: 600;
2023
+ color: #fff;
2024
+ line-height: 1.2;
2025
+ }
2026
+ .vw-cv-status {
2027
+ display: flex;
2028
+ align-items: center;
2029
+ gap: 5px;
2030
+ font-size: 11px;
2031
+ color: rgba(255,255,255,.45);
2032
+ }
2033
+
2034
+ .vw-cv-dot {
2035
+ width: 6px;
2036
+ height: 6px;
2037
+ border-radius: 50%;
2038
+ background: #4ade80;
2039
+ box-shadow: 0 0 6px rgba(74,222,128,.5);
2040
+ flex-shrink: 0;
2041
+ }
2042
+ .vw-cv-dot--off {
2043
+ background: #f59e0b;
2044
+ box-shadow: 0 0 6px rgba(245,158,11,.5);
2045
+ }
2046
+
2047
+ .vw-cv-actions {
2048
+ display: flex;
2049
+ gap: 4px;
2050
+ }
2051
+
2052
+ .vw-cv-action {
2053
+ background: none;
2054
+ border: 1px solid rgba(255,255,255,.08);
2055
+ color: rgba(255,255,255,.45);
2056
+ cursor: pointer;
2057
+ padding: 6px;
2058
+ border-radius: 8px;
2059
+ transition: all .2s;
2060
+ display: flex;
2061
+ align-items: center;
2062
+ justify-content: center;
2063
+ }
2064
+ .vw-cv-action:hover {
2065
+ color: #fff;
2066
+ background: rgba(255,255,255,.08);
2067
+ border-color: rgba(255,255,255,.15);
2068
+ }
2069
+ .vw-cv-action--voice {
2070
+ color: rgb(var(--vw-color-accent, 197,140,95));
2071
+ border-color: rgba(var(--vw-color-accent, 197,140,95), .25);
2072
+ }
2073
+ .vw-cv-action--voice:hover {
2074
+ background: rgba(var(--vw-color-accent, 197,140,95), .12);
2075
+ color: rgb(var(--vw-color-accent, 197,140,95));
2076
+ border-color: rgba(var(--vw-color-accent, 197,140,95), .4);
2077
+ }
2078
+
2079
+ /* \u2500\u2500 Messages body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2080
+ .vw-cv-body {
2081
+ flex: 1;
2082
+ overflow-y: auto;
2083
+ padding: 16px;
2084
+ display: flex;
2085
+ flex-direction: column;
2086
+ gap: 10px;
2087
+ scrollbar-width: thin;
2088
+ scrollbar-color: rgba(255,255,255,.1) transparent;
2089
+ }
2090
+
2091
+ .vw-cv-msg {
2092
+ max-width: 85%;
2093
+ padding: 10px 14px;
2094
+ border-radius: 14px;
2095
+ font-size: 13.5px;
2096
+ line-height: 1.5;
2097
+ word-break: break-word;
2098
+ }
2099
+
2100
+ .vw-cv-msg--user {
2101
+ align-self: flex-end;
2102
+ background: rgba(var(--vw-color-accent, 197,140,95), .18);
2103
+ color: rgba(255,255,255,.92);
2104
+ border-bottom-right-radius: 4px;
2105
+ }
2106
+
2107
+ .vw-cv-msg--bot {
2108
+ align-self: flex-start;
2109
+ background: rgba(255,255,255,.05);
2110
+ color: rgba(255,255,255,.85);
2111
+ border-bottom-left-radius: 4px;
2112
+ border: 1px solid rgba(255,255,255,.06);
2113
+ }
2114
+
2115
+ /* Markdown inside bot messages */
2116
+ .vw-cv-md p { margin: 0 0 6px; }
2117
+ .vw-cv-md p:last-child { margin-bottom: 0; }
2118
+ .vw-cv-md strong { color: #fff; font-weight: 600; }
2119
+ .vw-cv-md em { font-style: italic; }
2120
+ .vw-cv-md code {
2121
+ background: rgba(255,255,255,.08);
2122
+ padding: 1px 5px;
2123
+ border-radius: 4px;
2124
+ font-size: 12px;
2125
+ font-family: monospace;
2126
+ }
2127
+ .vw-cv-md a {
2128
+ color: rgb(var(--vw-color-accent, 197,140,95));
2129
+ text-decoration: underline;
2130
+ text-underline-offset: 2px;
2131
+ }
2132
+ .vw-cv-md ul, .vw-cv-md ol {
2133
+ margin: 4px 0;
2134
+ padding-left: 18px;
2135
+ }
2136
+ .vw-cv-md li { margin-bottom: 2px; }
2137
+
2138
+ /* Streaming cursor */
2139
+ .vw-cv-cursor {
2140
+ display: inline-block;
2141
+ width: 2px;
2142
+ height: 14px;
2143
+ background: rgb(var(--vw-color-accent, 197,140,95));
2144
+ margin-left: 2px;
2145
+ vertical-align: text-bottom;
2146
+ animation: vw-cv-blink .8s infinite;
2147
+ }
2148
+
2149
+ @keyframes vw-cv-blink {
2150
+ 0%, 100% { opacity: 1; }
2151
+ 50% { opacity: 0; }
2152
+ }
2153
+
2154
+ /* Typing skeleton */
2155
+ .vw-cv-skeleton {
2156
+ display: flex;
2157
+ gap: 4px;
2158
+ padding: 14px 18px;
2159
+ }
2160
+ .vw-cv-sk-dot {
2161
+ width: 7px;
2162
+ height: 7px;
2163
+ border-radius: 50%;
2164
+ background: rgba(255,255,255,.25);
2165
+ animation: vw-cv-bounce 1.4s infinite ease-in-out;
2166
+ }
2167
+ .vw-cv-sk-dot:nth-child(2) { animation-delay: .16s; }
2168
+ .vw-cv-sk-dot:nth-child(3) { animation-delay: .32s; }
2169
+
2170
+ @keyframes vw-cv-bounce {
2171
+ 0%, 80%, 100% { transform: translateY(0); }
2172
+ 40% { transform: translateY(-5px); }
2173
+ }
2174
+
2175
+ /* Tool calls */
2176
+ .vw-cv-tool {
2177
+ display: flex;
2178
+ align-items: center;
2179
+ gap: 6px;
2180
+ padding: 6px 12px;
2181
+ margin: 2px 0;
2182
+ border-radius: 10px;
2183
+ font-size: 11px;
2184
+ font-family: monospace;
2185
+ background: rgba(167,139,250,.06);
2186
+ border: 1px solid rgba(167,139,250,.15);
2187
+ align-self: center;
2188
+ max-width: 90%;
2189
+ }
2190
+ .vw-cv-tool-icon { font-size: 12px; }
2191
+ .vw-cv-tool-name { color: rgb(167,139,250); font-weight: 600; }
2192
+ .vw-cv-tool-args { color: rgba(255,255,255,.4); font-size: 10px; word-break: break-all; }
2193
+
2194
+ /* \u2500\u2500 Quick options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2195
+ .vw-cv-quick {
2196
+ display: flex;
2197
+ flex-wrap: wrap;
2198
+ gap: 6px;
2199
+ padding: 8px 16px;
2200
+ border-top: 1px solid rgba(255,255,255,.04);
2201
+ }
2202
+ .vw-cv-quick button {
2203
+ background: rgba(255,255,255,.05);
2204
+ border: 1px solid rgba(255,255,255,.1);
2205
+ color: rgba(255,255,255,.7);
2206
+ padding: 6px 12px;
2207
+ border-radius: 20px;
2208
+ font-size: 12px;
2209
+ cursor: pointer;
2210
+ transition: all .2s;
2211
+ white-space: nowrap;
2212
+ }
2213
+ .vw-cv-quick button:hover:not(:disabled) {
2214
+ background: rgba(var(--vw-color-accent, 197,140,95), .12);
2215
+ border-color: rgba(var(--vw-color-accent, 197,140,95), .3);
2216
+ color: rgb(var(--vw-color-accent, 197,140,95));
2217
+ }
2218
+ .vw-cv-quick button:disabled {
2219
+ opacity: .4;
2220
+ cursor: default;
2221
+ }
2222
+
2223
+ /* \u2500\u2500 Input bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
2224
+ .vw-cv-input {
2225
+ display: flex;
2226
+ gap: 8px;
2227
+ padding: 12px 16px;
2228
+ border-top: 1px solid rgba(255,255,255,.06);
2229
+ }
2230
+ .vw-cv-input input {
2231
+ flex: 1;
2232
+ background: rgba(255,255,255,.05);
2233
+ border: 1px solid rgba(255,255,255,.1);
2234
+ color: #fff;
2235
+ padding: 10px 14px;
2236
+ border-radius: 12px;
2237
+ font-size: 16px; /* \u226516px prevents iOS Safari auto-zoom on focus */
2238
+ outline: none;
2239
+ transition: border-color .2s;
2240
+ }
2241
+ .vw-cv-input input::placeholder {
2242
+ color: rgba(255,255,255,.3);
2243
+ }
2244
+ .vw-cv-input input:focus {
2245
+ border-color: rgba(var(--vw-color-accent, 197,140,95), .4);
2246
+ }
2247
+
2248
+ .vw-cv-send {
2249
+ background: rgb(var(--vw-color-accent, 197,140,95));
2250
+ border: none;
2251
+ color: #fff;
2252
+ width: 38px;
2253
+ height: 38px;
2254
+ border-radius: 10px;
2255
+ cursor: pointer;
2256
+ display: flex;
2257
+ align-items: center;
2258
+ justify-content: center;
2259
+ transition: opacity .2s, transform .15s;
2260
+ flex-shrink: 0;
2261
+ }
2262
+ .vw-cv-send:hover:not(:disabled) {
2263
+ transform: scale(1.05);
2264
+ }
2265
+ .vw-cv-send:disabled {
2266
+ opacity: .3;
2267
+ cursor: default;
2268
+ }
2269
+ `;
2270
+
2271
+ // src/chat/ChatSession.ts
2272
+ var INITIAL_STATE2 = {
2273
+ status: "idle",
2274
+ error: null,
2275
+ messages: [],
2276
+ typing: false,
2277
+ streamingText: "",
2278
+ sessionId: null
2279
+ };
2280
+ var ChatSession = class extends EventTarget {
2281
+ constructor(opts) {
2282
+ super();
2283
+ this.opts = opts;
2284
+ }
2285
+ opts;
2286
+ state = { ...INITIAL_STATE2 };
2287
+ listeners = /* @__PURE__ */ new Set();
2288
+ ws = null;
2289
+ reconnectTimer = null;
2290
+ msgCounter = 0;
2291
+ /** Read-only snapshot of current state (stable ref until next mutation). */
2292
+ getState() {
2293
+ return this.state;
2294
+ }
2295
+ /** Subscribe to ANY state change (for React useSyncExternalStore). */
2296
+ subscribe(listener) {
2297
+ this.listeners.add(listener);
2298
+ return () => {
2299
+ this.listeners.delete(listener);
2300
+ };
2301
+ }
2302
+ setState(patch) {
2303
+ const prev = this.state;
2304
+ this.state = { ...prev, ...patch };
2305
+ for (const l3 of this.listeners) l3();
2306
+ if (patch.status !== void 0 && patch.status !== prev.status) {
2307
+ this.dispatchEvent(
2308
+ new CustomEvent("status", { detail: { status: this.state.status } })
2309
+ );
2310
+ }
2311
+ if (patch.error !== void 0 && patch.error !== null && patch.error !== prev.error) {
2312
+ this.dispatchEvent(
2313
+ new CustomEvent("error", { detail: { error: this.state.error } })
2314
+ );
2315
+ }
2316
+ this.dispatchEvent(
2317
+ new CustomEvent("change", { detail: { state: this.state } })
2318
+ );
2319
+ }
2320
+ setMessages(updater) {
2321
+ const next = updater(this.state.messages);
2322
+ this.setState({ messages: next });
2323
+ const last = next[next.length - 1];
2324
+ if (last) {
2325
+ this.dispatchEvent(
2326
+ new CustomEvent("message", { detail: { message: last } })
2327
+ );
2328
+ }
2329
+ }
2330
+ // ── Connection ──────────────────────────────────────────────────────
2331
+ async connect() {
2332
+ if (this.ws) return;
2333
+ try {
2334
+ this.setState({
2335
+ status: "connecting",
2336
+ error: null
2337
+ });
2338
+ const base = (this.opts.server ?? "https://voice.pinecall.io").replace(
2339
+ /\/$/,
2340
+ ""
2341
+ );
2342
+ let token;
2343
+ let chatServer;
2344
+ if (this.opts.tokenProvider) {
2345
+ const t2 = await this.opts.tokenProvider();
2346
+ token = t2.token;
2347
+ chatServer = t2.server;
2348
+ } else {
2349
+ const tRes = await fetch(
2350
+ `${base}/chat/token?agent_id=${encodeURIComponent(this.opts.agent)}`
2351
+ );
2352
+ if (!tRes.ok) {
2353
+ const body = await tRes.text();
2354
+ throw new Error(`Token: ${tRes.status} ${body}`);
2355
+ }
2356
+ const t2 = await tRes.json();
2357
+ token = t2.token;
2358
+ chatServer = t2.server;
2359
+ }
2360
+ const wsBase = (chatServer || base).replace(/^http:/, "ws:").replace(/^https:/, "wss:");
2361
+ const ws = new WebSocket(`${wsBase}/chat/ws?token=${token}`);
2362
+ this.ws = ws;
2363
+ ws.onopen = () => {
2364
+ };
2365
+ ws.onmessage = (evt) => this.handleMessage(evt);
2366
+ ws.onerror = () => {
2367
+ this.setState({ error: "WebSocket error", status: "error" });
2368
+ };
2369
+ ws.onclose = (evt) => {
2370
+ this.ws = null;
2371
+ if (this.state.status === "connected") {
2372
+ this.setState({ status: "idle" });
2373
+ }
2374
+ };
2375
+ } catch (err) {
2376
+ this.setState({
2377
+ error: err instanceof Error ? err.message : String(err),
2378
+ status: "error"
2379
+ });
2380
+ this.ws = null;
2381
+ }
2382
+ }
2383
+ handleMessage(evt) {
2384
+ let d2;
2385
+ try {
2386
+ d2 = JSON.parse(evt.data);
2387
+ } catch {
2388
+ return;
2389
+ }
2390
+ switch (d2.event) {
2391
+ case "chat.connected":
2392
+ this.setState({
2393
+ status: "connected",
2394
+ sessionId: d2.session_id ?? null
2395
+ });
2396
+ break;
2397
+ case "chat.token":
2398
+ case "llm.chat.token":
2399
+ this.setState({
2400
+ typing: true,
2401
+ streamingText: d2.text ?? ""
2402
+ });
2403
+ this.setMessages((prev) => {
2404
+ const idx = prev.findIndex(
2405
+ (m2) => m2.messageId === d2.message_id && m2.isStreaming
2406
+ );
2407
+ if (idx >= 0) {
2408
+ return prev.map(
2409
+ (m2, i) => i === idx ? { ...m2, text: d2.text ?? "" } : m2
2410
+ );
2411
+ }
2412
+ return [
2413
+ ...prev,
2414
+ {
2415
+ id: ++this.msgCounter,
2416
+ role: "bot",
2417
+ text: d2.text ?? "",
2418
+ messageId: d2.message_id,
2419
+ isStreaming: true
2420
+ }
2421
+ ];
2422
+ });
2423
+ break;
2424
+ case "chat.done":
2425
+ case "llm.chat.done":
2426
+ this.setState({
2427
+ typing: false,
2428
+ streamingText: ""
2429
+ });
2430
+ this.setMessages((prev) => {
2431
+ const idx = prev.findIndex(
2432
+ (m2) => m2.messageId === d2.message_id && m2.isStreaming
2433
+ );
2434
+ if (idx >= 0) {
2435
+ return prev.map(
2436
+ (m2, i) => i === idx ? { ...m2, text: d2.text ?? m2.text, isStreaming: false } : m2
2437
+ );
2438
+ }
2439
+ return [
2440
+ ...prev,
2441
+ {
2442
+ id: ++this.msgCounter,
2443
+ role: "bot",
2444
+ text: d2.text ?? "",
2445
+ messageId: d2.message_id,
2446
+ isStreaming: false
2447
+ }
2448
+ ];
2449
+ });
2450
+ break;
2451
+ case "chat.error":
2452
+ case "llm.chat.error":
2453
+ this.setState({
2454
+ typing: false,
2455
+ error: d2.error ?? "Unknown error"
2456
+ });
2457
+ break;
2458
+ case "error":
2459
+ this.setState({
2460
+ error: d2.error ?? "Unknown error",
2461
+ status: "error"
2462
+ });
2463
+ break;
2464
+ }
2465
+ this.dispatchEvent(new CustomEvent("event", { detail: d2 }));
2466
+ }
2467
+ // ── Actions ─────────────────────────────────────────────────────────
2468
+ /** Send a text message to the agent. */
2469
+ send(text) {
2470
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
2471
+ const trimmed = text.trim();
2472
+ if (!trimmed) return;
2473
+ this.setMessages((prev) => [
2474
+ ...prev,
2475
+ {
2476
+ id: ++this.msgCounter,
2477
+ role: "user",
2478
+ text: trimmed
2479
+ }
2480
+ ]);
2481
+ this.ws.send(JSON.stringify({ event: "message", text: trimmed }));
2482
+ }
2483
+ /**
2484
+ * Set or clear a keyed context block in the LLM system prompt.
2485
+ *
2486
+ * @example
2487
+ * ```ts
2488
+ * session.setContext("form", JSON.stringify({ name: "Juan" }));
2489
+ * session.setContext("form", null); // clear
2490
+ * ```
2491
+ */
2492
+ setContext(key, value) {
2493
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
2494
+ this.ws.send(JSON.stringify({ event: "set_context", key, value }));
2495
+ }
2496
+ /** Disconnect the chat session. */
2497
+ disconnect() {
2498
+ if (this.reconnectTimer) {
2499
+ clearTimeout(this.reconnectTimer);
2500
+ this.reconnectTimer = null;
2501
+ }
2502
+ if (this.ws) {
2503
+ this.ws.close();
2504
+ this.ws = null;
2505
+ }
2506
+ this.setState({ status: "idle", typing: false, streamingText: "" });
2507
+ }
2508
+ /** Tear down the session and clear subscribers. Do not reuse after this. */
2509
+ destroy() {
2510
+ this.disconnect();
2511
+ this.setState({ status: "destroyed" });
2512
+ this.listeners.clear();
2513
+ }
2514
+ };
2515
+
2516
+ // src/chat/react.tsx
2517
+ function usePinecallChat(opts) {
2518
+ const [session, setSession] = react.useState(
2519
+ () => new ChatSession(opts)
2520
+ );
2521
+ const state = react.useSyncExternalStore(
2522
+ react.useCallback((cb) => session.subscribe(cb), [session]),
2523
+ react.useCallback(() => session.getState(), [session])
2524
+ );
2525
+ react.useEffect(() => {
2526
+ let activeSession = session;
2527
+ if (activeSession.getState().status === "destroyed") {
2528
+ activeSession = new ChatSession(opts);
2529
+ setSession(activeSession);
2530
+ }
2531
+ if (opts.autoConnect !== false) {
2532
+ activeSession.connect();
2533
+ }
2534
+ return () => {
2535
+ activeSession.destroy();
2536
+ };
2537
+ }, []);
2538
+ return {
2539
+ messages: state.messages,
2540
+ send: react.useCallback((text) => session.send(text), [session]),
2541
+ connected: state.status === "connected",
2542
+ typing: state.typing,
2543
+ streamingText: state.streamingText,
2544
+ error: state.error,
2545
+ setContext: react.useCallback(
2546
+ (key, value) => session.setContext(key, value),
2547
+ [session]
2548
+ ),
2549
+ connect: react.useCallback(() => session.connect(), [session]),
2550
+ disconnect: react.useCallback(() => session.disconnect(), [session])
2551
+ };
2552
+ }
2553
+
2554
+ // node_modules/.pnpm/marked@18.0.5/node_modules/marked/lib/marked.esm.js
2555
+ function M() {
2556
+ return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
2557
+ }
2558
+ var T = M();
2559
+ function N(l3) {
2560
+ T = l3;
2561
+ }
2562
+ var _ = { exec: () => null };
2563
+ function E(l3) {
2564
+ let e = [];
2565
+ return (t2) => {
2566
+ let n = Math.max(0, Math.min(3, t2 - 1)), s = e[n];
2567
+ return s || (s = l3(n), e[n] = s), s;
2568
+ };
2569
+ }
2570
+ function d(l3, e = "") {
2571
+ let t2 = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => {
2572
+ let i = typeof r == "string" ? r : r.source;
2573
+ return i = i.replace(m.caret, "$1"), t2 = t2.replace(s, i), n;
2574
+ }, getRegex: () => new RegExp(t2, e) };
2575
+ return n;
2576
+ }
2577
+ var Te = ((l3 = "") => {
2578
+ try {
2579
+ return !!new RegExp("(?<=1)(?<!1)" + l3);
2580
+ } catch {
2581
+ return false;
2582
+ }
2583
+ })();
2584
+ var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: E((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: E((l3) => new RegExp(`^ {0,${l3}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)), fencesBeginRegex: E((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: E((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: E((l3) => new RegExp(`^ {0,${l3}}<(?:[a-z].*>|!--)`, "i")), blockquoteBeginRegex: E((l3) => new RegExp(`^ {0,${l3}}>`)) };
2585
+ var Oe = /^(?:[ \t]*(?:\n|$))+/;
2586
+ var we = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
2587
+ var ye = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
2588
+ var B = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
2589
+ var Pe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
2590
+ var j = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
2591
+ var oe = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
2592
+ var ae = d(oe).replace(/bull/g, j).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
2593
+ var Se = d(oe).replace(/bull/g, j).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
2594
+ var F = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
2595
+ var $e = /^[^\n]+/;
2596
+ var U = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
2597
+ var Le = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", U).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
2598
+ var _e = d(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, j).getRegex();
2599
+ var H = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
2600
+ var K = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
2601
+ var ze = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", K).replace("tag", H).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
2602
+ var le = d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
2603
+ var Me = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", le).getRegex();
2604
+ var W = { blockquote: Me, code: we, def: Le, fences: ye, heading: Pe, hr: B, html: ze, lheading: ae, list: _e, newline: Oe, paragraph: le, table: _, text: $e };
2605
+ var se = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex();
2606
+ var Ee = { ...W, lheading: Se, table: se, paragraph: d(F).replace("hr", B).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", H).getRegex() };
2607
+ var Ie = { ...W, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", K).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: _, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(F).replace("hr", B).replace("heading", ` *#{1,6} *[^
2608
+ ]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
2609
+ var Ae = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
2610
+ var Ce = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
2611
+ var ue = /^( {2,}|\\)\n(?!\s*$)/;
2612
+ var Be = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
2613
+ var I = /[\p{P}\p{S}]/u;
2614
+ var Z = /[\s\p{P}\p{S}]/u;
2615
+ var X = /[^\s\p{P}\p{S}]/u;
2616
+ var De = d(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, Z).getRegex();
2617
+ var pe = /(?!~)[\p{P}\p{S}]/u;
2618
+ var qe = /(?!~)[\s\p{P}\p{S}]/u;
2619
+ var ve = /(?:[^\s\p{P}\p{S}]|~)/u;
2620
+ var He = d(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Te ? "(?<!`)()" : "(^^|[^`])").replace("code", /(?<b>`+)[^`]+\k<b>(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex();
2621
+ var ce = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
2622
+ var Ze = d(ce, "u").replace(/punct/g, I).getRegex();
2623
+ var Ge = d(ce, "u").replace(/punct/g, pe).getRegex();
2624
+ var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
2625
+ var Ne = d(he, "gu").replace(/notPunctSpace/g, X).replace(/punctSpace/g, Z).replace(/punct/g, I).getRegex();
2626
+ var Qe = d(he, "gu").replace(/notPunctSpace/g, ve).replace(/punctSpace/g, qe).replace(/punct/g, pe).getRegex();
2627
+ var je = d("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, X).replace(/punctSpace/g, Z).replace(/punct/g, I).getRegex();
2628
+ var Fe = d(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, I).getRegex();
2629
+ var Ue = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)";
2630
+ var Ke = d(Ue, "gu").replace(/notPunctSpace/g, X).replace(/punctSpace/g, Z).replace(/punct/g, I).getRegex();
2631
+ var We = d(/\\(punct)/, "gu").replace(/punct/g, I).getRegex();
2632
+ var Xe = d(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
2633
+ var Je = d(K).replace("(?:-->|$)", "-->").getRegex();
2634
+ var Ve = d("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", Je).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
2635
+ var v = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/;
2636
+ var Ye = d(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", v).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
2637
+ var ke = d(/^!?\[(label)\]\[(ref)\]/).replace("label", v).replace("ref", U).getRegex();
2638
+ var de2 = d(/^!?\[(ref)\](?:\[\])?/).replace("ref", U).getRegex();
2639
+ var et = d("reflink|nolink(?!\\()", "g").replace("reflink", ke).replace("nolink", de2).getRegex();
2640
+ var ie = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
2641
+ var J = { _backpedal: _, anyPunctuation: We, autolink: Xe, blockSkip: He, br: ue, code: Ce, del: _, delLDelim: _, delRDelim: _, emStrongLDelim: Ze, emStrongRDelimAst: Ne, emStrongRDelimUnd: je, escape: Ae, link: Ye, nolink: de2, punctuation: De, reflink: ke, reflinkSearch: et, tag: Ve, text: Be, url: _ };
2642
+ var tt = { ...J, link: d(/^!?\[(label)\]\((.*?)\)/).replace("label", v).getRegex(), reflink: d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", v).getRegex() };
2643
+ var Q = { ...J, emStrongRDelimAst: Qe, emStrongLDelim: Ge, delLDelim: Fe, delRDelim: Ke, url: d(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", ie).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: d(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/).replace("protocol", ie).getRegex() };
2644
+ var nt = { ...Q, br: d(ue).replace("{2,}", "*").getRegex(), text: d(Q.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
2645
+ var D = { normal: W, gfm: Ee, pedantic: Ie };
2646
+ var A = { normal: J, gfm: Q, breaks: nt, pedantic: tt };
2647
+ var rt = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
2648
+ var ge = (l3) => rt[l3];
2649
+ function O(l3, e) {
2650
+ if (e) {
2651
+ if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge);
2652
+ } else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge);
2653
+ return l3;
2654
+ }
2655
+ function V(l3) {
2656
+ try {
2657
+ l3 = encodeURI(l3).replace(m.percentDecode, "%");
2658
+ } catch {
2659
+ return null;
2660
+ }
2661
+ return l3;
2662
+ }
2663
+ function Y(l3, e) {
2664
+ let t2 = l3.replace(m.findPipe, (r, i, o) => {
2665
+ let u = false, a = i;
2666
+ for (; --a >= 0 && o[a] === "\\"; ) u = !u;
2667
+ return u ? "|" : " |";
2668
+ }), n = t2.split(m.splitPipe), s = 0;
2669
+ if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e);
2670
+ else for (; n.length < e; ) n.push("");
2671
+ for (; s < n.length; s++) n[s] = n[s].trim().replace(m.slashPipe, "|");
2672
+ return n;
2673
+ }
2674
+ function $(l3, e, t2) {
2675
+ let n = l3.length;
2676
+ if (n === 0) return "";
2677
+ let s = 0;
2678
+ for (; s < n; ) {
2679
+ let r = l3.charAt(n - s - 1);
2680
+ if (r === e && true) s++;
2681
+ else break;
2682
+ }
2683
+ return l3.slice(0, n - s);
2684
+ }
2685
+ function ee(l3) {
2686
+ let e = l3.split(`
2687
+ `), t2 = e.length - 1;
2688
+ for (; t2 >= 0 && m.blankLine.test(e[t2]); ) t2--;
2689
+ return e.length - t2 <= 2 ? l3 : e.slice(0, t2 + 1).join(`
2690
+ `);
2691
+ }
2692
+ function fe(l3, e) {
2693
+ if (l3.indexOf(e[1]) === -1) return -1;
2694
+ let t2 = 0;
2695
+ for (let n = 0; n < l3.length; n++) if (l3[n] === "\\") n++;
2696
+ else if (l3[n] === e[0]) t2++;
2697
+ else if (l3[n] === e[1] && (t2--, t2 < 0)) return n;
2698
+ return t2 > 0 ? -2 : -1;
2699
+ }
2700
+ function me(l3, e = 0) {
2701
+ let t2 = e, n = "";
2702
+ for (let s of l3) if (s === " ") {
2703
+ let r = 4 - t2 % 4;
2704
+ n += " ".repeat(r), t2 += r;
2705
+ } else n += s, t2++;
2706
+ return n;
2707
+ }
2708
+ function xe(l3, e, t2, n, s) {
2709
+ let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1");
2710
+ n.state.inLink = true;
2711
+ let u = { type: l3[0].charAt(0) === "!" ? "image" : "link", raw: t2, href: r, title: i, text: o, tokens: n.inlineTokens(o) };
2712
+ return n.state.inLink = false, u;
2713
+ }
2714
+ function st(l3, e, t2) {
2715
+ let n = l3.match(t2.other.indentCodeCompensation);
2716
+ if (n === null) return e;
2717
+ let s = n[1];
2718
+ return e.split(`
2719
+ `).map((r) => {
2720
+ let i = r.match(t2.other.beginningSpace);
2721
+ if (i === null) return r;
2722
+ let [o] = i;
2723
+ return o.length >= s.length ? r.slice(s.length) : r;
2724
+ }).join(`
2725
+ `);
2726
+ }
2727
+ var w = class {
2728
+ options;
2729
+ rules;
2730
+ lexer;
2731
+ constructor(e) {
2732
+ this.options = e || T;
2733
+ }
2734
+ space(e) {
2735
+ let t2 = this.rules.block.newline.exec(e);
2736
+ if (t2 && t2[0].length > 0) return { type: "space", raw: t2[0] };
2737
+ }
2738
+ code(e) {
2739
+ let t2 = this.rules.block.code.exec(e);
2740
+ if (t2) {
2741
+ let n = this.options.pedantic ? t2[0] : ee(t2[0]), s = n.replace(this.rules.other.codeRemoveIndent, "");
2742
+ return { type: "code", raw: n, codeBlockStyle: "indented", text: s };
2743
+ }
2744
+ }
2745
+ fences(e) {
2746
+ let t2 = this.rules.block.fences.exec(e);
2747
+ if (t2) {
2748
+ let n = t2[0], s = st(n, t2[3] || "", this.rules);
2749
+ return { type: "code", raw: n, lang: t2[2] ? t2[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t2[2], text: s };
2750
+ }
2751
+ }
2752
+ heading(e) {
2753
+ let t2 = this.rules.block.heading.exec(e);
2754
+ if (t2) {
2755
+ let n = t2[2].trim();
2756
+ if (this.rules.other.endingHash.test(n)) {
2757
+ let s = $(n, "#");
2758
+ (this.options.pedantic || !s || this.rules.other.endingSpaceChar.test(s)) && (n = s.trim());
2759
+ }
2760
+ return { type: "heading", raw: $(t2[0], `
2761
+ `), depth: t2[1].length, text: n, tokens: this.lexer.inline(n) };
2762
+ }
2763
+ }
2764
+ hr(e) {
2765
+ let t2 = this.rules.block.hr.exec(e);
2766
+ if (t2) return { type: "hr", raw: $(t2[0], `
2767
+ `) };
2768
+ }
2769
+ blockquote(e) {
2770
+ let t2 = this.rules.block.blockquote.exec(e);
2771
+ if (t2) {
2772
+ let n = $(t2[0], `
2773
+ `).split(`
2774
+ `), s = "", r = "", i = [];
2775
+ for (; n.length > 0; ) {
2776
+ let o = false, u = [], a;
2777
+ for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u.push(n[a]), o = true;
2778
+ else if (!o) u.push(n[a]);
2779
+ else break;
2780
+ n = n.slice(a);
2781
+ let c = u.join(`
2782
+ `), p = c.replace(this.rules.other.blockquoteSetextReplace, `
2783
+ $1`).replace(this.rules.other.blockquoteSetextReplace2, "");
2784
+ s = s ? `${s}
2785
+ ${c}` : c, r = r ? `${r}
2786
+ ${p}` : p;
2787
+ let k = this.lexer.state.top;
2788
+ if (this.lexer.state.top = true, this.lexer.blockTokens(p, i, true), this.lexer.state.top = k, n.length === 0) break;
2789
+ let h = i.at(-1);
2790
+ if (h?.type === "code") break;
2791
+ if (h?.type === "blockquote") {
2792
+ let R = h, f = R.raw + `
2793
+ ` + n.join(`
2794
+ `), S = this.blockquote(f);
2795
+ i[i.length - 1] = S, s = s.substring(0, s.length - R.raw.length) + S.raw, r = r.substring(0, r.length - R.text.length) + S.text;
2796
+ break;
2797
+ } else if (h?.type === "list") {
2798
+ let R = h, f = R.raw + `
2799
+ ` + n.join(`
2800
+ `), S = this.list(f);
2801
+ i[i.length - 1] = S, s = s.substring(0, s.length - h.raw.length) + S.raw, r = r.substring(0, r.length - R.raw.length) + S.raw, n = f.substring(i.at(-1).raw.length).split(`
2802
+ `);
2803
+ continue;
2804
+ }
2805
+ }
2806
+ return { type: "blockquote", raw: s, tokens: i, text: r };
2807
+ }
2808
+ }
2809
+ list(e) {
2810
+ let t2 = this.rules.block.list.exec(e);
2811
+ if (t2) {
2812
+ let n = t2[1].trim(), s = n.length > 1, r = { type: "list", raw: "", ordered: s, start: s ? +n.slice(0, -1) : "", loose: false, items: [] };
2813
+ n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]");
2814
+ let i = this.rules.other.listItemRegex(n), o = false;
2815
+ for (; e; ) {
2816
+ let a = false, c = "", p = "";
2817
+ if (!(t2 = i.exec(e)) || this.rules.block.hr.test(e)) break;
2818
+ c = t2[0], e = e.substring(c.length);
2819
+ let k = me(t2[2].split(`
2820
+ `, 1)[0], t2[1].length), h = e.split(`
2821
+ `, 1)[0], R = !k.trim(), f = 0;
2822
+ if (this.options.pedantic ? (f = 2, p = k.trimStart()) : R ? f = t2[1].length + 1 : (f = k.search(this.rules.other.nonSpaceChar), f = f > 4 ? 1 : f, p = k.slice(f), f += t2[1].length), R && this.rules.other.blankLine.test(h) && (c += h + `
2823
+ `, e = e.substring(h.length + 1), a = true), !a) {
2824
+ let S = this.rules.other.nextBulletRegex(f), te = this.rules.other.hrRegex(f), ne = this.rules.other.fencesBeginRegex(f), re = this.rules.other.headingBeginRegex(f), be = this.rules.other.htmlBeginRegex(f), Re = this.rules.other.blockquoteBeginRegex(f);
2825
+ for (; e; ) {
2826
+ let G = e.split(`
2827
+ `, 1)[0], C;
2828
+ if (h = G, this.options.pedantic ? (h = h.replace(this.rules.other.listReplaceNesting, " "), C = h) : C = h.replace(this.rules.other.tabCharGlobal, " "), ne.test(h) || re.test(h) || be.test(h) || Re.test(h) || S.test(h) || te.test(h)) break;
2829
+ if (C.search(this.rules.other.nonSpaceChar) >= f || !h.trim()) p += `
2830
+ ` + C.slice(f);
2831
+ else {
2832
+ if (R || k.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || ne.test(k) || re.test(k) || te.test(k)) break;
2833
+ p += `
2834
+ ` + h;
2835
+ }
2836
+ R = !h.trim(), c += G + `
2837
+ `, e = e.substring(G.length + 1), k = C.slice(f);
2838
+ }
2839
+ }
2840
+ r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(c) && (o = true)), r.items.push({ type: "list_item", raw: c, task: !!this.options.gfm && this.rules.other.listIsTask.test(p), loose: false, text: p, tokens: [] }), r.raw += c;
2841
+ }
2842
+ let u = r.items.at(-1);
2843
+ if (u) u.raw = u.raw.trimEnd(), u.text = u.text.trimEnd();
2844
+ else return;
2845
+ r.raw = r.raw.trimEnd();
2846
+ for (let a of r.items) {
2847
+ this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []);
2848
+ let c = a.tokens[0];
2849
+ if (a.task && (c?.type === "text" || c?.type === "paragraph")) {
2850
+ a.text = a.text.replace(this.rules.other.listReplaceTask, ""), c.raw = c.raw.replace(this.rules.other.listReplaceTask, ""), c.text = c.text.replace(this.rules.other.listReplaceTask, "");
2851
+ for (let k = this.lexer.inlineQueue.length - 1; k >= 0; k--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[k].src)) {
2852
+ this.lexer.inlineQueue[k].src = this.lexer.inlineQueue[k].src.replace(this.rules.other.listReplaceTask, "");
2853
+ break;
2854
+ }
2855
+ let p = this.rules.other.listTaskCheckbox.exec(a.raw);
2856
+ if (p) {
2857
+ let k = { type: "checkbox", raw: p[0] + " ", checked: p[0] !== "[ ]" };
2858
+ a.checked = k.checked, r.loose ? a.tokens[0] && ["paragraph", "text"].includes(a.tokens[0].type) && "tokens" in a.tokens[0] && a.tokens[0].tokens ? (a.tokens[0].raw = k.raw + a.tokens[0].raw, a.tokens[0].text = k.raw + a.tokens[0].text, a.tokens[0].tokens.unshift(k)) : a.tokens.unshift({ type: "paragraph", raw: k.raw, text: k.raw, tokens: [k] }) : a.tokens.unshift(k);
2859
+ }
2860
+ } else a.task && (a.task = false);
2861
+ if (!r.loose) {
2862
+ let p = a.tokens.filter((h) => h.type === "space"), k = p.length > 0 && p.some((h) => this.rules.other.anyLine.test(h.raw));
2863
+ r.loose = k;
2864
+ }
2865
+ }
2866
+ if (r.loose) for (let a of r.items) {
2867
+ a.loose = true;
2868
+ for (let c of a.tokens) c.type === "text" && (c.type = "paragraph");
2869
+ }
2870
+ return r;
2871
+ }
2872
+ }
2873
+ html(e) {
2874
+ let t2 = this.rules.block.html.exec(e);
2875
+ if (t2) {
2876
+ let n = ee(t2[0]);
2877
+ return { type: "html", block: true, raw: n, pre: t2[1] === "pre" || t2[1] === "script" || t2[1] === "style", text: n };
2878
+ }
2879
+ }
2880
+ def(e) {
2881
+ let t2 = this.rules.block.def.exec(e);
2882
+ if (t2) {
2883
+ let n = t2[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, " "), s = t2[2] ? t2[2].replace(this.rules.other.hrefBrackets, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "", r = t2[3] ? t2[3].substring(1, t2[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : t2[3];
2884
+ return { type: "def", tag: n, raw: $(t2[0], `
2885
+ `), href: s, title: r };
2886
+ }
2887
+ }
2888
+ table(e) {
2889
+ let t2 = this.rules.block.table.exec(e);
2890
+ if (!t2 || !this.rules.other.tableDelimiter.test(t2[2])) return;
2891
+ let n = Y(t2[1]), s = t2[2].replace(this.rules.other.tableAlignChars, "").split("|"), r = t2[3]?.trim() ? t2[3].replace(this.rules.other.tableRowBlankLine, "").split(`
2892
+ `) : [], i = { type: "table", raw: $(t2[0], `
2893
+ `), header: [], align: [], rows: [] };
2894
+ if (n.length === s.length) {
2895
+ for (let o of s) this.rules.other.tableAlignRight.test(o) ? i.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? i.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? i.align.push("left") : i.align.push(null);
2896
+ for (let o = 0; o < n.length; o++) i.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: i.align[o] });
2897
+ for (let o of r) i.rows.push(Y(o, i.header.length).map((u, a) => ({ text: u, tokens: this.lexer.inline(u), header: false, align: i.align[a] })));
2898
+ return i;
2899
+ }
2900
+ }
2901
+ lheading(e) {
2902
+ let t2 = this.rules.block.lheading.exec(e);
2903
+ if (t2) {
2904
+ let n = t2[1].trim();
2905
+ return { type: "heading", raw: $(t2[0], `
2906
+ `), depth: t2[2].charAt(0) === "=" ? 1 : 2, text: n, tokens: this.lexer.inline(n) };
2907
+ }
2908
+ }
2909
+ paragraph(e) {
2910
+ let t2 = this.rules.block.paragraph.exec(e);
2911
+ if (t2) {
2912
+ let n = t2[1].charAt(t2[1].length - 1) === `
2913
+ ` ? t2[1].slice(0, -1) : t2[1];
2914
+ return { type: "paragraph", raw: t2[0], text: n, tokens: this.lexer.inline(n) };
2915
+ }
2916
+ }
2917
+ text(e) {
2918
+ let t2 = this.rules.block.text.exec(e);
2919
+ if (t2) return { type: "text", raw: t2[0], text: t2[0], tokens: this.lexer.inline(t2[0]) };
2920
+ }
2921
+ escape(e) {
2922
+ let t2 = this.rules.inline.escape.exec(e);
2923
+ if (t2) return { type: "escape", raw: t2[0], text: t2[1] };
2924
+ }
2925
+ tag(e) {
2926
+ let t2 = this.rules.inline.tag.exec(e);
2927
+ if (t2) return !this.lexer.state.inLink && this.rules.other.startATag.test(t2[0]) ? this.lexer.state.inLink = true : this.lexer.state.inLink && this.rules.other.endATag.test(t2[0]) && (this.lexer.state.inLink = false), !this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(t2[0]) ? this.lexer.state.inRawBlock = true : this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(t2[0]) && (this.lexer.state.inRawBlock = false), { type: "html", raw: t2[0], inLink: this.lexer.state.inLink, inRawBlock: this.lexer.state.inRawBlock, block: false, text: t2[0] };
2928
+ }
2929
+ link(e) {
2930
+ let t2 = this.rules.inline.link.exec(e);
2931
+ if (t2) {
2932
+ let n = t2[2].trim();
2933
+ if (!this.options.pedantic && this.rules.other.startAngleBracket.test(n)) {
2934
+ if (!this.rules.other.endAngleBracket.test(n)) return;
2935
+ let i = $(n.slice(0, -1), "\\");
2936
+ if ((n.length - i.length) % 2 === 0) return;
2937
+ } else {
2938
+ let i = fe(t2[2], "()");
2939
+ if (i === -2) return;
2940
+ if (i > -1) {
2941
+ let u = (t2[0].indexOf("!") === 0 ? 5 : 4) + t2[1].length + i;
2942
+ t2[2] = t2[2].substring(0, i), t2[0] = t2[0].substring(0, u).trim(), t2[3] = "";
2943
+ }
2944
+ }
2945
+ let s = t2[2], r = "";
2946
+ if (this.options.pedantic) {
2947
+ let i = this.rules.other.pedanticHrefTitle.exec(s);
2948
+ i && (s = i[1], r = i[3]);
2949
+ } else r = t2[3] ? t2[3].slice(1, -1) : "";
2950
+ return s = s.trim(), this.rules.other.startAngleBracket.test(s) && (this.options.pedantic && !this.rules.other.endAngleBracket.test(n) ? s = s.slice(1) : s = s.slice(1, -1)), xe(t2, { href: s && s.replace(this.rules.inline.anyPunctuation, "$1"), title: r && r.replace(this.rules.inline.anyPunctuation, "$1") }, t2[0], this.lexer, this.rules);
2951
+ }
2952
+ }
2953
+ reflink(e, t2) {
2954
+ let n;
2955
+ if ((n = this.rules.inline.reflink.exec(e)) || (n = this.rules.inline.nolink.exec(e))) {
2956
+ let s = (n[2] || n[1]).replace(this.rules.other.multipleSpaceGlobal, " "), r = t2[s.toLowerCase()];
2957
+ if (!r) {
2958
+ let i = n[0].charAt(0);
2959
+ return { type: "text", raw: i, text: i };
2960
+ }
2961
+ return xe(n, r, n[0], this.lexer, this.rules);
2962
+ }
2963
+ }
2964
+ emStrong(e, t2, n = "") {
2965
+ let s = this.rules.inline.emStrongLDelim.exec(e);
2966
+ if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return;
2967
+ if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) {
2968
+ let i = [...s[0]].length - 1, o, u, a = i, c = 0, p = s[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
2969
+ for (p.lastIndex = 0, t2 = t2.slice(-1 * e.length + i); (s = p.exec(t2)) !== null; ) {
2970
+ if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o) continue;
2971
+ if (u = [...o].length, s[3] || s[4]) {
2972
+ a += u;
2973
+ continue;
2974
+ } else if ((s[5] || s[6]) && i % 3 && !((i + u) % 3)) {
2975
+ c += u;
2976
+ continue;
2977
+ }
2978
+ if (a -= u, a > 0) continue;
2979
+ u = Math.min(u, u + a + c);
2980
+ let k = [...s[0]][0].length, h = e.slice(0, i + s.index + k + u);
2981
+ if (Math.min(i, u) % 2) {
2982
+ let f = h.slice(1, -1);
2983
+ return { type: "em", raw: h, text: f, tokens: this.lexer.inlineTokens(f) };
2984
+ }
2985
+ let R = h.slice(2, -2);
2986
+ return { type: "strong", raw: h, text: R, tokens: this.lexer.inlineTokens(R) };
2987
+ }
2988
+ }
2989
+ }
2990
+ codespan(e) {
2991
+ let t2 = this.rules.inline.code.exec(e);
2992
+ if (t2) {
2993
+ let n = t2[2].replace(this.rules.other.newLineCharGlobal, " "), s = this.rules.other.nonSpaceChar.test(n), r = this.rules.other.startingSpaceChar.test(n) && this.rules.other.endingSpaceChar.test(n);
2994
+ return s && r && (n = n.substring(1, n.length - 1)), { type: "codespan", raw: t2[0], text: n };
2995
+ }
2996
+ }
2997
+ br(e) {
2998
+ let t2 = this.rules.inline.br.exec(e);
2999
+ if (t2) return { type: "br", raw: t2[0] };
3000
+ }
3001
+ del(e, t2, n = "") {
3002
+ let s = this.rules.inline.delLDelim.exec(e);
3003
+ if (!s) return;
3004
+ if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) {
3005
+ let i = [...s[0]].length - 1, o, u, a = i, c = this.rules.inline.delRDelim;
3006
+ for (c.lastIndex = 0, t2 = t2.slice(-1 * e.length + i); (s = c.exec(t2)) !== null; ) {
3007
+ if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (u = [...o].length, u !== i)) continue;
3008
+ if (s[3] || s[4]) {
3009
+ a += u;
3010
+ continue;
3011
+ }
3012
+ if (a -= u, a > 0) continue;
3013
+ u = Math.min(u, u + a);
3014
+ let p = [...s[0]][0].length, k = e.slice(0, i + s.index + p + u), h = k.slice(i, -i);
3015
+ return { type: "del", raw: k, text: h, tokens: this.lexer.inlineTokens(h) };
3016
+ }
3017
+ }
3018
+ }
3019
+ autolink(e) {
3020
+ let t2 = this.rules.inline.autolink.exec(e);
3021
+ if (t2) {
3022
+ let n, s;
3023
+ return t2[2] === "@" ? (n = t2[1], s = "mailto:" + n) : (n = t2[1], s = n), { type: "link", raw: t2[0], text: n, href: s, tokens: [{ type: "text", raw: n, text: n }] };
3024
+ }
3025
+ }
3026
+ url(e) {
3027
+ let t2;
3028
+ if (t2 = this.rules.inline.url.exec(e)) {
3029
+ let n, s;
3030
+ if (t2[2] === "@") n = t2[0], s = "mailto:" + n;
3031
+ else {
3032
+ let r;
3033
+ do
3034
+ r = t2[0], t2[0] = this.rules.inline._backpedal.exec(t2[0])?.[0] ?? "";
3035
+ while (r !== t2[0]);
3036
+ n = t2[0], t2[1] === "www." ? s = "http://" + t2[0] : s = t2[0];
3037
+ }
3038
+ return { type: "link", raw: t2[0], text: n, href: s, tokens: [{ type: "text", raw: n, text: n }] };
3039
+ }
3040
+ }
3041
+ inlineText(e) {
3042
+ let t2 = this.rules.inline.text.exec(e);
3043
+ if (t2) {
3044
+ let n = this.lexer.state.inRawBlock;
3045
+ return { type: "text", raw: t2[0], text: t2[0], escaped: n };
3046
+ }
3047
+ }
3048
+ };
3049
+ var x = class l {
3050
+ tokens;
3051
+ options;
3052
+ state;
3053
+ inlineQueue;
3054
+ tokenizer;
3055
+ constructor(e) {
3056
+ this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || T, this.options.tokenizer = this.options.tokenizer || new w(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, top: true };
3057
+ let t2 = { other: m, block: D.normal, inline: A.normal };
3058
+ this.options.pedantic ? (t2.block = D.pedantic, t2.inline = A.pedantic) : this.options.gfm && (t2.block = D.gfm, this.options.breaks ? t2.inline = A.breaks : t2.inline = A.gfm), this.tokenizer.rules = t2;
3059
+ }
3060
+ static get rules() {
3061
+ return { block: D, inline: A };
3062
+ }
3063
+ static lex(e, t2) {
3064
+ return new l(t2).lex(e);
3065
+ }
3066
+ static lexInline(e, t2) {
3067
+ return new l(t2).inlineTokens(e);
3068
+ }
3069
+ lex(e) {
3070
+ e = e.replace(m.carriageReturn, `
3071
+ `), this.blockTokens(e, this.tokens);
3072
+ for (let t2 = 0; t2 < this.inlineQueue.length; t2++) {
3073
+ let n = this.inlineQueue[t2];
3074
+ this.inlineTokens(n.src, n.tokens);
3075
+ }
3076
+ return this.inlineQueue = [], this.tokens;
3077
+ }
3078
+ blockTokens(e, t2 = [], n = false) {
3079
+ this.tokenizer.lexer = this, this.options.pedantic && (e = e.replace(m.tabCharGlobal, " ").replace(m.spaceLine, ""));
3080
+ let s = 1 / 0;
3081
+ for (; e; ) {
3082
+ if (e.length < s) s = e.length;
3083
+ else {
3084
+ this.infiniteLoopError(e.charCodeAt(0));
3085
+ break;
3086
+ }
3087
+ let r;
3088
+ if (this.options.extensions?.block?.some((o) => (r = o.call({ lexer: this }, e, t2)) ? (e = e.substring(r.raw.length), t2.push(r), true) : false)) continue;
3089
+ if (r = this.tokenizer.space(e)) {
3090
+ e = e.substring(r.raw.length);
3091
+ let o = t2.at(-1);
3092
+ r.raw.length === 1 && o !== void 0 ? o.raw += `
3093
+ ` : t2.push(r);
3094
+ continue;
3095
+ }
3096
+ if (r = this.tokenizer.code(e)) {
3097
+ e = e.substring(r.raw.length);
3098
+ let o = t2.at(-1);
3099
+ o?.type === "paragraph" || o?.type === "text" ? (o.raw += (o.raw.endsWith(`
3100
+ `) ? "" : `
3101
+ `) + r.raw, o.text += `
3102
+ ` + r.text, this.inlineQueue.at(-1).src = o.text) : t2.push(r);
3103
+ continue;
3104
+ }
3105
+ if (r = this.tokenizer.fences(e)) {
3106
+ e = e.substring(r.raw.length), t2.push(r);
3107
+ continue;
3108
+ }
3109
+ if (r = this.tokenizer.heading(e)) {
3110
+ e = e.substring(r.raw.length), t2.push(r);
3111
+ continue;
3112
+ }
3113
+ if (r = this.tokenizer.hr(e)) {
3114
+ e = e.substring(r.raw.length), t2.push(r);
3115
+ continue;
3116
+ }
3117
+ if (r = this.tokenizer.blockquote(e)) {
3118
+ e = e.substring(r.raw.length), t2.push(r);
3119
+ continue;
3120
+ }
3121
+ if (r = this.tokenizer.list(e)) {
3122
+ e = e.substring(r.raw.length), t2.push(r);
3123
+ continue;
3124
+ }
3125
+ if (r = this.tokenizer.html(e)) {
3126
+ e = e.substring(r.raw.length), t2.push(r);
3127
+ continue;
3128
+ }
3129
+ if (r = this.tokenizer.def(e)) {
3130
+ e = e.substring(r.raw.length);
3131
+ let o = t2.at(-1);
3132
+ o?.type === "paragraph" || o?.type === "text" ? (o.raw += (o.raw.endsWith(`
3133
+ `) ? "" : `
3134
+ `) + r.raw, o.text += `
3135
+ ` + r.raw, this.inlineQueue.at(-1).src = o.text) : this.tokens.links[r.tag] || (this.tokens.links[r.tag] = { href: r.href, title: r.title }, t2.push(r));
3136
+ continue;
3137
+ }
3138
+ if (r = this.tokenizer.table(e)) {
3139
+ e = e.substring(r.raw.length), t2.push(r);
3140
+ continue;
3141
+ }
3142
+ if (r = this.tokenizer.lheading(e)) {
3143
+ e = e.substring(r.raw.length), t2.push(r);
3144
+ continue;
3145
+ }
3146
+ let i = e;
3147
+ if (this.options.extensions?.startBlock) {
3148
+ let o = 1 / 0, u = e.slice(1), a;
3149
+ this.options.extensions.startBlock.forEach((c) => {
3150
+ a = c.call({ lexer: this }, u), typeof a == "number" && a >= 0 && (o = Math.min(o, a));
3151
+ }), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1));
3152
+ }
3153
+ if (this.state.top && (r = this.tokenizer.paragraph(i))) {
3154
+ let o = t2.at(-1);
3155
+ n && o?.type === "paragraph" ? (o.raw += (o.raw.endsWith(`
3156
+ `) ? "" : `
3157
+ `) + r.raw, o.text += `
3158
+ ` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = o.text) : t2.push(r), n = i.length !== e.length, e = e.substring(r.raw.length);
3159
+ continue;
3160
+ }
3161
+ if (r = this.tokenizer.text(e)) {
3162
+ e = e.substring(r.raw.length);
3163
+ let o = t2.at(-1);
3164
+ o?.type === "text" ? (o.raw += (o.raw.endsWith(`
3165
+ `) ? "" : `
3166
+ `) + r.raw, o.text += `
3167
+ ` + r.text, this.inlineQueue.pop(), this.inlineQueue.at(-1).src = o.text) : t2.push(r);
3168
+ continue;
3169
+ }
3170
+ if (e) {
3171
+ this.infiniteLoopError(e.charCodeAt(0));
3172
+ break;
3173
+ }
3174
+ }
3175
+ return this.state.top = true, t2;
3176
+ }
3177
+ inline(e, t2 = []) {
3178
+ return this.inlineQueue.push({ src: e, tokens: t2 }), t2;
3179
+ }
3180
+ inlineTokens(e, t2 = []) {
3181
+ this.tokenizer.lexer = this;
3182
+ let n = e, s = null;
3183
+ if (this.tokens.links) {
3184
+ let a = Object.keys(this.tokens.links);
3185
+ if (a.length > 0) for (; (s = this.tokenizer.rules.inline.reflinkSearch.exec(n)) !== null; ) a.includes(s[0].slice(s[0].lastIndexOf("[") + 1, -1)) && (n = n.slice(0, s.index) + "[" + "a".repeat(s[0].length - 2) + "]" + n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex));
3186
+ }
3187
+ for (; (s = this.tokenizer.rules.inline.anyPunctuation.exec(n)) !== null; ) n = n.slice(0, s.index) + "++" + n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
3188
+ let r;
3189
+ for (; (s = this.tokenizer.rules.inline.blockSkip.exec(n)) !== null; ) r = s[2] ? s[2].length : 0, n = n.slice(0, s.index + r) + "[" + "a".repeat(s[0].length - r - 2) + "]" + n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
3190
+ n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n;
3191
+ let i = false, o = "", u = 1 / 0;
3192
+ for (; e; ) {
3193
+ if (e.length < u) u = e.length;
3194
+ else {
3195
+ this.infiniteLoopError(e.charCodeAt(0));
3196
+ break;
3197
+ }
3198
+ i || (o = ""), i = false;
3199
+ let a;
3200
+ if (this.options.extensions?.inline?.some((p) => (a = p.call({ lexer: this }, e, t2)) ? (e = e.substring(a.raw.length), t2.push(a), true) : false)) continue;
3201
+ if (a = this.tokenizer.escape(e)) {
3202
+ e = e.substring(a.raw.length), t2.push(a);
3203
+ continue;
3204
+ }
3205
+ if (a = this.tokenizer.tag(e)) {
3206
+ e = e.substring(a.raw.length), t2.push(a);
3207
+ continue;
3208
+ }
3209
+ if (a = this.tokenizer.link(e)) {
3210
+ e = e.substring(a.raw.length), t2.push(a);
3211
+ continue;
3212
+ }
3213
+ if (a = this.tokenizer.reflink(e, this.tokens.links)) {
3214
+ e = e.substring(a.raw.length);
3215
+ let p = t2.at(-1);
3216
+ a.type === "text" && p?.type === "text" ? (p.raw += a.raw, p.text += a.text) : t2.push(a);
3217
+ continue;
3218
+ }
3219
+ if (a = this.tokenizer.emStrong(e, n, o)) {
3220
+ e = e.substring(a.raw.length), t2.push(a);
3221
+ continue;
3222
+ }
3223
+ if (a = this.tokenizer.codespan(e)) {
3224
+ e = e.substring(a.raw.length), t2.push(a);
3225
+ continue;
3226
+ }
3227
+ if (a = this.tokenizer.br(e)) {
3228
+ e = e.substring(a.raw.length), t2.push(a);
3229
+ continue;
3230
+ }
3231
+ if (a = this.tokenizer.del(e, n, o)) {
3232
+ e = e.substring(a.raw.length), t2.push(a);
3233
+ continue;
3234
+ }
3235
+ if (a = this.tokenizer.autolink(e)) {
3236
+ e = e.substring(a.raw.length), t2.push(a);
3237
+ continue;
3238
+ }
3239
+ if (!this.state.inLink && (a = this.tokenizer.url(e))) {
3240
+ e = e.substring(a.raw.length), t2.push(a);
3241
+ continue;
3242
+ }
3243
+ let c = e;
3244
+ if (this.options.extensions?.startInline) {
3245
+ let p = 1 / 0, k = e.slice(1), h;
3246
+ this.options.extensions.startInline.forEach((R) => {
3247
+ h = R.call({ lexer: this }, k), typeof h == "number" && h >= 0 && (p = Math.min(p, h));
3248
+ }), p < 1 / 0 && p >= 0 && (c = e.substring(0, p + 1));
3249
+ }
3250
+ if (a = this.tokenizer.inlineText(c)) {
3251
+ e = e.substring(a.raw.length), a.raw.slice(-1) !== "_" && (o = a.raw.slice(-1)), i = true;
3252
+ let p = t2.at(-1);
3253
+ p?.type === "text" ? (p.raw += a.raw, p.text += a.text) : t2.push(a);
3254
+ continue;
3255
+ }
3256
+ if (e) {
3257
+ this.infiniteLoopError(e.charCodeAt(0));
3258
+ break;
3259
+ }
3260
+ }
3261
+ return t2;
3262
+ }
3263
+ infiniteLoopError(e) {
3264
+ let t2 = "Infinite loop on byte: " + e;
3265
+ if (this.options.silent) console.error(t2);
3266
+ else throw new Error(t2);
3267
+ }
3268
+ };
3269
+ var y = class {
3270
+ options;
3271
+ parser;
3272
+ constructor(e) {
3273
+ this.options = e || T;
3274
+ }
3275
+ space(e) {
3276
+ return "";
3277
+ }
3278
+ code({ text: e, lang: t2, escaped: n }) {
3279
+ let s = (t2 || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + `
3280
+ `;
3281
+ return s ? '<pre><code class="language-' + O(s) + '">' + (n ? r : O(r, true)) + `</code></pre>
3282
+ ` : "<pre><code>" + (n ? r : O(r, true)) + `</code></pre>
3283
+ `;
3284
+ }
3285
+ blockquote({ tokens: e }) {
3286
+ return `<blockquote>
3287
+ ${this.parser.parse(e)}</blockquote>
3288
+ `;
3289
+ }
3290
+ html({ text: e }) {
3291
+ return e;
3292
+ }
3293
+ def(e) {
3294
+ return "";
3295
+ }
3296
+ heading({ tokens: e, depth: t2 }) {
3297
+ return `<h${t2}>${this.parser.parseInline(e)}</h${t2}>
3298
+ `;
3299
+ }
3300
+ hr(e) {
3301
+ return `<hr>
3302
+ `;
3303
+ }
3304
+ list(e) {
3305
+ let t2 = e.ordered, n = e.start, s = "";
3306
+ for (let o = 0; o < e.items.length; o++) {
3307
+ let u = e.items[o];
3308
+ s += this.listitem(u);
3309
+ }
3310
+ let r = t2 ? "ol" : "ul", i = t2 && n !== 1 ? ' start="' + n + '"' : "";
3311
+ return "<" + r + i + `>
3312
+ ` + s + "</" + r + `>
3313
+ `;
3314
+ }
3315
+ listitem(e) {
3316
+ return `<li>${this.parser.parse(e.tokens)}</li>
3317
+ `;
3318
+ }
3319
+ checkbox({ checked: e }) {
3320
+ return "<input " + (e ? 'checked="" ' : "") + 'disabled="" type="checkbox"> ';
3321
+ }
3322
+ paragraph({ tokens: e }) {
3323
+ return `<p>${this.parser.parseInline(e)}</p>
3324
+ `;
3325
+ }
3326
+ table(e) {
3327
+ let t2 = "", n = "";
3328
+ for (let r = 0; r < e.header.length; r++) n += this.tablecell(e.header[r]);
3329
+ t2 += this.tablerow({ text: n });
3330
+ let s = "";
3331
+ for (let r = 0; r < e.rows.length; r++) {
3332
+ let i = e.rows[r];
3333
+ n = "";
3334
+ for (let o = 0; o < i.length; o++) n += this.tablecell(i[o]);
3335
+ s += this.tablerow({ text: n });
3336
+ }
3337
+ return s && (s = `<tbody>${s}</tbody>`), `<table>
3338
+ <thead>
3339
+ ` + t2 + `</thead>
3340
+ ` + s + `</table>
3341
+ `;
3342
+ }
3343
+ tablerow({ text: e }) {
3344
+ return `<tr>
3345
+ ${e}</tr>
3346
+ `;
3347
+ }
3348
+ tablecell(e) {
3349
+ let t2 = this.parser.parseInline(e.tokens), n = e.header ? "th" : "td";
3350
+ return (e.align ? `<${n} align="${e.align}">` : `<${n}>`) + t2 + `</${n}>
3351
+ `;
3352
+ }
3353
+ strong({ tokens: e }) {
3354
+ return `<strong>${this.parser.parseInline(e)}</strong>`;
3355
+ }
3356
+ em({ tokens: e }) {
3357
+ return `<em>${this.parser.parseInline(e)}</em>`;
3358
+ }
3359
+ codespan({ text: e }) {
3360
+ return `<code>${O(e, true)}</code>`;
3361
+ }
3362
+ br(e) {
3363
+ return "<br>";
3364
+ }
3365
+ del({ tokens: e }) {
3366
+ return `<del>${this.parser.parseInline(e)}</del>`;
3367
+ }
3368
+ link({ href: e, title: t2, tokens: n }) {
3369
+ let s = this.parser.parseInline(n), r = V(e);
3370
+ if (r === null) return s;
3371
+ e = r;
3372
+ let i = '<a href="' + e + '"';
3373
+ return t2 && (i += ' title="' + O(t2) + '"'), i += ">" + s + "</a>", i;
3374
+ }
3375
+ image({ href: e, title: t2, text: n, tokens: s }) {
3376
+ s && (n = this.parser.parseInline(s, this.parser.textRenderer));
3377
+ let r = V(e);
3378
+ if (r === null) return O(n);
3379
+ e = r;
3380
+ let i = `<img src="${e}" alt="${O(n)}"`;
3381
+ return t2 && (i += ` title="${O(t2)}"`), i += ">", i;
3382
+ }
3383
+ text(e) {
3384
+ return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text : O(e.text);
3385
+ }
3386
+ };
3387
+ var L = class {
3388
+ strong({ text: e }) {
3389
+ return e;
3390
+ }
3391
+ em({ text: e }) {
3392
+ return e;
3393
+ }
3394
+ codespan({ text: e }) {
3395
+ return e;
3396
+ }
3397
+ del({ text: e }) {
3398
+ return e;
3399
+ }
3400
+ html({ text: e }) {
3401
+ return e;
3402
+ }
3403
+ text({ text: e }) {
3404
+ return e;
3405
+ }
3406
+ link({ text: e }) {
3407
+ return "" + e;
3408
+ }
3409
+ image({ text: e }) {
3410
+ return "" + e;
3411
+ }
3412
+ br() {
3413
+ return "";
3414
+ }
3415
+ checkbox({ raw: e }) {
3416
+ return e;
3417
+ }
3418
+ };
3419
+ var b = class l2 {
3420
+ options;
3421
+ renderer;
3422
+ textRenderer;
3423
+ constructor(e) {
3424
+ this.options = e || T, this.options.renderer = this.options.renderer || new y(), this.renderer = this.options.renderer, this.renderer.options = this.options, this.renderer.parser = this, this.textRenderer = new L();
3425
+ }
3426
+ static parse(e, t2) {
3427
+ return new l2(t2).parse(e);
3428
+ }
3429
+ static parseInline(e, t2) {
3430
+ return new l2(t2).parseInline(e);
3431
+ }
3432
+ parse(e) {
3433
+ this.renderer.parser = this;
3434
+ let t2 = "";
3435
+ for (let n = 0; n < e.length; n++) {
3436
+ let s = e[n];
3437
+ if (this.options.extensions?.renderers?.[s.type]) {
3438
+ let i = s, o = this.options.extensions.renderers[i.type].call({ parser: this }, i);
3439
+ if (o !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "def", "paragraph", "text"].includes(i.type)) {
3440
+ t2 += o || "";
3441
+ continue;
3442
+ }
3443
+ }
3444
+ let r = s;
3445
+ switch (r.type) {
3446
+ case "space": {
3447
+ t2 += this.renderer.space(r);
3448
+ break;
3449
+ }
3450
+ case "hr": {
3451
+ t2 += this.renderer.hr(r);
3452
+ break;
3453
+ }
3454
+ case "heading": {
3455
+ t2 += this.renderer.heading(r);
3456
+ break;
3457
+ }
3458
+ case "code": {
3459
+ t2 += this.renderer.code(r);
3460
+ break;
3461
+ }
3462
+ case "table": {
3463
+ t2 += this.renderer.table(r);
3464
+ break;
3465
+ }
3466
+ case "blockquote": {
3467
+ t2 += this.renderer.blockquote(r);
3468
+ break;
3469
+ }
3470
+ case "list": {
3471
+ t2 += this.renderer.list(r);
3472
+ break;
3473
+ }
3474
+ case "checkbox": {
3475
+ t2 += this.renderer.checkbox(r);
3476
+ break;
3477
+ }
3478
+ case "html": {
3479
+ t2 += this.renderer.html(r);
3480
+ break;
3481
+ }
3482
+ case "def": {
3483
+ t2 += this.renderer.def(r);
3484
+ break;
3485
+ }
3486
+ case "paragraph": {
3487
+ t2 += this.renderer.paragraph(r);
3488
+ break;
3489
+ }
3490
+ case "text": {
3491
+ t2 += this.renderer.text(r);
3492
+ break;
3493
+ }
3494
+ default: {
3495
+ let i = 'Token with "' + r.type + '" type was not found.';
3496
+ if (this.options.silent) return console.error(i), "";
3497
+ throw new Error(i);
3498
+ }
3499
+ }
3500
+ }
3501
+ return t2;
3502
+ }
3503
+ parseInline(e, t2 = this.renderer) {
3504
+ this.renderer.parser = this;
3505
+ let n = "";
3506
+ for (let s = 0; s < e.length; s++) {
3507
+ let r = e[s];
3508
+ if (this.options.extensions?.renderers?.[r.type]) {
3509
+ let o = this.options.extensions.renderers[r.type].call({ parser: this }, r);
3510
+ if (o !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(r.type)) {
3511
+ n += o || "";
3512
+ continue;
3513
+ }
3514
+ }
3515
+ let i = r;
3516
+ switch (i.type) {
3517
+ case "escape": {
3518
+ n += t2.text(i);
3519
+ break;
3520
+ }
3521
+ case "html": {
3522
+ n += t2.html(i);
3523
+ break;
3524
+ }
3525
+ case "link": {
3526
+ n += t2.link(i);
3527
+ break;
3528
+ }
3529
+ case "image": {
3530
+ n += t2.image(i);
3531
+ break;
3532
+ }
3533
+ case "checkbox": {
3534
+ n += t2.checkbox(i);
3535
+ break;
3536
+ }
3537
+ case "strong": {
3538
+ n += t2.strong(i);
3539
+ break;
3540
+ }
3541
+ case "em": {
3542
+ n += t2.em(i);
3543
+ break;
3544
+ }
3545
+ case "codespan": {
3546
+ n += t2.codespan(i);
3547
+ break;
3548
+ }
3549
+ case "br": {
3550
+ n += t2.br(i);
3551
+ break;
3552
+ }
3553
+ case "del": {
3554
+ n += t2.del(i);
3555
+ break;
3556
+ }
3557
+ case "text": {
3558
+ n += t2.text(i);
3559
+ break;
3560
+ }
3561
+ default: {
3562
+ let o = 'Token with "' + i.type + '" type was not found.';
3563
+ if (this.options.silent) return console.error(o), "";
3564
+ throw new Error(o);
3565
+ }
3566
+ }
3567
+ }
3568
+ return n;
3569
+ }
3570
+ };
3571
+ var P = class {
3572
+ options;
3573
+ block;
3574
+ constructor(e) {
3575
+ this.options = e || T;
3576
+ }
3577
+ static passThroughHooks = /* @__PURE__ */ new Set(["preprocess", "postprocess", "processAllTokens", "emStrongMask"]);
3578
+ static passThroughHooksRespectAsync = /* @__PURE__ */ new Set(["preprocess", "postprocess", "processAllTokens"]);
3579
+ preprocess(e) {
3580
+ return e;
3581
+ }
3582
+ postprocess(e) {
3583
+ return e;
3584
+ }
3585
+ processAllTokens(e) {
3586
+ return e;
3587
+ }
3588
+ emStrongMask(e) {
3589
+ return e;
3590
+ }
3591
+ provideLexer(e = this.block) {
3592
+ return e ? x.lex : x.lexInline;
3593
+ }
3594
+ provideParser(e = this.block) {
3595
+ return e ? b.parse : b.parseInline;
3596
+ }
3597
+ };
3598
+ var q = class {
3599
+ defaults = M();
3600
+ options = this.setOptions;
3601
+ parse = this.parseMarkdown(true);
3602
+ parseInline = this.parseMarkdown(false);
3603
+ Parser = b;
3604
+ Renderer = y;
3605
+ TextRenderer = L;
3606
+ Lexer = x;
3607
+ Tokenizer = w;
3608
+ Hooks = P;
3609
+ constructor(...e) {
3610
+ this.use(...e);
3611
+ }
3612
+ walkTokens(e, t2) {
3613
+ let n = [];
3614
+ for (let s of e) switch (n = n.concat(t2.call(this, s)), s.type) {
3615
+ case "table": {
3616
+ let r = s;
3617
+ for (let i of r.header) n = n.concat(this.walkTokens(i.tokens, t2));
3618
+ for (let i of r.rows) for (let o of i) n = n.concat(this.walkTokens(o.tokens, t2));
3619
+ break;
3620
+ }
3621
+ case "list": {
3622
+ let r = s;
3623
+ n = n.concat(this.walkTokens(r.items, t2));
3624
+ break;
3625
+ }
3626
+ default: {
3627
+ let r = s;
3628
+ this.defaults.extensions?.childTokens?.[r.type] ? this.defaults.extensions.childTokens[r.type].forEach((i) => {
3629
+ let o = r[i].flat(1 / 0);
3630
+ n = n.concat(this.walkTokens(o, t2));
3631
+ }) : r.tokens && (n = n.concat(this.walkTokens(r.tokens, t2)));
3632
+ }
3633
+ }
3634
+ return n;
3635
+ }
3636
+ use(...e) {
3637
+ let t2 = this.defaults.extensions || { renderers: {}, childTokens: {} };
3638
+ return e.forEach((n) => {
3639
+ let s = { ...n };
3640
+ if (s.async = this.defaults.async || s.async || false, n.extensions && (n.extensions.forEach((r) => {
3641
+ if (!r.name) throw new Error("extension name required");
3642
+ if ("renderer" in r) {
3643
+ let i = t2.renderers[r.name];
3644
+ i ? t2.renderers[r.name] = function(...o) {
3645
+ let u = r.renderer.apply(this, o);
3646
+ return u === false && (u = i.apply(this, o)), u;
3647
+ } : t2.renderers[r.name] = r.renderer;
3648
+ }
3649
+ if ("tokenizer" in r) {
3650
+ if (!r.level || r.level !== "block" && r.level !== "inline") throw new Error("extension level must be 'block' or 'inline'");
3651
+ let i = t2[r.level];
3652
+ i ? i.unshift(r.tokenizer) : t2[r.level] = [r.tokenizer], r.start && (r.level === "block" ? t2.startBlock ? t2.startBlock.push(r.start) : t2.startBlock = [r.start] : r.level === "inline" && (t2.startInline ? t2.startInline.push(r.start) : t2.startInline = [r.start]));
3653
+ }
3654
+ "childTokens" in r && r.childTokens && (t2.childTokens[r.name] = r.childTokens);
3655
+ }), s.extensions = t2), n.renderer) {
3656
+ let r = this.defaults.renderer || new y(this.defaults);
3657
+ for (let i in n.renderer) {
3658
+ if (!(i in r)) throw new Error(`renderer '${i}' does not exist`);
3659
+ if (["options", "parser"].includes(i)) continue;
3660
+ let o = i, u = n.renderer[o], a = r[o];
3661
+ r[o] = (...c) => {
3662
+ let p = u.apply(r, c);
3663
+ return p === false && (p = a.apply(r, c)), p || "";
3664
+ };
3665
+ }
3666
+ s.renderer = r;
3667
+ }
3668
+ if (n.tokenizer) {
3669
+ let r = this.defaults.tokenizer || new w(this.defaults);
3670
+ for (let i in n.tokenizer) {
3671
+ if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`);
3672
+ if (["options", "rules", "lexer"].includes(i)) continue;
3673
+ let o = i, u = n.tokenizer[o], a = r[o];
3674
+ r[o] = (...c) => {
3675
+ let p = u.apply(r, c);
3676
+ return p === false && (p = a.apply(r, c)), p;
3677
+ };
3678
+ }
3679
+ s.tokenizer = r;
3680
+ }
3681
+ if (n.hooks) {
3682
+ let r = this.defaults.hooks || new P();
3683
+ for (let i in n.hooks) {
3684
+ if (!(i in r)) throw new Error(`hook '${i}' does not exist`);
3685
+ if (["options", "block"].includes(i)) continue;
3686
+ let o = i, u = n.hooks[o], a = r[o];
3687
+ P.passThroughHooks.has(i) ? r[o] = (c) => {
3688
+ if (this.defaults.async && P.passThroughHooksRespectAsync.has(i)) return (async () => {
3689
+ let k = await u.call(r, c);
3690
+ return a.call(r, k);
3691
+ })();
3692
+ let p = u.call(r, c);
3693
+ return a.call(r, p);
3694
+ } : r[o] = (...c) => {
3695
+ if (this.defaults.async) return (async () => {
3696
+ let k = await u.apply(r, c);
3697
+ return k === false && (k = await a.apply(r, c)), k;
3698
+ })();
3699
+ let p = u.apply(r, c);
3700
+ return p === false && (p = a.apply(r, c)), p;
3701
+ };
3702
+ }
3703
+ s.hooks = r;
3704
+ }
3705
+ if (n.walkTokens) {
3706
+ let r = this.defaults.walkTokens, i = n.walkTokens;
3707
+ s.walkTokens = function(o) {
3708
+ let u = [];
3709
+ return u.push(i.call(this, o)), r && (u = u.concat(r.call(this, o))), u;
3710
+ };
3711
+ }
3712
+ this.defaults = { ...this.defaults, ...s };
3713
+ }), this;
3714
+ }
3715
+ setOptions(e) {
3716
+ return this.defaults = { ...this.defaults, ...e }, this;
3717
+ }
3718
+ lexer(e, t2) {
3719
+ return x.lex(e, t2 ?? this.defaults);
3720
+ }
3721
+ parser(e, t2) {
3722
+ return b.parse(e, t2 ?? this.defaults);
3723
+ }
3724
+ parseMarkdown(e) {
3725
+ return (n, s) => {
3726
+ let r = { ...s }, i = { ...this.defaults, ...r }, o = this.onError(!!i.silent, !!i.async);
3727
+ if (this.defaults.async === true && r.async === false) return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));
3728
+ if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null"));
3729
+ if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
3730
+ if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => {
3731
+ let u = i.hooks ? await i.hooks.preprocess(n) : n, c = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(u, i), p = i.hooks ? await i.hooks.processAllTokens(c) : c;
3732
+ i.walkTokens && await Promise.all(this.walkTokens(p, i.walkTokens));
3733
+ let h = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(p, i);
3734
+ return i.hooks ? await i.hooks.postprocess(h) : h;
3735
+ })().catch(o);
3736
+ try {
3737
+ i.hooks && (n = i.hooks.preprocess(n));
3738
+ let a = (i.hooks ? i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(n, i);
3739
+ i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens);
3740
+ let p = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i);
3741
+ return i.hooks && (p = i.hooks.postprocess(p)), p;
3742
+ } catch (u) {
3743
+ return o(u);
3744
+ }
3745
+ };
3746
+ }
3747
+ onError(e, t2) {
3748
+ return (n) => {
3749
+ if (n.message += `
3750
+ Please report this to https://github.com/markedjs/marked.`, e) {
3751
+ let s = "<p>An error occurred:</p><pre>" + O(n.message + "", true) + "</pre>";
3752
+ return t2 ? Promise.resolve(s) : s;
3753
+ }
3754
+ if (t2) return Promise.reject(n);
3755
+ throw n;
3756
+ };
3757
+ }
3758
+ };
3759
+ var z = new q();
3760
+ function g(l3, e) {
3761
+ return z.parse(l3, e);
3762
+ }
3763
+ g.options = g.setOptions = function(l3) {
3764
+ return z.setOptions(l3), g.defaults = z.defaults, N(g.defaults), g;
3765
+ };
3766
+ g.getDefaults = M;
3767
+ g.defaults = T;
3768
+ g.use = function(...l3) {
3769
+ return z.use(...l3), g.defaults = z.defaults, N(g.defaults), g;
3770
+ };
3771
+ g.walkTokens = function(l3, e) {
3772
+ return z.walkTokens(l3, e);
3773
+ };
3774
+ g.parseInline = z.parseInline;
3775
+ g.Parser = b;
3776
+ g.parser = b.parse;
3777
+ g.Renderer = y;
3778
+ g.TextRenderer = L;
3779
+ g.Lexer = x;
3780
+ g.lexer = x.lex;
3781
+ g.Tokenizer = w;
3782
+ g.Hooks = P;
3783
+ g.parse = g;
3784
+ g.options;
3785
+ g.setOptions;
3786
+ g.use;
3787
+ g.walkTokens;
3788
+ g.parseInline;
3789
+ b.parse;
3790
+ x.lex;
3791
+ g.setOptions({ breaks: true, gfm: true });
3792
+ function sanitize(html) {
3793
+ return html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "");
3794
+ }
3795
+ function renderMarkdown(text) {
3796
+ if (!text) return "";
3797
+ return sanitize(g.parse(text));
3798
+ }
3799
+ var IconMic = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3800
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "1", width: "6", height: "11", rx: "3" }),
3801
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 10v1a7 7 0 0 1-14 0v-1" }),
3802
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
3803
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
3804
+ ] });
3805
+ var IconSend = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 8l12-6-4 14-3-6-5-2z" }) });
3806
+ var IconTrash = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "13", height: "13", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 4h12M5 4V3a1 1 0 011-1h4a1 1 0 011 1v1M6 7v5M10 7v5M3 4l1 9a2 2 0 002 2h4a2 2 0 002-2l1-9" }) });
3807
+ var IconBack = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 18 9 12 15 6" }) });
3808
+ function StreamingText({ targetText, streaming }) {
3809
+ const CHARS_PER_FRAME = 2;
3810
+ const displayedRef = react.useRef(0);
3811
+ const rafRef = react.useRef(null);
3812
+ const nodeRef = react.useRef(null);
3813
+ const prevTargetRef = react.useRef("");
3814
+ if (targetText.length < prevTargetRef.current.length) {
3815
+ displayedRef.current = 0;
3816
+ }
3817
+ prevTargetRef.current = targetText;
3818
+ react.useEffect(() => {
3819
+ if (!streaming) {
3820
+ displayedRef.current = targetText.length;
3821
+ if (nodeRef.current) {
3822
+ nodeRef.current.innerHTML = renderMarkdown(targetText);
3823
+ }
3824
+ return;
3825
+ }
3826
+ function tick() {
3827
+ const target = targetText.length;
3828
+ const current = displayedRef.current;
3829
+ if (current < target) {
3830
+ displayedRef.current = Math.min(current + CHARS_PER_FRAME, target);
3831
+ const visible = targetText.slice(0, displayedRef.current);
3832
+ if (nodeRef.current) {
3833
+ nodeRef.current.innerHTML = renderMarkdown(visible);
3834
+ }
3835
+ rafRef.current = requestAnimationFrame(tick);
3836
+ }
3837
+ }
3838
+ rafRef.current = requestAnimationFrame(tick);
3839
+ return () => {
3840
+ if (rafRef.current) cancelAnimationFrame(rafRef.current);
3841
+ };
3842
+ }, [targetText, streaming]);
3843
+ const initialHtml = react.useMemo(() => {
3844
+ if (!streaming) return renderMarkdown(targetText);
3845
+ const visible = targetText.slice(0, displayedRef.current);
3846
+ return renderMarkdown(visible);
3847
+ }, [targetText, streaming]);
3848
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-cv-md", ref: nodeRef, dangerouslySetInnerHTML: { __html: initialHtml } });
3849
+ }
3850
+ function ChatView({
3851
+ agent,
3852
+ server,
3853
+ name,
3854
+ locale,
3855
+ labels,
3856
+ chat,
3857
+ tokenProvider,
3858
+ onBack,
3859
+ onVoiceCall
3860
+ }) {
3861
+ const [input, setInput] = react.useState("");
3862
+ const bodyRef = react.useRef(null);
3863
+ const inputRef = react.useRef(null);
3864
+ const chatTokenProvider = chat.tokenProvider || tokenProvider;
3865
+ const { messages, send, connected, typing, error } = usePinecallChat({
3866
+ agent,
3867
+ server,
3868
+ tokenProvider: chatTokenProvider
3869
+ });
3870
+ react.useEffect(() => {
3871
+ requestAnimationFrame(() => {
3872
+ if (bodyRef.current) {
3873
+ bodyRef.current.scrollTo({ top: bodyRef.current.scrollHeight, behavior: "smooth" });
3874
+ }
3875
+ });
3876
+ }, [messages, typing]);
3877
+ const allMessages = react.useMemo(() => {
3878
+ if (!chat.greeting) return messages;
3879
+ const greetingMsg = {
3880
+ id: 0,
3881
+ role: "bot",
3882
+ text: chat.greeting,
3883
+ isStreaming: false
3884
+ };
3885
+ return [greetingMsg, ...messages];
3886
+ }, [messages, chat.greeting]);
3887
+ const hasUserMessages = messages.some((m2) => m2.role === "user");
3888
+ const handleSubmit = (e) => {
3889
+ e.preventDefault();
3890
+ if (!input.trim() || typing) return;
3891
+ send(input.trim());
3892
+ setInput("");
3893
+ requestAnimationFrame(() => inputRef.current?.focus({ preventScroll: true }));
3894
+ };
3895
+ const handleQuick = react.useCallback((query) => {
3896
+ send(query);
3897
+ }, [send]);
3898
+ const handleClear = react.useCallback(() => {
3899
+ window.location.reload();
3900
+ }, []);
3901
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv", children: [
3902
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-head", children: [
3903
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "vw-cv-back", onClick: onBack, children: /* @__PURE__ */ jsxRuntime.jsx(IconBack, {}) }),
3904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-cv-avatar", children: name.charAt(0) }),
3905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-who", children: [
3906
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cv-name", children: name }),
3907
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-cv-status", children: [
3908
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `vw-cv-dot ${connected ? "" : "vw-cv-dot--off"}` }),
3909
+ connected ? "en l\xEDnea" : "conectando\u2026"
3910
+ ] })
3911
+ ] }),
3912
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-actions", children: [
3913
+ hasUserMessages && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "vw-cv-action", onClick: handleClear, title: "Nueva conversaci\xF3n", children: /* @__PURE__ */ jsxRuntime.jsx(IconTrash, {}) }),
3914
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "vw-cv-action vw-cv-action--voice", onClick: onVoiceCall, title: "Llamada de voz", children: /* @__PURE__ */ jsxRuntime.jsx(IconMic, {}) })
3915
+ ] })
3916
+ ] }),
3917
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-body", ref: bodyRef, children: [
3918
+ allMessages.map((msg) => {
3919
+ if (msg.role === "user") {
3920
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-cv-msg vw-cv-msg--user", children: msg.text }, msg.id);
3921
+ }
3922
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `vw-cv-msg vw-cv-msg--bot ${msg.isStreaming ? "vw-cv-streaming" : ""}`, children: [
3923
+ /* @__PURE__ */ jsxRuntime.jsx(StreamingText, { targetText: msg.text || "", streaming: !!msg.isStreaming }),
3924
+ msg.isStreaming && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cv-cursor" })
3925
+ ] }, msg.id);
3926
+ }),
3927
+ typing && !messages.some((m2) => m2.isStreaming) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-msg vw-cv-msg--bot vw-cv-skeleton", children: [
3928
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cv-sk-dot" }),
3929
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cv-sk-dot" }),
3930
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cv-sk-dot" })
3931
+ ] }),
3932
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cv-msg vw-cv-msg--bot", style: { color: "rgba(248,113,113,.9)", borderColor: "rgba(248,113,113,.2)" }, children: [
3933
+ "\u26A0\uFE0F ",
3934
+ error
3935
+ ] })
3936
+ ] }),
3937
+ !hasUserMessages && chat.quickOptions && chat.quickOptions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-cv-quick", children: chat.quickOptions.map((opt, i) => /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: () => handleQuick(opt.query), disabled: typing, children: opt.label }, i)) }),
3938
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "vw-cv-input", onSubmit: handleSubmit, autoComplete: "off", children: [
3939
+ /* @__PURE__ */ jsxRuntime.jsx(
3940
+ "input",
3941
+ {
3942
+ ref: inputRef,
3943
+ type: "text",
3944
+ placeholder: "Escrib\xED tu mensaje...",
3945
+ value: input,
3946
+ onChange: (e) => setInput(e.target.value),
3947
+ disabled: typing
3948
+ }
3949
+ ),
3950
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "vw-cv-send", disabled: typing || !input.trim(), children: /* @__PURE__ */ jsxRuntime.jsx(IconSend, {}) })
3951
+ ] })
3952
+ ] });
3953
+ }
3954
+ var IconMic2 = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3955
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "1", width: "6", height: "11", rx: "3" }),
3956
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 10v1a7 7 0 0 1-14 0v-1" }),
3957
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }),
3958
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })
3959
+ ] });
3960
+ var IconWhatsApp = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.5 14.4c-.3-.1-1.7-.8-2-.9-.3-.1-.5-.2-.7.1-.2.3-.8.9-1 1.1-.2.2-.4.2-.7.1-.3-.1-1.3-.5-2.4-1.5-.9-.8-1.5-1.8-1.7-2.1-.2-.3 0-.5.1-.6.1-.1.3-.4.4-.5.1-.2.2-.3.3-.5.1-.2 0-.4 0-.5-.1-.1-.7-1.7-.9-2.3-.2-.6-.5-.5-.7-.5h-.6c-.2 0-.5.1-.8.4-.3.3-1 1-1 2.4 0 1.4 1 2.8 1.2 3 .1.2 2 3 4.8 4.2.7.3 1.2.5 1.6.6.7.2 1.3.2 1.8.1.6-.1 1.7-.7 1.9-1.3.2-.7.2-1.2.2-1.3-.1-.1-.3-.2-.6-.3zM12 2C6.5 2 2 6.5 2 12c0 1.8.5 3.5 1.3 5L2 22l5.2-1.3c1.4.8 3 1.2 4.7 1.2h.1c5.5 0 10-4.5 10-10S17.5 2 12 2z" }) });
3961
+ var IconPhone = ({ size = 20 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z" }) });
3962
+ var IconX = ({ size = 16 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3963
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
3964
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
3965
+ ] });
3966
+ function findLastIndex(arr, fn) {
3967
+ for (let i = arr.length - 1; i >= 0; i--) {
3968
+ if (fn(arr[i])) return i;
3969
+ }
3970
+ return -1;
3971
+ }
3972
+ function whatsappUrl(phone) {
3973
+ const clean = phone.replace(/\D/g, "");
3974
+ return `https://wa.me/${clean}`;
3975
+ }
3976
+ function ContactHub({
3977
+ open,
3978
+ onClose,
3979
+ channels,
3980
+ name,
3981
+ locale,
3982
+ labels,
3983
+ avatar,
3984
+ callMeEndpoint,
3985
+ agent,
3986
+ server,
3987
+ chat,
3988
+ tokenProvider,
3989
+ onCallMeState,
3990
+ connect
3991
+ }) {
3992
+ const [view, setView] = react.useState("menu");
3993
+ const [phone, setPhone] = react.useState("");
3994
+ const [status, setStatus] = react.useState("idle");
3995
+ const [errorMsg, setErrorMsg] = react.useState("");
3996
+ react.useEffect(() => {
3997
+ if (document.getElementById("vw-hub-styles")) return;
3998
+ const el = document.createElement("style");
3999
+ el.id = "vw-hub-styles";
4000
+ el.textContent = HUB_CSS + CHAT_VIEW_CSS;
4001
+ document.head.appendChild(el);
4002
+ }, []);
4003
+ const sseAbortRef = react.useRef(null);
4004
+ const sseMessagesRef = react.useRef([]);
4005
+ const sseIdCounter = react.useRef(0);
4006
+ const sseDurationRef = react.useRef(0);
4007
+ const sseTimerRef = react.useRef(null);
4008
+ const sseStartTimeRef = react.useRef(null);
4009
+ const ssePhoneRef = react.useRef("");
4010
+ react.useEffect(() => {
4011
+ if (!open) {
4012
+ const timer = setTimeout(() => {
4013
+ setView("menu");
4014
+ setStatus("idle");
4015
+ setPhone("");
4016
+ setErrorMsg("");
4017
+ }, 300);
4018
+ return () => clearTimeout(timer);
4019
+ }
4020
+ }, [open]);
4021
+ react.useEffect(() => {
4022
+ return () => {
4023
+ sseAbortRef.current?.abort();
4024
+ if (sseTimerRef.current) clearInterval(sseTimerRef.current);
4025
+ };
4026
+ }, []);
4027
+ const hasWebrtc = channels.some((c) => c.type === "webrtc");
4028
+ const hasChat = channels.some((c) => c.type === "chat") && !!chat;
4029
+ const waChannel = channels.find((c) => c.type === "whatsapp" && c.phone);
4030
+ const phoneChannel = channels.find((c) => c.type === "phone");
4031
+ const showCallMe = !!callMeEndpoint && !!phoneChannel;
4032
+ const handleVoice = async () => {
4033
+ onClose();
4034
+ await connect();
4035
+ };
4036
+ const l3 = (key) => t(locale, key, labels, { name });
4037
+ const handleVoiceFromChat = async () => {
4038
+ onClose();
4039
+ await connect();
4040
+ };
4041
+ const emitSSEState = (status2) => {
4042
+ onCallMeState?.({
4043
+ status: status2,
4044
+ messages: [...sseMessagesRef.current],
4045
+ duration: sseDurationRef.current,
4046
+ phone: ssePhoneRef.current
4047
+ });
4048
+ };
4049
+ const handleSSEEvent = (event, data) => {
4050
+ switch (event) {
4051
+ case "call.started": {
4052
+ sseStartTimeRef.current = Date.now();
4053
+ sseTimerRef.current = setInterval(() => {
4054
+ sseDurationRef.current = Math.floor((Date.now() - sseStartTimeRef.current) / 1e3);
4055
+ emitSSEState("connected");
4056
+ }, 1e3);
4057
+ emitSSEState("connected");
4058
+ break;
4059
+ }
4060
+ case "bot.word": {
4061
+ const mid = data.messageId;
4062
+ const msgs = sseMessagesRef.current;
4063
+ const idx = msgs.findIndex((m2) => m2.role === "bot" && m2.messageId === mid);
4064
+ if (idx >= 0) {
4065
+ msgs[idx] = { ...msgs[idx], text: data.text, speaking: true };
4066
+ } else {
4067
+ msgs.push({ id: ++sseIdCounter.current, role: "bot", messageId: mid, text: data.text, speaking: true });
4068
+ }
4069
+ sseMessagesRef.current = [...msgs];
4070
+ emitSSEState("connected");
4071
+ break;
4072
+ }
4073
+ case "bot.confirmed": {
4074
+ const mid = data.messageId;
4075
+ const msgs = sseMessagesRef.current;
4076
+ const idx = msgs.findIndex((m2) => m2.role === "bot" && m2.messageId === mid);
4077
+ if (idx >= 0) {
4078
+ msgs[idx] = { ...msgs[idx], text: data.text, speaking: false };
4079
+ } else {
4080
+ msgs.push({ id: ++sseIdCounter.current, role: "bot", messageId: mid, text: data.text, speaking: false });
4081
+ }
4082
+ sseMessagesRef.current = [...msgs];
4083
+ emitSSEState("connected");
4084
+ break;
4085
+ }
4086
+ case "user.speaking": {
4087
+ if (!data.text) break;
4088
+ const msgs = sseMessagesRef.current;
4089
+ const lastUserIdx = findLastIndex(msgs, (m2) => m2.role === "user");
4090
+ const hasBotAfter = lastUserIdx >= 0 && msgs.slice(lastUserIdx + 1).some((m2) => m2.role === "bot");
4091
+ if (lastUserIdx >= 0 && !hasBotAfter) {
4092
+ msgs[lastUserIdx] = { ...msgs[lastUserIdx], text: data.text, isInterim: true };
4093
+ } else {
4094
+ msgs.push({ id: ++sseIdCounter.current, role: "user", text: data.text, isInterim: true });
4095
+ }
4096
+ sseMessagesRef.current = [...msgs];
4097
+ emitSSEState("connected");
4098
+ break;
4099
+ }
4100
+ case "user.message": {
4101
+ if (!data.text) break;
4102
+ const msgs = sseMessagesRef.current;
4103
+ const idx = findLastIndex(msgs, (m2) => m2.role === "user" && !!m2.isInterim);
4104
+ if (idx >= 0) {
4105
+ msgs[idx] = { ...msgs[idx], text: data.text, isInterim: false };
4106
+ } else {
4107
+ msgs.push({ id: ++sseIdCounter.current, role: "user", text: data.text, isInterim: false });
4108
+ }
4109
+ sseMessagesRef.current = [...msgs];
4110
+ emitSSEState("connected");
4111
+ break;
4112
+ }
4113
+ case "call.ended": {
4114
+ sseDurationRef.current = data.duration || sseDurationRef.current;
4115
+ if (sseTimerRef.current) clearInterval(sseTimerRef.current);
4116
+ onCallMeState?.({
4117
+ status: "ended",
4118
+ messages: [...sseMessagesRef.current],
4119
+ duration: sseDurationRef.current,
4120
+ phone: ssePhoneRef.current
4121
+ });
4122
+ break;
4123
+ }
4124
+ case "error": {
4125
+ if (sseTimerRef.current) clearInterval(sseTimerRef.current);
4126
+ onCallMeState?.({
4127
+ status: "error",
4128
+ messages: [],
4129
+ duration: 0,
4130
+ phone: ssePhoneRef.current,
4131
+ error: data.message
4132
+ });
4133
+ break;
4134
+ }
4135
+ }
4136
+ };
4137
+ const startCallMe = async (phoneNum) => {
4138
+ sseMessagesRef.current = [];
4139
+ sseIdCounter.current = 0;
4140
+ sseDurationRef.current = 0;
4141
+ sseStartTimeRef.current = null;
4142
+ ssePhoneRef.current = phoneNum;
4143
+ const controller = new AbortController();
4144
+ sseAbortRef.current = controller;
4145
+ onCallMeState?.({ status: "dialing", messages: [], duration: 0, phone: phoneNum });
4146
+ let res;
4147
+ try {
4148
+ res = await fetch(callMeEndpoint, {
4149
+ method: "POST",
4150
+ headers: { "Content-Type": "application/json" },
4151
+ body: JSON.stringify({ phone: phoneNum }),
4152
+ signal: controller.signal
4153
+ });
4154
+ } catch (err) {
4155
+ if (err.name === "AbortError") return;
4156
+ onCallMeState?.({ status: "error", messages: [], duration: 0, phone: phoneNum, error: l3("callMe.error") });
4157
+ return;
4158
+ }
4159
+ const ct = res.headers.get("content-type") || "";
4160
+ if (ct.includes("application/json")) {
4161
+ const data = await res.json().catch(() => ({}));
4162
+ onCallMeState?.({ status: "error", messages: [], duration: 0, phone: phoneNum, error: data.error || l3("callMe.error") });
4163
+ return;
4164
+ }
4165
+ const reader = res.body.getReader();
4166
+ const decoder = new TextDecoder();
4167
+ let buffer = "";
4168
+ try {
4169
+ while (true) {
4170
+ const { done, value } = await reader.read();
4171
+ if (done) break;
4172
+ buffer += decoder.decode(value, { stream: true });
4173
+ const lines = buffer.split("\n");
4174
+ buffer = lines.pop();
4175
+ let currentEvent = "";
4176
+ for (const line of lines) {
4177
+ if (line.startsWith("event: ")) {
4178
+ currentEvent = line.slice(7).trim();
4179
+ } else if (line.startsWith("data: ") && currentEvent) {
4180
+ try {
4181
+ const data = JSON.parse(line.slice(6));
4182
+ handleSSEEvent(currentEvent, data);
4183
+ } catch {
4184
+ }
4185
+ currentEvent = "";
4186
+ }
4187
+ }
4188
+ }
4189
+ } catch (err) {
4190
+ if (err.name !== "AbortError") {
4191
+ console.error("[ContactHub] Stream error:", err);
4192
+ }
4193
+ }
4194
+ };
4195
+ const handleStartCall = (e) => {
4196
+ e.preventDefault();
4197
+ if (!phone.trim()) return;
4198
+ onClose();
4199
+ startCallMe(phone.trim());
4200
+ };
4201
+ if (!open) return null;
4202
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-hub-backdrop", onClick: (e) => {
4203
+ if (e.target === e.currentTarget) onClose();
4204
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `vw-hub-panel${view === "chat" ? " vw-hub-panel--chat" : ""}`, children: [
4205
+ view !== "chat" && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "vw-hub-close", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(IconX, {}) }),
4206
+ view === "menu" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-hub-menu", children: [
4207
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-hub-header", children: [
4208
+ avatar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-hub-avatar", children: avatar }),
4209
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: l3("hub.title") }),
4210
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: l3("hub.subtitle") })
4211
+ ] }),
4212
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-hub-options", children: [
4213
+ hasWebrtc && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "vw-hub-opt", onClick: handleVoice, children: [
4214
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-icon vw-hub-icon--voice", children: /* @__PURE__ */ jsxRuntime.jsx(IconMic2, {}) }),
4215
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-hub-body", children: [
4216
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-title", children: l3("hub.voice") }),
4217
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-desc", children: l3("hub.voiceDesc") })
4218
+ ] }),
4219
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-arrow", children: "\u2192" })
4220
+ ] }),
4221
+ hasChat && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "vw-hub-opt", onClick: () => setView("chat"), children: [
4222
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-icon vw-hub-icon--chat", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }) }),
4223
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-hub-body", children: [
4224
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-title", children: l3("hub.chat") }),
4225
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-desc", children: l3("hub.chatDesc") })
4226
+ ] }),
4227
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-arrow", children: "\u2192" })
4228
+ ] }),
4229
+ waChannel && /* @__PURE__ */ jsxRuntime.jsxs(
4230
+ "a",
4231
+ {
4232
+ className: "vw-hub-opt",
4233
+ href: whatsappUrl(waChannel.phone),
4234
+ target: "_blank",
4235
+ rel: "noopener noreferrer",
4236
+ onClick: onClose,
4237
+ children: [
4238
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-icon vw-hub-icon--wa", children: /* @__PURE__ */ jsxRuntime.jsx(IconWhatsApp, {}) }),
4239
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-hub-body", children: [
4240
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-title", children: l3("hub.whatsapp") }),
4241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-desc", children: l3("hub.whatsappDesc") })
4242
+ ] }),
4243
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-arrow", children: "\u2192" })
4244
+ ]
4245
+ }
4246
+ ),
4247
+ showCallMe && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "vw-hub-opt", onClick: () => setView("call"), children: [
4248
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-icon vw-hub-icon--call", children: /* @__PURE__ */ jsxRuntime.jsx(IconPhone, {}) }),
4249
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "vw-hub-body", children: [
4250
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-title", children: l3("hub.callMe") }),
4251
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-desc", children: l3("hub.callMeDesc") })
4252
+ ] }),
4253
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-hub-arrow", children: "\u2192" })
4254
+ ] })
4255
+ ] })
4256
+ ] }) : view === "call" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "vw-cm", children: [
4257
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "vw-cm-back", onClick: () => setView("menu"), children: [
4258
+ "\u2190 ",
4259
+ l3("callMe.back")
4260
+ ] }),
4261
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "vw-cm-phone-icon", children: /* @__PURE__ */ jsxRuntime.jsx(IconPhone, { size: 24 }) }),
4262
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: l3("callMe.title") }),
4263
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "vw-cm-form", onSubmit: handleStartCall, children: [
4264
+ /* @__PURE__ */ jsxRuntime.jsx(
4265
+ "input",
4266
+ {
4267
+ type: "tel",
4268
+ placeholder: l3("callMe.placeholder"),
4269
+ value: phone,
4270
+ onChange: (e) => {
4271
+ setPhone(e.target.value);
4272
+ if (status === "error") setStatus("idle");
4273
+ },
4274
+ autoFocus: true
4275
+ }
4276
+ ),
4277
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", disabled: !phone.trim(), children: [
4278
+ /* @__PURE__ */ jsxRuntime.jsx(IconPhone, { size: 16 }),
4279
+ l3("callMe.submit")
4280
+ ] }),
4281
+ status === "error" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cm-error", children: errorMsg }),
4282
+ l3("callMe.formNote") && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "vw-cm-note", dangerouslySetInnerHTML: { __html: l3("callMe.formNote") } })
4283
+ ] })
4284
+ ] }) : view === "chat" ? /* @__PURE__ */ jsxRuntime.jsx(
4285
+ ChatView,
4286
+ {
4287
+ agent,
4288
+ server,
4289
+ name,
4290
+ locale,
4291
+ labels,
4292
+ chat,
4293
+ tokenProvider,
4294
+ onBack: () => setView("menu"),
4295
+ onVoiceCall: handleVoiceFromChat
4296
+ }
4297
+ ) : null
4298
+ ] }) });
4299
+ }
4300
+ var cache = /* @__PURE__ */ new Map();
4301
+ function useAgentInfo(agentId, server = "https://voice.pinecall.io") {
4302
+ const key = `${server}|${agentId}`;
4303
+ const [info, setInfo] = react.useState(cache.get(key) ?? null);
4304
+ const [loading, setLoading] = react.useState(!cache.has(key));
4305
+ const mountedRef = react.useRef(true);
4306
+ react.useEffect(() => {
4307
+ mountedRef.current = true;
4308
+ return () => {
4309
+ mountedRef.current = false;
4310
+ };
4311
+ }, []);
4312
+ react.useEffect(() => {
4313
+ if (cache.has(key)) {
4314
+ setInfo(cache.get(key));
4315
+ setLoading(false);
4316
+ return;
4317
+ }
4318
+ let cancelled = false;
4319
+ setLoading(true);
4320
+ const baseUrl = server.replace(/\/$/, "");
4321
+ fetch(`${baseUrl}/api/sdk/agent-info/${encodeURIComponent(agentId)}`).then((res) => {
4322
+ if (!res.ok) throw new Error(`${res.status}`);
4323
+ return res.json();
4324
+ }).then((data) => {
4325
+ if (!cancelled && mountedRef.current) {
4326
+ cache.set(key, data);
4327
+ setInfo(data);
4328
+ setLoading(false);
4329
+ }
4330
+ }).catch(() => {
4331
+ if (!cancelled && mountedRef.current) {
4332
+ setLoading(false);
4333
+ }
4334
+ });
4335
+ return () => {
4336
+ cancelled = true;
4337
+ };
4338
+ }, [key]);
4339
+ return { info, loading };
4340
+ }
4341
+
4342
+ exports.ChatView = ChatView;
4343
+ exports.ContactHub = ContactHub;
4344
+ exports.PRESETS = PRESETS;
4345
+ exports.VoiceWidget = VoiceWidget;
4346
+ exports.t = t;
4347
+ exports.useAgentInfo = useAgentInfo;
4348
+ exports.useVoice = useVoice;
4349
+ exports.useVoiceSession = useVoiceSession;
4350
+ //# sourceMappingURL=index.cjs.map
4351
+ //# sourceMappingURL=index.cjs.map