@oshara/voice-sdk 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/README.md +198 -0
- package/dist/appearance-CNWT8x1G.cjs +2 -0
- package/dist/appearance-CNWT8x1G.cjs.map +1 -0
- package/dist/appearance-i6QBkpCk.js +650 -0
- package/dist/appearance-i6QBkpCk.js.map +1 -0
- package/dist/consent-CK9VXNPa.js +54 -0
- package/dist/consent-CK9VXNPa.js.map +1 -0
- package/dist/consent-D7QNSkQD.cjs +2 -0
- package/dist/consent-D7QNSkQD.cjs.map +1 -0
- package/dist/core/analytics.d.ts +30 -0
- package/dist/core/appearance.d.ts +113 -0
- package/dist/core/audioSettings.d.ts +69 -0
- package/dist/core/consent.d.ts +17 -0
- package/dist/core/createVoiceAgent.d.ts +79 -0
- package/dist/core/events.d.ts +103 -0
- package/dist/core/formController.d.ts +28 -0
- package/dist/core/forms.d.ts +235 -0
- package/dist/core/index.d.ts +29 -0
- package/dist/core/prevContext.d.ts +26 -0
- package/dist/core/transport.d.ts +30 -0
- package/dist/core/types.d.ts +49 -0
- package/dist/core/voice.d.ts +79 -0
- package/dist/createVoiceAgent-BM3HODS6.js +1058 -0
- package/dist/createVoiceAgent-BM3HODS6.js.map +1 -0
- package/dist/createVoiceAgent-CJWxWzz6.cjs +4 -0
- package/dist/createVoiceAgent-CJWxWzz6.cjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +60 -0
- package/dist/react.cjs +2 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.js +115 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +1838 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/ui.d.ts +165 -0
- package/dist/ui.cjs +284 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.js +1153 -0
- package/dist/ui.js.map +1 -0
- package/package.json +67 -0
- package/src/core/analytics.ts +111 -0
- package/src/core/appearance.ts +464 -0
- package/src/core/audioSettings.ts +180 -0
- package/src/core/consent.ts +78 -0
- package/src/core/createVoiceAgent.ts +280 -0
- package/src/core/events.ts +120 -0
- package/src/core/formController.ts +317 -0
- package/src/core/forms.ts +861 -0
- package/src/core/index.ts +121 -0
- package/src/core/prevContext.ts +153 -0
- package/src/core/transport.ts +118 -0
- package/src/core/types.ts +66 -0
- package/src/core/voice.ts +1179 -0
- package/src/react/index.ts +238 -0
- package/src/ui/index.ts +507 -0
- package/src/ui/styles.css +1838 -0
- package/src/ui/ui.ts +1672 -0
- package/src/vite-env.d.ts +10 -0
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sources":["../src/ui/ui.ts","../src/ui/index.ts"],"sourcesContent":["import { AppearanceConfig, AppearanceLanguage, DEFAULT_APPEARANCE } from \"../core/appearance\";\nimport {\n FormDefinition,\n FormFieldDef,\n FormFieldOption,\n fieldsForStep,\n totalSteps,\n} from \"../core/forms\";\nimport stylesheet from \"./styles.css?inline\";\n\nexport interface FormInputBinding {\n /** Read the current field value as a string (comma-joined for multi-select groups). */\n read: () => string;\n /** Toggle the disabled state across one or more underlying inputs. */\n setDisabled: (busy: boolean) => void;\n /** Show (or, with an empty string, clear) an inline validation error under the field. */\n setError: (message: string) => void;\n}\n\nexport type Screen = \"welcome\" | \"call\" | \"form\";\n\nexport interface UIRefs {\n host: HTMLDivElement;\n shadow: ShadowRoot;\n fab: HTMLButtonElement;\n panel: HTMLDivElement;\n closeBtn: HTMLButtonElement;\n\n welcomeScreen: HTMLDivElement;\n welcomeLogo: HTMLImageElement;\n welcomeLogoFallback: HTMLDivElement;\n welcomeName: HTMLDivElement;\n welcomeDesc: HTMLDivElement;\n langPicker: HTMLDivElement;\n langTrigger: HTMLButtonElement;\n langTriggerLabel: HTMLSpanElement;\n langTriggerCode: HTMLSpanElement;\n langMenu: HTMLDivElement;\n startBtn: HTMLButtonElement;\n startBtnLabel: HTMLSpanElement;\n consent: HTMLDivElement;\n consentText: HTMLSpanElement;\n consentLink: HTMLAnchorElement;\n poweredBy: HTMLAnchorElement;\n /** Currently selected language code (BCP-47 short, e.g. \"en\"). */\n selectedLanguage: string;\n\n callScreen: HTMLDivElement;\n callLogo: HTMLImageElement;\n callLogoFallback: HTMLDivElement;\n callName: HTMLDivElement;\n callStatus: HTMLDivElement;\n callTimer: HTMLDivElement;\n callTimerText: HTMLSpanElement;\n orb: HTMLDivElement;\n orbLabel: HTMLDivElement;\n /** Contextual processing line under the orb (\"Searching the knowledge base…\"). */\n agentStatusLine: HTMLDivElement;\n transcript: HTMLDivElement;\n transcriptSegments: Map<string, HTMLDivElement>;\n /**\n * Per-role pointer to the bubble that is still in `interim` state. Used to\n * coalesce streaming + final segments when the STT backend emits different\n * `seg.id`s for interim vs final (e.g. sherpa), which would otherwise leave\n * the half-finished interim bubble onscreen alongside a new final bubble.\n */\n transcriptInterimBubble: Map<\"user\" | \"agent\", HTMLDivElement>;\n textInputRow: HTMLDivElement;\n textInput: HTMLTextAreaElement;\n textSendBtn: HTMLButtonElement;\n muteBtn: HTMLButtonElement;\n endBtn: HTMLButtonElement;\n settingsBtn: HTMLButtonElement;\n\n audioDrawer: HTMLDivElement;\n audioDrawerClose: HTMLButtonElement;\n audioMeterBar: HTMLDivElement;\n audioMicSelect: HTMLSelectElement;\n audioSpeakerSelect: HTMLSelectElement;\n audioSpeakerRow: HTMLLabelElement;\n audioVolume: HTMLInputElement;\n audioVolumeValue: HTMLSpanElement;\n audioNcEngine: HTMLSelectElement;\n audioDfStrengthRow: HTMLLabelElement;\n audioDfStrength: HTMLInputElement;\n audioDfStrengthValue: HTMLSpanElement;\n audioTogAec: HTMLInputElement;\n audioTogNs: HTMLInputElement;\n audioTogAgc: HTMLInputElement;\n audioTogVi: HTMLInputElement;\n audioTogViRow: HTMLLabelElement;\n audioTogHp: HTMLInputElement;\n audioTogTranscription: HTMLInputElement;\n audioTogTextInput: HTMLInputElement;\n audioDiagEngine: HTMLElement;\n audioDiagAec: HTMLElement;\n audioDiagNs: HTMLElement;\n audioDiagAgc: HTMLElement;\n audioDiagVi: HTMLElement;\n audioDiagSr: HTMLElement;\n audioDiagLoss: HTMLElement;\n audioDiagJitter: HTMLElement;\n audioDiagRtt: HTMLElement;\n\n formScreen: HTMLDivElement;\n formTranscript: HTMLDivElement;\n formTranscriptSegments: Map<string, HTMLDivElement>;\n formTranscriptInterimBubble: Map<\"user\" | \"agent\", HTMLDivElement>;\n formTitle: HTMLDivElement;\n formEyebrow: HTMLDivElement;\n formSubtitle: HTMLDivElement;\n formStepper: HTMLDivElement;\n formFields: HTMLDivElement;\n formError: HTMLDivElement;\n formSuccess: HTMLDivElement;\n formSubmitBtn: HTMLButtonElement;\n formBackBtn: HTMLButtonElement;\n formCancelBtn: HTMLButtonElement;\n formStepBackBtn: HTMLButtonElement;\n formCallControls: HTMLDivElement;\n formMuteBtn: HTMLButtonElement;\n formEndBtn: HTMLButtonElement;\n formInputs: Map<string, FormInputBinding>;\n\n appearance: AppearanceConfig;\n}\n\nconst ICON_FAB = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/>\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/>\n</svg>`;\n\nconst ICON_CLOSE = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n</svg>`;\n\nconst ICON_PHONE = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <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.72c.13.96.37 1.9.72 2.81a2 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-.45c.91.35 1.85.59 2.81.72A2 2 0 0 1 22 16.92z\"/>\n</svg>`;\n\nconst ICON_MIC_ON = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/>\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/>\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/>\n</svg>`;\n\nconst ICON_MIC_OFF = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"/>\n <path d=\"M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6\"/>\n <path d=\"M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23\"/>\n <line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/>\n <line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"/>\n</svg>`;\n\nconst ICON_HANGUP = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 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.91\"/>\n <line x1=\"23\" y1=\"1\" x2=\"1\" y2=\"23\"/>\n</svg>`;\n\nconst ICON_AGENT_FALLBACK = `\n<svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2a4 4 0 0 0-4 4v2a4 4 0 0 0 8 0V6a4 4 0 0 0-4-4zm6 8a1 1 0 0 0-2 0 4 4 0 0 1-8 0 1 1 0 0 0-2 0 6 6 0 0 0 5 5.91V19H8a1 1 0 0 0 0 2h8a1 1 0 0 0 0-2h-3v-3.09A6 6 0 0 0 18 10z\"/>\n</svg>`;\n\nconst ICON_GLOBE = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\"/>\n <path d=\"M3 12h18\"/>\n <path d=\"M12 3a13.5 13.5 0 0 1 0 18\"/>\n <path d=\"M12 3a13.5 13.5 0 0 0 0 18\"/>\n</svg>`;\n\nconst ICON_CHEVRON = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\"/>\n</svg>`;\n\nconst ICON_CHECK = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n</svg>`;\n\nconst ICON_SEND = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"/>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"/>\n</svg>`;\n\nconst ICON_SETTINGS = `\n<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"3\"/>\n <path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z\"/>\n</svg>`;\n\nexport function buildUI(\n rootId: string,\n opts: {\n inline?: boolean;\n parent?: HTMLElement;\n closeButtonHide?: boolean;\n } = {},\n): UIRefs {\n const host = document.createElement(\"div\");\n host.id = rootId;\n if (opts.closeButtonHide) {\n host.setAttribute(\"data-close-button-hide\", \"\");\n }\n if (opts.inline) {\n // Fill the embedding container (which must be a positioned ancestor)\n // rather than floating in a viewport corner.\n host.setAttribute(\"data-inline\", \"\");\n host.style.position = \"absolute\";\n host.style.inset = \"0\";\n host.style.zIndex = \"1\";\n } else {\n host.style.position = \"fixed\";\n host.style.inset = \"auto\";\n host.style.zIndex = \"2147483647\";\n }\n (opts.parent ?? document.body).appendChild(host);\n\n const shadow = host.attachShadow({ mode: \"open\" });\n\n const style = document.createElement(\"style\");\n style.textContent = stylesheet;\n shadow.appendChild(style);\n\n const fab = document.createElement(\"button\");\n fab.className = \"fab\";\n fab.setAttribute(\"aria-label\", \"Open voice agent\");\n // Hidden until appearance has loaded — avoids a flash of defaults.\n fab.style.visibility = \"hidden\";\n fab.innerHTML = `\n <span class=\"fab-icon\">${ICON_FAB}</span>\n <span class=\"fab-text\">\n <span class=\"fab-label\"></span>\n <span class=\"fab-sublabel\"></span>\n </span>\n `;\n shadow.appendChild(fab);\n\n const panel = document.createElement(\"div\");\n panel.className = \"panel\";\n panel.style.display = \"none\";\n panel.innerHTML = `\n <button class=\"close\" aria-label=\"Close\">${ICON_CLOSE}</button>\n\n <section class=\"screen screen-welcome\" data-screen=\"welcome\">\n <div class=\"welcome-inner\">\n <div class=\"welcome-logo\">\n <div class=\"welcome-logo-fallback\">${ICON_AGENT_FALLBACK}</div>\n <img class=\"welcome-logo-img\" alt=\"\" style=\"display:none\" />\n </div>\n <div class=\"welcome-status\"><span class=\"welcome-status-dot\"></span><span class=\"welcome-status-text\">Online</span></div>\n <h1 class=\"welcome-name\">Assistant</h1>\n <p class=\"welcome-desc\">Tap below to start a voice conversation.</p>\n <div class=\"lang-picker\" data-open=\"false\">\n <button class=\"lang-trigger\" type=\"button\" aria-haspopup=\"listbox\" aria-expanded=\"false\">\n <span class=\"lang-trigger-icon\">${ICON_GLOBE}</span>\n <span class=\"lang-trigger-text\">\n <span class=\"lang-trigger-eyebrow\">Language</span>\n <span class=\"lang-trigger-label\">English</span>\n </span>\n <span class=\"lang-trigger-code\">EN</span>\n <span class=\"lang-trigger-chev\">${ICON_CHEVRON}</span>\n </button>\n <div class=\"lang-menu\" role=\"listbox\" hidden></div>\n </div>\n <button class=\"start-btn\" aria-label=\"Start call\">\n <span class=\"start-btn-icon\">${ICON_PHONE}</span>\n <span class=\"start-btn-label\">Start Call</span>\n </button>\n <div class=\"consent\" hidden>\n <span class=\"consent-text\"></span>\n <a class=\"consent-link\" target=\"_blank\" rel=\"noopener noreferrer\"></a>\n </div>\n </div>\n <a class=\"welcome-foot\" target=\"_blank\" rel=\"noopener noreferrer\"></a>\n </section>\n\n <section class=\"screen screen-form\" data-screen=\"form\" style=\"display:none\">\n <header class=\"form-header\">\n <button class=\"form-back\" type=\"button\" aria-label=\"Back\">${ICON_CHEVRON}</button>\n <div class=\"form-header-text\">\n <div class=\"form-eyebrow\">Review before submit</div>\n <div class=\"form-title\">Form</div>\n </div>\n <div class=\"form-call-controls\" hidden>\n <button class=\"form-mute\" type=\"button\" aria-label=\"Mute\">${ICON_MIC_ON}</button>\n <button class=\"form-end\" type=\"button\" aria-label=\"End call\">${ICON_HANGUP}</button>\n </div>\n </header>\n <div class=\"form-transcript\" hidden aria-live=\"polite\"></div>\n <div class=\"form-stepper\" hidden></div>\n <div class=\"form-body\">\n <p class=\"form-subtitle\"></p>\n <div class=\"form-fields\"></div>\n <div class=\"form-error\" hidden></div>\n <div class=\"form-success\" hidden></div>\n </div>\n <footer class=\"form-actions\">\n <button class=\"form-step-back\" type=\"button\" hidden>Back</button>\n <button class=\"form-cancel\" type=\"button\">Cancel</button>\n <button class=\"form-submit\" type=\"button\">\n <span class=\"form-submit-label\">Confirm & send</span>\n </button>\n </footer>\n </section>\n\n <section class=\"screen screen-call\" data-screen=\"call\" style=\"display:none\">\n <header class=\"call-header\">\n <div class=\"call-id\">\n <div class=\"call-logo\">\n <div class=\"call-logo-fallback\">${ICON_AGENT_FALLBACK}</div>\n <img class=\"call-logo-img\" alt=\"\" style=\"display:none\" />\n </div>\n <div class=\"call-id-text\">\n <div class=\"call-name\">Assistant</div>\n <div class=\"call-status\">Connecting…</div>\n </div>\n </div>\n <div class=\"call-timer\" hidden aria-live=\"polite\">\n <span class=\"call-timer-text\">0:00</span>\n </div>\n </header>\n\n <div class=\"call-stage\">\n <div class=\"orb\">\n <span class=\"orb-ring orb-ring-1\"></span>\n <span class=\"orb-ring orb-ring-2\"></span>\n <span class=\"orb-ring orb-ring-3\"></span>\n <span class=\"orb-core\"></span>\n </div>\n <div class=\"orb-label\">Idle</div>\n <div class=\"agent-status-line\" aria-live=\"polite\" hidden></div>\n </div>\n\n <div class=\"transcript\" aria-live=\"polite\" style=\"display: none;\"></div>\n\n <div class=\"text-input-row\" hidden>\n <textarea class=\"text-input\" placeholder=\"Type a message…\" rows=\"1\" aria-label=\"Type a message\"></textarea>\n <button class=\"text-send-btn\" type=\"button\" aria-label=\"Send message\">${ICON_SEND}</button>\n </div>\n\n <div class=\"call-controls\">\n <button class=\"ctl-btn ctl-mute\" aria-label=\"Mute\" disabled>\n ${ICON_MIC_ON}\n </button>\n <button class=\"ctl-btn ctl-settings\" aria-label=\"Audio settings\" hidden>\n ${ICON_SETTINGS}\n </button>\n <button class=\"ctl-btn ctl-end\" aria-label=\"End call\" disabled>\n ${ICON_HANGUP}\n </button>\n </div>\n\n <div class=\"audio-drawer\" hidden aria-label=\"Audio settings\">\n <header class=\"audio-drawer-head\">\n <div class=\"audio-drawer-title\">Audio settings</div>\n <button class=\"audio-drawer-close\" type=\"button\" aria-label=\"Close audio settings\">${ICON_CLOSE}</button>\n </header>\n <div class=\"audio-drawer-body\">\n <div class=\"audio-row audio-row-meter\">\n <span class=\"audio-row-label\">Microphone level</span>\n <div class=\"audio-meter\"><div class=\"audio-meter-bar\"></div></div>\n </div>\n <label class=\"audio-row audio-row-select\">\n <span class=\"audio-row-label\">Microphone</span>\n <select class=\"audio-mic-select\"></select>\n </label>\n <label class=\"audio-row audio-row-select audio-row-speaker\">\n <span class=\"audio-row-label\">Speaker</span>\n <select class=\"audio-speaker-select\"></select>\n </label>\n <label class=\"audio-row audio-row-slider\">\n <span class=\"audio-row-label\">Speaker volume <span class=\"audio-volume-value\">85%</span></span>\n <input class=\"audio-volume\" type=\"range\" min=\"0\" max=\"100\" step=\"1\" value=\"85\" />\n </label>\n <label class=\"audio-row audio-row-select\">\n <span class=\"audio-row-label\">Noise cancellation engine</span>\n <select class=\"audio-nc-engine\">\n <option value=\"off\">Off (browser only)</option>\n <option value=\"krisp\">Krisp (default)</option>\n <option value=\"deepfilter\">DeepFilterNet3</option>\n </select>\n </label>\n <label class=\"audio-row audio-row-slider audio-row-df-strength\" hidden>\n <span class=\"audio-row-label\">DeepFilter strength <span class=\"audio-df-strength-value\">80</span></span>\n <input class=\"audio-df-strength\" type=\"range\" min=\"0\" max=\"100\" step=\"1\" value=\"80\" />\n </label>\n <div class=\"audio-toggles\">\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Echo cancellation</span>\n <span class=\"audio-toggle-hint\">Browser AEC</span>\n </span>\n <input class=\"audio-tog-aec\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Noise suppression</span>\n <span class=\"audio-toggle-hint\">Browser noise filter</span>\n </span>\n <input class=\"audio-tog-ns\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Auto gain</span>\n <span class=\"audio-toggle-hint\">Normalize my volume</span>\n </span>\n <input class=\"audio-tog-agc\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle audio-toggle-vi\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Voice isolation</span>\n <span class=\"audio-toggle-hint\">Edge/Chrome only</span>\n </span>\n <input class=\"audio-tog-vi\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Headphones mode</span>\n <span class=\"audio-toggle-hint\">Disable mic ducking</span>\n </span>\n <input class=\"audio-tog-hp\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Live Transcription</span>\n <span class=\"audio-toggle-hint\">Show conversation text</span>\n </span>\n <input class=\"audio-tog-transcription\" type=\"checkbox\" />\n </label>\n <label class=\"audio-toggle\">\n <span class=\"audio-toggle-text\">\n <span class=\"audio-toggle-label\">Text Input</span>\n <span class=\"audio-toggle-hint\">Type messages to the agent</span>\n </span>\n <input class=\"audio-tog-text-input\" type=\"checkbox\" />\n </label>\n </div>\n <details class=\"audio-diag\">\n <summary>Diagnostics</summary>\n <dl class=\"audio-diag-list\">\n <dt>NC engine</dt><dd class=\"audio-diag-engine\">—</dd>\n <dt>Echo cancellation</dt><dd class=\"audio-diag-aec\">—</dd>\n <dt>Noise suppression</dt><dd class=\"audio-diag-ns\">—</dd>\n <dt>Auto gain</dt><dd class=\"audio-diag-agc\">—</dd>\n <dt>Voice isolation</dt><dd class=\"audio-diag-vi\">—</dd>\n <dt>Sample rate</dt><dd class=\"audio-diag-sr\">—</dd>\n <dt>Packet loss</dt><dd class=\"audio-diag-loss\">—</dd>\n <dt>Jitter</dt><dd class=\"audio-diag-jitter\">—</dd>\n <dt>RTT</dt><dd class=\"audio-diag-rtt\">—</dd>\n </dl>\n </details>\n </div>\n </div>\n </section>\n `;\n shadow.appendChild(panel);\n\n const q = <T extends Element>(sel: string) => panel.querySelector(sel) as T;\n\n return {\n host,\n shadow,\n fab,\n panel,\n closeBtn: q<HTMLButtonElement>(\".close\"),\n\n welcomeScreen: q<HTMLDivElement>(\".screen-welcome\"),\n welcomeLogo: q<HTMLImageElement>(\".welcome-logo-img\"),\n welcomeLogoFallback: q<HTMLDivElement>(\".welcome-logo-fallback\"),\n welcomeName: q<HTMLDivElement>(\".welcome-name\"),\n welcomeDesc: q<HTMLDivElement>(\".welcome-desc\"),\n langPicker: q<HTMLDivElement>(\".lang-picker\"),\n langTrigger: q<HTMLButtonElement>(\".lang-trigger\"),\n langTriggerLabel: q<HTMLSpanElement>(\".lang-trigger-label\"),\n langTriggerCode: q<HTMLSpanElement>(\".lang-trigger-code\"),\n langMenu: q<HTMLDivElement>(\".lang-menu\"),\n startBtn: q<HTMLButtonElement>(\".start-btn\"),\n startBtnLabel: q<HTMLSpanElement>(\".start-btn-label\"),\n consent: q<HTMLDivElement>(\".consent\"),\n consentText: q<HTMLSpanElement>(\".consent-text\"),\n consentLink: q<HTMLAnchorElement>(\".consent-link\"),\n poweredBy: q<HTMLAnchorElement>(\".welcome-foot\"),\n\n callScreen: q<HTMLDivElement>(\".screen-call\"),\n callLogo: q<HTMLImageElement>(\".call-logo-img\"),\n callLogoFallback: q<HTMLDivElement>(\".call-logo-fallback\"),\n callName: q<HTMLDivElement>(\".call-name\"),\n callStatus: q<HTMLDivElement>(\".call-status\"),\n callTimer: q<HTMLDivElement>(\".call-timer\"),\n callTimerText: q<HTMLSpanElement>(\".call-timer-text\"),\n orb: q<HTMLDivElement>(\".orb\"),\n orbLabel: q<HTMLDivElement>(\".orb-label\"),\n agentStatusLine: q<HTMLDivElement>(\".agent-status-line\"),\n transcript: q<HTMLDivElement>(\".transcript\"),\n transcriptSegments: new Map<string, HTMLDivElement>(),\n transcriptInterimBubble: new Map<\"user\" | \"agent\", HTMLDivElement>(),\n textInputRow: q<HTMLDivElement>(\".text-input-row\"),\n textInput: q<HTMLTextAreaElement>(\".text-input\"),\n textSendBtn: q<HTMLButtonElement>(\".text-send-btn\"),\n muteBtn: q<HTMLButtonElement>(\".ctl-mute\"),\n endBtn: q<HTMLButtonElement>(\".ctl-end\"),\n settingsBtn: q<HTMLButtonElement>(\".ctl-settings\"),\n\n audioDrawer: q<HTMLDivElement>(\".audio-drawer\"),\n audioDrawerClose: q<HTMLButtonElement>(\".audio-drawer-close\"),\n audioMeterBar: q<HTMLDivElement>(\".audio-meter-bar\"),\n audioMicSelect: q<HTMLSelectElement>(\".audio-mic-select\"),\n audioSpeakerSelect: q<HTMLSelectElement>(\".audio-speaker-select\"),\n audioSpeakerRow: q<HTMLLabelElement>(\".audio-row-speaker\"),\n audioVolume: q<HTMLInputElement>(\".audio-volume\"),\n audioVolumeValue: q<HTMLSpanElement>(\".audio-volume-value\"),\n audioNcEngine: q<HTMLSelectElement>(\".audio-nc-engine\"),\n audioDfStrengthRow: q<HTMLLabelElement>(\".audio-row-df-strength\"),\n audioDfStrength: q<HTMLInputElement>(\".audio-df-strength\"),\n audioDfStrengthValue: q<HTMLSpanElement>(\".audio-df-strength-value\"),\n audioTogAec: q<HTMLInputElement>(\".audio-tog-aec\"),\n audioTogNs: q<HTMLInputElement>(\".audio-tog-ns\"),\n audioTogAgc: q<HTMLInputElement>(\".audio-tog-agc\"),\n audioTogVi: q<HTMLInputElement>(\".audio-tog-vi\"),\n audioTogViRow: q<HTMLLabelElement>(\".audio-toggle-vi\"),\n audioTogHp: q<HTMLInputElement>(\".audio-tog-hp\"),\n audioTogTranscription: q<HTMLInputElement>(\".audio-tog-transcription\"),\n audioTogTextInput: q<HTMLInputElement>(\".audio-tog-text-input\"),\n audioDiagEngine: q<HTMLElement>(\".audio-diag-engine\"),\n audioDiagAec: q<HTMLElement>(\".audio-diag-aec\"),\n audioDiagNs: q<HTMLElement>(\".audio-diag-ns\"),\n audioDiagAgc: q<HTMLElement>(\".audio-diag-agc\"),\n audioDiagVi: q<HTMLElement>(\".audio-diag-vi\"),\n audioDiagSr: q<HTMLElement>(\".audio-diag-sr\"),\n audioDiagLoss: q<HTMLElement>(\".audio-diag-loss\"),\n audioDiagJitter: q<HTMLElement>(\".audio-diag-jitter\"),\n audioDiagRtt: q<HTMLElement>(\".audio-diag-rtt\"),\n\n formScreen: q<HTMLDivElement>(\".screen-form\"),\n formTranscript: q<HTMLDivElement>(\".form-transcript\"),\n formTranscriptSegments: new Map<string, HTMLDivElement>(),\n formTranscriptInterimBubble: new Map<\"user\" | \"agent\", HTMLDivElement>(),\n formTitle: q<HTMLDivElement>(\".form-title\"),\n formEyebrow: q<HTMLDivElement>(\".form-eyebrow\"),\n formSubtitle: q<HTMLDivElement>(\".form-subtitle\"),\n formStepper: q<HTMLDivElement>(\".form-stepper\"),\n formFields: q<HTMLDivElement>(\".form-fields\"),\n formError: q<HTMLDivElement>(\".form-error\"),\n formSuccess: q<HTMLDivElement>(\".form-success\"),\n formSubmitBtn: q<HTMLButtonElement>(\".form-submit\"),\n formBackBtn: q<HTMLButtonElement>(\".form-back\"),\n formCancelBtn: q<HTMLButtonElement>(\".form-cancel\"),\n formStepBackBtn: q<HTMLButtonElement>(\".form-step-back\"),\n formCallControls: q<HTMLDivElement>(\".form-call-controls\"),\n formMuteBtn: q<HTMLButtonElement>(\".form-mute\"),\n formEndBtn: q<HTMLButtonElement>(\".form-end\"),\n formInputs: new Map(),\n\n appearance: DEFAULT_APPEARANCE,\n selectedLanguage: DEFAULT_APPEARANCE.default_language,\n };\n}\n\nfunction hostStyleVar(name: string, value: string) {\n return `${name}: ${value};`;\n}\n\nexport function applyAppearance(refs: UIRefs, appearance: AppearanceConfig) {\n refs.appearance = appearance;\n\n const t = appearance.theme;\n const d = appearance.dimensions;\n const primaryText = contrastingText(t.primary_color);\n const accentText = contrastingText(t.accent_color);\n\n const vars = [\n hostStyleVar(\"--va-primary\", t.primary_color),\n hostStyleVar(\"--va-primary-text\", primaryText),\n hostStyleVar(\"--va-accent\", t.accent_color),\n hostStyleVar(\"--va-accent-text\", accentText),\n hostStyleVar(\"--va-background\", t.background_color),\n hostStyleVar(\"--va-surface\", surfaceFromBackground(t.background_color)),\n hostStyleVar(\"--va-text\", t.text_color),\n hostStyleVar(\"--va-text-muted\", muteText(t.text_color, t.background_color)),\n hostStyleVar(\"--va-user-bubble\", t.user_bubble_color),\n hostStyleVar(\"--va-user-bubble-text\", t.user_bubble_text_color),\n hostStyleVar(\"--va-agent-bubble\", t.agent_bubble_color),\n hostStyleVar(\"--va-agent-bubble-text\", t.agent_bubble_text_color),\n hostStyleVar(\"--va-fab-size\", `${d.fab_size}px`),\n hostStyleVar(\"--va-panel-width\", `${d.panel_width}px`),\n hostStyleVar(\"--va-panel-height\", `${d.panel_height}px`),\n hostStyleVar(\"--va-border-radius\", `${d.border_radius}px`),\n hostStyleVar(\"--va-font\", appearance.layout.font_family),\n ];\n const inline = refs.host.hasAttribute(\"data-inline\");\n refs.host.style.cssText = inline\n ? `position: absolute; inset: 0; z-index: 1; ${vars.join(\" \")}`\n : `position: fixed; inset: auto; z-index: 2147483647; ${vars.join(\" \")}`;\n // Corner anchoring is meaningless inline (the panel fills the host).\n if (!inline) refs.host.setAttribute(\"data-position\", appearance.layout.position);\n\n refs.transcript.setAttribute(\n \"data-placeholder\",\n appearance.labels.transcript_placeholder ||\n DEFAULT_APPEARANCE.labels.transcript_placeholder,\n );\n\n const name = appearance.name || DEFAULT_APPEARANCE.name;\n const desc = appearance.subtitle || DEFAULT_APPEARANCE.subtitle;\n\n refs.welcomeName.textContent = name;\n refs.welcomeDesc.textContent = desc;\n refs.callName.textContent = name;\n refs.fab.setAttribute(\"aria-label\", `Open ${name}`);\n\n refs.startBtnLabel.textContent =\n appearance.start_button_text || DEFAULT_APPEARANCE.start_button_text;\n refs.startBtn.setAttribute(\n \"aria-label\",\n appearance.start_button_text || DEFAULT_APPEARANCE.start_button_text,\n );\n\n const poweredByText = appearance.powered_by_text ?? \"\";\n refs.poweredBy.textContent = poweredByText;\n refs.poweredBy.style.display = poweredByText ? \"\" : \"none\";\n const poweredByHref = safeHref(appearance.powered_by_url);\n if (poweredByHref) {\n refs.poweredBy.href = poweredByHref;\n refs.poweredBy.classList.add(\"is-link\");\n } else {\n refs.poweredBy.removeAttribute(\"href\");\n refs.poweredBy.classList.remove(\"is-link\");\n }\n\n const logoSrc = safeImageSrc(appearance.logo_url);\n const hasLogo = Boolean(\n logoSrc && logoSrc !== \"https://example.com/logo.png\",\n );\n\n if (hasLogo) {\n refs.welcomeLogo.src = logoSrc;\n refs.welcomeLogo.alt = name;\n refs.welcomeLogo.style.display = \"\";\n refs.welcomeLogoFallback.style.display = \"none\";\n\n refs.callLogo.src = logoSrc;\n refs.callLogo.alt = name;\n refs.callLogo.style.display = \"\";\n refs.callLogoFallback.style.display = \"none\";\n } else {\n refs.welcomeLogo.removeAttribute(\"src\");\n refs.welcomeLogo.style.display = \"none\";\n refs.welcomeLogoFallback.style.display = \"\";\n\n refs.callLogo.removeAttribute(\"src\");\n refs.callLogo.style.display = \"none\";\n refs.callLogoFallback.style.display = \"\";\n }\n\n applyFabContent(refs, appearance, hasLogo, name);\n renderLanguagePicker(refs, appearance);\n renderConsent(refs, appearance);\n\n // Reveal the FAB now that real values are in place.\n refs.fab.style.visibility = \"\";\n}\n\nfunction renderConsent(refs: UIRefs, appearance: AppearanceConfig) {\n const url = (appearance.terms_url || \"\").trim();\n if (!url) {\n refs.consent.hidden = true;\n return;\n }\n refs.consent.hidden = false;\n\n const prefix =\n appearance.consent_text || DEFAULT_APPEARANCE.consent_text;\n const label =\n appearance.terms_label || DEFAULT_APPEARANCE.terms_label;\n\n refs.consentText.textContent = `${prefix} `;\n refs.consentLink.textContent = label;\n refs.consentLink.href = safeHref(url);\n refs.consentLink.setAttribute(\"aria-label\", label);\n}\n\nfunction renderLanguagePicker(refs: UIRefs, appearance: AppearanceConfig) {\n const languages = appearance.languages.length\n ? appearance.languages\n : DEFAULT_APPEARANCE.languages;\n\n const previous = refs.selectedLanguage;\n const initial =\n languages.find((l) => l.code === previous) ??\n languages.find((l) => l.code === appearance.default_language) ??\n languages[0];\n\n refs.selectedLanguage = initial.code;\n\n if (languages.length <= 1) {\n refs.langPicker.style.display = \"none\";\n return;\n }\n refs.langPicker.style.display = \"\";\n\n const eyebrowEl = refs.langTrigger.querySelector(\n \".lang-trigger-eyebrow\",\n ) as HTMLSpanElement | null;\n if (eyebrowEl) {\n eyebrowEl.textContent =\n appearance.labels.language_label ||\n DEFAULT_APPEARANCE.labels.language_label;\n }\n\n refs.langMenu.innerHTML = \"\";\n for (const lang of languages) {\n const opt = document.createElement(\"button\");\n opt.type = \"button\";\n opt.className = \"lang-option\";\n opt.setAttribute(\"role\", \"option\");\n opt.dataset.code = lang.code;\n opt.innerHTML = `\n <span class=\"lang-option-text\">\n <span class=\"lang-option-native\">${escapeHtml(lang.native_label)}</span>\n <span class=\"lang-option-label\">${escapeHtml(lang.label)}</span>\n </span>\n <span class=\"lang-option-code\">${escapeHtml(lang.code.toUpperCase())}</span>\n <span class=\"lang-option-check\">${ICON_CHECK}</span>\n `;\n opt.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n selectLanguage(refs, languages, lang.code);\n closeLanguageMenu(refs);\n });\n refs.langMenu.appendChild(opt);\n }\n\n selectLanguage(refs, languages, initial.code);\n wireLanguageTrigger(refs);\n}\n\nfunction wireLanguageTrigger(refs: UIRefs) {\n const picker = refs.langPicker;\n if (picker.dataset.wired === \"1\") return;\n picker.dataset.wired = \"1\";\n\n refs.langTrigger.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const open = picker.dataset.open === \"true\";\n if (open) closeLanguageMenu(refs);\n else openLanguageMenu(refs);\n });\n\n refs.shadow.addEventListener(\"click\", (e) => {\n if (picker.dataset.open !== \"true\") return;\n const target = e.target as Node | null;\n if (target && picker.contains(target)) return;\n closeLanguageMenu(refs);\n });\n\n refs.shadow.addEventListener(\"keydown\", (e) => {\n if ((e as KeyboardEvent).key === \"Escape\" && picker.dataset.open === \"true\") {\n closeLanguageMenu(refs);\n refs.langTrigger.focus();\n }\n });\n}\n\nfunction openLanguageMenu(refs: UIRefs) {\n refs.langPicker.dataset.open = \"true\";\n refs.langMenu.hidden = false;\n refs.langTrigger.setAttribute(\"aria-expanded\", \"true\");\n}\n\nfunction closeLanguageMenu(refs: UIRefs) {\n refs.langPicker.dataset.open = \"false\";\n refs.langMenu.hidden = true;\n refs.langTrigger.setAttribute(\"aria-expanded\", \"false\");\n}\n\nfunction selectLanguage(\n refs: UIRefs,\n languages: AppearanceLanguage[],\n code: string,\n) {\n const lang =\n languages.find((l) => l.code === code) ??\n languages[0];\n refs.selectedLanguage = lang.code;\n\n refs.langTriggerLabel.textContent = lang.native_label || lang.label;\n refs.langTriggerCode.textContent = lang.code.toUpperCase();\n refs.langTrigger.setAttribute(\n \"aria-label\",\n `${refs.appearance.labels.language_label || \"Language\"}: ${lang.label}`,\n );\n\n const options = refs.langMenu.querySelectorAll<HTMLButtonElement>(\".lang-option\");\n options.forEach((el) => {\n const isActive = el.dataset.code === lang.code;\n el.classList.toggle(\"is-active\", isActive);\n el.setAttribute(\"aria-selected\", isActive ? \"true\" : \"false\");\n });\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Return `url` only if it uses a safe protocol, else \"\". The widget runs in the\n * host page's origin, and appearance config (powered_by_url / terms_url /\n * logo_url) is tenant-controlled — without this, a `javascript:` URL assigned\n * to an anchor href or image src is stored XSS against every visitor.\n */\nfunction safeHref(url: string | undefined): string {\n if (!url) return \"\";\n try {\n const proto = new URL(url, location.href).protocol;\n return [\"https:\", \"http:\", \"mailto:\"].includes(proto) ? url : \"\";\n } catch {\n return \"\";\n }\n}\n\n/** Like safeHref but for image sources — http/https only (no data:/javascript:). */\nfunction safeImageSrc(url: string | undefined): string {\n if (!url) return \"\";\n try {\n const proto = new URL(url, location.href).protocol;\n return [\"https:\", \"http:\"].includes(proto) ? url : \"\";\n } catch {\n return \"\";\n }\n}\n\nfunction renderInline(text: string): string {\n return escapeHtml(text)\n .replace(/\\*\\*(.+?)\\*\\*/g, \"<strong>$1</strong>\")\n .replace(/__(.+?)__/g, \"<strong>$1</strong>\")\n .replace(/\\*(.+?)\\*/g, \"<em>$1</em>\")\n .replace(/_(.+?)_/g, \"<em>$1</em>\")\n .replace(/`([^`]+)`/g, \"<code>$1</code>\")\n .replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g, '<a href=\"$2\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>');\n}\n\nfunction renderMarkdown(text: string): string {\n const lines = text.split(\"\\n\");\n const blocks: string[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n const h3 = line.match(/^### (.+)/);\n const h2 = line.match(/^## (.+)/);\n const h1 = line.match(/^# (.+)/);\n if (h3) { blocks.push(`<h3>${renderInline(h3[1])}</h3>`); i++; continue; }\n if (h2) { blocks.push(`<h2>${renderInline(h2[1])}</h2>`); i++; continue; }\n if (h1) { blocks.push(`<h1>${renderInline(h1[1])}</h1>`); i++; continue; }\n\n if (line.match(/^[-*] /)) {\n const items: string[] = [];\n while (i < lines.length && lines[i].match(/^[-*] /)) {\n items.push(`<li>${renderInline(lines[i].slice(2))}</li>`);\n i++;\n }\n blocks.push(`<ul>${items.join(\"\")}</ul>`);\n continue;\n }\n\n if (line.match(/^\\d+\\. /)) {\n const items: string[] = [];\n while (i < lines.length && lines[i].match(/^\\d+\\. /)) {\n items.push(`<li>${renderInline(lines[i].replace(/^\\d+\\. /, \"\"))}</li>`);\n i++;\n }\n blocks.push(`<ol>${items.join(\"\")}</ol>`);\n continue;\n }\n\n if (line.startsWith(\"```\")) {\n const codeLines: string[] = [];\n i++;\n while (i < lines.length && !lines[i].startsWith(\"```\")) {\n codeLines.push(escapeHtml(lines[i]));\n i++;\n }\n if (i < lines.length) i++;\n blocks.push(`<pre><code>${codeLines.join(\"\\n\")}</code></pre>`);\n continue;\n }\n\n if (!line.trim()) { i++; continue; }\n\n const paraLines: string[] = [];\n while (i < lines.length && lines[i].trim() && !lines[i].match(/^(#{1,3} |[-*] |\\d+\\. |```)/)) {\n paraLines.push(renderInline(lines[i]));\n i++;\n }\n if (paraLines.length) blocks.push(`<p>${paraLines.join(\"<br>\")}</p>`);\n }\n\n return blocks.join(\"\");\n}\n\nfunction applyFabContent(\n refs: UIRefs,\n appearance: AppearanceConfig,\n hasLogo: boolean,\n name: string,\n) {\n const label = (appearance.fab_label || \"\").trim();\n const sublabel = (appearance.fab_sublabel || \"\").trim();\n const iconEl = refs.fab.querySelector(\".fab-icon\") as HTMLSpanElement | null;\n const textEl = refs.fab.querySelector(\".fab-text\") as HTMLSpanElement | null;\n const labelEl = refs.fab.querySelector(\".fab-label\") as HTMLSpanElement | null;\n const sublabelEl = refs.fab.querySelector(\".fab-sublabel\") as HTMLSpanElement | null;\n\n if (!iconEl || !textEl || !labelEl || !sublabelEl) return;\n\n if (hasLogo) {\n iconEl.innerHTML = `<img class=\"fab-logo\" alt=\"${escapeAttr(name)}\" src=\"${escapeAttr(safeImageSrc(appearance.logo_url))}\" />`;\n } else {\n iconEl.innerHTML = ICON_FAB;\n }\n\n if (label) {\n labelEl.textContent = label;\n refs.fab.classList.add(\"has-label\");\n refs.fab.setAttribute(\n \"aria-label\",\n sublabel ? `${label}, ${sublabel} — open ${name}` : `${label} — open ${name}`,\n );\n } else {\n labelEl.textContent = \"\";\n refs.fab.classList.remove(\"has-label\");\n refs.fab.setAttribute(\"aria-label\", `Open ${name}`);\n }\n\n if (label && sublabel) {\n sublabelEl.textContent = sublabel;\n refs.fab.classList.add(\"has-sublabel\");\n } else {\n sublabelEl.textContent = \"\";\n refs.fab.classList.remove(\"has-sublabel\");\n }\n\n textEl.style.display = label ? \"\" : \"none\";\n}\n\nexport function setScreen(refs: UIRefs, screen: Screen) {\n refs.panel.setAttribute(\"data-screen\", screen);\n refs.welcomeScreen.style.display = screen === \"welcome\" ? \"flex\" : \"none\";\n refs.callScreen.style.display = screen === \"call\" ? \"flex\" : \"none\";\n refs.formScreen.style.display = screen === \"form\" ? \"flex\" : \"none\";\n}\n\nexport interface RenderFormArgs {\n definition: FormDefinition;\n values: Record<string, string>;\n /** Label for the cancel/back button — \"Back to call\" while connected, etc. */\n cancelLabel?: string;\n /** Which step (0-indexed) to render; ignored when the form has no `steps`. */\n stepIndex?: number;\n /** Fired whenever the user edits a field. Used to push state to the agent. */\n onFieldChange?: () => void;\n}\n\n/**\n * Render a form definition's fields into the form screen. Existing values are\n * read from `values`; the on-screen inputs are kept in `refs.formInputs` so\n * the controller can pull current values back out at submit time.\n */\nexport function renderForm(refs: UIRefs, args: RenderFormArgs) {\n const { definition, values, cancelLabel } = args;\n const steps = totalSteps(definition);\n const stepIndex = Math.max(0, Math.min(args.stepIndex ?? 0, steps - 1));\n const isStepper = steps > 1;\n const isFinalStep = stepIndex === steps - 1;\n const currentStep = definition.steps?.[stepIndex];\n\n refs.formTitle.textContent = currentStep?.title ?? definition.title;\n refs.formEyebrow.textContent = isStepper\n ? `Step ${stepIndex + 1} of ${steps}${definition.title ? ` · ${definition.title}` : \"\"}`\n : \"Review before submit\";\n\n const subtitle = currentStep?.subtitle ?? (stepIndex === 0 ? definition.subtitle : \"\");\n refs.formSubtitle.textContent = subtitle ?? \"\";\n refs.formSubtitle.style.display = subtitle ? \"\" : \"none\";\n\n renderStepper(refs, steps, stepIndex);\n\n const submitLabel = refs.formSubmitBtn.querySelector(\".form-submit-label\");\n if (submitLabel) {\n submitLabel.textContent = isFinalStep\n ? definition.submit_label ?? \"Confirm & send\"\n : currentStep?.next_label ?? \"Continue\";\n }\n\n refs.formCancelBtn.textContent = cancelLabel ?? \"Cancel\";\n\n // Step-back button: visible only on stepper, hidden on first step.\n const showStepBack = isStepper && stepIndex > 0;\n refs.formStepBackBtn.hidden = !showStepBack;\n if (showStepBack) {\n refs.formStepBackBtn.textContent = currentStep?.back_label ?? \"Back\";\n }\n\n // Apply layout modifiers on the fields wrapper.\n const layout = definition.layout ?? {};\n refs.formFields.classList.toggle(\"is-grid\", layout.field_layout === \"grid\");\n refs.formFields.classList.toggle(\"is-compact\", layout.density === \"compact\");\n refs.formFields.classList.toggle(\"is-inline-labels\", layout.label_position === \"inline\");\n\n setFormError(refs, \"\");\n setFormSuccess(refs, \"\");\n\n refs.formFields.innerHTML = \"\";\n refs.formInputs.clear();\n\n for (const field of fieldsForStep(definition, stepIndex)) {\n refs.formFields.appendChild(\n buildFieldNode(\n refs,\n field,\n field.name ? values[field.name] ?? \"\" : \"\",\n args.onFieldChange,\n ),\n );\n }\n}\n\nfunction renderStepper(refs: UIRefs, steps: number, current: number) {\n if (steps <= 1) {\n refs.formStepper.hidden = true;\n refs.formStepper.innerHTML = \"\";\n return;\n }\n refs.formStepper.hidden = false;\n refs.formStepper.innerHTML = \"\";\n for (let i = 0; i < steps; i++) {\n const dot = document.createElement(\"span\");\n dot.className = \"form-stepper-dot\";\n if (i < current) dot.classList.add(\"is-done\");\n if (i === current) dot.classList.add(\"is-active\");\n refs.formStepper.appendChild(dot);\n }\n}\n\nfunction optionEntries(options: FormFieldOption[] | undefined): Array<{ value: string; label: string }> {\n if (!options) return [];\n return options.map((opt) =>\n typeof opt === \"string\"\n ? { value: opt, label: opt }\n : { value: opt.value, label: opt.label ?? opt.value },\n );\n}\n\nfunction buildFieldNode(\n refs: UIRefs,\n field: FormFieldDef,\n value: string,\n onChange?: () => void,\n): HTMLElement {\n // Reassigned once the error node exists; lets `notifyChange` clear a field's\n // inline error the moment the visitor starts fixing it.\n let clearError = () => {};\n const notifyChange = () => {\n clearError();\n if (onChange) onChange();\n };\n // Display blocks render their own wrapper with no input registered.\n if (field.type === \"display\") {\n const block = document.createElement(\"div\");\n block.className = \"form-display\";\n if (field.label) {\n const heading = document.createElement(\"div\");\n heading.className = \"form-display-title\";\n heading.textContent = field.label;\n block.appendChild(heading);\n }\n if (field.help_text) {\n const body = document.createElement(\"p\");\n body.className = \"form-display-body\";\n body.textContent = field.help_text;\n block.appendChild(body);\n }\n return block;\n }\n\n const fieldName = field.name!;\n const wrapper = document.createElement(\"label\");\n wrapper.className = \"form-field\";\n if (field.width === \"half\") wrapper.classList.add(\"is-half\");\n\n const labelEl = document.createElement(\"span\");\n labelEl.className = \"form-label\";\n labelEl.textContent = field.required ? `${field.label} *` : field.label;\n wrapper.appendChild(labelEl);\n\n // `setError` is grafted on after the error node is built below.\n let binding: Omit<FormInputBinding, \"setError\">;\n\n if (field.type === \"textarea\") {\n const ta = document.createElement(\"textarea\");\n ta.rows = field.rows ?? 4;\n ta.placeholder = field.placeholder ?? \"\";\n ta.value = value;\n ta.name = fieldName;\n ta.className = \"form-input form-textarea\";\n if (field.required) ta.required = true;\n ta.addEventListener(\"input\", notifyChange);\n wrapper.appendChild(ta);\n binding = {\n read: () => ta.value.trim(),\n setDisabled: (busy) => { ta.disabled = busy; },\n };\n } else if (field.type === \"select\") {\n const sel = document.createElement(\"select\");\n sel.className = \"form-input form-select\";\n sel.name = fieldName;\n if (field.required) sel.required = true;\n const opts = optionEntries(field.options);\n const seen = new Set(opts.map((o) => o.value));\n const all = !value || seen.has(value) ? opts : [...opts, { value, label: value }];\n for (const opt of all) {\n const o = document.createElement(\"option\");\n o.value = opt.value;\n o.textContent = opt.label;\n if (opt.value === value) o.selected = true;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", notifyChange);\n wrapper.appendChild(sel);\n binding = {\n read: () => sel.value.trim(),\n setDisabled: (busy) => { sel.disabled = busy; },\n };\n } else if (field.type === \"radio\") {\n wrapper.classList.add(\"is-choice-group\");\n const groupName = `${fieldName}-${Math.random().toString(36).slice(2, 8)}`;\n const group = document.createElement(\"div\");\n group.className = \"form-choice-group\";\n const inputs: HTMLInputElement[] = [];\n for (const opt of optionEntries(field.options)) {\n const choice = document.createElement(\"label\");\n choice.className = \"form-choice\";\n const input = document.createElement(\"input\");\n input.type = \"radio\";\n input.name = groupName;\n input.value = opt.value;\n if (opt.value === value) input.checked = true;\n if (field.required) input.required = true;\n input.addEventListener(\"change\", notifyChange);\n const text = document.createElement(\"span\");\n text.textContent = opt.label;\n choice.appendChild(input);\n choice.appendChild(text);\n group.appendChild(choice);\n inputs.push(input);\n }\n wrapper.appendChild(group);\n binding = {\n read: () => inputs.find((i) => i.checked)?.value.trim() ?? \"\",\n setDisabled: (busy) => inputs.forEach((i) => { i.disabled = busy; }),\n };\n } else if (field.type === \"checkbox\") {\n wrapper.classList.add(\"is-choice-group\");\n if (field.options && field.options.length) {\n // Multi-checkbox group — value is comma-joined.\n const selected = new Set(\n value\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean),\n );\n const group = document.createElement(\"div\");\n group.className = \"form-choice-group\";\n const inputs: HTMLInputElement[] = [];\n for (const opt of optionEntries(field.options)) {\n const choice = document.createElement(\"label\");\n choice.className = \"form-choice\";\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.value = opt.value;\n if (selected.has(opt.value)) input.checked = true;\n input.addEventListener(\"change\", notifyChange);\n const text = document.createElement(\"span\");\n text.textContent = opt.label;\n choice.appendChild(input);\n choice.appendChild(text);\n group.appendChild(choice);\n inputs.push(input);\n }\n wrapper.appendChild(group);\n binding = {\n read: () =>\n inputs\n .filter((i) => i.checked)\n .map((i) => i.value)\n .join(\",\"),\n setDisabled: (busy) => inputs.forEach((i) => { i.disabled = busy; }),\n };\n } else {\n // Single boolean checkbox.\n const single = document.createElement(\"input\");\n single.type = \"checkbox\";\n single.name = fieldName;\n single.className = \"form-checkbox\";\n if (value === \"true\" || value === \"1\" || value === \"on\") single.checked = true;\n if (field.required) single.required = true;\n single.addEventListener(\"change\", notifyChange);\n wrapper.appendChild(single);\n // Move the label text next to the checkbox for natural \"agree to terms\" layout.\n wrapper.classList.add(\"is-inline-bool\");\n binding = {\n read: () => (single.checked ? \"true\" : \"\"),\n setDisabled: (busy) => { single.disabled = busy; },\n };\n }\n } else {\n const text = document.createElement(\"input\");\n // text/email/tel/number/date/time map 1:1 to native input types.\n text.type = field.type;\n text.placeholder = field.placeholder ?? \"\";\n text.value = value;\n text.name = fieldName;\n text.className = \"form-input\";\n if (field.required) text.required = true;\n if (field.pattern) text.pattern = field.pattern;\n if (field.type === \"number\") {\n if (field.min !== undefined) text.min = String(field.min);\n if (field.max !== undefined) text.max = String(field.max);\n }\n text.addEventListener(\"input\", notifyChange);\n wrapper.appendChild(text);\n binding = {\n read: () => text.value.trim(),\n setDisabled: (busy) => { text.disabled = busy; },\n };\n }\n\n if (field.help_text) {\n const help = document.createElement(\"span\");\n help.className = \"form-help\";\n help.textContent = field.help_text;\n wrapper.appendChild(help);\n }\n\n // Inline validation error slot, hidden until `setError` fills it.\n const errorEl = document.createElement(\"span\");\n errorEl.className = \"form-field-error\";\n errorEl.hidden = true;\n wrapper.appendChild(errorEl);\n\n const setError = (message: string) => {\n errorEl.textContent = message;\n errorEl.hidden = !message;\n wrapper.classList.toggle(\"is-invalid\", Boolean(message));\n };\n clearError = () => setError(\"\");\n\n refs.formInputs.set(fieldName, { ...binding, setError });\n return wrapper;\n}\n\n/** Apply inline validation errors, then clear any field not in the list. */\nexport function setFieldErrors(\n refs: UIRefs,\n errors: { name: string; message: string }[],\n) {\n const byName = new Map(errors.map((e) => [e.name, e.message]));\n for (const [name, binding] of refs.formInputs.entries()) {\n binding.setError(byName.get(name) ?? \"\");\n }\n}\n\n/** Clear every inline field error. */\nexport function clearFieldErrors(refs: UIRefs) {\n for (const binding of refs.formInputs.values()) binding.setError(\"\");\n}\n\nexport function readFormValues(refs: UIRefs): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, binding] of refs.formInputs.entries()) {\n out[name] = binding.read();\n }\n return out;\n}\n\nexport function setFormBusy(refs: UIRefs, busy: boolean) {\n refs.formSubmitBtn.disabled = busy;\n refs.formCancelBtn.disabled = busy;\n refs.formBackBtn.disabled = busy;\n refs.formStepBackBtn.disabled = busy;\n for (const binding of refs.formInputs.values()) binding.setDisabled(busy);\n refs.formSubmitBtn.classList.toggle(\"is-busy\", busy);\n}\n\nexport function setFormError(refs: UIRefs, message: string) {\n refs.formError.textContent = message;\n refs.formError.hidden = !message;\n}\n\nexport function setFormSuccess(refs: UIRefs, message: string) {\n refs.formSuccess.textContent = message;\n refs.formSuccess.hidden = !message;\n}\n\nexport function setCallStatus(refs: UIRefs, text: string) {\n refs.callStatus.textContent = text;\n}\n\nexport function setCallTimer(refs: UIRefs, remainingMs: number | null) {\n if (remainingMs === null) {\n refs.callTimer.hidden = true;\n refs.callTimer.classList.remove(\"warning\");\n refs.callTimerText.textContent = \"\";\n return;\n }\n refs.callTimer.hidden = false;\n const totalSeconds = Math.max(0, Math.ceil(remainingMs / 1000));\n refs.callTimerText.textContent = formatMmSs(totalSeconds);\n // Flash a warning tint in the final 30 seconds.\n refs.callTimer.classList.toggle(\"warning\", totalSeconds <= 30);\n}\n\nfunction formatMmSs(totalSeconds: number): string {\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${s.toString().padStart(2, \"0\")}`;\n}\n\nexport type OrbState =\n | \"idle\"\n | \"listening\"\n | \"speaking\"\n | \"connecting\"\n | \"thinking\";\n\nexport function setOrbState(refs: UIRefs, state: OrbState) {\n refs.orb.classList.remove(\"listening\", \"speaking\", \"connecting\", \"thinking\");\n if (state !== \"idle\") refs.orb.classList.add(state);\n const labels = refs.appearance.labels;\n refs.orbLabel.textContent =\n state === \"listening\"\n ? labels.listening\n : state === \"speaking\"\n ? labels.speaking\n : state === \"connecting\"\n ? labels.connecting\n : state === \"thinking\"\n ? labels.thinking\n : labels.idle;\n // The contextual status line only makes sense while thinking; any other\n // state clears it so a stale \"Searching…\" can't linger over a reply.\n if (state !== \"thinking\") setAgentStatusLine(refs, null);\n}\n\n/**\n * Write the contextual processing text (e.g. \"Searching the knowledge base…\")\n * under the orb, or clear it when `text` is null/empty. This is the per-tool\n * label from a voice.agent_status event; the generic orb label still reads\n * \"Thinking…\".\n */\nexport function setAgentStatusLine(refs: UIRefs, text: string | null) {\n const trimmed = (text || \"\").trim();\n if (!trimmed) {\n refs.agentStatusLine.hidden = true;\n refs.agentStatusLine.textContent = \"\";\n return;\n }\n refs.agentStatusLine.textContent = trimmed;\n refs.agentStatusLine.hidden = false;\n}\n\nexport function setMuteVisual(refs: UIRefs, muted: boolean) {\n const label = muted ? \"Unmute\" : \"Mute\";\n const icon = muted ? ICON_MIC_OFF : ICON_MIC_ON;\n for (const btn of [refs.muteBtn, refs.formMuteBtn]) {\n btn.classList.toggle(\"is-active\", muted);\n btn.setAttribute(\"aria-label\", label);\n btn.innerHTML = icon;\n }\n}\n\nexport function setFormCallControlsVisible(refs: UIRefs, visible: boolean) {\n refs.formCallControls.hidden = !visible;\n}\n\nexport function setAudioDrawerOpen(refs: UIRefs, open: boolean) {\n refs.audioDrawer.hidden = !open;\n refs.audioDrawer.classList.toggle(\"is-open\", open);\n refs.settingsBtn.classList.toggle(\"is-active\", open);\n refs.settingsBtn.setAttribute(\"aria-expanded\", open ? \"true\" : \"false\");\n}\n\nexport function setAudioMeterLevel(refs: UIRefs, level: number) {\n const clamped = Math.max(0, Math.min(1, level));\n refs.audioMeterBar.style.width = `${(clamped * 100).toFixed(1)}%`;\n}\n\nexport function populateAudioDeviceSelect(\n select: HTMLSelectElement,\n devices: MediaDeviceInfo[],\n selectedDeviceId: string,\n defaultLabel: string,\n) {\n const previousValue = selectedDeviceId || \"\";\n select.innerHTML = \"\";\n const defaultOpt = document.createElement(\"option\");\n defaultOpt.value = \"\";\n defaultOpt.textContent = defaultLabel;\n select.appendChild(defaultOpt);\n for (const device of devices) {\n const opt = document.createElement(\"option\");\n opt.value = device.deviceId;\n // Empty labels happen before mic permission is granted; show a stub.\n opt.textContent = device.label || `Device ${device.deviceId.slice(0, 6)}`;\n select.appendChild(opt);\n }\n // Re-select previous value if still present, otherwise fall back to default.\n if (Array.from(select.options).some((o) => o.value === previousValue)) {\n select.value = previousValue;\n } else {\n select.value = \"\";\n }\n}\n\nconst typewriterTimers = new WeakMap<HTMLDivElement, number>();\nconst TYPEWRITER_CHARS_PER_TICK = 2;\nconst TYPEWRITER_TICK_MS = 18;\n\nfunction updateOneTranscript(\n container: HTMLDivElement,\n segments: Map<string, HTMLDivElement>,\n interimBubbles: Map<\"user\" | \"agent\", HTMLDivElement>,\n role: \"user\" | \"agent\",\n segmentId: string,\n trimmed: string,\n isFinal: boolean,\n) {\n const key = `${role}:${segmentId}`;\n let bubble = segments.get(key);\n let isNewBubble = !bubble;\n\n if (!bubble) {\n const lingeringInterim = interimBubbles.get(role);\n if (lingeringInterim) {\n bubble = lingeringInterim;\n segments.set(key, bubble);\n isNewBubble = false;\n } else {\n bubble = document.createElement(\"div\");\n bubble.className = `transcript-msg ${role} interim`;\n container.appendChild(bubble);\n segments.set(key, bubble);\n }\n }\n\n const pendingTimer = typewriterTimers.get(bubble);\n if (pendingTimer !== undefined) {\n window.clearInterval(pendingTimer);\n typewriterTimers.delete(bubble);\n }\n\n if (isNewBubble && isFinal && trimmed.length > 3) {\n bubble.classList.add(\"interim\");\n bubble.classList.remove(\"final\");\n bubble.textContent = \"\";\n let pos = 0;\n const tick = () => {\n pos = Math.min(trimmed.length, pos + TYPEWRITER_CHARS_PER_TICK);\n bubble!.textContent = trimmed.slice(0, pos);\n container.scrollTop = container.scrollHeight;\n if (pos >= trimmed.length) {\n const t = typewriterTimers.get(bubble!);\n if (t !== undefined) window.clearInterval(t);\n typewriterTimers.delete(bubble!);\n if (role === \"agent\") {\n bubble!.innerHTML = renderMarkdown(trimmed);\n } else {\n bubble!.textContent = trimmed;\n }\n bubble!.classList.remove(\"interim\");\n bubble!.classList.add(\"final\");\n }\n };\n const timer = window.setInterval(tick, TYPEWRITER_TICK_MS);\n typewriterTimers.set(bubble, timer);\n tick();\n interimBubbles.delete(role);\n return;\n }\n\n if (role === \"agent\") {\n bubble.innerHTML = renderMarkdown(trimmed);\n } else {\n bubble.textContent = trimmed;\n }\n bubble.classList.toggle(\"interim\", !isFinal);\n bubble.classList.toggle(\"final\", isFinal);\n container.scrollTop = container.scrollHeight;\n\n if (isFinal) {\n interimBubbles.delete(role);\n } else {\n interimBubbles.set(role, bubble);\n }\n}\n\nexport function appendOrUpdateTranscriptSegment(\n refs: UIRefs,\n role: \"user\" | \"agent\",\n segmentId: string,\n text: string,\n isFinal: boolean,\n) {\n const trimmed = (text || \"\").trim();\n if (!trimmed) return;\n\n updateOneTranscript(\n refs.transcript,\n refs.transcriptSegments,\n refs.transcriptInterimBubble,\n role,\n segmentId,\n trimmed,\n isFinal,\n );\n\n if (!refs.formTranscript.hidden) {\n updateOneTranscript(\n refs.formTranscript,\n refs.formTranscriptSegments,\n refs.formTranscriptInterimBubble,\n role,\n segmentId,\n trimmed,\n isFinal,\n );\n }\n}\n\nfunction clearOneTranscript(\n container: HTMLDivElement,\n segments: Map<string, HTMLDivElement>,\n interimBubbles: Map<\"user\" | \"agent\", HTMLDivElement>,\n) {\n for (const bubble of segments.values()) {\n const t = typewriterTimers.get(bubble);\n if (t !== undefined) {\n window.clearInterval(t);\n typewriterTimers.delete(bubble);\n }\n }\n container.innerHTML = \"\";\n segments.clear();\n interimBubbles.clear();\n}\n\nexport function clearTranscript(refs: UIRefs) {\n clearOneTranscript(refs.transcript, refs.transcriptSegments, refs.transcriptInterimBubble);\n clearOneTranscript(refs.formTranscript, refs.formTranscriptSegments, refs.formTranscriptInterimBubble);\n}\n\nexport function showAgentTransition(refs: UIRefs, agentName: string) {\n const name = (agentName || \"\").trim() || \"the next agent\";\n const text = `Connecting you with ${name}...`;\n for (const container of [refs.transcript, refs.formTranscript]) {\n if (!container) continue;\n const bubble = document.createElement(\"div\");\n bubble.className = \"transcript-msg system agent-transition\";\n bubble.textContent = text;\n container.appendChild(bubble);\n container.scrollTop = container.scrollHeight;\n }\n setCallStatus(refs, text);\n}\n\nexport function setFormTranscriptVisible(refs: UIRefs, visible: boolean) {\n refs.formTranscript.hidden = !visible;\n if (visible) {\n // Sync existing transcript messages into the form transcript\n clearOneTranscript(refs.formTranscript, refs.formTranscriptSegments, refs.formTranscriptInterimBubble);\n for (const [key, bubble] of refs.transcriptSegments) {\n const clone = bubble.cloneNode(true) as HTMLDivElement;\n refs.formTranscript.appendChild(clone);\n refs.formTranscriptSegments.set(key, clone);\n }\n for (const [role, bubble] of refs.transcriptInterimBubble) {\n for (const [key, original] of refs.transcriptSegments) {\n if (original === bubble) {\n const clone = refs.formTranscriptSegments.get(key);\n if (clone) refs.formTranscriptInterimBubble.set(role, clone);\n break;\n }\n }\n }\n refs.formTranscript.scrollTop = refs.formTranscript.scrollHeight;\n }\n}\n\nfunction contrastingText(color: string): string {\n const rgb = parseColor(color);\n if (!rgb) return \"#ffffff\";\n const [r, g, b] = rgb;\n const toLin = (c: number) => {\n const s = c / 255;\n return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);\n };\n const lum = 0.2126 * toLin(r) + 0.7152 * toLin(g) + 0.0722 * toLin(b);\n return lum > 0.5 ? \"#111111\" : \"#ffffff\";\n}\n\nfunction surfaceFromBackground(bg: string): string {\n const rgb = parseColor(bg);\n if (!rgb) return \"#f6f7f9\";\n const [r, g, b] = rgb;\n const lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;\n const shift = lum > 0.5 ? -10 : 18;\n const c = (v: number) => Math.max(0, Math.min(255, v + shift));\n return `rgb(${c(r)}, ${c(g)}, ${c(b)})`;\n}\n\nfunction muteText(text: string, bg: string): string {\n const trgb = parseColor(text);\n const brgb = parseColor(bg);\n if (!trgb || !brgb) return \"rgba(0,0,0,0.55)\";\n const mix = (i: 0 | 1 | 2) => Math.round(trgb[i] * 0.55 + brgb[i] * 0.45);\n return `rgb(${mix(0)}, ${mix(1)}, ${mix(2)})`;\n}\n\nfunction parseColor(input: string): [number, number, number] | null {\n const s = input.trim();\n const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);\n if (hex) {\n let h = hex[1];\n if (h.length === 3) h = h.split(\"\").map((c) => c + c).join(\"\");\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n }\n const rgb = s.match(/^rgba?\\(\\s*(\\d+)[,\\s]+(\\d+)[,\\s]+(\\d+)/i);\n if (rgb) return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];\n return null;\n}\n\nfunction escapeAttr(s: string): string {\n return s\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n","/**\n * @oshara/voice-sdk/ui — the prebuilt widget UI.\n *\n * `mountVoiceUI(client, opts)` builds the shadow-DOM widget (FAB + panel +\n * call/form screens + audio drawer), subscribes it to the headless client's\n * events, and wires DOM interactions back to the client's methods. It's the\n * exact UI the embeddable widget ships — now an optional layer over the core.\n */\n\nimport type { VoiceAgentClient } from \"../core\";\nimport { saveConsent } from \"../core\";\nimport type { AudioStateSnapshot } from \"../core\";\nimport {\n applyAppearance,\n appendOrUpdateTranscriptSegment,\n buildUI,\n clearFieldErrors,\n clearTranscript,\n populateAudioDeviceSelect,\n readFormValues,\n renderForm,\n setAgentStatusLine,\n setAudioDrawerOpen,\n setAudioMeterLevel,\n setCallStatus,\n setCallTimer,\n setFieldErrors,\n setFormBusy,\n setFormCallControlsVisible,\n setFormError,\n setFormSuccess,\n setFormTranscriptVisible,\n setMuteVisual,\n setOrbState,\n setScreen,\n showAgentTransition,\n Screen,\n UIRefs,\n} from \"./ui\";\n\nexport interface MountVoiceUIOptions {\n /** Where to mount. Default document.body. */\n target?: HTMLElement;\n /** Fill the parent container instead of floating (hides FAB). */\n inline?: boolean;\n /** Open the panel on mount. */\n openChat?: boolean;\n /** Hide the panel close button. */\n closeButtonHide?: boolean;\n /** Host element id. Default \"voice-agent-widget-root\". */\n rootId?: string;\n}\n\nexport interface VoiceUIHandle {\n destroy: () => void;\n /** The shadow-DOM refs, for advanced host integrations. */\n refs: UIRefs;\n}\n\nexport function mountVoiceUI(\n client: VoiceAgentClient,\n opts: MountVoiceUIOptions = {},\n): VoiceUIHandle {\n const parent = opts.target ?? document.body;\n const refs = buildUI(opts.rootId ?? \"voice-agent-widget-root\", {\n inline: opts.inline,\n parent,\n closeButtonHide: opts.closeButtonHide,\n });\n applyAppearance(refs, client.getAppearance());\n setScreen(refs, \"welcome\");\n\n const audioCaps = client.getAudioCapabilities();\n const unsubs: Array<() => void> = [];\n const timers: number[] = [];\n let panelOpen = false;\n let formOpen = false;\n let audioDrawerOpen = false;\n let audioStatsTimer: number | null = null;\n let audioMeterCtx: AudioContext | null = null;\n let audioMeterRaf: number | null = null;\n\n const trackedSetInterval = (handler: () => void, ms: number): number => {\n const id = window.setInterval(handler, ms);\n timers.push(id);\n return id;\n };\n\n const togglePanel = (show: boolean) => {\n panelOpen = show;\n refs.panel.style.display = show ? \"flex\" : \"none\";\n };\n const ensurePanelOpen = () => {\n if (!panelOpen) togglePanel(true);\n };\n const returnTo = (): Screen => (client.isActive() ? \"call\" : \"welcome\");\n\n // ── event subscriptions (replace the controller's old direct UI calls) ──\n unsubs.push(\n client.on(\"appearance\", (a) => applyAppearance(refs, a)),\n client.on(\"state\", ({ orb, statusLabel }) => {\n setOrbState(refs, orb);\n setAgentStatusLine(refs, orb === \"thinking\" ? statusLabel : null);\n }),\n client.on(\"call:status\", ({ status }) => setCallStatus(refs, status)),\n client.on(\"call:timer\", ({ remainingMs }) => setCallTimer(refs, remainingMs)),\n client.on(\"mute\", ({ muted }) => setMuteVisual(refs, muted)),\n client.on(\"controls\", ({ canStart, canMute, canEnd }) => {\n refs.startBtn.disabled = !canStart;\n refs.muteBtn.disabled = !canMute;\n refs.endBtn.disabled = !canEnd;\n }),\n client.on(\"connection\", ({ phase }) => {\n if (phase === \"connecting\") {\n if (!formOpen) setScreen(refs, \"call\");\n } else if (phase === \"disconnected\" || phase === \"failed\") {\n if (!formOpen) setScreen(refs, \"welcome\");\n }\n }),\n client.on(\"transcript\", ({ role, segmentId, text, isFinal }) =>\n appendOrUpdateTranscriptSegment(refs, role, segmentId, text, isFinal),\n ),\n client.on(\"transcript:clear\", () => clearTranscript(refs)),\n client.on(\"agent:handoff\", ({ agentName }) =>\n showAgentTransition(refs, agentName),\n ),\n client.on(\"audio\", (state: AudioStateSnapshot) =>\n syncDrawerToState(refs, state, audioCaps),\n ),\n );\n\n // ── forms ──\n const renderActiveForm = () => {\n const active = client.getActiveForm();\n if (!active) return;\n renderForm(refs, {\n definition: active.definition,\n values: active.values,\n stepIndex: active.stepIndex,\n cancelLabel: client.isActive() ? \"Back to call\" : \"Cancel\",\n onFieldChange: () => client.updateFormValues(readFormValues(refs)),\n });\n };\n\n unsubs.push(\n client.on(\"form:show\", ({ inCall, transcriptionEnabled }) => {\n formOpen = true;\n ensurePanelOpen();\n setScreen(refs, \"form\");\n setFormCallControlsVisible(refs, inCall);\n setFormTranscriptVisible(refs, inCall && transcriptionEnabled);\n renderActiveForm();\n }),\n client.on(\"form:update\", () => renderActiveForm()),\n client.on(\"form:validation\", ({ errors }) => {\n setFormError(refs, \"\");\n setFieldErrors(refs, errors);\n }),\n client.on(\"form:submitting\", () => {\n setFormError(refs, \"\");\n setFormSuccess(refs, \"\");\n clearFieldErrors(refs);\n setFormBusy(refs, true);\n }),\n client.on(\"form:submitted\", ({ successMessage }) => {\n setFormSuccess(refs, successMessage);\n setFormBusy(refs, false);\n }),\n client.on(\"form:error\", ({ message }) => {\n setFormError(refs, message);\n setFormBusy(refs, false);\n }),\n client.on(\"form:close\", () => {\n formOpen = false;\n setFormCallControlsVisible(refs, false);\n setFormTranscriptVisible(refs, false);\n setScreen(refs, returnTo());\n }),\n );\n\n // Form buttons → client (capture on-screen values before submit/step).\n const captureValues = () => client.updateFormValues(readFormValues(refs));\n refs.formBackBtn.addEventListener(\"click\", () => client.closeForm());\n refs.formCancelBtn.addEventListener(\"click\", () => client.closeForm());\n refs.formStepBackBtn.addEventListener(\"click\", () => {\n captureValues();\n client.stepForm(\"back\");\n });\n refs.formSubmitBtn.addEventListener(\"click\", () => {\n captureValues();\n client.submitForm();\n });\n\n // ── audio drawer + meters + stats (ported from the widget) ──\n const refreshAudioDevices = async () => {\n try {\n const { inputs, outputs } = await client.enumerateAudioDevices();\n const state = client.getAudioState();\n populateAudioDeviceSelect(\n refs.audioMicSelect,\n inputs,\n state.prefs.micDeviceId,\n \"System default\",\n );\n populateAudioDeviceSelect(\n refs.audioSpeakerSelect,\n outputs,\n state.prefs.speakerDeviceId,\n \"System default\",\n );\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] device enumeration failed:\", err);\n }\n };\n\n const stopMeter = () => {\n if (audioMeterRaf !== null) {\n cancelAnimationFrame(audioMeterRaf);\n audioMeterRaf = null;\n }\n if (audioMeterCtx) {\n void audioMeterCtx.close().catch(() => undefined);\n audioMeterCtx = null;\n }\n setAudioMeterLevel(refs, 0);\n };\n\n const startMeter = async () => {\n stopMeter();\n if (!client.isActive()) return;\n try {\n const state = client.getAudioState();\n const stream = await navigator.mediaDevices.getUserMedia({\n audio: state.prefs.micDeviceId\n ? { deviceId: { exact: state.prefs.micDeviceId } }\n : true,\n video: false,\n });\n const ctx = new (window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext)();\n audioMeterCtx = ctx;\n const source = ctx.createMediaStreamSource(stream);\n const analyser = ctx.createAnalyser();\n analyser.fftSize = 1024;\n source.connect(analyser);\n const buffer = new Float32Array(analyser.fftSize);\n const tick = () => {\n if (!audioMeterCtx) return;\n analyser.getFloatTimeDomainData(buffer);\n let sum = 0;\n for (let i = 0; i < buffer.length; i++) sum += buffer[i] * buffer[i];\n const rms = Math.sqrt(sum / buffer.length);\n const db = 20 * Math.log10(Math.max(rms, 1e-6));\n const norm = Math.max(0, Math.min(1, (db + 50) / 50));\n setAudioMeterLevel(refs, norm);\n audioMeterRaf = requestAnimationFrame(tick);\n };\n audioMeterRaf = requestAnimationFrame(tick);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] mic-level meter unavailable:\", err);\n }\n };\n\n const stopAudioStatsPoll = () => {\n if (audioStatsTimer !== null) {\n window.clearInterval(audioStatsTimer);\n audioStatsTimer = null;\n }\n };\n\n const startAudioStatsPoll = () => {\n stopAudioStatsPoll();\n const tick = async () => {\n const stats = await client.getAudioStats();\n if (!stats) {\n refs.audioDiagLoss.textContent = \"—\";\n refs.audioDiagJitter.textContent = \"—\";\n refs.audioDiagRtt.textContent = \"—\";\n return;\n }\n refs.audioDiagLoss.textContent = String(stats.packetsLost);\n refs.audioDiagJitter.textContent = `${stats.jitter.toFixed(1)} ms`;\n refs.audioDiagRtt.textContent = stats.roundTripTime\n ? `${stats.roundTripTime.toFixed(0)} ms`\n : \"—\";\n };\n void tick();\n audioStatsTimer = trackedSetInterval(() => void tick(), 2000);\n };\n\n const openAudioDrawer = async () => {\n audioDrawerOpen = true;\n setAudioDrawerOpen(refs, true);\n syncDrawerToState(refs, client.getAudioState(), audioCaps);\n await refreshAudioDevices();\n await startMeter();\n startAudioStatsPoll();\n };\n\n const closeAudioDrawer = () => {\n audioDrawerOpen = false;\n setAudioDrawerOpen(refs, false);\n stopMeter();\n stopAudioStatsPoll();\n };\n\n // Auto-close the drawer when the call ends.\n trackedSetInterval(() => {\n if (audioDrawerOpen && !client.isActive()) closeAudioDrawer();\n }, 1000);\n\n // ── top-level DOM wiring ──\n refs.fab.addEventListener(\"click\", () => {\n if (!panelOpen) client.trackEvent(\"bubble_clicked\");\n togglePanel(!panelOpen);\n });\n refs.closeBtn.addEventListener(\"click\", () => {\n if (client.isActive()) void client.end();\n client.closeForm();\n togglePanel(false);\n });\n if (opts.openChat || opts.inline) ensurePanelOpen();\n\n refs.startBtn.addEventListener(\"click\", () => {\n const termsUrl = refs.appearance.terms_url;\n if (termsUrl) saveConsent(client.agentSlug || \"default\", termsUrl);\n void client.start();\n });\n refs.endBtn.addEventListener(\"click\", () => void client.end());\n refs.muteBtn.addEventListener(\"click\", () => void client.toggleMute());\n refs.formMuteBtn.addEventListener(\"click\", () => void client.toggleMute());\n refs.formEndBtn.addEventListener(\"click\", () => {\n client.closeForm();\n void client.end();\n });\n\n refs.settingsBtn.addEventListener(\"click\", () => {\n if (!client.isActive()) return;\n if (audioDrawerOpen) closeAudioDrawer();\n else void openAudioDrawer();\n });\n refs.audioDrawerClose.addEventListener(\"click\", closeAudioDrawer);\n\n refs.audioMicSelect.addEventListener(\"change\", () => {\n void client.updateAudioSettings({ micDeviceId: refs.audioMicSelect.value });\n void startMeter();\n });\n refs.audioSpeakerSelect.addEventListener(\"change\", () => {\n void client.updateAudioSettings({\n speakerDeviceId: refs.audioSpeakerSelect.value,\n });\n });\n refs.audioVolume.addEventListener(\"input\", () => {\n const value = Number(refs.audioVolume.value);\n refs.audioVolumeValue.textContent = `${value}%`;\n void client.updateAudioSettings({ outputVolume: value });\n });\n refs.audioNcEngine.addEventListener(\"change\", () => {\n const engine = refs.audioNcEngine.value as \"off\" | \"krisp\" | \"deepfilter\";\n void client.updateAudioSettings({ noiseFilter: engine });\n refs.audioDfStrengthRow.hidden = engine !== \"deepfilter\";\n });\n refs.audioDfStrength.addEventListener(\"input\", () => {\n const value = Number(refs.audioDfStrength.value);\n refs.audioDfStrengthValue.textContent = String(value);\n void client.updateAudioSettings({ deepFilterStrength: value });\n });\n refs.audioTogAec.addEventListener(\"change\", () =>\n void client.updateAudioSettings({ echoCancellation: refs.audioTogAec.checked }),\n );\n refs.audioTogNs.addEventListener(\"change\", () =>\n void client.updateAudioSettings({ noiseSuppression: refs.audioTogNs.checked }),\n );\n refs.audioTogAgc.addEventListener(\"change\", () =>\n void client.updateAudioSettings({ autoGainControl: refs.audioTogAgc.checked }),\n );\n refs.audioTogVi.addEventListener(\"change\", () =>\n void client.updateAudioSettings({ voiceIsolation: refs.audioTogVi.checked }),\n );\n refs.audioTogHp.addEventListener(\"change\", () =>\n void client.updateAudioSettings({ headphonesMode: refs.audioTogHp.checked }),\n );\n refs.audioTogTranscription.addEventListener(\"change\", () =>\n void client.updateAudioSettings({\n transcriptionEnabled: refs.audioTogTranscription.checked,\n }),\n );\n refs.audioTogTextInput.addEventListener(\"change\", () =>\n void client.updateAudioSettings({\n textInputEnabled: refs.audioTogTextInput.checked,\n }),\n );\n\n // Gear / text-input visibility tied to call state + prefs.\n trackedSetInterval(() => {\n const active = client.isActive();\n refs.settingsBtn.hidden = !active || !refs.appearance.show_audio_settings;\n const showText = active && client.getAudioState().prefs.textInputEnabled;\n refs.textInputRow.hidden = !showText;\n refs.textSendBtn.disabled = !showText;\n }, 500);\n\n refs.textInput.addEventListener(\"input\", () => {\n refs.textInput.style.height = \"auto\";\n refs.textInput.style.height = `${refs.textInput.scrollHeight}px`;\n });\n const sendTextMessage = () => {\n const text = refs.textInput.value.trim();\n if (!text || !client.isActive()) return;\n refs.textInput.value = \"\";\n refs.textInput.style.height = \"auto\";\n void client.sendText(text);\n };\n refs.textSendBtn.addEventListener(\"click\", sendTextMessage);\n refs.textInput.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n sendTextMessage();\n }\n });\n\n const destroy = () => {\n for (const u of unsubs.splice(0)) {\n try {\n u();\n } catch {\n /* best-effort */\n }\n }\n for (const id of timers.splice(0)) window.clearInterval(id);\n stopMeter();\n stopAudioStatsPoll();\n refs.host.remove();\n };\n\n return { destroy, refs };\n}\n\n// ── audio drawer sync (ported from widget.ts) ──\nfunction syncDrawerToState(\n refs: UIRefs,\n state: AudioStateSnapshot,\n caps: { setSinkIdSupported: boolean; voiceIsolationSupported: boolean },\n) {\n const { prefs, applied, noiseFilter } = state;\n\n refs.audioNcEngine.value = prefs.noiseFilter;\n refs.audioDfStrength.value = String(prefs.deepFilterStrength);\n refs.audioDfStrengthValue.textContent = String(prefs.deepFilterStrength);\n refs.audioDfStrengthRow.hidden = prefs.noiseFilter !== \"deepfilter\";\n\n refs.audioTogAec.checked = prefs.echoCancellation;\n refs.audioTogNs.checked = prefs.noiseSuppression;\n refs.audioTogAgc.checked = prefs.autoGainControl;\n refs.audioTogVi.checked = prefs.voiceIsolation;\n refs.audioTogHp.checked = prefs.headphonesMode;\n refs.audioTogTranscription.checked = prefs.transcriptionEnabled;\n refs.audioTogTextInput.checked = prefs.textInputEnabled;\n refs.transcript.style.display = prefs.transcriptionEnabled ? \"flex\" : \"none\";\n if (prefs.transcriptionEnabled) {\n refs.panel.classList.add(\"with-transcript\");\n } else {\n refs.panel.classList.remove(\"with-transcript\");\n }\n refs.audioVolume.value = String(prefs.outputVolume);\n refs.audioVolumeValue.textContent = `${prefs.outputVolume}%`;\n\n refs.audioTogViRow.style.display = caps.voiceIsolationSupported ? \"\" : \"none\";\n refs.audioSpeakerRow.style.display = caps.setSinkIdSupported ? \"\" : \"none\";\n\n refs.audioDiagEngine.textContent = formatEngineStatus(noiseFilter);\n refs.audioDiagAec.textContent = boolLabel(applied.echoCancellation);\n refs.audioDiagNs.textContent = boolLabel(applied.noiseSuppression);\n refs.audioDiagAgc.textContent = boolLabel(applied.autoGainControl);\n refs.audioDiagVi.textContent =\n applied.voiceIsolation === undefined\n ? \"Unsupported\"\n : applied.voiceIsolation\n ? \"On\"\n : \"Off\";\n refs.audioDiagSr.textContent =\n applied.sampleRate !== undefined ? `${applied.sampleRate} Hz` : \"—\";\n}\n\nfunction boolLabel(v: boolean | undefined): string {\n if (v === undefined) return \"—\";\n return v ? \"On\" : \"Off\";\n}\n\nfunction formatEngineStatus(nf: AudioStateSnapshot[\"noiseFilter\"]): string {\n const engineLabel =\n nf.engine === \"krisp\"\n ? \"Krisp\"\n : nf.engine === \"deepfilter\"\n ? \"DeepFilterNet3\"\n : \"Off\";\n if (nf.engine === \"off\") return \"Off\";\n if (nf.status === \"active\") return `${engineLabel} (active)`;\n if (nf.status === \"unsupported\") return `${engineLabel} (unsupported)`;\n if (nf.status === \"failed\") return `${engineLabel} (failed)`;\n return engineLabel;\n}\n\nexport type { UIRefs } from \"./ui\";\n"],"names":["ICON_FAB","ICON_CLOSE","ICON_PHONE","ICON_MIC_ON","ICON_MIC_OFF","ICON_HANGUP","ICON_AGENT_FALLBACK","ICON_GLOBE","ICON_CHEVRON","ICON_CHECK","ICON_SEND","ICON_SETTINGS","buildUI","rootId","opts","host","shadow","style","stylesheet","fab","panel","q","sel","DEFAULT_APPEARANCE","hostStyleVar","name","value","applyAppearance","refs","appearance","t","d","primaryText","contrastingText","accentText","vars","surfaceFromBackground","muteText","inline","desc","poweredByText","poweredByHref","safeHref","logoSrc","safeImageSrc","hasLogo","applyFabContent","renderLanguagePicker","renderConsent","url","prefix","label","languages","previous","initial","l","eyebrowEl","lang","opt","escapeHtml","e","selectLanguage","closeLanguageMenu","wireLanguageTrigger","picker","target","openLanguageMenu","code","el","isActive","s","proto","renderInline","text","renderMarkdown","lines","blocks","i","line","h3","h2","h1","items","codeLines","paraLines","sublabel","iconEl","textEl","labelEl","sublabelEl","escapeAttr","setScreen","screen","renderForm","args","definition","values","cancelLabel","steps","totalSteps","stepIndex","isStepper","isFinalStep","currentStep","_a","subtitle","renderStepper","submitLabel","showStepBack","layout","setFormError","setFormSuccess","field","fieldsForStep","buildFieldNode","current","dot","optionEntries","options","onChange","clearError","notifyChange","block","heading","body","fieldName","wrapper","binding","ta","busy","seen","o","all","groupName","group","inputs","choice","input","selected","single","help","errorEl","setError","message","setFieldErrors","errors","byName","clearFieldErrors","readFormValues","out","setFormBusy","setCallStatus","setCallTimer","remainingMs","totalSeconds","formatMmSs","m","setOrbState","state","labels","setAgentStatusLine","trimmed","setMuteVisual","muted","icon","btn","setFormCallControlsVisible","visible","setAudioDrawerOpen","open","setAudioMeterLevel","level","clamped","populateAudioDeviceSelect","select","devices","selectedDeviceId","defaultLabel","previousValue","defaultOpt","device","typewriterTimers","TYPEWRITER_CHARS_PER_TICK","TYPEWRITER_TICK_MS","updateOneTranscript","container","segments","interimBubbles","role","segmentId","isFinal","key","bubble","isNewBubble","lingeringInterim","pendingTimer","pos","tick","timer","appendOrUpdateTranscriptSegment","clearOneTranscript","clearTranscript","showAgentTransition","agentName","setFormTranscriptVisible","clone","original","color","rgb","parseColor","r","g","b","toLin","c","bg","shift","v","trgb","brgb","mix","hex","h","mountVoiceUI","client","parent","audioCaps","unsubs","timers","panelOpen","formOpen","audioDrawerOpen","audioStatsTimer","audioMeterCtx","audioMeterRaf","trackedSetInterval","handler","ms","id","togglePanel","show","ensurePanelOpen","returnTo","a","orb","statusLabel","status","canStart","canMute","canEnd","phase","syncDrawerToState","renderActiveForm","active","inCall","transcriptionEnabled","successMessage","captureValues","refreshAudioDevices","outputs","err","stopMeter","startMeter","stream","ctx","source","analyser","buffer","sum","rms","db","norm","stopAudioStatsPoll","startAudioStatsPoll","stats","openAudioDrawer","closeAudioDrawer","termsUrl","saveConsent","engine","showText","sendTextMessage","u","caps","prefs","applied","noiseFilter","formatEngineStatus","boolLabel","nf","engineLabel"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oshara/voice-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless + prebuilt SDK for embedding the Oshara voice agent: add an API key (or agent slug) and everything initializes like the widget.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"sideEffects": [
|
|
11
|
+
"./dist/styles.css",
|
|
12
|
+
"**/*.css"
|
|
13
|
+
],
|
|
14
|
+
"main": "./dist/index.cjs",
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/core/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/core/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"require": "./dist/index.cjs"
|
|
22
|
+
},
|
|
23
|
+
"./ui": {
|
|
24
|
+
"types": "./dist/ui/index.d.ts",
|
|
25
|
+
"import": "./dist/ui.js",
|
|
26
|
+
"require": "./dist/ui.cjs"
|
|
27
|
+
},
|
|
28
|
+
"./react": {
|
|
29
|
+
"types": "./dist/react/index.d.ts",
|
|
30
|
+
"import": "./dist/react.js",
|
|
31
|
+
"require": "./dist/react.cjs"
|
|
32
|
+
},
|
|
33
|
+
"./styles.css": "./dist/styles.css",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "vite build",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"dev": "vite build --watch"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@livekit/krisp-noise-filter": "^0.4.3",
|
|
47
|
+
"livekit-client": "^2.5.7"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": ">=17",
|
|
51
|
+
"react-dom": ">=17"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"react": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"react-dom": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/react": "^18.3.3",
|
|
63
|
+
"typescript": "^5.5.4",
|
|
64
|
+
"vite": "^5.4.8",
|
|
65
|
+
"vite-plugin-dts": "^4.2.1"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Lightweight widget engagement analytics.
|
|
2
|
+
//
|
|
3
|
+
// Fires two fire-and-forget events to the org backend so a tenant can see, per
|
|
4
|
+
// agent, how many people saw the chat bubble (`widget_loaded`) versus opened it
|
|
5
|
+
// (`bubble_clicked`). A persistent visitor id (localStorage) lets the backend
|
|
6
|
+
// tell unique visitors apart from repeat page loads. Nothing here ever throws
|
|
7
|
+
// or blocks widget boot — analytics must never break the widget.
|
|
8
|
+
|
|
9
|
+
const VISITOR_KEY = "voiceAgent.visitor";
|
|
10
|
+
|
|
11
|
+
type WidgetEventType = "widget_loaded" | "bubble_clicked";
|
|
12
|
+
|
|
13
|
+
// Each event type is reported at most once per page load. This lives at module
|
|
14
|
+
// scope so it survives a widget remount (e.g. SPA re-init) within the same
|
|
15
|
+
// page, but resets on a real navigation/refresh — so opening and closing the
|
|
16
|
+
// panel several times counts as one `bubble_clicked`, while a refresh yields a
|
|
17
|
+
// fresh `widget_loaded` + `bubble_clicked`.
|
|
18
|
+
const sentThisLoad = new Set<WidgetEventType>();
|
|
19
|
+
|
|
20
|
+
interface TrackConfig {
|
|
21
|
+
apiUrl: string;
|
|
22
|
+
agentSlug: string;
|
|
23
|
+
/** Secret key sent as `x-api-key` (server/trusted mode). */
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
/** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
/** When true, suppress all event reporting. */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return a stable per-browser id, generating and persisting one on first use.
|
|
33
|
+
* Falls back to an ephemeral id if localStorage is unavailable (private mode,
|
|
34
|
+
* blocked storage) so callers always get a usable value.
|
|
35
|
+
*/
|
|
36
|
+
export function getVisitorId(): string {
|
|
37
|
+
try {
|
|
38
|
+
const existing = localStorage.getItem(VISITOR_KEY);
|
|
39
|
+
if (existing) return existing;
|
|
40
|
+
const id = newId();
|
|
41
|
+
localStorage.setItem(VISITOR_KEY, id);
|
|
42
|
+
return id;
|
|
43
|
+
} catch {
|
|
44
|
+
return newId();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function newId(): string {
|
|
49
|
+
try {
|
|
50
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
51
|
+
return crypto.randomUUID();
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// fall through to the manual id below
|
|
55
|
+
}
|
|
56
|
+
return `v-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Report a widget engagement event. Best-effort and fire-and-forget.
|
|
61
|
+
*
|
|
62
|
+
* Uses `fetch` with `keepalive: true` and `Content-Type: application/json` —
|
|
63
|
+
* the same cross-origin transport the session and form-submit POSTs already
|
|
64
|
+
* use successfully (see fetchSession in widget.ts). We deliberately do NOT use
|
|
65
|
+
* navigator.sendBeacon: it returns true the moment it *queues* the request
|
|
66
|
+
* (so callers can't tell it failed), and a cross-origin beacon with a JSON
|
|
67
|
+
* content type needs a CORS preflight that can be silently dropped — events
|
|
68
|
+
* would vanish. Errors are swallowed so analytics never breaks the widget.
|
|
69
|
+
*/
|
|
70
|
+
export function trackWidgetEvent(
|
|
71
|
+
cfg: TrackConfig,
|
|
72
|
+
eventType: WidgetEventType,
|
|
73
|
+
metadata: Record<string, unknown> = {},
|
|
74
|
+
): void {
|
|
75
|
+
if (!cfg.agentSlug || cfg.disabled) return;
|
|
76
|
+
|
|
77
|
+
// Distinct per page load: skip if this event type already fired this load.
|
|
78
|
+
if (sentThisLoad.has(eventType)) return;
|
|
79
|
+
sentThisLoad.add(eventType);
|
|
80
|
+
|
|
81
|
+
const url = `${cfg.apiUrl}/api/agents/${encodeURIComponent(
|
|
82
|
+
cfg.agentSlug,
|
|
83
|
+
)}/events/`;
|
|
84
|
+
const payload = JSON.stringify({
|
|
85
|
+
event_type: eventType,
|
|
86
|
+
visitor_id: getVisitorId(),
|
|
87
|
+
origin_url:
|
|
88
|
+
(typeof window !== "undefined" && window.location?.href) || "",
|
|
89
|
+
metadata,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
93
|
+
if (cfg.apiKey?.trim()) headers["x-api-key"] = cfg.apiKey.trim();
|
|
94
|
+
const doFetch =
|
|
95
|
+
cfg.fetch ??
|
|
96
|
+
(typeof globalThis.fetch === "function"
|
|
97
|
+
? globalThis.fetch.bind(globalThis)
|
|
98
|
+
: null);
|
|
99
|
+
if (!doFetch) return;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
void doFetch(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers,
|
|
105
|
+
body: payload,
|
|
106
|
+
keepalive: true,
|
|
107
|
+
}).catch(() => {});
|
|
108
|
+
} catch {
|
|
109
|
+
// analytics is best-effort; never surface errors to the widget
|
|
110
|
+
}
|
|
111
|
+
}
|