@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createVoiceAgent-CJWxWzz6.cjs","sources":["../src/core/analytics.ts","../src/core/audioSettings.ts","../src/core/events.ts","../src/core/formController.ts","../src/core/transport.ts","../src/core/types.ts","../src/core/prevContext.ts","../src/core/voice.ts","../src/core/createVoiceAgent.ts"],"sourcesContent":["// Lightweight widget engagement analytics.\n//\n// Fires two fire-and-forget events to the org backend so a tenant can see, per\n// agent, how many people saw the chat bubble (`widget_loaded`) versus opened it\n// (`bubble_clicked`). A persistent visitor id (localStorage) lets the backend\n// tell unique visitors apart from repeat page loads. Nothing here ever throws\n// or blocks widget boot — analytics must never break the widget.\n\nconst VISITOR_KEY = \"voiceAgent.visitor\";\n\ntype WidgetEventType = \"widget_loaded\" | \"bubble_clicked\";\n\n// Each event type is reported at most once per page load. This lives at module\n// scope so it survives a widget remount (e.g. SPA re-init) within the same\n// page, but resets on a real navigation/refresh — so opening and closing the\n// panel several times counts as one `bubble_clicked`, while a refresh yields a\n// fresh `widget_loaded` + `bubble_clicked`.\nconst sentThisLoad = new Set<WidgetEventType>();\n\ninterface TrackConfig {\n apiUrl: string;\n agentSlug: string;\n /** Secret key sent as `x-api-key` (server/trusted mode). */\n apiKey?: string;\n /** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */\n fetch?: typeof fetch;\n /** When true, suppress all event reporting. */\n disabled?: boolean;\n}\n\n/**\n * Return a stable per-browser id, generating and persisting one on first use.\n * Falls back to an ephemeral id if localStorage is unavailable (private mode,\n * blocked storage) so callers always get a usable value.\n */\nexport function getVisitorId(): string {\n try {\n const existing = localStorage.getItem(VISITOR_KEY);\n if (existing) return existing;\n const id = newId();\n localStorage.setItem(VISITOR_KEY, id);\n return id;\n } catch {\n return newId();\n }\n}\n\nfunction newId(): string {\n try {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n } catch {\n // fall through to the manual id below\n }\n return `v-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Report a widget engagement event. Best-effort and fire-and-forget.\n *\n * Uses `fetch` with `keepalive: true` and `Content-Type: application/json` —\n * the same cross-origin transport the session and form-submit POSTs already\n * use successfully (see fetchSession in widget.ts). We deliberately do NOT use\n * navigator.sendBeacon: it returns true the moment it *queues* the request\n * (so callers can't tell it failed), and a cross-origin beacon with a JSON\n * content type needs a CORS preflight that can be silently dropped — events\n * would vanish. Errors are swallowed so analytics never breaks the widget.\n */\nexport function trackWidgetEvent(\n cfg: TrackConfig,\n eventType: WidgetEventType,\n metadata: Record<string, unknown> = {},\n): void {\n if (!cfg.agentSlug || cfg.disabled) return;\n\n // Distinct per page load: skip if this event type already fired this load.\n if (sentThisLoad.has(eventType)) return;\n sentThisLoad.add(eventType);\n\n const url = `${cfg.apiUrl}/api/agents/${encodeURIComponent(\n cfg.agentSlug,\n )}/events/`;\n const payload = JSON.stringify({\n event_type: eventType,\n visitor_id: getVisitorId(),\n origin_url:\n (typeof window !== \"undefined\" && window.location?.href) || \"\",\n metadata,\n });\n\n const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (cfg.apiKey?.trim()) headers[\"x-api-key\"] = cfg.apiKey.trim();\n const doFetch =\n cfg.fetch ??\n (typeof globalThis.fetch === \"function\"\n ? globalThis.fetch.bind(globalThis)\n : null);\n if (!doFetch) return;\n\n try {\n void doFetch(url, {\n method: \"POST\",\n headers,\n body: payload,\n keepalive: true,\n }).catch(() => {});\n } catch {\n // analytics is best-effort; never surface errors to the widget\n }\n}\n","/**\n * Per-agent audio preferences for the voice widget.\n *\n * Persists the user's chosen mic/speaker devices, processor toggles, and\n * speaker volume so subsequent calls open with the same setup. Reads &\n * writes are namespaced by agent slug so different agents can hold\n * different preferences in the same browser.\n */\n\n/**\n * Choice of deep-learning noise-suppression engine layered on top of the\n * browser's native NS. Picked per-user via the audio settings drawer.\n *\n * - `off` — rely on browser NS + voiceIsolation only.\n * - `krisp` — @livekit/krisp-noise-filter (closed-source, free for LK).\n * - `deepfilter` — deepfilternet3-noise-filter (mezonai/mezon-noise-suppression),\n * open-source DeepFilterNet3 with a 0-100 strength knob.\n */\nexport type NoiseFilterEngine = \"off\" | \"krisp\" | \"deepfilter\";\n\nexport interface AudioPrefs {\n /** Browser-native AEC. Standard MediaTrackSettings.echoCancellation. */\n echoCancellation: boolean;\n /** Browser-native noise suppression. Standard MediaTrackSettings. */\n noiseSuppression: boolean;\n /** Browser-native automatic gain control. */\n autoGainControl: boolean;\n /** Microsoft / Chrome non-standard hardware voice isolation. */\n voiceIsolation: boolean;\n /** Which deep-learning noise-suppression processor to attach. */\n noiseFilter: NoiseFilterEngine;\n /** DeepFilterNet3 strength 0-100 (ignored unless noiseFilter === \"deepfilter\"). */\n deepFilterStrength: number;\n /** Selected mic deviceId (empty string = system default). */\n micDeviceId: string;\n /** Selected speaker deviceId for setSinkId (empty = default). */\n speakerDeviceId: string;\n /** Speaker volume 0-100, applied to HTMLAudioElement.volume / 100. */\n outputVolume: number;\n /**\n * When true, disables half-duplex ducking. The user wears headphones so\n * the mic can't physically pick up the speaker — no need to mute them\n * while the agent talks.\n */\n headphonesMode: boolean;\n /** Whether to show live transcription in the UI. */\n transcriptionEnabled: boolean;\n /** Whether to show the text input box during a call. */\n textInputEnabled: boolean;\n}\n\nexport const DEFAULT_AUDIO_PREFS: AudioPrefs = {\n echoCancellation: true,\n noiseSuppression: true,\n // Off by default: AGC boosts the trailing tail of an utterance, which\n // confuses the agent's contextual turn detector (\"user is still\n // talking softly\") and delays preemptive LLM generation. Users can\n // toggle this on per-call from the audio settings drawer if they're\n // soft-spoken and the agent isn't hearing them.\n autoGainControl: false,\n voiceIsolation: true,\n noiseFilter: \"deepfilter\",\n deepFilterStrength: 40,\n micDeviceId: \"\",\n speakerDeviceId: \"\",\n outputVolume: 85,\n headphonesMode: true,\n transcriptionEnabled: false,\n textInputEnabled: false,\n};\n\nconst STORAGE_PREFIX = \"voice-agent:audio-prefs:\";\n\nfunction storageKey(agentSlug: string): string {\n return `${STORAGE_PREFIX}${agentSlug || \"default\"}`;\n}\n\nexport function loadAudioPrefs(agentSlug: string): AudioPrefs {\n try {\n const raw = window.localStorage.getItem(storageKey(agentSlug));\n if (!raw) return { ...DEFAULT_AUDIO_PREFS };\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") {\n return { ...DEFAULT_AUDIO_PREFS };\n }\n return { ...DEFAULT_AUDIO_PREFS, ...normalize(parsed) };\n } catch {\n return { ...DEFAULT_AUDIO_PREFS };\n }\n}\n\nexport function saveAudioPrefs(agentSlug: string, prefs: AudioPrefs): void {\n try {\n window.localStorage.setItem(\n storageKey(agentSlug),\n JSON.stringify(normalize(prefs as unknown as Record<string, unknown>)),\n );\n } catch {\n // Storage may be disabled (private mode, quota); preferences just\n // won't survive a reload. Not worth surfacing to the user.\n }\n}\n\nfunction normalize(input: Record<string, unknown>): Partial<AudioPrefs> {\n const out: Partial<AudioPrefs> = {};\n if (typeof input.echoCancellation === \"boolean\") out.echoCancellation = input.echoCancellation;\n if (typeof input.noiseSuppression === \"boolean\") out.noiseSuppression = input.noiseSuppression;\n if (typeof input.autoGainControl === \"boolean\") out.autoGainControl = input.autoGainControl;\n if (typeof input.voiceIsolation === \"boolean\") out.voiceIsolation = input.voiceIsolation;\n if (input.noiseFilter === \"off\" || input.noiseFilter === \"krisp\" || input.noiseFilter === \"deepfilter\") {\n out.noiseFilter = input.noiseFilter;\n } else if (typeof input.krispEnabled === \"boolean\") {\n // Migration path from the pre-engine-picker AudioPrefs shape.\n out.noiseFilter = input.krispEnabled ? \"krisp\" : \"off\";\n }\n if (typeof input.deepFilterStrength === \"number\" && Number.isFinite(input.deepFilterStrength)) {\n out.deepFilterStrength = Math.max(0, Math.min(100, input.deepFilterStrength));\n }\n if (typeof input.micDeviceId === \"string\") out.micDeviceId = input.micDeviceId;\n if (typeof input.speakerDeviceId === \"string\") out.speakerDeviceId = input.speakerDeviceId;\n if (typeof input.outputVolume === \"number\" && Number.isFinite(input.outputVolume)) {\n out.outputVolume = Math.max(0, Math.min(100, input.outputVolume));\n }\n if (typeof input.headphonesMode === \"boolean\") out.headphonesMode = input.headphonesMode;\n if (typeof input.transcriptionEnabled === \"boolean\") out.transcriptionEnabled = input.transcriptionEnabled;\n if (typeof input.textInputEnabled === \"boolean\") out.textInputEnabled = input.textInputEnabled;\n return out;\n}\n\nexport interface AudioDevices {\n inputs: MediaDeviceInfo[];\n outputs: MediaDeviceInfo[];\n}\n\n/**\n * Enumerate audio I/O devices. Device labels are only populated after the\n * user has granted mic permission for the page; before that the labels\n * come back empty and the caller should show a placeholder.\n */\nexport async function enumerateAudioDevices(): Promise<AudioDevices> {\n if (!navigator.mediaDevices?.enumerateDevices) {\n return { inputs: [], outputs: [] };\n }\n const devices = await navigator.mediaDevices.enumerateDevices();\n const inputs = devices.filter((d) => d.kind === \"audioinput\");\n const outputs = devices.filter((d) => d.kind === \"audiooutput\");\n return { inputs, outputs };\n}\n\n/** Browser-feature probes used to decide which UI controls make sense. */\nexport interface AudioCapabilities {\n /** setSinkId is unsupported on Safari and Firefox at the time of writing. */\n setSinkIdSupported: boolean;\n /** voiceIsolation is honored on Chromium/Edge with system support only. */\n voiceIsolationSupported: boolean;\n}\n\nexport function probeAudioCapabilities(): AudioCapabilities {\n const audioEl = typeof HTMLAudioElement !== \"undefined\"\n ? HTMLAudioElement.prototype\n : null;\n const setSinkIdSupported = Boolean(\n audioEl && typeof (audioEl as unknown as { setSinkId?: unknown }).setSinkId === \"function\",\n );\n\n // No reliable feature flag for voiceIsolation; the actual support comes\n // out in MediaTrackSettings.getSettings() once the track is published.\n // Best we can do up-front is gate on getSupportedConstraints().\n let voiceIsolationSupported = false;\n try {\n const supported = navigator.mediaDevices?.getSupportedConstraints?.() as\n | (MediaTrackSupportedConstraints & { voiceIsolation?: boolean })\n | undefined;\n voiceIsolationSupported = Boolean(supported?.voiceIsolation);\n } catch {\n voiceIsolationSupported = false;\n }\n\n return { setSinkIdSupported, voiceIsolationSupported };\n}\n","/**\n * Typed event emitter for the voice-agent SDK.\n *\n * The headless core publishes everything that the widget used to do via direct\n * UI calls (orb state, transcripts, call status, mute, forms, …) as typed\n * events. The prebuilt UI subscribes to these and renders; custom UIs do the\n * same. This is the seam that decouples logic from presentation.\n */\n\nimport type { AppearanceConfig } from \"./appearance\";\nimport type { FormDefinition, FieldValidationError } from \"./forms\";\nimport type { AudioStateSnapshot } from \"./voice\";\nimport type { OrbState, ConnectionPhase, ControlsState } from \"./types\";\n\nexport interface VoiceAgentEvents {\n /** Orb state machine + contextual status line (e.g. \"Searching…\"). */\n state: { orb: OrbState; statusLabel: string | null };\n /** Free-form call status text (Connecting…, Connected, Muted, errors). */\n \"call:status\": { status: string };\n /** Remaining call time in ms, or null to hide the timer. */\n \"call:timer\": { remainingMs: number | null };\n /** Connection lifecycle. UI maps this to which screen to show. */\n connection: { phase: ConnectionPhase; error?: string };\n /** Which call controls should be enabled. */\n controls: ControlsState;\n /** A transcript segment (interim or final) for either party. */\n transcript: {\n role: \"user\" | \"agent\";\n segmentId: string;\n text: string;\n isFinal: boolean;\n };\n /** Clear all transcript content (new call). */\n \"transcript:clear\": Record<string, never>;\n /** A system transcript line (e.g. agent handoff transition message). */\n \"transcript:system\": { text: string };\n /** Mute state changed. */\n mute: { muted: boolean };\n /** Audio preferences / applied settings snapshot changed. */\n audio: AudioStateSnapshot;\n /** Raw JSON data message received from the agent over LiveKit. */\n data: { data: unknown; topic: string | undefined };\n /** A form should be shown (agent-triggered or programmatic). */\n \"form:show\": {\n definition: FormDefinition;\n draft: Record<string, string>;\n stepIndex: number;\n inCall: boolean;\n transcriptionEnabled: boolean;\n };\n /** The form values / current step changed (agent merge or step nav). */\n \"form:update\": { values: Record<string, string>; stepIndex: number };\n /** Client-side validation failed; render these inline errors. */\n \"form:validation\": { errors: FieldValidationError[] };\n /** A submit POST is in flight. */\n \"form:submitting\": Record<string, never>;\n /** A form was submitted successfully. */\n \"form:submitted\": {\n formId: string;\n values: Record<string, string>;\n successMessage: string;\n };\n /** A submit POST failed. */\n \"form:error\": { message: string };\n /** The active form was closed. */\n \"form:close\": Record<string, never>;\n /** Mid-call handoff to another agent. */\n \"agent:handoff\": { agentName: string };\n /** Appearance config resolved (after init fetch). */\n appearance: AppearanceConfig;\n /** A non-fatal error occurred in some scope. */\n error: { scope: string; error: Error };\n}\n\nexport type EventName = keyof VoiceAgentEvents;\nexport type EventHandler<K extends EventName> = (\n payload: VoiceAgentEvents[K],\n) => void;\nexport type Emit = <K extends EventName>(\n event: K,\n payload: VoiceAgentEvents[K],\n) => void;\n\n/** Minimal typed event emitter. No deps, works in browser and Node. */\nexport class Emitter {\n private listeners = new Map<EventName, Set<EventHandler<EventName>>>();\n\n on<K extends EventName>(event: K, handler: EventHandler<K>): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(handler as EventHandler<EventName>);\n return () => this.off(event, handler);\n }\n\n off<K extends EventName>(event: K, handler: EventHandler<K>): void {\n this.listeners.get(event)?.delete(handler as EventHandler<EventName>);\n }\n\n emit: Emit = (event, payload) => {\n const set = this.listeners.get(event);\n if (!set) return;\n // Iterate a copy so a handler can unsubscribe during dispatch.\n for (const handler of Array.from(set)) {\n try {\n (handler as EventHandler<typeof event>)(payload);\n } catch (err) {\n // A faulty listener must never break the controller's control flow.\n // eslint-disable-next-line no-console\n console.error(`[voice-agent] listener for \"${String(event)}\" threw`, err);\n }\n }\n };\n\n clear(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Headless form controller.\n *\n * Lifted from the widget's `createFormController`, with every DOM touch\n * (`renderForm`, `readFormValues`, `setFieldErrors`, `setScreen`) replaced by\n * typed events. The controller now owns the form `values` model; consumers\n * (custom or prebuilt UI) push on-screen edits in via `updateValues()` — this\n * replaces the old `readFormValues(refs)` reads.\n *\n * The agent round-trip (form.state publishing, form_submit_failed and\n * confirmation messages) is preserved exactly.\n */\n\nimport type { Emit } from \"./events\";\nimport {\n FormDefinition,\n FormStateSnapshot,\n buildFieldSchema,\n buildSubmissionText,\n collectInputFields,\n fieldsForStep,\n initialFormValues,\n mergeFormDraft,\n submitForm,\n totalSteps,\n validateFields,\n} from \"./forms\";\nimport type { VoiceController } from \"./voice\";\n\nexport interface FormControllerOptions {\n emit: Emit;\n voice: VoiceController;\n apiUrl: string;\n agentSlug: string;\n apiKey?: string;\n fetch?: typeof fetch;\n}\n\nexport interface FormController {\n open: (definition: FormDefinition, draft?: Record<string, string>) => void;\n merge: (draft: Record<string, string>) => void;\n close: () => void;\n current: () => string | null;\n step: (direction: \"next\" | \"back\" | number) => void;\n submit: () => void;\n /** Push on-screen edits into the values model (replaces readFormValues). */\n updateValues: (values: Record<string, string>) => void;\n /** Snapshot of the active form, or null. */\n getActive: () => {\n definition: FormDefinition;\n values: Record<string, string>;\n stepIndex: number;\n } | null;\n}\n\n/** Index of the first stepper step containing one of the named (invalid)\n * fields, or -1 if none match (or the form isn't a stepper). */\nfunction firstStepWithError(\n definition: FormDefinition,\n errorNames: Set<string>,\n): number {\n const steps = definition.steps;\n if (!steps?.length) return -1;\n for (let i = 0; i < steps.length; i++) {\n const hit = steps[i].fields.some((f) => f.name && errorNames.has(f.name));\n if (hit) return i;\n }\n return -1;\n}\n\nexport function createFormController(\n opts: FormControllerOptions,\n): FormController {\n const { emit, voice, apiUrl, agentSlug, apiKey, fetch: fetchImpl } = opts;\n let active: FormDefinition | null = null;\n let values: Record<string, string> = {};\n let stepIndex = 0;\n let stateTimer: ReturnType<typeof setTimeout> | null = null;\n\n const buildSnapshot = (isOpen: boolean): FormStateSnapshot | null => {\n if (!active) return null;\n const definition = active;\n return {\n type: \"form_state\",\n form_id: definition.id,\n is_open: isOpen,\n step_index: stepIndex,\n total_steps: totalSteps(definition),\n values: { ...values },\n // Ship the field schema (incl. select/radio/checkbox options) so the\n // agent can register enum-constrained voice-fill tools even when the\n // session token has no appearance.forms entry for this form.\n fields: buildFieldSchema(definition),\n };\n };\n\n const flushState = (isOpen: boolean) => {\n if (stateTimer !== null) {\n clearTimeout(stateTimer);\n stateTimer = null;\n }\n if (!voice.isActive()) return;\n const snapshot = isOpen\n ? buildSnapshot(true)\n : ({\n type: \"form_state\",\n form_id: \"\",\n is_open: false,\n step_index: 0,\n total_steps: 0,\n values: {},\n fields: [],\n } satisfies FormStateSnapshot);\n if (!snapshot) return;\n void voice.publishData(snapshot, \"form.state\");\n };\n\n const scheduleStatePublish = () => {\n if (stateTimer !== null) clearTimeout(stateTimer);\n stateTimer = setTimeout(() => {\n stateTimer = null;\n flushState(active !== null);\n }, 250);\n };\n\n const emitUpdate = () => {\n emit(\"form:update\", { values: { ...values }, stepIndex });\n };\n\n const close = () => {\n if (stateTimer !== null) {\n clearTimeout(stateTimer);\n stateTimer = null;\n }\n const wasOpen = active !== null;\n active = null;\n values = {};\n stepIndex = 0;\n emit(\"form:close\", {});\n if (wasOpen) flushState(false);\n };\n\n const open = (definition: FormDefinition, draft?: Record<string, string>) => {\n active = definition;\n stepIndex = 0;\n values = mergeFormDraft(initialFormValues(definition), draft ?? null);\n emit(\"form:show\", {\n definition,\n draft: { ...values },\n stepIndex,\n inCall: voice.isActive(),\n transcriptionEnabled:\n voice.isActive() && voice.getAudioState().prefs.transcriptionEnabled,\n });\n flushState(true);\n };\n\n const merge = (draft: Record<string, string>) => {\n if (!active) return;\n // `values` is kept current by updateValues() on every keystroke, so the\n // agent's draft overlays only the fields it set without wiping manual edits.\n values = mergeFormDraft(values, draft);\n emitUpdate();\n flushState(true);\n };\n\n const updateValues = (next: Record<string, string>) => {\n if (!active) return;\n Object.assign(values, next);\n scheduleStatePublish();\n };\n\n const goToStep = (target: number) => {\n if (!active) return;\n const steps = totalSteps(active);\n const clamped = Math.max(0, Math.min(target, steps - 1));\n if (clamped === stepIndex) return;\n stepIndex = clamped;\n emitUpdate();\n flushState(true);\n };\n\n const stepBack = () => {\n if (!active || stepIndex === 0) return;\n goToStep(stepIndex - 1);\n };\n\n const attemptSubmit = async () => {\n if (!active) return;\n const definition = active;\n const steps = totalSteps(definition);\n const isFinalStep = stepIndex >= steps - 1;\n\n // While navigating, validate just the current step. On the final step\n // (actual submit) validate every required field across all steps so a\n // skipped or agent-jumped step can't slip an empty required field past.\n const fieldsToCheck = isFinalStep\n ? collectInputFields(definition)\n : fieldsForStep(definition, stepIndex);\n const errors = validateFields(fieldsToCheck, values);\n if (errors.length) {\n // If an offending field lives on an earlier step, jump there so the\n // visitor can actually see and fix it.\n if (isFinalStep && totalSteps(definition) > 1) {\n const targetStep = firstStepWithError(\n definition,\n new Set(errors.map((e) => e.name)),\n );\n if (targetStep >= 0 && targetStep !== stepIndex) {\n stepIndex = targetStep;\n emitUpdate();\n }\n }\n emit(\"form:validation\", { errors });\n // Tell the agent the submit was rejected so it doesn't claim success.\n if (voice.isActive()) {\n void voice.publishData(\n {\n type: \"form_submit_failed\",\n form_id: definition.id,\n text:\n `(System: the \"${definition.title}\" form was NOT submitted — ` +\n `these fields need attention: ` +\n `${errors.map((e) => `${e.label} (${e.message})`).join(\", \")}. ` +\n `Ask the visitor to correct them and do not say it was submitted.)`,\n },\n \"voice.user_text\",\n );\n }\n return;\n }\n\n if (!isFinalStep) {\n stepIndex += 1;\n emitUpdate();\n flushState(true);\n return;\n }\n\n // Final step — submit.\n emit(\"form:submitting\", {});\n try {\n await submitForm({\n definition,\n values,\n apiUrl,\n slug: agentSlug,\n sessionId: voice.sessionId(),\n apiKey,\n fetch: fetchImpl,\n });\n const successMessage =\n definition.success_message ?? \"Submitted. We'll be in touch shortly.\";\n emit(\"form:submitted\", {\n formId: definition.id,\n values: { ...values },\n successMessage,\n });\n\n if (voice.isActive()) {\n void voice.publishData(\n {\n type: definition.confirmation_type ?? `${definition.id}_submitted`,\n form_id: definition.id,\n text: buildSubmissionText(definition, values),\n form: { ...values },\n },\n definition.confirmation_topic ?? \"voice.user_text\",\n );\n }\n\n // Let the success message breathe, then close the form so the user is\n // dropped back on the call (or welcome if the call has ended).\n setTimeout(() => {\n if (active === definition) close();\n }, 1500);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : \"We couldn't send your request right now. Please try again.\";\n emit(\"form:error\", { message });\n if (voice.isActive()) {\n void voice.publishData(\n {\n type: \"form_submit_failed\",\n form_id: definition.id,\n text:\n `(System: the \"${definition.title}\" form submission failed: ` +\n `${message} Tell the visitor it didn't go through and offer ` +\n `to try again.)`,\n },\n \"voice.user_text\",\n );\n }\n }\n };\n\n return {\n open,\n merge,\n close,\n updateValues,\n current: () => active?.id ?? null,\n getActive: () =>\n active ? { definition: active, values: { ...values }, stepIndex } : null,\n step: (direction) => {\n if (!active) return;\n if (direction === \"next\") goToStep(stepIndex + 1);\n else if (direction === \"back\") stepBack();\n else if (typeof direction === \"number\") goToStep(direction);\n },\n submit: () => {\n void attemptSubmit();\n },\n };\n}\n","/**\n * HTTP transport helpers shared by every backend call the SDK makes\n * (session minting, appearance, form submit, analytics).\n *\n * Two auth modes (see docs/Authentication):\n * - Public browser embed (default): no key. The backend gates on the\n * AICharacter's origin allow-list. This is exactly how the widget works.\n * - Trusted/server mode: an `sk_…` key sent as `x-api-key`, bypassing origin\n * gating. Intended for Node / server-side usage. We warn loudly if such a\n * secret key is used in a browser, where it would be world-readable.\n */\n\nimport type { SessionInit } from \"./types\";\n\nexport interface TransportConfig {\n apiUrl: string;\n /** Secret API key (sk_…). Sent as `x-api-key`. Omit for public embeds. */\n apiKey?: string;\n /** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */\n fetch?: typeof fetch;\n}\n\n/** Merge auth headers onto a base header set. */\nexport function buildHeaders(\n config: Pick<TransportConfig, \"apiKey\">,\n base: Record<string, string> = {},\n): Record<string, string> {\n const headers: Record<string, string> = { ...base };\n const key = config.apiKey?.trim();\n if (key) headers[\"x-api-key\"] = key;\n return headers;\n}\n\n/** Resolve a usable fetch implementation or throw a helpful error. */\nexport function resolveFetch(fetchImpl?: typeof fetch): typeof fetch {\n if (fetchImpl) return fetchImpl;\n if (typeof globalThis.fetch === \"function\") {\n return globalThis.fetch.bind(globalThis);\n }\n throw new Error(\n \"[voice-agent] No global fetch available. Pass `fetch` in the SDK config (Node < 18).\",\n );\n}\n\n/**\n * Warn when a secret key is embedded in browser JS, where it is exposed to\n * anyone who views source. Call once at init.\n */\nexport function warnIfBrowserSecret(apiKey?: string): void {\n if (\n apiKey &&\n apiKey.trim().startsWith(\"sk_\") &&\n typeof window !== \"undefined\"\n ) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[voice-agent] A secret API key (sk_…) was provided in a browser context — \" +\n \"it is visible to anyone who views the page. For public embeds omit `apiKey` \" +\n \"and rely on origin allow-listing; reserve `apiKey` for server-side/Node usage.\",\n );\n }\n}\n\nexport interface FetchSessionArgs {\n apiUrl: string;\n agentSlug: string;\n language: string;\n apiKey?: string;\n fetch?: typeof fetch;\n /** Page URL recorded server-side; defaults to window.location.href. */\n originUrl?: string;\n /** Optional extra fields honored only with a valid x-api-key (system_prompt, etc.). */\n extra?: Record<string, unknown>;\n}\n\n/** Mint a LiveKit session token. Moved verbatim from the widget's fetchSession. */\nexport async function fetchSession(args: FetchSessionArgs): Promise<SessionInit> {\n const doFetch = resolveFetch(args.fetch);\n // The browser's Origin/Referer headers don't survive server-side relays.\n // Send the page URL explicitly so the backend can record where the session\n // was initiated regardless of the network path.\n const originUrl =\n args.originUrl ??\n ((typeof window !== \"undefined\" && window.location?.href) || \"\");\n\n const r = await doFetch(`${args.apiUrl}/api/agents/agent-session/`, {\n method: \"POST\",\n headers: buildHeaders(args, { \"Content-Type\": \"application/json\" }),\n body: JSON.stringify({\n agent: args.agentSlug,\n origin_url: originUrl,\n language: args.language,\n ...(args.extra ?? {}),\n }),\n });\n\n let body: any;\n try {\n body = await r.json();\n } catch {\n throw new Error(`session request failed (${r.status}): non-JSON response`);\n }\n\n if (!r.ok || body?.success === false) {\n const msg = body?.message || `HTTP ${r.status}`;\n throw new Error(`session request failed: ${msg}`);\n }\n\n const data = body?.data ?? body;\n if (!data?.livekit_url || !data?.token) {\n throw new Error(\n `session response missing livekit_url/token (got keys: ${Object.keys(\n data || {},\n ).join(\",\")})`,\n );\n }\n return data as SessionInit;\n}\n","/**\n * Cross-cutting types shared between the headless core, the prebuilt UI, and\n * external SDK consumers. Kept free of any DOM or LiveKit imports so this\n * module is safe to import from anywhere (including Node).\n */\n\n/** Visual state of the agent orb / call indicator. */\nexport type OrbState =\n | \"idle\"\n | \"listening\"\n | \"speaking\"\n | \"connecting\"\n | \"thinking\";\n\n/** Shape returned by Django's `POST /api/agents/agent-session/`. */\nexport interface SessionInit {\n token: string;\n livekit_url: string;\n room_name: string;\n participant_identity: string;\n expires_in_seconds: number;\n session_id: string;\n character_slug: string;\n system_prompt: string;\n greeting: string;\n voice_inference_url: string;\n}\n\n/** High-level connection lifecycle phase, surfaced via the `connection` event. */\nexport type ConnectionPhase =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\"\n | \"failed\";\n\n/**\n * Which call controls the UI should enable. Replaces the direct\n * `refs.startBtn.disabled = …` side effects the controller used to perform.\n */\nexport interface ControlsState {\n canStart: boolean;\n canMute: boolean;\n canEnd: boolean;\n}\n\n/**\n * DeepFilterNet3 asset overrides. Mirrors the widget's boot config exactly,\n * including precedence: `wasmUrl`/`onnxUrl` take priority over `cdnUrl`.\n * Passed to the voice controller per-instance (no module-level globals) so\n * multiple agents can coexist on one page.\n */\nexport interface DeepFilterUrls {\n /** Base CDN URL; the package appends `/v2/pkg/df_bg.wasm` etc. Empty = default. */\n cdnUrl?: string;\n /** Direct URL to `df_bg.wasm`. Takes precedence over `cdnUrl`. */\n wasmUrl?: string;\n /** Direct URL to `DeepFilterNet3_onnx.tar.gz`. Takes precedence over `cdnUrl`. */\n onnxUrl?: string;\n /** Where the DeepFilterNet3 ESM module is loaded from. Empty = esm.sh default. */\n moduleUrl?: string;\n}\n\n/** Default DeepFilterNet3 ESM module mirror (esm.sh) when none is configured. */\nexport const DEFAULT_DEEPFILTER_MODULE_URL =\n \"https://esm.sh/deepfilternet3-noise-filter@1.2.1\";\n","/**\n * Per-agent storage for the most-recent conversation transcript, so the\n * agent can be primed with prior context when the user returns.\n *\n * The widget stores the *raw last turns* (not an LLM-generated summary) —\n * the agent-side LLM is perfectly capable of using the transcript as\n * context, and skipping a summarization step keeps the widget standalone.\n */\n\nconst STORAGE_PREFIX = \"voiceAgent.prevContext\";\nconst MAX_STORED_CHARS = 4000;\nconst MAX_STORED_TURNS = 30;\nconst MAX_TURN_CHARS = 500;\nconst MAX_SUMMARY_CHARS = 1500;\n/** Drop saved context older than this so stale conversations don't haunt new ones. */\nconst STALE_AFTER_MS = 14 * 24 * 60 * 60 * 1000;\n\nexport interface PrevTurn {\n role: \"user\" | \"agent\";\n text: string;\n}\n\nexport interface PrevContextRecord {\n savedAt: number;\n turns: PrevTurn[];\n /** Agent-generated short user-profile summary (preferred over raw turns\n * when priming the next session). May be empty. */\n summary?: string;\n}\n\nfunction storageKey(agentSlug: string): string {\n return `${STORAGE_PREFIX}.${agentSlug}`;\n}\n\nfunction safeLocalStorage(): Storage | null {\n try {\n return window.localStorage;\n } catch {\n return null;\n }\n}\n\nexport function loadPrevContext(agentSlug: string): PrevContextRecord | null {\n if (!agentSlug) return null;\n const ls = safeLocalStorage();\n if (!ls) return null;\n\n let raw: string | null = null;\n try {\n raw = ls.getItem(storageKey(agentSlug));\n } catch {\n return null;\n }\n if (!raw) return null;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (!parsed || typeof parsed !== \"object\") return null;\n const obj = parsed as Partial<PrevContextRecord>;\n const savedAt = typeof obj.savedAt === \"number\" ? obj.savedAt : 0;\n if (!savedAt || Date.now() - savedAt > STALE_AFTER_MS) {\n clearPrevContext(agentSlug);\n return null;\n }\n\n const turns: PrevTurn[] = [];\n if (Array.isArray(obj.turns)) {\n for (const t of obj.turns) {\n if (!t || typeof t !== \"object\") continue;\n const role = (t as PrevTurn).role;\n const text = (t as PrevTurn).text;\n if ((role !== \"user\" && role !== \"agent\") || typeof text !== \"string\") continue;\n const cleaned = text.trim();\n if (!cleaned) continue;\n turns.push({ role, text: cleaned });\n }\n }\n const summary =\n typeof obj.summary === \"string\" ? obj.summary.trim().slice(0, MAX_SUMMARY_CHARS) : \"\";\n if (!turns.length && !summary) return null;\n return { savedAt, turns, summary };\n}\n\nexport function savePrevContext(\n agentSlug: string,\n turns: PrevTurn[],\n summary?: string,\n): void {\n if (!agentSlug) return;\n const ls = safeLocalStorage();\n if (!ls) return;\n\n // Trim per-turn, drop empties, keep most recent MAX_STORED_TURNS, then\n // truncate from the front until we fit MAX_STORED_CHARS.\n const cleaned: PrevTurn[] = [];\n for (const t of turns) {\n const text = (t.text || \"\").trim().slice(0, MAX_TURN_CHARS);\n if (!text) continue;\n cleaned.push({ role: t.role, text });\n }\n const recent = cleaned.slice(-MAX_STORED_TURNS);\n\n let totalChars = recent.reduce((n, t) => n + t.text.length, 0);\n while (recent.length > 1 && totalChars > MAX_STORED_CHARS) {\n const dropped = recent.shift();\n if (dropped) totalChars -= dropped.text.length;\n }\n\n const trimmedSummary = (summary || \"\").trim().slice(0, MAX_SUMMARY_CHARS);\n if (!recent.length && !trimmedSummary) {\n clearPrevContext(agentSlug);\n return;\n }\n\n const record: PrevContextRecord = {\n savedAt: Date.now(),\n turns: recent,\n summary: trimmedSummary,\n };\n try {\n ls.setItem(storageKey(agentSlug), JSON.stringify(record));\n } catch {\n // Quota or privacy mode — silently drop.\n }\n}\n\nexport function clearPrevContext(agentSlug: string): void {\n if (!agentSlug) return;\n const ls = safeLocalStorage();\n if (!ls) return;\n try {\n ls.removeItem(storageKey(agentSlug));\n } catch {\n // ignore\n }\n}\n\n/** Format stored context for the agent's system prompt.\n * Prefers the agent-generated user-profile summary when available; falls\n * back to a labelled raw-turn transcript otherwise. */\nexport function formatPrevContextForAgent(record: PrevContextRecord): string {\n const summary = (record.summary || \"\").trim();\n if (summary) return `User profile from previous session:\\n${summary}`;\n const lines = record.turns.map((t) =>\n t.role === \"user\" ? `User: ${t.text}` : `Assistant: ${t.text}`,\n );\n return lines.join(\"\\n\");\n}\n","import {\n AudioPresets,\n ConnectionState,\n LocalAudioTrack,\n RemoteAudioTrack,\n RemoteParticipant,\n RemoteTrack,\n RemoteTrackPublication,\n Room,\n RoomEvent,\n Track,\n TranscriptionSegment,\n} from \"livekit-client\";\nimport {\n AudioPrefs,\n loadAudioPrefs,\n NoiseFilterEngine,\n saveAudioPrefs,\n} from \"./audioSettings\";\n\nimport type { AppearanceConfig } from \"./appearance\";\nimport type { Emit } from \"./events\";\nimport {\n DEFAULT_DEEPFILTER_MODULE_URL,\n type DeepFilterUrls,\n type OrbState,\n type SessionInit,\n} from \"./types\";\nimport {\n formatPrevContextForAgent,\n loadPrevContext,\n PrevTurn,\n savePrevContext,\n} from \"./prevContext\";\n\nexport type { SessionInit } from \"./types\";\n\n/**\n * Snapshot reported back to the UI after the mic publishes or when any\n * audio setting changes. The `applied*` flags are read from the live\n * MediaStreamTrack via getSettings(), so they reflect what the browser\n * actually honored — which may differ from what we requested.\n */\nexport interface AudioStateSnapshot {\n prefs: AudioPrefs;\n applied: {\n echoCancellation: boolean | undefined;\n noiseSuppression: boolean | undefined;\n autoGainControl: boolean | undefined;\n voiceIsolation: boolean | undefined;\n sampleRate: number | undefined;\n channelCount: number | undefined;\n deviceId: string | undefined;\n };\n /**\n * Effective state of the deep-learning NS engine:\n * - `engine` — what the user picked (off / krisp / deepfilter).\n * - `status` — what actually happened: \"active\" if the processor is\n * attached, \"unsupported\" if the chosen engine isn't available in\n * this browser, \"failed\" if attach errored, \"off\" if engine === \"off\".\n */\n noiseFilter: {\n engine: NoiseFilterEngine;\n status: \"active\" | \"off\" | \"unsupported\" | \"failed\";\n };\n}\n\nexport interface AudioStats {\n /** Local outbound audio level (0-1) from RTCStats. */\n outboundAudioLevel: number;\n /** Remote inbound audio level (0-1) from RTCStats. */\n inboundAudioLevel: number;\n /** Packets lost on inbound (agent → user) stream. */\n packetsLost: number;\n /** Inbound jitter in ms. */\n jitter: number;\n /** Round-trip time in ms (peer connection). */\n roundTripTime: number;\n}\n\nexport interface VoiceController {\n start: () => Promise<void>;\n end: () => Promise<void>;\n toggleMute: () => Promise<boolean>;\n isActive: () => boolean;\n /** Returns the session_id for the active session, or null if no session is running. */\n sessionId: () => string | null;\n /** Publish a JSON data message back to the agent (used after form submit). */\n publishData: (payload: unknown, topic: string) => Promise<void>;\n /** Apply a partial update to the audio preferences (live, no reconnect). */\n updateAudioSettings: (delta: Partial<AudioPrefs>) => Promise<AudioStateSnapshot>;\n /** Read the current audio state (preferences + actually-applied values). */\n getAudioState: () => AudioStateSnapshot;\n /** Poll a snapshot of audio RTC stats (returns null if no call). */\n getAudioStats: () => Promise<AudioStats | null>;\n}\n\nexport interface VoiceControllerOptions {\n /** Mint a LiveKit session (POST /api/agents/agent-session/). */\n fetchSession: () => Promise<SessionInit>;\n /** AICharacter slug — namespaces persisted prefs / context. */\n agentSlug: string;\n /** Read the live appearance config (labels, max_call_seconds). */\n getAppearance: () => AppearanceConfig;\n /** Typed event emitter — replaces all the old direct UI calls. */\n emit: Emit;\n /** Per-instance DeepFilterNet3 asset overrides. */\n deepFilter?: DeepFilterUrls;\n /** Pre-resolved audio prefs to start from. Defaults to loadAudioPrefs(slug). */\n initialPrefs?: AudioPrefs;\n /** Persist pref changes to localStorage (default true). */\n persistPrefs?: boolean;\n}\n\nexport function createVoiceController(\n opts: VoiceControllerOptions,\n): VoiceController {\n const {\n fetchSession,\n agentSlug,\n getAppearance,\n emit,\n deepFilter = {},\n persistPrefs = true,\n } = opts;\n const deepFilterModuleUrl =\n (deepFilter.moduleUrl && deepFilter.moduleUrl.trim()) ||\n DEFAULT_DEEPFILTER_MODULE_URL;\n const deepFilterCdnUrl =\n deepFilter.cdnUrl && deepFilter.cdnUrl.trim() ? deepFilter.cdnUrl.trim() : undefined;\n const deepFilterWasmUrl =\n deepFilter.wasmUrl && deepFilter.wasmUrl.trim() ? deepFilter.wasmUrl.trim() : undefined;\n const deepFilterOnnxUrl =\n deepFilter.onnxUrl && deepFilter.onnxUrl.trim() ? deepFilter.onnxUrl.trim() : undefined;\n\n let room: Room | null = null;\n let currentSessionId: string | null = null;\n let audioEl: HTMLAudioElement | null = null;\n let muted = false;\n let duckingMuted = false;\n let duckTimerId: number | null = null;\n let unduckTimerId: number | null = null;\n // Duck-in delay: long enough that a quick user barge-in / reply isn't\n // chopped before the agent-side VAD locks on, short enough that the\n // mic-vs-speaker echo path is closed before too much leaks back. 80ms\n // (an earlier value) made preemptive generation fire late because the\n // very start of user replies was getting muted before VAD confirmed\n // speech. 200ms keeps the agent's turn detector happy. We unmute\n // immediately when the agent stops speaking — any post-agent grace\n // just delays the user's next turn reaching VAD.\n const DUCK_DELAY_MS = 200;\n let prefs: AudioPrefs = opts.initialPrefs\n ? { ...opts.initialPrefs }\n : loadAudioPrefs(agentSlug);\n const persistPrefsToStorage = () => {\n if (persistPrefs) saveAudioPrefs(agentSlug, prefs);\n };\n let noiseFilterStatus: AudioStateSnapshot[\"noiseFilter\"][\"status\"] = \"off\";\n\n // ── Orb-state reconciliation ──────────────────────────────────────\n // The orb is driven by three independent signals: agent audio + user audio\n // (RoomEvent.ActiveSpeakersChanged) and an explicit \"thinking\" status\n // (voice.agent_status data events, with lk.agent.state as a fallback).\n // Real audio is authoritative; the inferred \"no speakers → idle\" must not\n // stomp a live thinking state. Priority: speaking > thinking > listening >\n // idle.\n let agentSpeaking = false;\n let userSpeaking = false;\n let agentThinking = false;\n let lastAgentStatusLabel = \"\";\n // Debounce the thinking → idle clear so a very fast tool doesn't flash the\n // status line on then instantly off.\n let thinkingClearTimer: number | null = null;\n const THINKING_CLEAR_DELAY_MS = 150;\n\n const reconcileOrb = () => {\n let next: OrbState;\n if (agentSpeaking) next = \"speaking\";\n else if (agentThinking) next = \"thinking\";\n else if (userSpeaking) next = \"listening\";\n else next = \"idle\";\n emit(\"state\", {\n orb: next,\n statusLabel: next === \"thinking\" ? lastAgentStatusLabel || null : null,\n });\n };\n\n const cancelThinkingClear = () => {\n if (thinkingClearTimer !== null) {\n window.clearTimeout(thinkingClearTimer);\n thinkingClearTimer = null;\n }\n };\n\n /** Enter the thinking state with a contextual label (may be empty). */\n const setThinking = (label: string) => {\n cancelThinkingClear();\n agentThinking = true;\n lastAgentStatusLabel = label || \"\";\n reconcileOrb();\n };\n\n /** Leave thinking, debounced so brief tool calls don't flicker. */\n const clearThinking = () => {\n if (!agentThinking || thinkingClearTimer !== null) return;\n thinkingClearTimer = window.setTimeout(() => {\n thinkingClearTimer = null;\n agentThinking = false;\n lastAgentStatusLabel = \"\";\n reconcileOrb();\n }, THINKING_CLEAR_DELAY_MS);\n };\n /** Live handle on the deepfilter processor so we can tweak strength without re-attaching. */\n let deepFilterProcessor: { setSuppressionLevel?: (n: number) => void } | null = null;\n let callDeadline: number | null = null;\n let callTickId: number | null = null;\n /** Final transcript turns captured during the current call, in arrival order. */\n const turns = new Map<string, PrevTurn>();\n let prevContextSent = false;\n let unloadHandler: (() => void) | null = null;\n /** Most-recent agent-generated session summary received over data channel. */\n let latestSummary = \"\";\n let pendingSummaryResolver: ((summary: string) => void) | null = null;\n\n const persistTurns = (summary?: string) => {\n if (turns.size === 0 && !(summary && summary.trim())) return;\n savePrevContext(\n agentSlug,\n Array.from(turns.values()),\n summary ?? latestSummary,\n );\n };\n\n const recordTranscriptSegment = (\n role: \"user\" | \"agent\",\n segmentId: string,\n text: string,\n isFinal: boolean,\n ) => {\n const cleaned = (text || \"\").trim();\n if (!cleaned) return;\n const key = `${role}:${segmentId}`;\n if (isFinal) {\n turns.set(key, { role, text: cleaned });\n } else if (turns.has(key)) {\n // Update interim text in place so the final-only persist below still\n // captures the latest text if the call dies before a final segment.\n turns.set(key, { role, text: cleaned });\n }\n };\n\n const clearCallTimeout = () => {\n callDeadline = null;\n if (callTickId !== null) {\n window.clearInterval(callTickId);\n callTickId = null;\n }\n emit(\"call:timer\", { remainingMs: null });\n };\n\n const reset = () => {\n cancelThinkingClear();\n agentSpeaking = false;\n userSpeaking = false;\n agentThinking = false;\n lastAgentStatusLabel = \"\";\n emit(\"state\", { orb: \"idle\", statusLabel: null });\n emit(\"controls\", { canStart: true, canMute: false, canEnd: false });\n emit(\"mute\", { muted: false });\n muted = false;\n duckingMuted = false;\n if (duckTimerId !== null) {\n window.clearTimeout(duckTimerId);\n duckTimerId = null;\n }\n if (unduckTimerId !== null) {\n window.clearTimeout(unduckTimerId);\n unduckTimerId = null;\n }\n clearCallTimeout();\n if (audioEl) {\n audioEl.remove();\n audioEl = null;\n }\n noiseFilterStatus = \"off\";\n deepFilterProcessor = null;\n };\n\n const requestSessionSummary = async (target: Room): Promise<string> => {\n // Ask the agent to summarize. If the agent answers within the timeout\n // we get a freshly-generated user-profile summary; otherwise we fall\n // through and persist whatever raw turns + cached summary we have.\n const TIMEOUT_MS = 2500;\n const waiter = new Promise<string>((resolve) => {\n pendingSummaryResolver = resolve;\n });\n try {\n await target.localParticipant.publishData(\n new TextEncoder().encode(JSON.stringify({ type: \"request_summary\" })),\n { reliable: true, topic: \"voice.request_summary\" },\n );\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] Failed to publish summary request:\", err);\n pendingSummaryResolver = null;\n return latestSummary;\n }\n const timeout = new Promise<string>((resolve) =>\n window.setTimeout(() => resolve(\"\"), TIMEOUT_MS),\n );\n const result = await Promise.race([waiter, timeout]);\n pendingSummaryResolver = null;\n return result || latestSummary;\n };\n\n const end = async (skipSummary = false) => {\n clearCallTimeout();\n if (unloadHandler) {\n window.removeEventListener(\"beforeunload\", unloadHandler);\n window.removeEventListener(\"pagehide\", unloadHandler);\n unloadHandler = null;\n }\n let summary = latestSummary;\n if (room && !skipSummary) {\n emit(\"call:status\", { status: \"Saving…\" });\n try {\n summary = await requestSessionSummary(room);\n } catch {\n summary = latestSummary;\n }\n }\n try {\n if (room) {\n await room.disconnect();\n }\n } finally {\n room = null;\n currentSessionId = null;\n try { persistTurns(summary); } catch { /* ignore storage errors */ }\n reset();\n emit(\"connection\", { phase: \"disconnected\" });\n }\n };\n\n const publishPreviousContext = async (target: Room): Promise<void> => {\n if (prevContextSent) return;\n const record = loadPrevContext(agentSlug);\n if (!record) {\n prevContextSent = true;\n return;\n }\n const text = formatPrevContextForAgent(record);\n if (!text) {\n prevContextSent = true;\n return;\n }\n const payload = JSON.stringify({\n type: \"previous_context\",\n text,\n saved_at: new Date(record.savedAt).toISOString(),\n });\n try {\n await target.localParticipant.publishData(\n new TextEncoder().encode(payload),\n {\n reliable: true,\n topic: \"voice.previous_context\",\n },\n );\n prevContextSent = true;\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] Failed to publish previous context:\", err);\n }\n };\n\n const startCallTimeout = () => {\n clearCallTimeout();\n const limit = getAppearance().max_call_seconds;\n if (!Number.isFinite(limit) || limit <= 0) return;\n\n callDeadline = Date.now() + limit * 1000;\n emit(\"call:timer\", { remainingMs: limit * 1000 });\n\n callTickId = window.setInterval(() => {\n if (callDeadline === null) return;\n const remaining = callDeadline - Date.now();\n if (remaining <= 0) {\n emit(\"call:timer\", { remainingMs: 0 });\n clearCallTimeout();\n emit(\"call:status\", { status: getAppearance().labels.call_ended });\n void end();\n return;\n }\n emit(\"call:timer\", { remainingMs: remaining });\n }, 500);\n };\n\n const start = async () => {\n emit(\"controls\", { canStart: false, canMute: false, canEnd: false });\n emit(\"transcript:clear\", {});\n turns.clear();\n prevContextSent = false;\n latestSummary = \"\";\n pendingSummaryResolver = null;\n emit(\"connection\", { phase: \"connecting\" });\n emit(\"state\", { orb: \"connecting\", statusLabel: null });\n emit(\"call:status\", { status: getAppearance().labels.connecting });\n\n let init: SessionInit;\n try {\n init = await fetchSession();\n currentSessionId = init.session_id;\n } catch (e) {\n emit(\"call:status\", { status: `Error: ${(e as Error).message}` });\n emit(\"error\", { scope: \"session\", error: e as Error });\n emit(\"controls\", { canStart: true, canMute: false, canEnd: false });\n emit(\"connection\", { phase: \"failed\", error: (e as Error).message });\n return;\n }\n\n room = new Room({\n adaptiveStream: true,\n dynacast: true,\n webAudioMix: false,\n });\n\n room.on(RoomEvent.ConnectionStateChanged, (state: ConnectionState) => {\n if (state === ConnectionState.Connected) {\n emit(\"call:status\", { status: \"Connected\" });\n emit(\"connection\", { phase: \"connected\" });\n }\n if (state === ConnectionState.Disconnected) {\n emit(\"call:status\", { status: \"Disconnected\" });\n }\n });\n\n // Surface autoplay blocks so the user can tap to start audio. Browsers\n // refuse to autoplay audio without a prior user gesture; in our flow\n // the FAB tap satisfies that, but not always (e.g. iframe contexts).\n room.on(RoomEvent.AudioPlaybackStatusChanged, () => {\n if (!room?.canPlaybackAudio) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] Audio playback blocked by browser — call room.startAudio() after a user gesture\");\n }\n });\n\n room.on(\n RoomEvent.TrackSubscribed,\n (track: RemoteTrack, _pub: RemoteTrackPublication, _p: RemoteParticipant) => {\n if (track.kind === Track.Kind.Audio) {\n const remoteAudio = track as RemoteAudioTrack;\n audioEl = remoteAudio.attach() as HTMLAudioElement;\n audioEl.autoplay = true;\n audioEl.setAttribute(\"playsinline\", \"\");\n audioEl.style.display = \"none\";\n audioEl.volume = Math.max(0, Math.min(1, prefs.outputVolume / 100));\n document.body.appendChild(audioEl);\n // Pin the saved output device, if any. Silently no-ops on\n // browsers without setSinkId support (Safari/Firefox).\n void applySinkId(audioEl, prefs.speakerDeviceId);\n }\n },\n );\n\n room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {\n const localId = room?.localParticipant.identity;\n agentSpeaking = speakers.some((s) => s.identity !== localId);\n userSpeaking = speakers.some((s) => s.identity === localId);\n // Real audio is authoritative over an inferred thinking state — clear\n // it immediately (no debounce) when either side actually speaks.\n if (agentSpeaking || userSpeaking) {\n cancelThinkingClear();\n agentThinking = false;\n lastAgentStatusLabel = \"\";\n }\n reconcileOrb();\n applyDucking(agentSpeaking);\n });\n\n room.on(\n RoomEvent.TranscriptionReceived,\n (segments: TranscriptionSegment[], participant) => {\n const isSelf = participant?.identity === room?.localParticipant.identity;\n const role: \"user\" | \"agent\" = isSelf ? \"user\" : \"agent\";\n for (const seg of segments) {\n emit(\"transcript\", {\n role,\n segmentId: seg.id,\n text: seg.text,\n isFinal: seg.final,\n });\n recordTranscriptSegment(role, seg.id, seg.text, seg.final);\n }\n },\n );\n\n // Once the agent participant joins we have a real peer to receive\n // data on `voice.previous_context`. We give the agent a brief moment\n // to register its data handler (it runs after ctx.connect in main.py\n // but before AgentSession.start in session_agent.py) before sending.\n room.on(RoomEvent.ParticipantConnected, (participant: RemoteParticipant) => {\n if (!room || prevContextSent) return;\n window.setTimeout(() => {\n if (!room || prevContextSent) return;\n void publishPreviousContext(room);\n }, 750);\n // Acknowledge the unused identity in dev/typecheck without renaming.\n void participant;\n });\n\n room.on(\n RoomEvent.DataReceived,\n (\n payload: Uint8Array,\n _participant?: RemoteParticipant,\n _kind?: unknown,\n topic?: string,\n ) => {\n if (topic !== \"voice.session_summary\") return;\n try {\n const decoded = new TextDecoder().decode(payload);\n if (!decoded) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(decoded);\n } catch {\n parsed = null;\n }\n const text =\n parsed && typeof parsed === \"object\" &&\n typeof (parsed as { text?: unknown }).text === \"string\"\n ? ((parsed as { text: string }).text || \"\").trim()\n : decoded.trim();\n if (!text) return;\n latestSummary = text;\n if (pendingSummaryResolver) {\n const resolve = pendingSummaryResolver;\n pendingSummaryResolver = null;\n resolve(text);\n }\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] Failed to handle session summary:\", err);\n }\n },\n );\n\n room.on(\n RoomEvent.DataReceived,\n (_payload: Uint8Array, _participant?: RemoteParticipant, _kind?: unknown, topic?: string) => {\n if (topic !== \"voice.end_call\") return;\n void end(true); // agent is shutting down — skip summary request\n },\n );\n\n // Contextual processing status (source of truth) — drives the thinking\n // orb and the status line.\n room.on(\n RoomEvent.DataReceived,\n (payload, _participant?, _kind?, topic?) => {\n if (topic !== \"voice.agent_status\") return;\n try {\n const decoded = new TextDecoder().decode(payload);\n const parsed = decoded ? JSON.parse(decoded) : null;\n if (!parsed || typeof parsed !== \"object\") return;\n const state = String((parsed as { state?: unknown }).state || \"\");\n const label = String((parsed as { label?: unknown }).label || \"\");\n if (state === \"thinking\") setThinking(label);\n else clearThinking(); // idle / speaking → leave thinking\n } catch (err) {\n // eslint-disable-next-line no-console\n console.debug(\"[voice-agent] bad agent_status payload\", err);\n }\n },\n );\n\n // Free secondary signal: the SDK publishes lk.agent.state\n // (connecting/thinking/listening/speaking) on the agent participant's\n // attributes. Used as a fallback when a voice.agent_status event is\n // missed (e.g. an older backend that doesn't emit them).\n room.on(\n RoomEvent.ParticipantAttributesChanged,\n (changed: Record<string, string>, participant) => {\n if (participant?.identity === room?.localParticipant.identity) return;\n const state = changed?.[\"lk.agent.state\"];\n if (typeof state !== \"string\") return;\n if (state === \"thinking\") {\n // Don't clobber a richer contextual label already in flight.\n if (!agentThinking) setThinking(\"\");\n } else if (state === \"speaking\") {\n cancelThinkingClear();\n agentThinking = false;\n lastAgentStatusLabel = \"\";\n reconcileOrb();\n } else if (state === \"listening\" || state === \"idle\") {\n clearThinking();\n }\n },\n );\n\n room.on(RoomEvent.DataReceived, (payload, _participant, _kind, topic) => {\n try {\n const decoded = new TextDecoder().decode(payload);\n const data = decoded ? JSON.parse(decoded) : null;\n emit(\"data\", { data, topic });\n } catch (err) {\n // eslint-disable-next-line no-console\n console.debug(\"[voice-agent] non-JSON data message ignored\", err);\n }\n });\n\n room.on(RoomEvent.Disconnected, () => {\n void end();\n });\n\n // Backstop for agent-driven end-of-call. The agent signals end via a\n // `voice.end_call` data message, but reliable data can race the agent's\n // own session teardown. When the agent participant leaves the room\n // (session.aclose on the server), tear the call down here too — this is\n // a signaling event, so it can't be dropped like a data packet.\n room.on(\n RoomEvent.ParticipantDisconnected,\n (_participant: RemoteParticipant) => {\n if (!room) return;\n // In this 1:1 widget↔agent topology any remote leaving means the\n // agent is gone; if no remotes remain, end the call.\n if (room.remoteParticipants.size === 0) {\n void end(true);\n }\n },\n );\n\n try {\n await room.connect(init.livekit_url, init.token);\n await room.localParticipant.setMicrophoneEnabled(\n true,\n buildCaptureOptions(prefs),\n {\n // Opus \"speech\" preset — narrower band optimized for voice.\n audioPreset: AudioPresets.speech,\n // DTX intentionally OFF: it skips frames during silence, which\n // disrupts the steady cadence that the agent's contextual turn\n // detector + Silero VAD rely on to time end-of-turn. With DTX\n // on, preemptive LLM generation fires noticeably later.\n dtx: false,\n // RED intentionally OFF: redundancy adds decode jitter without\n // a clear win on the typical low-loss WiFi/wired path. Re-enable\n // if you observe audible packet-loss artifacts.\n red: false,\n },\n );\n // Honor saved mic device if the user picked one in a prior call.\n if (prefs.micDeviceId) {\n try {\n await room.switchActiveDevice(\"audioinput\", prefs.micDeviceId);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] saved mic device unavailable:\", err);\n }\n }\n await applyNoiseFilter(prefs.noiseFilter);\n emitAudioState();\n emit(\"state\", { orb: \"listening\", statusLabel: null });\n emit(\"call:status\", { status: getAppearance().labels.listening });\n emit(\"controls\", { canStart: true, canMute: true, canEnd: true });\n startCallTimeout();\n\n // The ParticipantConnected event only fires for participants that\n // join *after* we subscribe. If the agent worker is already in the\n // room when we connect, fire the publish path anyway after the same\n // grace period.\n if (room.remoteParticipants.size > 0) {\n window.setTimeout(() => {\n if (!room || prevContextSent) return;\n void publishPreviousContext(room);\n }, 750);\n }\n\n // Persist whatever we have if the user closes the tab mid-call.\n unloadHandler = () => persistTurns();\n window.addEventListener(\"beforeunload\", unloadHandler);\n window.addEventListener(\"pagehide\", unloadHandler);\n } catch (e) {\n emit(\"call:status\", { status: `Connect failed: ${(e as Error).message}` });\n emit(\"error\", { scope: \"connect\", error: e as Error });\n await end();\n }\n };\n\n /**\n * Half-duplex ducking. While the agent is speaking we mute the user's\n * mic so the speaker's audio can't loop back into the published track\n * (causing audible echo + STT confusion). We use\n * `LocalAudioTrack.mute()` / `.unmute()` — WebRTC-level mute that flips\n * the sender's enabled flag without unpublishing the track, so it\n * returns audio orders of magnitude faster than re-running\n * `setMicrophoneEnabled(true)`.\n *\n * Disabled entirely when `prefs.headphonesMode` is on — the user has no\n * acoustic echo path through speakers, so cutting them off mid-thought\n * just chops their barge-in.\n */\n const applyDucking = (agentSpeaking: boolean) => {\n if (!room || muted || prefs.headphonesMode) {\n cancelDuckTimers();\n // If we'd previously muted via ducking but the user just enabled\n // headphones mode, undo it.\n if (duckingMuted) {\n duckingMuted = false;\n void getLocalAudioTrack(room)?.unmute();\n }\n return;\n }\n\n if (agentSpeaking) {\n if (unduckTimerId !== null) {\n window.clearTimeout(unduckTimerId);\n unduckTimerId = null;\n }\n if (duckingMuted || duckTimerId !== null) return;\n duckTimerId = window.setTimeout(() => {\n duckTimerId = null;\n if (!room || muted || duckingMuted || prefs.headphonesMode) return;\n const track = getLocalAudioTrack(room);\n if (!track) return;\n duckingMuted = true;\n void track.mute();\n }, DUCK_DELAY_MS);\n } else {\n if (duckTimerId !== null) {\n window.clearTimeout(duckTimerId);\n duckTimerId = null;\n }\n if (unduckTimerId !== null) {\n window.clearTimeout(unduckTimerId);\n unduckTimerId = null;\n }\n if (!duckingMuted) return;\n // Unmute immediately: any post-agent grace delays the start of the\n // user's next turn reaching the agent's VAD/turn-detector, which is\n // exactly what makes preemptive LLM generation fire late.\n duckingMuted = false;\n void getLocalAudioTrack(room)?.unmute();\n }\n };\n\n const cancelDuckTimers = () => {\n if (duckTimerId !== null) {\n window.clearTimeout(duckTimerId);\n duckTimerId = null;\n }\n if (unduckTimerId !== null) {\n window.clearTimeout(unduckTimerId);\n unduckTimerId = null;\n }\n };\n\n const toggleMute = async () => {\n if (!room) return muted;\n muted = !muted;\n cancelDuckTimers();\n duckingMuted = false;\n // Mirror the ducking primitive: WebRTC-level mute/unmute is much\n // faster than (un)publishing the track and avoids a brief audio gap\n // on the agent side when the user toggles their own mute.\n const track = getLocalAudioTrack(room);\n if (track) {\n if (muted) await track.mute();\n else await track.unmute();\n } else {\n await room.localParticipant.setMicrophoneEnabled(!muted);\n }\n emit(\"mute\", { muted });\n emit(\"call:status\", {\n status: muted\n ? getAppearance().labels.muted\n : getAppearance().labels.listening,\n });\n return muted;\n };\n\n const publishData = async (payload: unknown, topic: string) => {\n if (!room) return;\n try {\n await room.localParticipant.publishData(\n new TextEncoder().encode(JSON.stringify(payload)),\n { reliable: true, topic },\n );\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] failed to publish data:\", err);\n }\n };\n\n /**\n * Attach/detach the deep-learning NS processor according to `engine`.\n * Krisp is statically imported (small, always present); DeepFilterNet3\n * is loaded with a dynamic `import()` so the package only enters the\n * bundle when the user actually picks it (and the widget still works\n * if the package isn't installed at all).\n */\n const applyNoiseFilter = async (engine: NoiseFilterEngine): Promise<void> => {\n const track = room ? getLocalAudioTrack(room) : null;\n if (!track) {\n noiseFilterStatus = engine === \"off\" ? \"off\" : \"failed\";\n deepFilterProcessor = null;\n return;\n }\n\n // Detach whatever's currently attached before switching engines.\n try {\n await track.stopProcessor();\n } catch {\n /* no-op: no processor was attached */\n }\n deepFilterProcessor = null;\n\n if (engine === \"off\") {\n noiseFilterStatus = \"off\";\n return;\n }\n\n if (engine === \"krisp\") {\n noiseFilterStatus = await attachKrispNoiseFilter(track);\n return;\n }\n\n if (engine === \"deepfilter\") {\n try {\n // Runtime-computed URL so Rollup's `inlineDynamicImports` can't\n // statically resolve the path. The widget bundles cleanly even\n // when `deepfilternet3-noise-filter` isn't installed locally;\n // the package is fetched from esm.sh (or a self-hosted ESM\n // mirror set via `setDeepFilterModuleUrl`) on first use.\n const moduleUrl = deepFilterModuleUrl;\n const mod = await import(/* @vite-ignore */ moduleUrl);\n const Ctor = mod.DeepFilterNoiseFilterProcessor as new (\n options: {\n noiseReductionLevel?: number;\n assetConfig?: { cdnUrl?: string };\n },\n ) => unknown;\n if (typeof Ctor !== \"function\") {\n throw new Error(\"DeepFilterNoiseFilterProcessor export missing\");\n }\n // The upstream package only honors `assetConfig.cdnUrl` and then\n // appends fixed paths (`v2/pkg/df_bg.wasm`, `v2/models/DeepFilterNet3_onnx.tar.gz`).\n // To support arbitrary self-hosted file URLs we temporarily patch\n // `globalThis.fetch` while the processor initializes and rewrite\n // any request matching those filenames to the override URLs.\n const assetCfg: { cdnUrl?: string } = {};\n if (deepFilterCdnUrl) assetCfg.cdnUrl = deepFilterCdnUrl;\n const instance = new Ctor({\n noiseReductionLevel: prefs.deepFilterStrength,\n assetConfig: Object.keys(assetCfg).length > 0 ? assetCfg : undefined,\n });\n\n const wasmOverride = deepFilterWasmUrl;\n const modelOverride = deepFilterOnnxUrl;\n const originalFetch =\n wasmOverride || modelOverride\n ? globalThis.fetch.bind(globalThis)\n : null;\n if (originalFetch) {\n const patched: typeof fetch = (input, init) => {\n const url =\n typeof input === \"string\"\n ? input\n : input instanceof URL\n ? input.href\n : (input as Request).url;\n if (wasmOverride && /\\/df_bg\\.wasm(?:$|[?#])/.test(url)) {\n return originalFetch(wasmOverride, init);\n }\n if (\n modelOverride &&\n /\\/DeepFilterNet3_onnx\\.tar\\.gz(?:$|[?#])/.test(url)\n ) {\n return originalFetch(modelOverride, init);\n }\n return originalFetch(input, init);\n };\n globalThis.fetch = patched;\n }\n\n try {\n await track.setProcessor(\n instance as unknown as Parameters<typeof track.setProcessor>[0],\n );\n } finally {\n if (originalFetch) globalThis.fetch = originalFetch;\n }\n deepFilterProcessor = instance as { setSuppressionLevel?: (n: number) => void };\n noiseFilterStatus = \"active\";\n // eslint-disable-next-line no-console\n console.info(\n \"[voice-agent] DeepFilterNet3 noise filter attached (strength=\" +\n prefs.deepFilterStrength +\n \")\",\n );\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[voice-agent] DeepFilterNet3 unavailable, falling back to Krisp:\",\n err,\n );\n noiseFilterStatus = \"unsupported\";\n // Best-effort fallback so the user isn't left with raw audio.\n const krispResult = await attachKrispNoiseFilter(track);\n if (krispResult === \"active\") {\n noiseFilterStatus = \"active\";\n }\n }\n }\n };\n\n const emitAudioState = () => {\n emit(\"audio\", snapshotAudioState());\n };\n\n const snapshotAudioState = (): AudioStateSnapshot => {\n const track = room ? getLocalAudioTrack(room) : null;\n const settings = track?.mediaStreamTrack?.getSettings() as\n | (MediaTrackSettings & { voiceIsolation?: boolean })\n | undefined;\n return {\n prefs: { ...prefs },\n applied: {\n echoCancellation: settings?.echoCancellation,\n noiseSuppression: settings?.noiseSuppression,\n autoGainControl: settings?.autoGainControl,\n voiceIsolation: settings?.voiceIsolation,\n sampleRate: settings?.sampleRate,\n channelCount: settings?.channelCount,\n deviceId: settings?.deviceId,\n },\n noiseFilter: {\n engine: prefs.noiseFilter,\n status: noiseFilterStatus,\n },\n };\n };\n\n const updateAudioSettings = async (\n delta: Partial<AudioPrefs>,\n ): Promise<AudioStateSnapshot> => {\n const next: AudioPrefs = { ...prefs, ...delta };\n const localTrack = room ? getLocalAudioTrack(room) : null;\n\n // 1. Standard MediaTrackSettings — flip live via applyConstraints. Falls\n // back to restartTrack only if applyConstraints rejects.\n const constraintKeys = [\n \"echoCancellation\",\n \"noiseSuppression\",\n \"autoGainControl\",\n \"voiceIsolation\",\n ] as const;\n const constraintDelta: MediaTrackConstraints & { voiceIsolation?: boolean } = {};\n for (const k of constraintKeys) {\n if (k in delta && delta[k] !== prefs[k]) {\n (constraintDelta as Record<string, unknown>)[k] = next[k];\n }\n }\n if (localTrack && Object.keys(constraintDelta).length > 0) {\n try {\n await localTrack.mediaStreamTrack.applyConstraints(constraintDelta);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[voice-agent] applyConstraints rejected, falling back to restartTrack:\",\n err,\n );\n try {\n await localTrack.restartTrack(buildCaptureOptions(next));\n } catch (restartErr) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] restartTrack failed:\", restartErr);\n }\n }\n }\n\n // 2. Noise-filter engine — separate audio-graph attach/detach.\n if (\"noiseFilter\" in delta && delta.noiseFilter !== prefs.noiseFilter) {\n // Commit the new engine choice into `prefs` *before* applying so\n // applyNoiseFilter sees the strength etc. for the right engine.\n prefs = { ...prefs, noiseFilter: next.noiseFilter };\n await applyNoiseFilter(next.noiseFilter);\n }\n\n // DeepFilterNet3 strength — live-adjust if the processor exposes the\n // hook, otherwise reattach. Skip when not on deepfilter.\n if (\n \"deepFilterStrength\" in delta &&\n delta.deepFilterStrength !== prefs.deepFilterStrength &&\n next.noiseFilter === \"deepfilter\"\n ) {\n prefs = { ...prefs, deepFilterStrength: next.deepFilterStrength };\n if (deepFilterProcessor && typeof deepFilterProcessor.setSuppressionLevel === \"function\") {\n try {\n deepFilterProcessor.setSuppressionLevel(next.deepFilterStrength);\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] DeepFilter setSuppressionLevel failed:\", err);\n }\n } else {\n await applyNoiseFilter(\"deepfilter\");\n }\n }\n\n // 3. Mic device — switchActiveDevice does the heavy lifting.\n if (\"micDeviceId\" in delta && delta.micDeviceId !== prefs.micDeviceId && room) {\n try {\n await room.switchActiveDevice(\"audioinput\", next.micDeviceId || \"default\");\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] failed to switch mic device:\", err);\n }\n }\n\n // 4. Speaker device — HTMLAudioElement.setSinkId.\n if (\n \"speakerDeviceId\" in delta &&\n delta.speakerDeviceId !== prefs.speakerDeviceId &&\n audioEl\n ) {\n await applySinkId(audioEl, next.speakerDeviceId);\n }\n\n // 5. Volume — direct property on the element.\n if (\"outputVolume\" in delta && audioEl) {\n audioEl.volume = Math.max(0, Math.min(1, next.outputVolume / 100));\n }\n\n // 6. Headphones mode — toggling on while currently ducked needs to\n // immediately un-mute the local track; applyDucking handles both\n // directions via its early-return path.\n if (\"headphonesMode\" in delta && delta.headphonesMode !== prefs.headphonesMode) {\n // Re-run with current speaker state. If no one is currently\n // speaking we pass false to force un-mute on next pass.\n applyDucking(false);\n }\n\n prefs = next;\n persistPrefsToStorage();\n const state = snapshotAudioState();\n emit(\"audio\", state);\n return state;\n };\n\n const getAudioStats = async (): Promise<AudioStats | null> => {\n if (!room) return null;\n const reports: RTCStatsReport[] = [];\n\n const localTrack = getLocalAudioTrack(room);\n if (localTrack) {\n try {\n const r = await localTrack.getRTCStatsReport();\n if (r) reports.push(r);\n } catch { /* ignore */ }\n }\n\n // Walk remote participants for inbound audio stats.\n for (const participant of room.remoteParticipants.values()) {\n for (const pub of participant.audioTrackPublications.values()) {\n const track = pub.track;\n if (!track) continue;\n try {\n const r = await track.getRTCStatsReport();\n if (r) reports.push(r);\n } catch { /* ignore */ }\n }\n }\n\n const stats: AudioStats = {\n outboundAudioLevel: 0,\n inboundAudioLevel: 0,\n packetsLost: 0,\n jitter: 0,\n roundTripTime: 0,\n };\n for (const report of reports) {\n report.forEach((entry) => {\n const e = entry as RTCStats & Record<string, unknown>;\n if (e.type === \"outbound-rtp\" && e.kind === \"audio\") {\n const lvl = e.audioLevel;\n if (typeof lvl === \"number\") stats.outboundAudioLevel = lvl;\n }\n if (e.type === \"inbound-rtp\" && e.kind === \"audio\") {\n const lvl = e.audioLevel;\n if (typeof lvl === \"number\") stats.inboundAudioLevel = lvl;\n const lost = e.packetsLost;\n if (typeof lost === \"number\") stats.packetsLost = lost;\n const j = e.jitter;\n if (typeof j === \"number\") stats.jitter = j * 1000;\n }\n if (e.type === \"candidate-pair\" && (e.selected === true || e.nominated === true)) {\n const rtt = e.currentRoundTripTime;\n if (typeof rtt === \"number\") stats.roundTripTime = rtt * 1000;\n }\n });\n }\n return stats;\n };\n\n return {\n start,\n end,\n toggleMute,\n isActive: () => room !== null,\n sessionId: () => currentSessionId,\n publishData,\n updateAudioSettings,\n getAudioState: snapshotAudioState,\n getAudioStats,\n };\n}\n\nfunction buildCaptureOptions(prefs: AudioPrefs): MediaTrackConstraints & {\n voiceIsolation?: boolean;\n} {\n return {\n echoCancellation: prefs.echoCancellation,\n noiseSuppression: prefs.noiseSuppression,\n autoGainControl: prefs.autoGainControl,\n voiceIsolation: prefs.voiceIsolation,\n channelCount: 1,\n sampleRate: 48000,\n deviceId: prefs.micDeviceId ? { ideal: prefs.micDeviceId } : undefined,\n };\n}\n\nfunction getLocalAudioTrack(room: Room | null): LocalAudioTrack | null {\n if (!room) return null;\n const pub = room.localParticipant.getTrackPublication(Track.Source.Microphone);\n const track = pub?.audioTrack;\n return track instanceof LocalAudioTrack ? track : null;\n}\n\nasync function applySinkId(el: HTMLAudioElement, deviceId: string): Promise<void> {\n const setSinkId = (el as unknown as { setSinkId?: (id: string) => Promise<void> })\n .setSinkId;\n if (typeof setSinkId !== \"function\") return;\n try {\n await setSinkId.call(el, deviceId || \"\");\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] setSinkId failed:\", err);\n }\n}\n\nasync function attachKrispNoiseFilter(\n track: LocalAudioTrack,\n): Promise<\"active\" | \"failed\" | \"unsupported\"> {\n // Loaded lazily: the package evaluates `class extends Worker` at module load,\n // which throws in Node. Deferring the import keeps `import \"@oshara/voice-sdk\"`\n // safe server-side; this path only ever runs in a browser call.\n const { KrispNoiseFilter, isKrispNoiseFilterSupported } = await import(\n \"@livekit/krisp-noise-filter\"\n );\n if (!isKrispNoiseFilterSupported()) {\n // eslint-disable-next-line no-console\n console.warn(\n \"[voice-agent] Krisp noise filter NOT supported in this browser — relying on browser noiseSuppression only\",\n );\n return \"unsupported\";\n }\n\n try {\n await track.setProcessor(KrispNoiseFilter());\n // eslint-disable-next-line no-console\n console.info(\"[voice-agent] Krisp noise filter attached\");\n return \"active\";\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn(\"[voice-agent] Failed to attach Krisp noise filter:\", err);\n return \"failed\";\n }\n}\n","/**\n * createVoiceAgent — the headless entry point of the SDK.\n *\n * A developer adds their agent slug (and optionally an API key) and everything\n * initializes exactly like the widget: appearance is fetched, the voice\n * controller + form controller are wired, agent-triggered forms surface as\n * events. Build a fully custom UI on the events, or mount the prebuilt UI from\n * `@oshara/voice-sdk/ui`.\n */\n\nimport { trackWidgetEvent } from \"./analytics\";\nimport {\n AppearanceConfig,\n DEFAULT_APPEARANCE,\n fetchAppearance,\n} from \"./appearance\";\nimport {\n AudioPrefs,\n AudioCapabilities,\n AudioDevices,\n DEFAULT_AUDIO_PREFS,\n enumerateAudioDevices,\n loadAudioPrefs,\n probeAudioCapabilities,\n} from \"./audioSettings\";\nimport { Emitter, EventHandler, EventName } from \"./events\";\nimport {\n createFormController,\n FormController,\n} from \"./formController\";\nimport {\n FormDefinition,\n extractFormDraft,\n handleFormAction,\n matchForm,\n} from \"./forms\";\nimport { fetchSession, warnIfBrowserSecret } from \"./transport\";\nimport type { DeepFilterUrls } from \"./types\";\nimport {\n AudioStateSnapshot,\n AudioStats,\n createVoiceController,\n VoiceController,\n} from \"./voice\";\n\nexport interface VoiceAgentConfig {\n /** AICharacter.slug on the backend. Required. */\n agentSlug: string;\n /** Backend base URL. Default \"https://api.oshara.ai\". */\n apiUrl?: string;\n /**\n * Secret API key (sk_…) sent as `x-api-key`, bypassing origin gating.\n * OMIT for public browser embeds (rely on the origin allow-list). Intended\n * for server-side / Node usage; warns if used in a browser.\n */\n apiKey?: string;\n /** Override the appearance endpoint URL. */\n appearanceUrl?: string;\n /** UI language (BCP-47 short). Default \"en\". */\n language?: string;\n /** DeepFilterNet3 asset overrides (same defaults/precedence as the widget). */\n deepFilter?: DeepFilterUrls;\n /** Auto-fetch appearance during init() (default true). */\n fetchAppearanceOnInit?: boolean;\n /** Disable analytics events (default false). */\n disableAnalytics?: boolean;\n /** Custom fetch (Node <18 / testing). Defaults to globalThis.fetch. */\n fetch?: typeof fetch;\n /**\n * Seed audio preferences. Merged over DEFAULT_AUDIO_PREFS and (when\n * persistAudioPrefs) the stored per-agent prefs.\n */\n audio?: Partial<AudioPrefs>;\n /** Read/write audio prefs to localStorage (default true). */\n persistAudioPrefs?: boolean;\n}\n\nexport interface VoiceAgentClient {\n /** The AICharacter slug this client targets. */\n readonly agentSlug: string;\n /** Fetch appearance (if enabled) and prepare the agent. Idempotent. */\n init: () => Promise<{ appearance: AppearanceConfig }>;\n /** Start a call. */\n start: () => Promise<void>;\n /** End the call. */\n end: () => Promise<void>;\n /** Tear down listeners + any active call. */\n destroy: () => void;\n\n toggleMute: () => Promise<boolean>;\n isActive: () => boolean;\n sessionId: () => string | null;\n /** Send a typed text message to the agent (also emits a transcript line). */\n sendText: (text: string) => Promise<void>;\n publishData: (payload: unknown, topic: string) => Promise<void>;\n\n updateAudioSettings: (\n delta: Partial<AudioPrefs>,\n ) => Promise<AudioStateSnapshot>;\n getAudioState: () => AudioStateSnapshot;\n getAudioStats: () => Promise<AudioStats | null>;\n enumerateAudioDevices: () => Promise<AudioDevices>;\n getAudioCapabilities: () => AudioCapabilities;\n\n // ── forms ──\n getActiveForm: () => ReturnType<FormController[\"getActive\"]>;\n /** Push on-screen form edits into the model (custom UIs call this on input). */\n updateFormValues: (values: Record<string, string>) => void;\n stepForm: (direction: \"next\" | \"back\" | number) => void;\n submitForm: () => void;\n closeForm: () => void;\n /** Programmatically open a form. */\n openForm: (definition: FormDefinition, draft?: Record<string, string>) => void;\n\n getAppearance: () => AppearanceConfig;\n setLanguage: (code: string) => void;\n getLanguage: () => string;\n\n /** Report an engagement event (widget_loaded / bubble_clicked). */\n trackEvent: (\n type: \"widget_loaded\" | \"bubble_clicked\",\n metadata?: Record<string, unknown>,\n ) => void;\n\n on: <K extends EventName>(event: K, handler: EventHandler<K>) => () => void;\n off: <K extends EventName>(event: K, handler: EventHandler<K>) => void;\n}\n\nconst DEFAULT_API_URL = \"https://api.oshara.ai\";\n\nexport function createVoiceAgent(config: VoiceAgentConfig): VoiceAgentClient {\n if (!config.agentSlug) {\n // eslint-disable-next-line no-console\n console.error(\"[voice-agent] agentSlug is required\");\n }\n warnIfBrowserSecret(config.apiKey);\n\n const apiUrl = config.apiUrl ?? DEFAULT_API_URL;\n const persistAudioPrefs = config.persistAudioPrefs ?? true;\n let language = config.language ?? \"en\";\n\n const emitter = new Emitter();\n const emit = emitter.emit;\n\n // Resolve audio prefs: defaults < stored (if persisting) < explicit config.\n const initialPrefs: AudioPrefs = {\n ...DEFAULT_AUDIO_PREFS,\n ...(persistAudioPrefs ? loadAudioPrefs(config.agentSlug) : {}),\n ...(config.audio ?? {}),\n };\n\n let appearance: AppearanceConfig = DEFAULT_APPEARANCE;\n const getAppearance = () => appearance;\n\n const voice: VoiceController = createVoiceController({\n fetchSession: () =>\n fetchSession({\n apiUrl,\n agentSlug: config.agentSlug,\n language,\n apiKey: config.apiKey,\n fetch: config.fetch,\n }),\n agentSlug: config.agentSlug,\n getAppearance,\n emit,\n deepFilter: config.deepFilter,\n initialPrefs,\n persistPrefs: persistAudioPrefs,\n });\n\n const forms: FormController = createFormController({\n emit,\n voice,\n apiUrl,\n agentSlug: config.agentSlug,\n apiKey: config.apiKey,\n fetch: config.fetch,\n });\n\n // Route agent data messages → handoff / form actions / form render.\n emitter.on(\"data\", ({ data, topic }) => {\n if (topic === \"voice.agent_handoff\") {\n const agentName =\n data && typeof data === \"object\" &&\n typeof (data as { agent_name?: unknown }).agent_name === \"string\"\n ? ((data as { agent_name: string }).agent_name || \"\").trim()\n : \"\";\n emit(\"agent:handoff\", { agentName });\n return;\n }\n // Agent-driven step / submit / close. Must run before matchForm.\n if (handleFormAction(topic, data, forms)) return;\n\n const match = matchForm(topic, data, getAppearance().forms);\n if (!match) return;\n const draft = extractFormDraft(data, match) ?? {};\n if (forms.current() === match.id) forms.merge(draft);\n else forms.open(match, draft);\n });\n\n const trackEvent = (\n type: \"widget_loaded\" | \"bubble_clicked\",\n metadata: Record<string, unknown> = {},\n ) =>\n trackWidgetEvent(\n {\n apiUrl,\n agentSlug: config.agentSlug,\n apiKey: config.apiKey,\n fetch: config.fetch,\n disabled: config.disableAnalytics,\n },\n type,\n metadata,\n );\n\n let initialized = false;\n const init = async (): Promise<{ appearance: AppearanceConfig }> => {\n if (!initialized && (config.fetchAppearanceOnInit ?? true)) {\n appearance = await fetchAppearance({\n appearanceUrl: config.appearanceUrl ?? \"\",\n apiUrl,\n slug: config.agentSlug,\n apiKey: config.apiKey,\n fetch: config.fetch,\n });\n }\n initialized = true;\n emit(\"appearance\", appearance);\n return { appearance };\n };\n\n const destroy = () => {\n if (voice.isActive()) void voice.end();\n emitter.clear();\n };\n\n return {\n agentSlug: config.agentSlug,\n init,\n start: voice.start,\n end: voice.end,\n destroy,\n toggleMute: voice.toggleMute,\n isActive: voice.isActive,\n sessionId: voice.sessionId,\n sendText: async (text: string) => {\n const t = (text || \"\").trim();\n if (!t || !voice.isActive()) return;\n emit(\"transcript\", {\n role: \"user\",\n segmentId: `typed-${Date.now()}`,\n text: t,\n isFinal: true,\n });\n await voice.publishData({ type: \"user_text\", text: t }, \"voice.user_text\");\n },\n publishData: voice.publishData,\n updateAudioSettings: voice.updateAudioSettings,\n getAudioState: voice.getAudioState,\n getAudioStats: voice.getAudioStats,\n enumerateAudioDevices,\n getAudioCapabilities: probeAudioCapabilities,\n getActiveForm: forms.getActive,\n updateFormValues: forms.updateValues,\n stepForm: forms.step,\n submitForm: forms.submit,\n closeForm: forms.close,\n openForm: forms.open,\n getAppearance,\n setLanguage: (code: string) => {\n language = code || \"en\";\n },\n getLanguage: () => language,\n trackEvent,\n on: emitter.on.bind(emitter),\n off: emitter.off.bind(emitter),\n };\n}\n"],"names":["VISITOR_KEY","sentThisLoad","getVisitorId","existing","id","newId","trackWidgetEvent","cfg","eventType","metadata","url","payload","_a","headers","_b","doFetch","DEFAULT_AUDIO_PREFS","STORAGE_PREFIX","storageKey","agentSlug","loadAudioPrefs","raw","parsed","normalize","saveAudioPrefs","prefs","input","out","enumerateAudioDevices","devices","inputs","d","outputs","probeAudioCapabilities","audioEl","setSinkIdSupported","voiceIsolationSupported","supported","Emitter","__publicField","event","set","handler","err","firstStepWithError","definition","errorNames","steps","i","f","createFormController","opts","emit","voice","apiUrl","apiKey","fetchImpl","active","values","stepIndex","stateTimer","buildSnapshot","isOpen","totalSteps","buildFieldSchema","flushState","snapshot","scheduleStatePublish","emitUpdate","close","wasOpen","open","draft","mergeFormDraft","initialFormValues","merge","updateValues","next","goToStep","target","clamped","stepBack","attemptSubmit","isFinalStep","fieldsToCheck","collectInputFields","fieldsForStep","errors","validateFields","targetStep","e","submitForm","successMessage","buildSubmissionText","message","direction","buildHeaders","config","base","key","resolveFetch","warnIfBrowserSecret","fetchSession","args","originUrl","r","body","msg","data","DEFAULT_DEEPFILTER_MODULE_URL","MAX_STORED_CHARS","MAX_TURN_CHARS","MAX_SUMMARY_CHARS","STALE_AFTER_MS","safeLocalStorage","loadPrevContext","ls","obj","savedAt","clearPrevContext","turns","t","role","text","cleaned","summary","savePrevContext","recent","totalChars","n","dropped","trimmedSummary","record","formatPrevContextForAgent","createVoiceController","getAppearance","deepFilter","persistPrefs","deepFilterModuleUrl","deepFilterCdnUrl","deepFilterWasmUrl","deepFilterOnnxUrl","room","currentSessionId","muted","duckingMuted","duckTimerId","DUCK_DELAY_MS","persistPrefsToStorage","noiseFilterStatus","agentSpeaking","userSpeaking","agentThinking","lastAgentStatusLabel","thinkingClearTimer","THINKING_CLEAR_DELAY_MS","reconcileOrb","cancelThinkingClear","setThinking","label","clearThinking","deepFilterProcessor","callDeadline","callTickId","prevContextSent","unloadHandler","latestSummary","pendingSummaryResolver","persistTurns","recordTranscriptSegment","segmentId","isFinal","clearCallTimeout","reset","requestSessionSummary","waiter","resolve","timeout","result","end","skipSummary","publishPreviousContext","startCallTimeout","limit","remaining","start","init","Room","RoomEvent","state","ConnectionState","track","_pub","_p","Track","applySinkId","speakers","localId","s","applyDucking","segments","participant","seg","_participant","_kind","topic","decoded","_payload","changed","buildCaptureOptions","AudioPresets","applyNoiseFilter","emitAudioState","cancelDuckTimers","getLocalAudioTrack","toggleMute","publishData","engine","attachKrispNoiseFilter","Ctor","assetCfg","instance","wasmOverride","modelOverride","originalFetch","patched","snapshotAudioState","settings","delta","localTrack","constraintKeys","constraintDelta","k","restartErr","reports","pub","stats","report","entry","lvl","lost","j","rtt","LocalAudioTrack","el","deviceId","setSinkId","KrispNoiseFilter","isKrispNoiseFilterSupported","DEFAULT_API_URL","createVoiceAgent","persistAudioPrefs","language","emitter","initialPrefs","appearance","DEFAULT_APPEARANCE","forms","agentName","handleFormAction","match","matchForm","extractFormDraft","trackEvent","type","initialized","fetchAppearance","destroy","code"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./createVoiceAgent-CJWxWzz6.cjs"),t=require("./appearance-CNWT8x1G.cjs"),r=require("./consent-D7QNSkQD.cjs");exports.DEFAULT_AUDIO_PREFS=e.DEFAULT_AUDIO_PREFS;exports.DEFAULT_DEEPFILTER_MODULE_URL=e.DEFAULT_DEEPFILTER_MODULE_URL;exports.Emitter=e.Emitter;exports.buildHeaders=e.buildHeaders;exports.clearPrevContext=e.clearPrevContext;exports.createFormController=e.createFormController;exports.createVoiceAgent=e.createVoiceAgent;exports.createVoiceController=e.createVoiceController;exports.enumerateAudioDevices=e.enumerateAudioDevices;exports.fetchSession=e.fetchSession;exports.formatPrevContextForAgent=e.formatPrevContextForAgent;exports.getVisitorId=e.getVisitorId;exports.loadAudioPrefs=e.loadAudioPrefs;exports.loadPrevContext=e.loadPrevContext;exports.probeAudioCapabilities=e.probeAudioCapabilities;exports.saveAudioPrefs=e.saveAudioPrefs;exports.savePrevContext=e.savePrevContext;exports.trackWidgetEvent=e.trackWidgetEvent;exports.warnIfBrowserSecret=e.warnIfBrowserSecret;exports.DEFAULT_APPEARANCE=t.DEFAULT_APPEARANCE;exports.DEFAULT_FORM_DEFINITIONS=t.DEFAULT_FORM_DEFINITIONS;exports.buildFieldSchema=t.buildFieldSchema;exports.collectInputFields=t.collectInputFields;exports.extractFormDraft=t.extractFormDraft;exports.fetchAppearance=t.fetchAppearance;exports.fieldsForStep=t.fieldsForStep;exports.handleFormAction=t.handleFormAction;exports.initialFormValues=t.initialFormValues;exports.matchForm=t.matchForm;exports.mergeAppearance=t.mergeAppearance;exports.mergeFormDraft=t.mergeFormDraft;exports.normalizeFormDefinitions=t.normalizeFormDefinitions;exports.submitForm=t.submitForm;exports.totalSteps=t.totalSteps;exports.validateFields=t.validateFields;exports.hasAcceptedTerms=r.hasAcceptedTerms;exports.loadConsent=r.loadConsent;exports.saveConsent=r.saveConsent;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { D as s, a as t, E as r, b as o, c as i, d as n, e as l, f as c, g as m, h as d, i as F, j as A, l as f, k as E, p, s as D, m as v, t as u, w as C } from "./createVoiceAgent-BM3HODS6.js";
|
|
2
|
+
import { D as h, a as g, b as x, c as I, e as _, f as b, d as L, h as S, i as T, m as U, g as R, j as O, n as V, s as w, t as N, v as j } from "./appearance-i6QBkpCk.js";
|
|
3
|
+
import { h as M, l as z, s as B } from "./consent-CK9VXNPa.js";
|
|
4
|
+
export {
|
|
5
|
+
h as DEFAULT_APPEARANCE,
|
|
6
|
+
s as DEFAULT_AUDIO_PREFS,
|
|
7
|
+
t as DEFAULT_DEEPFILTER_MODULE_URL,
|
|
8
|
+
g as DEFAULT_FORM_DEFINITIONS,
|
|
9
|
+
r as Emitter,
|
|
10
|
+
x as buildFieldSchema,
|
|
11
|
+
o as buildHeaders,
|
|
12
|
+
i as clearPrevContext,
|
|
13
|
+
I as collectInputFields,
|
|
14
|
+
n as createFormController,
|
|
15
|
+
l as createVoiceAgent,
|
|
16
|
+
c as createVoiceController,
|
|
17
|
+
m as enumerateAudioDevices,
|
|
18
|
+
_ as extractFormDraft,
|
|
19
|
+
b as fetchAppearance,
|
|
20
|
+
d as fetchSession,
|
|
21
|
+
L as fieldsForStep,
|
|
22
|
+
F as formatPrevContextForAgent,
|
|
23
|
+
A as getVisitorId,
|
|
24
|
+
S as handleFormAction,
|
|
25
|
+
M as hasAcceptedTerms,
|
|
26
|
+
T as initialFormValues,
|
|
27
|
+
f as loadAudioPrefs,
|
|
28
|
+
z as loadConsent,
|
|
29
|
+
E as loadPrevContext,
|
|
30
|
+
U as matchForm,
|
|
31
|
+
R as mergeAppearance,
|
|
32
|
+
O as mergeFormDraft,
|
|
33
|
+
V as normalizeFormDefinitions,
|
|
34
|
+
p as probeAudioCapabilities,
|
|
35
|
+
D as saveAudioPrefs,
|
|
36
|
+
B as saveConsent,
|
|
37
|
+
v as savePrevContext,
|
|
38
|
+
w as submitForm,
|
|
39
|
+
N as totalSteps,
|
|
40
|
+
u as trackWidgetEvent,
|
|
41
|
+
j as validateFields,
|
|
42
|
+
C as warnIfBrowserSecret
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AppearanceConfig, AudioStateSnapshot, FieldValidationError, FormDefinition, OrbState, VoiceAgentClient, VoiceAgentConfig } from '../core';
|
|
2
|
+
export interface TranscriptItem {
|
|
3
|
+
key: string;
|
|
4
|
+
role: "user" | "agent";
|
|
5
|
+
text: string;
|
|
6
|
+
isFinal: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface ActiveForm {
|
|
9
|
+
definition: FormDefinition;
|
|
10
|
+
values: Record<string, string>;
|
|
11
|
+
stepIndex: number;
|
|
12
|
+
}
|
|
13
|
+
export interface UseVoiceAgentResult {
|
|
14
|
+
client: VoiceAgentClient;
|
|
15
|
+
ready: boolean;
|
|
16
|
+
orb: OrbState;
|
|
17
|
+
statusLabel: string | null;
|
|
18
|
+
callStatus: string;
|
|
19
|
+
connected: boolean;
|
|
20
|
+
muted: boolean;
|
|
21
|
+
appearance: AppearanceConfig;
|
|
22
|
+
audio: AudioStateSnapshot | null;
|
|
23
|
+
transcript: TranscriptItem[];
|
|
24
|
+
activeForm: ActiveForm | null;
|
|
25
|
+
formErrors: FieldValidationError[];
|
|
26
|
+
formSubmitting: boolean;
|
|
27
|
+
start: () => Promise<void>;
|
|
28
|
+
end: () => Promise<void>;
|
|
29
|
+
toggleMute: () => Promise<boolean>;
|
|
30
|
+
sendText: (text: string) => Promise<void>;
|
|
31
|
+
updateFormValues: (values: Record<string, string>) => void;
|
|
32
|
+
submitForm: () => void;
|
|
33
|
+
stepForm: (direction: "next" | "back" | number) => void;
|
|
34
|
+
closeForm: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create and manage a voice-agent client for the lifetime of the component.
|
|
38
|
+
* The `config` is read once on mount (changing it later does not recreate the
|
|
39
|
+
* client — remount the component, e.g. with a `key`, to switch agents).
|
|
40
|
+
*/
|
|
41
|
+
export declare function useVoiceAgent(config: VoiceAgentConfig): UseVoiceAgentResult;
|
|
42
|
+
export interface VoiceWidgetProps {
|
|
43
|
+
config: VoiceAgentConfig;
|
|
44
|
+
/** Forwarded to mountVoiceUI. */
|
|
45
|
+
inline?: boolean;
|
|
46
|
+
openChat?: boolean;
|
|
47
|
+
closeButtonHide?: boolean;
|
|
48
|
+
className?: string;
|
|
49
|
+
style?: Record<string, string | number>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Drop-in prebuilt widget for React apps. Creates a client, mounts the prebuilt
|
|
53
|
+
* UI into a container div, and tears both down on unmount.
|
|
54
|
+
*/
|
|
55
|
+
export declare function VoiceWidget(props: VoiceWidgetProps): import('react').DetailedReactHTMLElement<{
|
|
56
|
+
ref: import('react').MutableRefObject<HTMLDivElement | null>;
|
|
57
|
+
className: string | undefined;
|
|
58
|
+
style: Record<string, string | number> | undefined;
|
|
59
|
+
}, HTMLDivElement>;
|
|
60
|
+
export type { VoiceAgentConfig, VoiceAgentClient } from '../core';
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),h=require("./createVoiceAgent-CJWxWzz6.cjs");function w(o){const u=n.useRef(o),e=n.useMemo(()=>h.createVoiceAgent(u.current),[]),[r,c]=n.useState(!1),[s,l]=n.useState("idle"),[f,p]=n.useState(null),[x,E]=n.useState(""),[M,S]=n.useState(!1),[R,C]=n.useState(!1),[T,k]=n.useState(e.getAppearance()),[q,L]=n.useState(null),[B,b]=n.useState([]),[H,d]=n.useState(null),[N,a]=n.useState([]),[O,i]=n.useState(!1);return n.useEffect(()=>{const P=[e.on("appearance",k),e.on("state",({orb:t,statusLabel:m})=>{l(t),p(t==="thinking"?m:null)}),e.on("call:status",({status:t})=>E(t)),e.on("connection",({phase:t})=>{S(t==="connected"||t==="connecting"),(t==="disconnected"||t==="failed")&&S(!1)}),e.on("mute",({muted:t})=>C(t)),e.on("audio",t=>L(t)),e.on("transcript:clear",()=>b([])),e.on("transcript",({role:t,segmentId:m,text:W,isFinal:$})=>{const F=`${t}:${m}`;b(g=>{const A=g.findIndex(j=>j.key===F),v={key:F,role:t,text:W,isFinal:$};if(A===-1)return[...g,v];const V=g.slice();return V[A]=v,V})}),e.on("form:show",()=>{a([]),d(e.getActiveForm())}),e.on("form:update",()=>d(e.getActiveForm())),e.on("form:validation",({errors:t})=>a(t)),e.on("form:submitting",()=>{i(!0),a([])}),e.on("form:submitted",()=>i(!1)),e.on("form:error",()=>i(!1)),e.on("form:close",()=>{d(null),a([]),i(!1)})];let y=!1;return e.init().then(()=>{y||c(!0)}),()=>{y=!0;for(const t of P)t();e.destroy()}},[e]),{client:e,ready:r,orb:s,statusLabel:f,callStatus:x,connected:M,muted:R,appearance:T,audio:q,transcript:B,activeForm:H,formErrors:N,formSubmitting:O,start:e.start,end:e.end,toggleMute:e.toggleMute,sendText:e.sendText,updateFormValues:e.updateFormValues,submitForm:e.submitForm,stepForm:e.stepForm,closeForm:e.closeForm}}function I(o){const u=n.useRef(null);return n.useEffect(()=>{const e=u.current;if(!e)return;let r=!1,c=null;const s=h.createVoiceAgent(o.config);return s.init().then(()=>{if(r){s.destroy();return}Promise.resolve().then(()=>require("./ui.cjs")).then(({mountVoiceUI:l})=>{if(r){s.destroy();return}const f=l(s,{target:e,inline:o.inline??!0,openChat:o.openChat,closeButtonHide:o.closeButtonHide});c=()=>{f.destroy(),s.destroy()}})}),()=>{r=!0,c&&c()}},[]),n.createElement("div",{ref:u,className:o.className,style:o.style})}exports.VoiceWidget=I;exports.useVoiceAgent=w;
|
|
2
|
+
//# sourceMappingURL=react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.cjs","sources":["../src/react/index.ts"],"sourcesContent":["/**\n * @oshara/voice-sdk/react — thin React bindings over the headless core.\n *\n * - `useVoiceAgent(config)` creates and owns a client, subscribes to its\n * events, and returns reactive state + bound actions.\n * - `<VoiceWidget config />` mounts the prebuilt UI into a div.\n *\n * `react`/`react-dom` are optional peer dependencies; importing this entry is\n * what pulls them in.\n */\n\nimport {\n createElement,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n createVoiceAgent,\n type AppearanceConfig,\n type AudioStateSnapshot,\n type FieldValidationError,\n type FormDefinition,\n type OrbState,\n type VoiceAgentClient,\n type VoiceAgentConfig,\n} from \"../core\";\n\nexport interface TranscriptItem {\n key: string;\n role: \"user\" | \"agent\";\n text: string;\n isFinal: boolean;\n}\n\nexport interface ActiveForm {\n definition: FormDefinition;\n values: Record<string, string>;\n stepIndex: number;\n}\n\nexport interface UseVoiceAgentResult {\n client: VoiceAgentClient;\n ready: boolean;\n orb: OrbState;\n statusLabel: string | null;\n callStatus: string;\n connected: boolean;\n muted: boolean;\n appearance: AppearanceConfig;\n audio: AudioStateSnapshot | null;\n transcript: TranscriptItem[];\n activeForm: ActiveForm | null;\n formErrors: FieldValidationError[];\n formSubmitting: boolean;\n // actions\n start: () => Promise<void>;\n end: () => Promise<void>;\n toggleMute: () => Promise<boolean>;\n sendText: (text: string) => Promise<void>;\n updateFormValues: (values: Record<string, string>) => void;\n submitForm: () => void;\n stepForm: (direction: \"next\" | \"back\" | number) => void;\n closeForm: () => void;\n}\n\n/**\n * Create and manage a voice-agent client for the lifetime of the component.\n * The `config` is read once on mount (changing it later does not recreate the\n * client — remount the component, e.g. with a `key`, to switch agents).\n */\nexport function useVoiceAgent(config: VoiceAgentConfig): UseVoiceAgentResult {\n // Freeze the initial config so the client is created exactly once.\n const configRef = useRef(config);\n const client = useMemo(() => createVoiceAgent(configRef.current), []);\n\n const [ready, setReady] = useState(false);\n const [orb, setOrb] = useState<OrbState>(\"idle\");\n const [statusLabel, setStatusLabel] = useState<string | null>(null);\n const [callStatus, setCallStatus] = useState(\"\");\n const [connected, setConnected] = useState(false);\n const [muted, setMuted] = useState(false);\n const [appearance, setAppearance] = useState<AppearanceConfig>(\n client.getAppearance(),\n );\n const [audio, setAudio] = useState<AudioStateSnapshot | null>(null);\n const [transcript, setTranscript] = useState<TranscriptItem[]>([]);\n const [activeForm, setActiveForm] = useState<ActiveForm | null>(null);\n const [formErrors, setFormErrors] = useState<FieldValidationError[]>([]);\n const [formSubmitting, setFormSubmitting] = useState(false);\n\n useEffect(() => {\n const offs = [\n client.on(\"appearance\", setAppearance),\n client.on(\"state\", ({ orb: o, statusLabel: s }) => {\n setOrb(o);\n setStatusLabel(o === \"thinking\" ? s : null);\n }),\n client.on(\"call:status\", ({ status }) => setCallStatus(status)),\n client.on(\"connection\", ({ phase }) => {\n setConnected(phase === \"connected\" || phase === \"connecting\");\n if (phase === \"disconnected\" || phase === \"failed\") setConnected(false);\n }),\n client.on(\"mute\", ({ muted: m }) => setMuted(m)),\n client.on(\"audio\", (a) => setAudio(a)),\n client.on(\"transcript:clear\", () => setTranscript([])),\n client.on(\"transcript\", ({ role, segmentId, text, isFinal }) => {\n const key = `${role}:${segmentId}`;\n setTranscript((prev) => {\n const idx = prev.findIndex((t) => t.key === key);\n const item: TranscriptItem = { key, role, text, isFinal };\n if (idx === -1) return [...prev, item];\n const next = prev.slice();\n next[idx] = item;\n return next;\n });\n }),\n client.on(\"form:show\", () => {\n setFormErrors([]);\n setActiveForm(client.getActiveForm());\n }),\n client.on(\"form:update\", () => setActiveForm(client.getActiveForm())),\n client.on(\"form:validation\", ({ errors }) => setFormErrors(errors)),\n client.on(\"form:submitting\", () => {\n setFormSubmitting(true);\n setFormErrors([]);\n }),\n client.on(\"form:submitted\", () => setFormSubmitting(false)),\n client.on(\"form:error\", () => setFormSubmitting(false)),\n client.on(\"form:close\", () => {\n setActiveForm(null);\n setFormErrors([]);\n setFormSubmitting(false);\n }),\n ];\n\n let cancelled = false;\n void client.init().then(() => {\n if (!cancelled) setReady(true);\n });\n\n return () => {\n cancelled = true;\n for (const off of offs) off();\n client.destroy();\n };\n }, [client]);\n\n return {\n client,\n ready,\n orb,\n statusLabel,\n callStatus,\n connected,\n muted,\n appearance,\n audio,\n transcript,\n activeForm,\n formErrors,\n formSubmitting,\n start: client.start,\n end: client.end,\n toggleMute: client.toggleMute,\n sendText: client.sendText,\n updateFormValues: client.updateFormValues,\n submitForm: client.submitForm,\n stepForm: client.stepForm,\n closeForm: client.closeForm,\n };\n}\n\nexport interface VoiceWidgetProps {\n config: VoiceAgentConfig;\n /** Forwarded to mountVoiceUI. */\n inline?: boolean;\n openChat?: boolean;\n closeButtonHide?: boolean;\n className?: string;\n style?: Record<string, string | number>;\n}\n\n/**\n * Drop-in prebuilt widget for React apps. Creates a client, mounts the prebuilt\n * UI into a container div, and tears both down on unmount.\n */\nexport function VoiceWidget(props: VoiceWidgetProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n let destroyed = false;\n let cleanup: (() => void) | null = null;\n\n const client = createVoiceAgent(props.config);\n void client.init().then(() => {\n if (destroyed) {\n client.destroy();\n return;\n }\n // Dynamically import the UI layer so a headless-only React app that never\n // renders <VoiceWidget> doesn't pull the DOM UI into its bundle.\n void import(\"../ui\").then(({ mountVoiceUI }) => {\n if (destroyed) {\n client.destroy();\n return;\n }\n const ui = mountVoiceUI(client, {\n target: host,\n inline: props.inline ?? true,\n openChat: props.openChat,\n closeButtonHide: props.closeButtonHide,\n });\n cleanup = () => {\n ui.destroy();\n client.destroy();\n };\n });\n });\n\n return () => {\n destroyed = true;\n if (cleanup) cleanup();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return createElement(\"div\", {\n ref: hostRef,\n className: props.className,\n style: props.style,\n });\n}\n\nexport type { VoiceAgentConfig, VoiceAgentClient } from \"../core\";\n"],"names":["useVoiceAgent","config","configRef","useRef","client","useMemo","createVoiceAgent","ready","setReady","useState","orb","setOrb","statusLabel","setStatusLabel","callStatus","setCallStatus","connected","setConnected","muted","setMuted","appearance","setAppearance","audio","setAudio","transcript","setTranscript","activeForm","setActiveForm","formErrors","setFormErrors","formSubmitting","setFormSubmitting","useEffect","offs","o","s","status","phase","m","a","role","segmentId","text","isFinal","key","prev","idx","t","item","next","errors","cancelled","off","VoiceWidget","props","hostRef","host","destroyed","cleanup","mountVoiceUI","ui","createElement"],"mappings":"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"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useRef as x, useEffect as S, createElement as D, useMemo as G, useState as n } from "react";
|
|
2
|
+
import { e as V } from "./createVoiceAgent-BM3HODS6.js";
|
|
3
|
+
function P(o) {
|
|
4
|
+
const i = x(o), t = G(() => V(i.current), []), [r, c] = n(!1), [s, u] = n("idle"), [f, C] = n(null), [E, M] = n(""), [R, F] = n(!1), [k, L] = n(!1), [T, B] = n(
|
|
5
|
+
t.getAppearance()
|
|
6
|
+
), [H, N] = n(null), [$, b] = n([]), [w, d] = n(null), [I, a] = n([]), [O, l] = n(!1);
|
|
7
|
+
return S(() => {
|
|
8
|
+
const W = [
|
|
9
|
+
t.on("appearance", B),
|
|
10
|
+
t.on("state", ({ orb: e, statusLabel: m }) => {
|
|
11
|
+
u(e), C(e === "thinking" ? m : null);
|
|
12
|
+
}),
|
|
13
|
+
t.on("call:status", ({ status: e }) => M(e)),
|
|
14
|
+
t.on("connection", ({ phase: e }) => {
|
|
15
|
+
F(e === "connected" || e === "connecting"), (e === "disconnected" || e === "failed") && F(!1);
|
|
16
|
+
}),
|
|
17
|
+
t.on("mute", ({ muted: e }) => L(e)),
|
|
18
|
+
t.on("audio", (e) => N(e)),
|
|
19
|
+
t.on("transcript:clear", () => b([])),
|
|
20
|
+
t.on("transcript", ({ role: e, segmentId: m, text: j, isFinal: q }) => {
|
|
21
|
+
const p = `${e}:${m}`;
|
|
22
|
+
b((g) => {
|
|
23
|
+
const A = g.findIndex((z) => z.key === p), v = { key: p, role: e, text: j, isFinal: q };
|
|
24
|
+
if (A === -1) return [...g, v];
|
|
25
|
+
const h = g.slice();
|
|
26
|
+
return h[A] = v, h;
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
t.on("form:show", () => {
|
|
30
|
+
a([]), d(t.getActiveForm());
|
|
31
|
+
}),
|
|
32
|
+
t.on("form:update", () => d(t.getActiveForm())),
|
|
33
|
+
t.on("form:validation", ({ errors: e }) => a(e)),
|
|
34
|
+
t.on("form:submitting", () => {
|
|
35
|
+
l(!0), a([]);
|
|
36
|
+
}),
|
|
37
|
+
t.on("form:submitted", () => l(!1)),
|
|
38
|
+
t.on("form:error", () => l(!1)),
|
|
39
|
+
t.on("form:close", () => {
|
|
40
|
+
d(null), a([]), l(!1);
|
|
41
|
+
})
|
|
42
|
+
];
|
|
43
|
+
let y = !1;
|
|
44
|
+
return t.init().then(() => {
|
|
45
|
+
y || c(!0);
|
|
46
|
+
}), () => {
|
|
47
|
+
y = !0;
|
|
48
|
+
for (const e of W) e();
|
|
49
|
+
t.destroy();
|
|
50
|
+
};
|
|
51
|
+
}, [t]), {
|
|
52
|
+
client: t,
|
|
53
|
+
ready: r,
|
|
54
|
+
orb: s,
|
|
55
|
+
statusLabel: f,
|
|
56
|
+
callStatus: E,
|
|
57
|
+
connected: R,
|
|
58
|
+
muted: k,
|
|
59
|
+
appearance: T,
|
|
60
|
+
audio: H,
|
|
61
|
+
transcript: $,
|
|
62
|
+
activeForm: w,
|
|
63
|
+
formErrors: I,
|
|
64
|
+
formSubmitting: O,
|
|
65
|
+
start: t.start,
|
|
66
|
+
end: t.end,
|
|
67
|
+
toggleMute: t.toggleMute,
|
|
68
|
+
sendText: t.sendText,
|
|
69
|
+
updateFormValues: t.updateFormValues,
|
|
70
|
+
submitForm: t.submitForm,
|
|
71
|
+
stepForm: t.stepForm,
|
|
72
|
+
closeForm: t.closeForm
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function Q(o) {
|
|
76
|
+
const i = x(null);
|
|
77
|
+
return S(() => {
|
|
78
|
+
const t = i.current;
|
|
79
|
+
if (!t) return;
|
|
80
|
+
let r = !1, c = null;
|
|
81
|
+
const s = V(o.config);
|
|
82
|
+
return s.init().then(() => {
|
|
83
|
+
if (r) {
|
|
84
|
+
s.destroy();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
import("./ui.js").then(({ mountVoiceUI: u }) => {
|
|
88
|
+
if (r) {
|
|
89
|
+
s.destroy();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const f = u(s, {
|
|
93
|
+
target: t,
|
|
94
|
+
inline: o.inline ?? !0,
|
|
95
|
+
openChat: o.openChat,
|
|
96
|
+
closeButtonHide: o.closeButtonHide
|
|
97
|
+
});
|
|
98
|
+
c = () => {
|
|
99
|
+
f.destroy(), s.destroy();
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}), () => {
|
|
103
|
+
r = !0, c && c();
|
|
104
|
+
};
|
|
105
|
+
}, []), D("div", {
|
|
106
|
+
ref: i,
|
|
107
|
+
className: o.className,
|
|
108
|
+
style: o.style
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
Q as VoiceWidget,
|
|
113
|
+
P as useVoiceAgent
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","sources":["../src/react/index.ts"],"sourcesContent":["/**\n * @oshara/voice-sdk/react — thin React bindings over the headless core.\n *\n * - `useVoiceAgent(config)` creates and owns a client, subscribes to its\n * events, and returns reactive state + bound actions.\n * - `<VoiceWidget config />` mounts the prebuilt UI into a div.\n *\n * `react`/`react-dom` are optional peer dependencies; importing this entry is\n * what pulls them in.\n */\n\nimport {\n createElement,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport {\n createVoiceAgent,\n type AppearanceConfig,\n type AudioStateSnapshot,\n type FieldValidationError,\n type FormDefinition,\n type OrbState,\n type VoiceAgentClient,\n type VoiceAgentConfig,\n} from \"../core\";\n\nexport interface TranscriptItem {\n key: string;\n role: \"user\" | \"agent\";\n text: string;\n isFinal: boolean;\n}\n\nexport interface ActiveForm {\n definition: FormDefinition;\n values: Record<string, string>;\n stepIndex: number;\n}\n\nexport interface UseVoiceAgentResult {\n client: VoiceAgentClient;\n ready: boolean;\n orb: OrbState;\n statusLabel: string | null;\n callStatus: string;\n connected: boolean;\n muted: boolean;\n appearance: AppearanceConfig;\n audio: AudioStateSnapshot | null;\n transcript: TranscriptItem[];\n activeForm: ActiveForm | null;\n formErrors: FieldValidationError[];\n formSubmitting: boolean;\n // actions\n start: () => Promise<void>;\n end: () => Promise<void>;\n toggleMute: () => Promise<boolean>;\n sendText: (text: string) => Promise<void>;\n updateFormValues: (values: Record<string, string>) => void;\n submitForm: () => void;\n stepForm: (direction: \"next\" | \"back\" | number) => void;\n closeForm: () => void;\n}\n\n/**\n * Create and manage a voice-agent client for the lifetime of the component.\n * The `config` is read once on mount (changing it later does not recreate the\n * client — remount the component, e.g. with a `key`, to switch agents).\n */\nexport function useVoiceAgent(config: VoiceAgentConfig): UseVoiceAgentResult {\n // Freeze the initial config so the client is created exactly once.\n const configRef = useRef(config);\n const client = useMemo(() => createVoiceAgent(configRef.current), []);\n\n const [ready, setReady] = useState(false);\n const [orb, setOrb] = useState<OrbState>(\"idle\");\n const [statusLabel, setStatusLabel] = useState<string | null>(null);\n const [callStatus, setCallStatus] = useState(\"\");\n const [connected, setConnected] = useState(false);\n const [muted, setMuted] = useState(false);\n const [appearance, setAppearance] = useState<AppearanceConfig>(\n client.getAppearance(),\n );\n const [audio, setAudio] = useState<AudioStateSnapshot | null>(null);\n const [transcript, setTranscript] = useState<TranscriptItem[]>([]);\n const [activeForm, setActiveForm] = useState<ActiveForm | null>(null);\n const [formErrors, setFormErrors] = useState<FieldValidationError[]>([]);\n const [formSubmitting, setFormSubmitting] = useState(false);\n\n useEffect(() => {\n const offs = [\n client.on(\"appearance\", setAppearance),\n client.on(\"state\", ({ orb: o, statusLabel: s }) => {\n setOrb(o);\n setStatusLabel(o === \"thinking\" ? s : null);\n }),\n client.on(\"call:status\", ({ status }) => setCallStatus(status)),\n client.on(\"connection\", ({ phase }) => {\n setConnected(phase === \"connected\" || phase === \"connecting\");\n if (phase === \"disconnected\" || phase === \"failed\") setConnected(false);\n }),\n client.on(\"mute\", ({ muted: m }) => setMuted(m)),\n client.on(\"audio\", (a) => setAudio(a)),\n client.on(\"transcript:clear\", () => setTranscript([])),\n client.on(\"transcript\", ({ role, segmentId, text, isFinal }) => {\n const key = `${role}:${segmentId}`;\n setTranscript((prev) => {\n const idx = prev.findIndex((t) => t.key === key);\n const item: TranscriptItem = { key, role, text, isFinal };\n if (idx === -1) return [...prev, item];\n const next = prev.slice();\n next[idx] = item;\n return next;\n });\n }),\n client.on(\"form:show\", () => {\n setFormErrors([]);\n setActiveForm(client.getActiveForm());\n }),\n client.on(\"form:update\", () => setActiveForm(client.getActiveForm())),\n client.on(\"form:validation\", ({ errors }) => setFormErrors(errors)),\n client.on(\"form:submitting\", () => {\n setFormSubmitting(true);\n setFormErrors([]);\n }),\n client.on(\"form:submitted\", () => setFormSubmitting(false)),\n client.on(\"form:error\", () => setFormSubmitting(false)),\n client.on(\"form:close\", () => {\n setActiveForm(null);\n setFormErrors([]);\n setFormSubmitting(false);\n }),\n ];\n\n let cancelled = false;\n void client.init().then(() => {\n if (!cancelled) setReady(true);\n });\n\n return () => {\n cancelled = true;\n for (const off of offs) off();\n client.destroy();\n };\n }, [client]);\n\n return {\n client,\n ready,\n orb,\n statusLabel,\n callStatus,\n connected,\n muted,\n appearance,\n audio,\n transcript,\n activeForm,\n formErrors,\n formSubmitting,\n start: client.start,\n end: client.end,\n toggleMute: client.toggleMute,\n sendText: client.sendText,\n updateFormValues: client.updateFormValues,\n submitForm: client.submitForm,\n stepForm: client.stepForm,\n closeForm: client.closeForm,\n };\n}\n\nexport interface VoiceWidgetProps {\n config: VoiceAgentConfig;\n /** Forwarded to mountVoiceUI. */\n inline?: boolean;\n openChat?: boolean;\n closeButtonHide?: boolean;\n className?: string;\n style?: Record<string, string | number>;\n}\n\n/**\n * Drop-in prebuilt widget for React apps. Creates a client, mounts the prebuilt\n * UI into a container div, and tears both down on unmount.\n */\nexport function VoiceWidget(props: VoiceWidgetProps) {\n const hostRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n let destroyed = false;\n let cleanup: (() => void) | null = null;\n\n const client = createVoiceAgent(props.config);\n void client.init().then(() => {\n if (destroyed) {\n client.destroy();\n return;\n }\n // Dynamically import the UI layer so a headless-only React app that never\n // renders <VoiceWidget> doesn't pull the DOM UI into its bundle.\n void import(\"../ui\").then(({ mountVoiceUI }) => {\n if (destroyed) {\n client.destroy();\n return;\n }\n const ui = mountVoiceUI(client, {\n target: host,\n inline: props.inline ?? true,\n openChat: props.openChat,\n closeButtonHide: props.closeButtonHide,\n });\n cleanup = () => {\n ui.destroy();\n client.destroy();\n };\n });\n });\n\n return () => {\n destroyed = true;\n if (cleanup) cleanup();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return createElement(\"div\", {\n ref: hostRef,\n className: props.className,\n style: props.style,\n });\n}\n\nexport type { VoiceAgentConfig, VoiceAgentClient } from \"../core\";\n"],"names":["useVoiceAgent","config","configRef","useRef","client","useMemo","createVoiceAgent","ready","setReady","useState","orb","setOrb","statusLabel","setStatusLabel","callStatus","setCallStatus","connected","setConnected","muted","setMuted","appearance","setAppearance","audio","setAudio","transcript","setTranscript","activeForm","setActiveForm","formErrors","setFormErrors","formSubmitting","setFormSubmitting","useEffect","offs","o","s","status","phase","m","a","role","segmentId","text","isFinal","key","prev","idx","t","item","next","errors","cancelled","off","VoiceWidget","props","hostRef","host","destroyed","cleanup","mountVoiceUI","ui","createElement"],"mappings":"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"}
|