{"version":3,"sources":["../src/core/auth.ts","../src/core/api.ts","../src/core/sse.ts","../src/core/chat-controller.ts","../src/element/styles.ts","../src/element/markdown.ts","../src/core/voice-driver.ts","../src/element/HwChat.ts"],"sourcesContent":["/**\n * Auth token plumbing: caches the latest HMAC signature and refreshes it\n * lazily before the 5-minute HMAC window expires. Works with either a\n * direct callback (programmatic) or a URL (declarative) — customer picks\n * whichever fits their stack.\n */\nimport type { AuthProvider, AuthToken } from './types.js';\n\nconst SAFETY_MARGIN_SECONDS = 60;\n\nexport class AuthManager {\n  /** Cached token keyed by sessionId — signatures bind to a single session. */\n  private token: { sessionId: string; value: AuthToken } | null = null;\n  private inflight: Promise<AuthToken | null> | null = null;\n  /** Bumped whenever provider/endpoint change so an in-flight fetch from the\n   *  previous config can't poison the cache after it resolves. */\n  private generation = 0;\n\n  constructor(\n    private provider: AuthProvider | null,\n    private endpoint: string | null,\n  ) {}\n\n  /**\n   * Swap the provider after construction. Required by the React wrapper:\n   * `connectedCallback` fires before React's `useEffect`, so the element\n   * boots with `authProvider: null` and we have to inject the provider\n   * once it's available. Invalidates the cached token + in-flight fetch.\n   */\n  setProvider(provider: AuthProvider | null): void {\n    this.provider = provider;\n    this.token = null;\n    this.inflight = null;\n    this.generation++;\n  }\n\n  /** Same idea for the URL-based path — kept symmetric so element-level\n   *  attribute updates propagate too. */\n  setEndpoint(endpoint: string | null): void {\n    this.endpoint = endpoint;\n    this.token = null;\n    this.inflight = null;\n    this.generation++;\n  }\n\n  /**\n   * Returns a valid token or null if auth isn't configured (public mode).\n   * Never throws — a failure to fetch a signature should surface to the\n   * caller as \"no token\" and let the ingress return 401 with a clear\n   * reason rather than crashing the widget.\n   */\n  async get(sessionId: string): Promise<AuthToken | null> {\n    if (!this.provider && !this.endpoint) return null;\n    if (!sessionId) return null;\n\n    const now = Math.floor(Date.now() / 1000);\n    if (\n      this.token &&\n      this.token.sessionId === sessionId &&\n      this.token.value.ts + 300 > now + SAFETY_MARGIN_SECONDS\n    ) {\n      return this.token.value;\n    }\n\n    if (this.inflight) return this.inflight;\n\n    const myGen = this.generation;\n    this.inflight = this.fetchFresh(sessionId).then((tok) => {\n      // Drop the result if setProvider/setEndpoint was called mid-fetch —\n      // otherwise the cache would carry a token derived from the old config.\n      if (myGen !== this.generation) return tok;\n      this.token = tok ? { sessionId, value: tok } : null;\n      this.inflight = null;\n      return tok;\n    }).catch(() => {\n      if (myGen === this.generation) this.inflight = null;\n      return null;\n    });\n\n    return this.inflight;\n  }\n\n  private async fetchFresh(sessionId: string): Promise<AuthToken | null> {\n    if (this.provider) {\n      const result = await this.provider({ sessionId });\n      return normalizeToken(result);\n    }\n    if (this.endpoint) {\n      const res = await fetch(this.endpoint, {\n        method: 'POST',\n        credentials: 'include',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({ sessionId }),\n      });\n      if (!res.ok) return null;\n      const raw = await res.json();\n      return normalizeToken(raw);\n    }\n    return null;\n  }\n}\n\nfunction normalizeToken(raw: unknown): AuthToken | null {\n  if (!raw || typeof raw !== 'object') return null;\n  const obj = raw as Record<string, unknown>;\n  const sig = typeof obj.sig === 'string' ? obj.sig : null;\n  const ts = typeof obj.ts === 'number' ? obj.ts : typeof obj.ts === 'string' ? parseInt(obj.ts, 10) : null;\n  if (!sig || !ts) return null;\n  return { sig, ts };\n}\n","/**\n * Thin fetch wrappers around the three public endpoints the widget talks to.\n * Keeps HTTP plumbing out of the UI rendering code so both the Web Component\n * and the React wrapper can reuse it identically.\n */\nimport type { AuthManager } from './auth.js';\nimport type { ChatConfig } from './types.js';\n\nexport async function fetchConfig(apiBase: string, chatId: string): Promise<ChatConfig> {\n  const res = await fetch(`${apiBase}/api/chat-triggers/public/${chatId}`);\n  if (!res.ok) throw new Error(`Chat config fetch failed (HTTP ${res.status})`);\n  return (await res.json()) as ChatConfig;\n}\n\nexport async function requestUploadUrl(\n  apiBase: string,\n  chatId: string,\n  body: { fileName: string; mimeType: string; sessionId: string; size: number },\n): Promise<{ fileId: string; uploadUrl: string; key: string }> {\n  const res = await fetch(`${apiBase}/api/chat-triggers/in/${chatId}/upload-url`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(body),\n  });\n  if (!res.ok) {\n    const err = await res.json().catch(() => null);\n    throw new Error((err as { error?: string } | null)?.error ?? `upload-url ${res.status}`);\n  }\n  return res.json();\n}\n\nexport async function confirmUpload(\n  apiBase: string,\n  chatId: string,\n  fileId: string,\n): Promise<{ scanStatus: string }> {\n  const res = await fetch(`${apiBase}/api/chat-triggers/in/${chatId}/confirm-upload`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ fileId }),\n  });\n  if (!res.ok) {\n    const err = await res.json().catch(() => null);\n    throw new Error((err as { error?: string } | null)?.error ?? `confirm ${res.status}`);\n  }\n  return res.json();\n}\n\nexport async function pollFileStatus(\n  apiBase: string,\n  chatId: string,\n  fileId: string,\n): Promise<{ scanStatus: string; scanMessage?: string }> {\n  const res = await fetch(`${apiBase}/api/chat-triggers/in/${chatId}/file-status/${fileId}`);\n  if (!res.ok) throw new Error(`file-status ${res.status}`);\n  return res.json();\n}\n\n/**\n * Discriminated provider ticket the backend returns from\n * /api/voice-sessions/start. Each browser SDK consumes a different\n * bootstrap field — the voice-driver matches on `provider` and forwards\n * the right one (ElevenLabs uses signedUrl, Vapi uses publicKey, Retell\n * uses accessToken). agentId is always present so the driver can fall\n * back to direct mode if needed.\n */\nexport type VoiceSessionTicket =\n  | { provider: 'elevenlabs'; signedUrl: string; ttlSeconds: number; agentId: string }\n  | { provider: 'vapi'; publicKey: string; ttlSeconds: number; agentId: string }\n  | { provider: 'retell'; accessToken: string; ttlSeconds: number; agentId: string };\n\n/**\n * Mint a one-shot voice session ticket. Required for ElevenLabs voice\n * because forcing the SDK onto its WebSocket transport (away from\n * LiveKit/WebRTC) only works when we hand it a pre-signed URL — public\n * agent_id mode silently ignores `connectionType: 'websocket'` and still\n * negotiates WebRTC, which times out against ElevenLabs's older LiveKit\n * server (debugged 2026-05-02).\n */\nexport async function fetchVoiceTicket(\n  apiBase: string,\n  chatId: string,\n  sessionId: string,\n  auth: AuthManager,\n): Promise<VoiceSessionTicket> {\n  const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n  const token = await auth.get(sessionId);\n  if (token) headers.Authorization = `Bearer ${token.sig}:${token.ts}`;\n\n  const res = await fetch(`${apiBase}/api/voice-sessions/start`, {\n    method: 'POST',\n    headers,\n    body: JSON.stringify({ chatId, sessionId }),\n  });\n\n  if (!res.ok) {\n    const errBody = await res.text().catch(() => '');\n    throw new Error(\n      `voice-sessions/start failed (HTTP ${res.status}): ${errBody || res.statusText}`,\n    );\n  }\n\n  const ticket = (await res.json()) as VoiceSessionTicket;\n  if (!ticket?.agentId || !ticket?.provider) {\n    throw new Error(\n      `voice-sessions/start returned malformed payload — missing agentId or provider`,\n    );\n  }\n  return ticket;\n}\n\n/**\n * Open the SSE stream for one message turn. Returns a reader the caller\n * walks with the standard `event:` / `data:` parse loop. Attaches the\n * signed auth header when the manager has one; otherwise sends plain\n * (public-mode triggers ignore the header).\n */\nexport async function openMessageStream(\n  apiBase: string,\n  chatId: string,\n  auth: AuthManager,\n  body: {\n    message: string;\n    sessionId: string;\n    fileRefs?: Array<{ fileId: string }>;\n  },\n  signal: AbortSignal,\n  /** Short-lived preview token from the dashboard iframe, sent as\n   *  X-Preview-Token when present. Public ingress accepts it in place\n   *  of the HMAC signature for signed triggers. */\n  previewToken: string | null = null,\n): Promise<Response> {\n  const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n  const token = await auth.get(body.sessionId);\n  if (token) headers.Authorization = `Bearer ${token.sig}:${token.ts}`;\n  if (previewToken) headers['X-Preview-Token'] = previewToken;\n  return fetch(`${apiBase}/api/chat-triggers/in/${chatId}/message`, {\n    method: 'POST',\n    headers,\n    body: JSON.stringify(body),\n    signal,\n  });\n}\n","/**\n * SSE frame parser. Given a fetch Response's body, yields `{ event, data }`\n * pairs as they arrive. Handles CRLF + LF separators, malformed frames\n * (silently skipped), and aborts cleanly when the abort signal fires.\n *\n * Frames follow the shape the chat ingress emits:\n *   event: token\n *   data: {\"text\": \"hello\"}\n *\n *   event: done\n *   data: {\"response\": \"...\", \"conversationId\": \"...\"}\n */\n\nexport interface SseFrame {\n  event: string;\n  data: unknown;\n}\n\nexport async function* parseSseStream(body: ReadableStream<Uint8Array>): AsyncGenerator<SseFrame> {\n  const reader = body.getReader();\n  const decoder = new TextDecoder();\n  let buffer = '';\n\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      // Normalise CRLF to LF so the split works identically whether the\n      // origin proxied through a Windows-flavoured intermediary or not.\n      buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, '\\n');\n      const parts = buffer.split('\\n\\n');\n      buffer = parts.pop() ?? '';\n      for (const part of parts) {\n        const frame = parseFrame(part);\n        if (frame) yield frame;\n      }\n    }\n    // Flush any trailing frame that didn't end with a blank line.\n    if (buffer.trim().length > 0) {\n      const frame = parseFrame(buffer);\n      if (frame) yield frame;\n    }\n  } finally {\n    try { reader.releaseLock(); } catch { /* already released */ }\n  }\n}\n\nfunction parseFrame(raw: string): SseFrame | null {\n  const eventMatch = raw.match(/^event:\\s*(.+)$/m);\n  const dataMatch = raw.match(/^data:\\s*([\\s\\S]+)$/m);\n  if (!eventMatch || !dataMatch) return null;\n  let data: unknown;\n  try { data = JSON.parse(dataMatch[1]); } catch { return null; }\n  return { event: eventMatch[1].trim(), data };\n}\n","/**\n * Framework-agnostic chat state machine. Owns everything except rendering:\n *   - session id + history persistence (localStorage)\n *   - messages array + live token accumulation\n *   - streaming lifecycle + abort\n *   - attachment upload + scan-status polling\n *\n * The element (or any other renderer) subscribes via `onChange` and calls\n * methods like `sendMessage`, `addFiles`, `removeAttachment`. No DOM\n * references live here.\n */\nimport { AuthManager } from './auth.js';\nimport {\n  fetchConfig,\n  openMessageStream,\n  requestUploadUrl,\n  confirmUpload,\n  pollFileStatus,\n} from './api.js';\nimport { parseSseStream } from './sse.js';\nimport type { AuthProvider, ChatConfig } from './types.js';\n\nexport type Role = 'user' | 'assistant';\n\nexport interface Msg {\n  role: Role;\n  content: string;\n  toolCalls?: Array<{ name: string; input: unknown; output?: string; running?: boolean }>;\n  attachments?: Array<{ id: string; originalName: string; mimeType: string; size: number }>;\n}\n\nexport type AttachmentStatus = 'uploading' | 'scanning' | 'clean' | 'infected' | 'scan_failed' | 'error';\n\nexport interface Attachment {\n  clientId: string;\n  file: File;\n  fileId?: string;\n  status: AttachmentStatus;\n  progress: number;\n  error?: string;\n}\n\nexport interface ChatState {\n  config: ChatConfig | null;\n  sessionId: string | null;\n  messages: Msg[];\n  input: string;\n  streaming: boolean;\n  activeTool: string | null;\n  error: string | null;\n  attachments: Attachment[];\n  dragActive: boolean;\n  bootError: string | null;\n}\n\nexport interface ChatControllerOptions {\n  chatId: string;\n  apiBase: string;\n  authProvider: AuthProvider | null;\n  authEndpoint: string | null;\n  /** Override attribute — if set, overrides the server's theme.mode config. */\n  themeOverride?: 'dark' | 'light' | 'auto';\n  /** Override attribute — if set, overrides the server's primaryColor config. */\n  primaryColorOverride?: string;\n  /** Dashboard preview iframe only — short-lived token sent as X-Preview-Token. */\n  previewToken?: string | null;\n}\n\nexport class ChatController {\n  readonly options: ChatControllerOptions;\n  state: ChatState;\n  private previewToken: string | null;\n\n  private auth: AuthManager;\n  private listeners = new Set<(s: ChatState) => void>();\n  private abortController: AbortController | null = null;\n  private pollInterval: ReturnType<typeof setInterval> | null = null;\n\n  constructor(opts: ChatControllerOptions) {\n    this.options = opts;\n    this.previewToken = opts.previewToken ?? null;\n    this.auth = new AuthManager(opts.authProvider, opts.authEndpoint);\n    this.state = {\n      config: null,\n      sessionId: null,\n      messages: [],\n      input: '',\n      streaming: false,\n      activeTool: null,\n      error: null,\n      attachments: [],\n      dragActive: false,\n      bootError: null,\n    };\n  }\n\n  subscribe(fn: (s: ChatState) => void): () => void {\n    this.listeners.add(fn);\n    return () => { this.listeners.delete(fn); };\n  }\n\n  /** Swap the preview token at runtime — the dashboard refreshes it every\n   *  25 minutes over postMessage so long preview sessions stay authenticated\n   *  without forcing an iframe reload. */\n  setPreviewToken(token: string | null): void {\n    this.previewToken = token;\n  }\n\n  /** Re-bind the auth provider after construction. The React wrapper needs\n   *  this because `connectedCallback` fires before React's `useEffect` runs,\n   *  so the controller boots with whatever was on the element at append time\n   *  (null when only `authProvider` was passed as a JSX prop). Without this\n   *  hook the signed Authorization header would never be sent. */\n  setAuthProvider(provider: AuthProvider | null): void {\n    this.auth.setProvider(provider);\n  }\n\n  /** Read-only access to the AuthManager so the voice flow can sign its\n   *  call to /api/voice-sessions/start with the same Bearer-HMAC scheme\n   *  the chat message endpoint uses. Kept as a getter so the field stays\n   *  private — callers can't swap the manager out. */\n  get authManager(): AuthManager {\n    return this.auth;\n  }\n\n  /** Same idea for declarative auth — kept symmetric. */\n  setAuthEndpoint(endpoint: string | null): void {\n    this.auth.setEndpoint(endpoint);\n  }\n\n  async boot(): Promise<void> {\n    try {\n      const config = await fetchConfig(this.options.apiBase, this.options.chatId);\n      const sessionId = this.loadOrCreateSession();\n      const savedMessages = this.loadHistory();\n      this.setState({\n        config,\n        sessionId,\n        messages: savedMessages.length > 0\n          ? savedMessages\n          : config.initialMessages.map<Msg>((m) => ({ role: m.role, content: m.content })),\n      });\n    } catch (err) {\n      this.setState({ bootError: err instanceof Error ? err.message : 'Failed to load chat' });\n    }\n  }\n\n  setInput(text: string): void { this.setState({ input: text }); }\n\n  async sendMessage(text: string): Promise<void> {\n    const { sessionId, streaming, attachments } = this.state;\n    if (!sessionId || streaming) return;\n    const trimmed = text.trim();\n    const ready = attachments.filter((a) => a.status === 'clean' && a.fileId);\n    if (attachments.length !== ready.length) {\n      this.setState({ error: 'Wait for all attachments to finish scanning, or remove the flagged ones.' });\n      return;\n    }\n    if (!trimmed && ready.length === 0) return;\n\n    const attachmentSnapshot = ready.map((a) => ({\n      id: a.fileId!,\n      originalName: a.file.name,\n      mimeType: a.file.type,\n      size: a.file.size,\n    }));\n\n    this.setState({\n      input: '',\n      error: null,\n      streaming: true,\n      activeTool: null,\n      attachments: [],\n      messages: [\n        ...this.state.messages,\n        { role: 'user', content: trimmed, attachments: attachmentSnapshot.length > 0 ? attachmentSnapshot : undefined },\n        { role: 'assistant', content: '' },\n      ],\n    });\n\n    const ctrl = new AbortController();\n    this.abortController = ctrl;\n\n    try {\n      const res = await openMessageStream(\n        this.options.apiBase,\n        this.options.chatId,\n        this.auth,\n        {\n          message: trimmed,\n          sessionId,\n          ...(ready.length > 0 ? { fileRefs: ready.map((a) => ({ fileId: a.fileId! })) } : {}),\n        },\n        ctrl.signal,\n        this.previewToken,\n      );\n      if (!res.ok) {\n        const errBody = await res.json().catch(() => null);\n        throw new Error((errBody as { error?: string } | null)?.error ?? `HTTP ${res.status}`);\n      }\n      if (!res.body) throw new Error('No response body');\n\n      for await (const frame of parseSseStream(res.body)) {\n        this.applyFrame(frame.event, frame.data);\n      }\n      this.persistHistory();\n    } catch (err) {\n      if ((err as { name?: string } | null)?.name === 'AbortError') return;\n      const message = err instanceof Error ? err.message : 'Failed to reach the chat server';\n      this.setState({ error: message });\n      this.replaceLastAssistantIfEmpty(`**Error:** ${message}`);\n    } finally {\n      this.abortController = null;\n      this.setState({ streaming: false, activeTool: null });\n    }\n  }\n\n  stop(): void {\n    this.abortController?.abort();\n  }\n\n  reset(): void {\n    if (!this.state.config) return;\n    const sid = this.createSessionId();\n    this.writeSession(sid);\n    this.clearHistory();\n    this.setState({\n      sessionId: sid,\n      messages: this.state.config.initialMessages.map<Msg>((m) => ({ role: m.role, content: m.content })),\n      error: null,\n      attachments: [],\n      input: '',\n    });\n  }\n\n  setDragActive(active: boolean): void {\n    if (this.state.dragActive !== active) this.setState({ dragActive: active });\n  }\n\n  /**\n   * Append a message to the live conversation. Used by the voice\n   * session to surface transcript turns inline as the call streams,\n   * so the visitor sees the same text history they'd see in a\n   * text-only chat.\n   */\n  appendMessage(role: 'user' | 'assistant', content: string): void {\n    const trimmed = content.trim();\n    if (!trimmed) return;\n    this.setState({ messages: [...this.state.messages, { role, content: trimmed }] });\n  }\n\n  addFiles(files: FileList | File[]): void {\n    const cfg = this.state.config;\n    const sessionId = this.state.sessionId;\n    if (!cfg?.attachments?.enabled || !sessionId) return;\n\n    const allowed = new Set(cfg.attachments.allowedMimeTypes);\n    const maxFileSize = cfg.attachments.maxFileSize;\n    const maxPerMessage = cfg.attachments.maxPerMessage;\n\n    const toAdd: Attachment[] = [];\n    for (const file of Array.from(files)) {\n      if (this.state.attachments.length + toAdd.length >= maxPerMessage) {\n        this.setState({ error: `You can attach up to ${maxPerMessage} files per message.` });\n        break;\n      }\n      const mime = (file.type || 'application/octet-stream').toLowerCase();\n      if (allowed.size > 0 && !allowed.has(mime)) {\n        this.setState({ error: `File type \"${mime}\" is not allowed here.` });\n        continue;\n      }\n      if (maxFileSize > 0 && file.size > maxFileSize) {\n        this.setState({ error: `\"${file.name}\" is too large — max ${formatBytes(maxFileSize)}.` });\n        continue;\n      }\n      toAdd.push({ clientId: randomId(), file, status: 'uploading', progress: 0 });\n    }\n    if (toAdd.length === 0) return;\n    this.setState({ error: null, attachments: [...this.state.attachments, ...toAdd] });\n    for (const a of toAdd) void this.startUpload(a);\n    this.ensurePoller();\n  }\n\n  removeAttachment(clientId: string): void {\n    this.setState({ attachments: this.state.attachments.filter((a) => a.clientId !== clientId) });\n  }\n\n  private async startUpload(att: Attachment): Promise<void> {\n    const { apiBase, chatId } = this.options;\n    const sessionId = this.state.sessionId!;\n    try {\n      const { fileId, uploadUrl } = await requestUploadUrl(apiBase, chatId, {\n        fileName: att.file.name,\n        mimeType: att.file.type || 'application/octet-stream',\n        sessionId,\n        size: att.file.size,\n      });\n      this.updateAttachment(att.clientId, { fileId });\n\n      await new Promise<void>((resolve, reject) => {\n        const xhr = new XMLHttpRequest();\n        xhr.open('PUT', uploadUrl);\n        xhr.setRequestHeader('Content-Type', att.file.type || 'application/octet-stream');\n        xhr.upload.onprogress = (ev) => {\n          if (!ev.lengthComputable) return;\n          this.updateAttachment(att.clientId, { progress: Math.round((ev.loaded / ev.total) * 100) });\n        };\n        xhr.onload = () => {\n          if (xhr.status >= 200 && xhr.status < 300) return resolve();\n          const body = (xhr.responseText || '').slice(0, 200);\n          reject(new Error(`R2 rejected upload (HTTP ${xhr.status})${body ? `: ${body}` : ''}`));\n        };\n        xhr.onerror = () => reject(new Error('Upload blocked — check R2 bucket CORS allows PUT from this origin'));\n        xhr.send(att.file);\n      });\n\n      await confirmUpload(apiBase, chatId, fileId);\n      this.updateAttachment(att.clientId, { status: 'scanning', progress: 100 });\n      this.ensurePoller();\n    } catch (err) {\n      const msg = err instanceof Error ? err.message : 'upload failed';\n      this.updateAttachment(att.clientId, { status: 'error', error: msg });\n    }\n  }\n\n  private ensurePoller(): void {\n    if (this.pollInterval) return;\n    this.pollInterval = setInterval(async () => {\n      const scanning = this.state.attachments.filter((a) => a.status === 'scanning' && a.fileId);\n      if (scanning.length === 0) {\n        if (this.pollInterval) { clearInterval(this.pollInterval); this.pollInterval = null; }\n        return;\n      }\n      for (const a of scanning) {\n        try {\n          const { scanStatus, scanMessage } = await pollFileStatus(this.options.apiBase, this.options.chatId, a.fileId!);\n          if (scanStatus === 'clean') this.updateAttachment(a.clientId, { status: 'clean' });\n          else if (scanStatus === 'infected') this.updateAttachment(a.clientId, { status: 'infected', error: scanMessage });\n          else if (scanStatus === 'scan_failed') this.updateAttachment(a.clientId, { status: 'scan_failed', error: scanMessage });\n        } catch { /* transient — retry on next tick */ }\n      }\n    }, 600);\n  }\n\n  private updateAttachment(clientId: string, patch: Partial<Attachment>): void {\n    this.setState({\n      attachments: this.state.attachments.map((a) => (a.clientId === clientId ? { ...a, ...patch } : a)),\n    });\n  }\n\n  private applyFrame(event: string, data: unknown): void {\n    const d = data as Record<string, unknown>;\n    switch (event) {\n      case 'session': {\n        if (typeof d.sessionId === 'string' && d.sessionId !== this.state.sessionId) {\n          this.setState({ sessionId: d.sessionId });\n          this.writeSession(d.sessionId);\n        }\n        return;\n      }\n      case 'token': {\n        const text = typeof d.text === 'string' ? d.text : '';\n        this.updateLastAssistant((last) => ({ ...last, content: last.content + text }));\n        return;\n      }\n      case 'tool_start': {\n        this.setState({ activeTool: typeof d.name === 'string' ? d.name : null });\n        this.updateLastAssistant((last) => {\n          const calls = last.toolCalls ? [...last.toolCalls] : [];\n          calls.push({ name: String(d.name ?? ''), input: d.input, running: true });\n          return { ...last, toolCalls: calls };\n        });\n        return;\n      }\n      case 'tool_end': {\n        this.setState({ activeTool: null });\n        this.updateLastAssistant((last) => {\n          if (!last.toolCalls) return last;\n          const calls = [...last.toolCalls];\n          const idx = calls.findIndex((c) => c.name === d.name && c.running);\n          if (idx >= 0) calls[idx] = { ...calls[idx], output: String(d.output ?? ''), running: false };\n          return { ...last, toolCalls: calls };\n        });\n        return;\n      }\n      case 'done': {\n        const response = typeof d.response === 'string' ? d.response : '';\n        if (response) {\n          this.updateLastAssistant((last) => ({ ...last, content: response }));\n        }\n        this.setState({ activeTool: null });\n        return;\n      }\n      case 'error': {\n        throw new Error(typeof d.message === 'string' ? d.message : 'Chat failed');\n      }\n    }\n  }\n\n  private updateLastAssistant(patch: (m: Msg) => Msg): void {\n    const next = [...this.state.messages];\n    const last = next[next.length - 1];\n    if (last?.role !== 'assistant') return;\n    next[next.length - 1] = patch(last);\n    this.setState({ messages: next });\n  }\n\n  private replaceLastAssistantIfEmpty(content: string): void {\n    this.updateLastAssistant((last) => (last.content ? last : { ...last, content }));\n  }\n\n  private setState(patch: Partial<ChatState>): void {\n    this.state = { ...this.state, ...patch };\n    for (const fn of this.listeners) fn(this.state);\n  }\n\n  /* ── localStorage helpers ────────────────────────────────────────────── */\n\n  private storageKey(): string { return `hwchat:${this.options.chatId}:session`; }\n  private historyKey(): string { return `hwchat:${this.options.chatId}:history`; }\n\n  private loadOrCreateSession(): string {\n    const existing = this.readSession();\n    if (existing) return existing;\n    const sid = this.createSessionId();\n    this.writeSession(sid);\n    return sid;\n  }\n\n  private readSession(): string | null {\n    try { return localStorage.getItem(this.storageKey()); } catch { return null; }\n  }\n\n  private writeSession(sid: string): void {\n    try { localStorage.setItem(this.storageKey(), sid); } catch { /* quota / disabled */ }\n  }\n\n  private createSessionId(): string {\n    if (typeof crypto !== 'undefined' && crypto.getRandomValues) {\n      const buf = new Uint8Array(8);\n      crypto.getRandomValues(buf);\n      return 'sess_' + Array.from(buf).map((b) => b.toString(16).padStart(2, '0')).join('');\n    }\n    return 'sess_' + Math.random().toString(16).slice(2, 18);\n  }\n\n  private loadHistory(): Msg[] {\n    try {\n      const raw = localStorage.getItem(this.historyKey());\n      if (!raw) return [];\n      const parsed = JSON.parse(raw);\n      return Array.isArray(parsed) ? (parsed as Msg[]) : [];\n    } catch { return []; }\n  }\n\n  private persistHistory(): void {\n    try {\n      const hasRealTurn = this.state.messages.some((m) => m.role === 'user');\n      if (!hasRealTurn) return;\n      localStorage.setItem(this.historyKey(), JSON.stringify(this.state.messages));\n    } catch { /* quota */ }\n  }\n\n  private clearHistory(): void {\n    try { localStorage.removeItem(this.historyKey()); } catch { /* ignore */ }\n  }\n}\n\nfunction randomId(): string {\n  return Math.random().toString(36).slice(2, 10);\n}\n\nfunction formatBytes(b: number): string {\n  if (b < 1024) return `${b} B`;\n  if (b < 1024 * 1024) return `${(b / 1024).toFixed(1)} KB`;\n  return `${(b / 1024 / 1024).toFixed(1)} MB`;\n}\n\nexport { formatBytes };\n","/**\n * Scoped Shadow DOM styles. Shipped as a constant string so the package has\n * zero runtime CSS dependencies and the host page can't leak styles in.\n * Every token that a customer might want to override is a CSS variable on\n * :host, with a hard fallback so the default always looks finished.\n */\nexport const styles = `\n  :host {\n    --hw-primary: #7c3aed;\n    --hw-primary-rgb: 124, 58, 237;\n    --hw-radius: 16px;\n    --hw-font: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n    --hw-bg: #09090b;\n    --hw-surface: rgba(24, 24, 27, 0.95);\n    --hw-surface-hover: rgba(39, 39, 42, 0.95);\n    --hw-border: rgba(63, 63, 70, 0.8);\n    --hw-text: #fafafa;\n    --hw-text-muted: #a1a1aa;\n    --hw-text-subtle: #71717a;\n    --hw-input-bg: #18181b;\n    --hw-input-text: #fafafa;\n    --hw-input-placeholder: #52525b;\n    --hw-danger: #ef4444;\n    --hw-success: #10b981;\n    --hw-warning: #f59e0b;\n    display: block;\n    height: 100%;\n    width: 100%;\n    font-family: var(--hw-font);\n    color: var(--hw-text);\n    background: var(--hw-bg);\n    -webkit-font-smoothing: antialiased;\n    box-sizing: border-box;\n  }\n  :host([data-theme=\"light\"]) {\n    --hw-bg: #ffffff;\n    --hw-surface: rgba(244, 244, 245, 0.95);\n    --hw-surface-hover: rgba(228, 228, 231, 0.95);\n    --hw-border: rgba(228, 228, 231, 1);\n    --hw-text: #18181b;\n    --hw-text-muted: #52525b;\n    --hw-text-subtle: #a1a1aa;\n    --hw-input-bg: #ffffff;\n    --hw-input-text: #18181b;\n    --hw-input-placeholder: #a1a1aa;\n  }\n  * { box-sizing: border-box; }\n  button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; padding: 0; }\n  a { color: var(--hw-primary); text-decoration: none; }\n  a:hover { text-decoration: underline; }\n  .shell {\n    display: flex; flex-direction: column; height: 100%;\n    background: var(--hw-bg);\n    overflow: hidden;\n  }\n  .header {\n    flex-shrink: 0;\n    display: flex; align-items: center; gap: 12px;\n    padding: 14px 16px;\n    border-bottom: 1px solid var(--hw-border);\n    background: var(--hw-surface);\n    backdrop-filter: blur(8px);\n  }\n  .avatar {\n    width: 36px; height: 36px; border-radius: 10px;\n    background: rgba(var(--hw-primary-rgb), 0.13);\n    border: 1px solid rgba(var(--hw-primary-rgb), 0.3);\n    display: flex; align-items: center; justify-content: center;\n    overflow: hidden; flex-shrink: 0;\n  }\n  .avatar img { width: 100%; height: 100%; object-fit: cover; }\n  .avatar svg { width: 18px; height: 18px; color: var(--hw-primary); }\n  .header-text { flex: 1; min-width: 0; }\n  .title { font-size: 13px; font-weight: 600; color: var(--hw-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n  .subtitle { font-size: 11px; color: var(--hw-text-subtle); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }\n  .header-btn {\n    border: 1px solid var(--hw-border);\n    background: var(--hw-input-bg);\n    border-radius: 8px;\n    font-size: 11px;\n    color: var(--hw-text-muted);\n    padding: 5px 10px;\n    transition: all 0.15s;\n  }\n  .header-btn:hover { color: var(--hw-text); border-color: rgba(var(--hw-primary-rgb), 0.5); }\n\n  .messages {\n    flex: 1; overflow-y: auto; padding: 20px 16px;\n  }\n  .msg { max-width: 720px; margin: 0 auto 20px; }\n  /* Column layout stacks attachments above the bubble; align-items: flex-end\n     keeps everything right-aligned. Previously the bubble sat inside an\n     inline-styled wrapper that ALSO had max-width: 80%, creating nested\n     percentages that browsers resolve to degenerate widths — \"hola\" wrapped\n     as \"h / ol / a\" and longer strings broke at arbitrary points. */\n  .msg-row.user { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }\n  .bubble-user {\n    max-width: 80%;\n    width: fit-content;\n    background: var(--hw-primary);\n    color: white;\n    padding: 10px 14px;\n    border-radius: 16px 16px 4px 16px;\n    font-size: 13px; line-height: 1.55;\n    white-space: pre-wrap; overflow-wrap: break-word;\n  }\n  .msg-attachments { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; margin-bottom: 6px; }\n  .msg-attach-chip {\n    display: inline-flex; align-items: center; gap: 6px;\n    background: rgba(255, 255, 255, 0.2);\n    color: white;\n    border-radius: 999px;\n    padding: 4px 10px;\n    font-size: 11px;\n  }\n  .msg-attach-chip svg { width: 12px; height: 12px; }\n  .msg-attach-chip .name { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n  .assistant { color: var(--hw-text); font-size: 14px; line-height: 1.65; }\n  .assistant p { margin: 0 0 8px; }\n  .assistant p:last-child { margin-bottom: 0; }\n  .assistant h1, .assistant h2, .assistant h3 { margin: 14px 0 6px; font-weight: 600; color: var(--hw-text); }\n  .assistant h1 { font-size: 18px; }\n  .assistant h2 { font-size: 16px; }\n  .assistant h3 { font-size: 14px; }\n  .assistant h1:first-child, .assistant h2:first-child, .assistant h3:first-child { margin-top: 0; }\n  .assistant a {\n    color: var(--hw-primary);\n    text-decoration: underline;\n    text-underline-offset: 2px;\n    word-break: break-word;\n  }\n  .assistant a:hover { opacity: 0.85; }\n  .assistant ul, .assistant ol { margin: 6px 0 8px; padding-left: 20px; }\n  .assistant li { margin: 2px 0; }\n  .assistant blockquote {\n    margin: 8px 0;\n    padding: 4px 12px;\n    border-left: 3px solid rgba(var(--hw-primary-rgb), 0.5);\n    color: var(--hw-text-muted);\n    font-style: italic;\n  }\n  .assistant pre {\n    background: var(--hw-input-bg);\n    border: 1px solid var(--hw-border);\n    border-radius: 10px;\n    padding: 12px; font-size: 12px;\n    overflow-x: auto; margin: 8px 0;\n    font-family: ui-monospace, SFMono-Regular, 'Menlo', monospace;\n  }\n  .assistant code { background: rgba(var(--hw-primary-rgb), 0.1); color: var(--hw-primary); padding: 1px 6px; border-radius: 4px; font-size: 0.92em; font-family: ui-monospace, SFMono-Regular, 'Menlo', monospace; }\n  .assistant pre code { background: transparent; color: inherit; padding: 0; }\n  .assistant strong { font-weight: 600; color: var(--hw-text); }\n  .assistant em { font-style: italic; }\n\n  .tool-calls { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }\n  .tool-pill {\n    display: inline-flex; align-items: center; gap: 6px;\n    border: 1px solid; border-radius: 6px; padding: 2px 8px;\n    font-size: 10px; font-family: ui-monospace, monospace;\n  }\n  .tool-pill.running { border-color: rgba(245, 158, 11, 0.3); background: rgba(245, 158, 11, 0.1); color: var(--hw-warning); }\n  .tool-pill.done { border-color: rgba(16, 185, 129, 0.3); background: rgba(16, 185, 129, 0.1); color: var(--hw-success); }\n  .tool-pill svg { width: 10px; height: 10px; }\n  .tool-pill .spin { animation: hw-spin 1s linear infinite; }\n\n  .typing { display: inline-flex; gap: 4px; align-items: center; height: 16px; }\n  .typing span { width: 6px; height: 6px; border-radius: 999px; background: var(--hw-text-subtle); animation: hw-bounce 1.4s infinite; }\n  .typing span:nth-child(2) { animation-delay: 0.15s; }\n  .typing span:nth-child(3) { animation-delay: 0.3s; }\n  .cursor { display: inline-block; width: 6px; height: 14px; margin-left: 2px; vertical-align: middle; background: var(--hw-primary); animation: hw-pulse 0.9s infinite; }\n\n  .error-box {\n    margin: 0 auto 14px; max-width: 720px;\n    border-radius: 10px; padding: 10px 12px;\n    background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3);\n    color: var(--hw-danger); font-size: 12px;\n  }\n\n  .composer {\n    flex-shrink: 0; border-top: 1px solid var(--hw-border); background: var(--hw-surface);\n    backdrop-filter: blur(8px);\n  }\n  .composer-inner { max-width: 720px; margin: 0 auto; padding: 12px 16px 10px; }\n  .composer-row { display: flex; gap: 8px; align-items: flex-end; }\n  .textarea {\n    flex: 1; resize: none; overflow: hidden;\n    border-radius: 12px;\n    border: 1px solid var(--hw-border);\n    background: var(--hw-input-bg);\n    color: var(--hw-input-text);\n    padding: 11px 14px;\n    font-size: 13px; font-family: inherit; line-height: 1.5;\n    /* Match the 44px send/attach buttons so the row reads as one strip\n       even before autoresize finishes laying out (some browsers report\n       scrollHeight=0 on first paint inside a freshly-mounted iframe,\n       which used to collapse the textarea below the placeholder). */\n    min-height: 44px;\n    max-height: 200px;\n    outline: none;\n    transition: border-color 0.15s;\n  }\n  .textarea::placeholder { color: var(--hw-input-placeholder); }\n  .textarea:focus { border-color: var(--hw-primary); box-shadow: 0 0 0 1px var(--hw-primary); }\n  .textarea:disabled { opacity: 0.6; cursor: not-allowed; }\n\n  .btn-icon {\n    flex-shrink: 0;\n    width: 44px; height: 44px;\n    border-radius: 12px; border: 1px solid var(--hw-border);\n    background: var(--hw-input-bg);\n    color: var(--hw-text-muted);\n    display: flex; align-items: center; justify-content: center;\n    transition: all 0.15s;\n  }\n  .btn-icon:hover:not(:disabled) { color: var(--hw-text); border-color: rgba(var(--hw-primary-rgb), 0.4); }\n  .btn-icon:disabled { opacity: 0.4; cursor: not-allowed; }\n  .btn-icon svg { width: 18px; height: 18px; }\n\n  .btn-send {\n    flex-shrink: 0;\n    width: 44px; height: 44px; border-radius: 12px;\n    background: var(--hw-primary);\n    color: white;\n    display: flex; align-items: center; justify-content: center;\n    transition: opacity 0.15s, transform 0.15s;\n  }\n  .btn-send:hover:not(:disabled) { transform: translateY(-1px); }\n  .btn-send:disabled { opacity: 0.3; cursor: not-allowed; }\n  .btn-send.stop { background: var(--hw-danger); }\n  .btn-send svg { width: 18px; height: 18px; }\n\n  .attachments-strip { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }\n  .attach-chip {\n    display: inline-flex; align-items: center; gap: 6px;\n    border: 1px solid; border-radius: 999px;\n    padding: 4px 10px; font-size: 11px;\n    max-width: 100%;\n  }\n  .attach-chip.neutral { border-color: var(--hw-border); background: var(--hw-input-bg); color: var(--hw-text-muted); }\n  .attach-chip.ok { border-color: rgba(16, 185, 129, 0.4); background: rgba(16, 185, 129, 0.1); color: var(--hw-success); }\n  .attach-chip.bad { border-color: rgba(239, 68, 68, 0.4); background: rgba(239, 68, 68, 0.1); color: var(--hw-danger); }\n  .attach-chip svg { width: 12px; height: 12px; }\n  .attach-chip .spin { animation: hw-spin 1s linear infinite; }\n  .attach-chip .name { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n  .attach-chip .status { opacity: 0.75; }\n  .attach-chip .remove { padding: 1px 3px; border-radius: 4px; display: inline-flex; }\n  .attach-chip .remove:hover { background: rgba(0, 0, 0, 0.15); }\n\n  .drop-overlay {\n    position: absolute; inset: 0; z-index: 10;\n    display: flex; align-items: center; justify-content: center;\n    background: rgba(9, 9, 11, 0.75);\n    backdrop-filter: blur(4px);\n    pointer-events: none;\n    border: 3px dashed var(--hw-primary);\n    border-radius: var(--hw-radius);\n  }\n  :host([data-theme=\"light\"]) .drop-overlay { background: rgba(255, 255, 255, 0.8); }\n  .drop-overlay .drop-text { text-align: center; }\n  .drop-overlay svg { width: 40px; height: 40px; color: var(--hw-primary); margin-bottom: 8px; }\n  .drop-overlay p { margin: 0; font-weight: 600; color: var(--hw-primary); font-size: 14px; }\n\n  .footer { text-align: center; font-size: 10px; color: var(--hw-text-subtle); padding: 4px 0 8px; }\n\n  .boot-error {\n    padding: 20px;\n    color: var(--hw-danger);\n    border: 1px solid rgba(239, 68, 68, 0.3);\n    background: rgba(239, 68, 68, 0.05);\n    border-radius: 10px;\n    margin: 16px;\n    font-size: 13px;\n  }\n  .boot-error strong { display: block; margin-bottom: 4px; }\n\n  .shell-wrap { position: relative; height: 100%; display: flex; flex-direction: column; }\n\n  @keyframes hw-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }\n  @keyframes hw-bounce { 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; } 40% { transform: translateY(-4px); opacity: 1; } }\n  @keyframes hw-pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }\n\n  /* Light-weight Markdown polish. We don't ship a parser — assistant content\n     is inserted as text by default. A future pass can add a tiny renderer. */\n`;\n","/**\n * Minimal Markdown → HTML renderer. Optimized for LLM output: covers the\n * ~95% of formatting models actually produce — headings, bold, italic,\n * inline code, fenced code, links, auto-linked URLs, lists, blockquotes —\n * without shipping a full CommonMark parser.\n *\n * All user-facing text is HTML-escaped first, then markdown markers are\n * transformed on the escaped string. Fenced code blocks are extracted\n * before the inline pass so their contents stay literal.\n *\n * ~1.5 KB minified — good tradeoff versus ~30 KB for marked / remark.\n */\n\nexport function renderMarkdown(raw: string): string {\n  if (!raw) return '';\n  // 1. Extract fenced code blocks first so their contents aren't processed\n  //    by the inline passes. Replace with placeholders, restore at the end.\n  const codeBlocks: string[] = [];\n  let text = raw.replace(/```(\\w*)\\n?([\\s\\S]*?)```/g, (_m, lang: string, body: string) => {\n    codeBlocks.push(\n      `<pre><code${lang ? ` class=\"lang-${escapeAttr(lang)}\"` : ''}>${escapeHtml(body.replace(/\\n$/, ''))}</code></pre>`,\n    );\n    return `\\0CODEBLOCK${codeBlocks.length - 1}\\0`;\n  });\n\n  // 2. Escape everything else up front. Subsequent regex replacements build\n  //    HTML from these escaped characters.\n  text = escapeHtml(text);\n\n  // 3. Headings — # / ## / ### at line start.\n  text = text.replace(/^###\\s+(.+)$/gm, '<h3>$1</h3>');\n  text = text.replace(/^##\\s+(.+)$/gm, '<h2>$1</h2>');\n  text = text.replace(/^#\\s+(.+)$/gm, '<h1>$1</h1>');\n\n  // 4. Blockquotes (one line each for now — good enough for LLM asides).\n  text = text.replace(/^&gt;\\s?(.+)$/gm, '<blockquote>$1</blockquote>');\n\n  // 5. Unordered + ordered lists — grouped so consecutive items become one <ul>/<ol>.\n  text = groupList(text, /^(?:[-*+])\\s+(.+)$/gm, 'ul');\n  text = groupList(text, /^(\\d+)\\.\\s+(.+)$/gm, 'ol');\n\n  // 6. Inline formatting — order matters: code before bold/italic so **foo**\n  //    inside `backticks` stays literal.\n  text = text.replace(/`([^`\\n]+?)`/g, (_m, inner: string) => `<code>${inner}</code>`);\n  text = text.replace(/\\*\\*([^*\\n]+?)\\*\\*/g, '<strong>$1</strong>');\n  text = text.replace(/(?<!\\*)\\*([^*\\n]+?)\\*(?!\\*)/g, '<em>$1</em>');\n\n  // 7. Explicit markdown links [text](url).\n  text = text.replace(/\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s)]+)\\)/g, (_m, label: string, url: string) => {\n    return `<a href=\"${escapeAttr(url)}\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">${label}</a>`;\n  });\n\n  // 8. Autolink bare URLs — skip ones already wrapped in an <a>.\n  text = text.replace(/(^|[\\s(])(https?:\\/\\/[^\\s)<]+)/g, (_m, pre: string, url: string) => {\n    // Trailing punctuation like \"url.\" shouldn't be part of the href.\n    const trail = url.match(/[.,;:!?]+$/);\n    const clean = trail ? url.slice(0, -trail[0].length) : url;\n    const tail = trail ? trail[0] : '';\n    return `${pre}<a href=\"${escapeAttr(clean)}\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">${clean}</a>${tail}`;\n  });\n\n  // 9. Paragraph + line-break splitting. Blank line → </p><p>. Single \\n → <br>.\n  const paragraphs = text.split(/\\n{2,}/).map((p) => p.trim()).filter(Boolean);\n  text = paragraphs\n    .map((p) => (isBlockLevel(p) ? p : `<p>${p.replace(/\\n/g, '<br />')}</p>`))\n    .join('');\n\n  // 10. Restore fenced code blocks.\n  text = text.replace(/\\0CODEBLOCK(\\d+)\\0/g, (_m, i: string) => codeBlocks[Number(i)] ?? '');\n\n  return text;\n}\n\n/**\n * Collapse consecutive lines matched by `marker` into a single <ul> / <ol>.\n * Other lines pass through. Works line-by-line so nested prose + lists\n * interleave correctly.\n */\nfunction groupList(text: string, marker: RegExp, tag: 'ul' | 'ol'): string {\n  const lines = text.split('\\n');\n  const out: string[] = [];\n  let buffer: string[] = [];\n  const flush = () => {\n    if (buffer.length === 0) return;\n    out.push(`<${tag}>${buffer.map((b) => `<li>${b}</li>`).join('')}</${tag}>`);\n    buffer = [];\n  };\n  for (const line of lines) {\n    // Each iteration needs a fresh RegExp because /g regexes carry state.\n    const m = new RegExp(marker.source).exec(line);\n    if (m) {\n      // Ordered lists capture the number in group 1 and content in group 2,\n      // unordered captures content in group 1 — take the last non-undefined group.\n      buffer.push(m[m.length - 1] ?? '');\n    } else {\n      flush();\n      out.push(line);\n    }\n  }\n  flush();\n  return out.join('\\n');\n}\n\n/**\n * Detect \"already a block element\" so we don't wrap it in another <p>.\n * Cheap heuristic: starts with one of the known block tags we emit above.\n */\nfunction isBlockLevel(s: string): boolean {\n  return /^<(h[1-6]|ul|ol|pre|blockquote)\\b/.test(s);\n}\n\nfunction escapeHtml(s: string): string {\n  return s.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]!));\n}\n\nfunction escapeAttr(s: string): string {\n  return escapeHtml(s);\n}\n","/**\n * Voice session controller — wraps ElevenLabs's Conversational AI client\n * SDK behind a small interface so the widget code stays SDK-agnostic.\n *\n * Why dynamic-import: the SDK ships ~150kB of audio plumbing (WebRTC,\n * Opus codec) that most chat-widget consumers never touch. We load it\n * lazily the first time the user clicks the mic button, so the widget's\n * baseline cold-load stays the size it is today.\n *\n * Vapi + Retell SDKs have their own dynamic-import drivers — see the\n * future vapi.driver.ts / retell.driver.ts. Only ElevenLabs is wired in\n * Fase 5; the others stub-throw with a clear message until Fase 6.\n */\n\nexport type VoiceProvider = 'elevenlabs' | 'vapi' | 'retell';\n\nexport type VoiceTurn = {\n  role: 'user' | 'agent';\n  text: string;\n  /** When true the turn is still streaming and may grow on subsequent\n   *  callbacks. False once the speaker stops talking. */\n  final: boolean;\n};\n\nexport type VoiceMode = 'idle' | 'connecting' | 'listening' | 'speaking' | 'ended';\n\nexport interface VoiceSessionHandle {\n  /** End the session and tear down audio capture/playback. Idempotent. */\n  end(): Promise<void>;\n}\n\nexport interface VoiceSessionCallbacks {\n  /** Fires for each transcript turn (user or agent). The widget pipes\n   *  these into the chat message list so users see what was said. */\n  onTurn?: (turn: VoiceTurn) => void;\n  /** Fires when the agent's listening/speaking state changes. The\n   *  widget uses this to render a \"speaking\" indicator. */\n  onMode?: (mode: VoiceMode) => void;\n  /** Fires when the session ends naturally (agent hung up, user\n   *  closed the call, audio device disappeared). */\n  onEnd?: (reason: string) => void;\n  /** Fires for any unrecoverable error during the session. */\n  onError?: (err: Error) => void;\n}\n\nexport interface StartVoiceSessionOptions extends VoiceSessionCallbacks {\n  provider: VoiceProvider;\n  /** Provider-side agent id. Required when no signedUrl is provided\n   *  (direct mode, ElevenLabs only). Optional when signedUrl is set —\n   *  the backend already bound the session to the right agent. */\n  agentId?: string;\n  /** Pre-signed WebSocket URL minted by HW backend\n   *  (`/api/voice-sessions/start`). When set, ElevenLabs uses it as the\n   *  bootstrap and forces WebSocket transport — the only way to skip\n   *  the LiveKit/WebRTC negotiation that times out against ElevenLabs's\n   *  older LiveKit cluster. */\n  signedUrl?: string;\n  /**\n   * Optional CDN override for the SDK module. esm.sh is the default —\n   * users self-hosting under strict CSP can ship the SDK from their own\n   * origin and pass the URL here.\n   */\n  sdkUrl?: string;\n}\n\n// Pinned to 1.4.0 — older versions (0.0.10) ship code that breaks\n// when bundlers like Turbopack pre-bundle the SDK into the host app's\n// chunk graph (manifested as \"TypeError: e.x is not a function\").\n// 1.4.0 is the version @elevenlabs/react@1.3.x depends on so we know\n// it works with the modern Conversation namespace.\nconst DEFAULT_ELEVENLABS_SDK_URL = 'https://esm.sh/@elevenlabs/client@1.4.0';\n\n// Bundler-opaque dynamic import. Webpack/Turbopack/Vite peek inside\n// `await import(url)` calls and try to pre-bundle external URLs at\n// build time — for esm.sh modules this produces broken inline code\n// or fails silently. Wrapping the call inside a `new Function()`\n// body hides the import() from static analysis.\n//\n// CSP requirement: `unsafe-eval`. Most bundler-driven apps allow it\n// by default. Strict-CSP customer embeds can override the loader by\n// setting `globalThis.__hwVoiceImport = (url) => importFn(url)`\n// BEFORE loading the widget.\nconst dynamicImport: (url: string) => Promise<unknown> = (() => {\n  const override = (globalThis as { __hwVoiceImport?: (u: string) => Promise<unknown> })\n    .__hwVoiceImport;\n  if (typeof override === 'function') return override;\n  try {\n    return new Function('u', 'return import(u);') as (u: string) => Promise<unknown>;\n  } catch {\n    return (u: string) => import(u);\n  }\n})();\n\n/**\n * Start a voice session against the configured agent. Resolves once\n * the WebSocket connection is up and the mic is capturing — errors\n * before that point reject the promise; errors during the session\n * fire onError instead.\n */\nexport async function startVoiceSession(\n  opts: StartVoiceSessionOptions,\n): Promise<VoiceSessionHandle> {\n  if (opts.provider === 'elevenlabs') {\n    return startElevenLabsSession(opts);\n  }\n  // Vapi/Retell drivers ship in a follow-up — stub-throwing now keeps\n  // the option API forward-compatible without hiding the gap.\n  throw new Error(\n    `Voice provider \"${opts.provider}\" is not yet supported in the chat widget. Only \"elevenlabs\" works in Fase 5.`,\n  );\n}\n\n// Subset of @elevenlabs/client@1.x types we actually consume.\ntype ElevenLabsDisconnect =\n  | { reason: 'error'; message?: string; closeCode?: number; closeReason?: string }\n  | { reason: 'agent'; closeCode?: number; closeReason?: string }\n  | { reason: 'user' };\n\ninterface ElevenLabsMessagePayload {\n  message: string;\n  /** @deprecated — present in payload but `role` is the supported field. */\n  source?: 'user' | 'ai';\n  role?: 'user' | 'agent' | string;\n  event_id?: number;\n}\n\ninterface ElevenLabsSdk {\n  Conversation: {\n    startSession(params: {\n      agentId?: string;\n      /** Pre-signed WebSocket URL — forces WS transport and skips the\n       *  LiveKit/WebRTC negotiation that fails against ElevenLabs's\n       *  older LiveKit cluster (debugged 2026-05-02). */\n      signedUrl?: string;\n      onConnect?: (props: { conversationId: string }) => void;\n      onDisconnect?: (details: ElevenLabsDisconnect) => void;\n      onError?: (message: string, context?: unknown) => void;\n      onMessage?: (msg: ElevenLabsMessagePayload) => void;\n      onModeChange?: (info: { mode: 'speaking' | 'listening' }) => void;\n    }): Promise<{ endSession(): Promise<void> }>;\n  };\n}\n\nasync function startElevenLabsSession(\n  opts: StartVoiceSessionOptions,\n): Promise<VoiceSessionHandle> {\n  if (!opts.agentId && !opts.signedUrl) {\n    throw new Error(\n      'ElevenLabs session requires either `signedUrl` (HW-mediated, recommended) or `agentId` (direct public agents only).',\n    );\n  }\n\n  const sdkUrl = opts.sdkUrl || DEFAULT_ELEVENLABS_SDK_URL;\n  let sdk: ElevenLabsSdk;\n  try {\n    sdk = (await dynamicImport(sdkUrl)) as ElevenLabsSdk;\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : String(err);\n    throw new Error(`Failed to load ElevenLabs SDK from ${sdkUrl}: ${msg}`);\n  }\n\n  if (!sdk?.Conversation?.startSession) {\n    throw new Error(\n      `Loaded ${sdkUrl} but it doesn't expose Conversation.startSession — pin the SDK URL via sdkUrl if you self-host.`,\n    );\n  }\n\n  // Mic permission must be granted before the SDK opens its socket;\n  // doing it explicitly gives us a clean rejection path if the user\n  // blocks the prompt instead of letting the SDK throw a cryptic\n  // \"audio device busy\" deep in its stack.\n  try {\n    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n    // We don't need the stream object — we just needed the prompt.\n    // Stop tracks immediately; the SDK reopens its own capture.\n    stream.getTracks().forEach((t) => t.stop());\n  } catch (err) {\n    throw new Error(\n      `Microphone permission denied: ${(err as Error).message}. Allow access in your browser to start a voice call.`,\n    );\n  }\n\n  let lastUserText = '';\n  let lastAgentText = '';\n\n  // Prefer signedUrl when present — it's the only way to force the SDK\n  // off the LiveKit/WebRTC path. With agentId alone, the SDK ignores\n  // `connectionType` and still negotiates WebRTC, which times out\n  // against ElevenLabs's older LiveKit cluster.\n  const sessionParams: Parameters<ElevenLabsSdk['Conversation']['startSession']>[0] = {\n    onConnect: () => opts.onMode?.('listening'),\n    onDisconnect: (details) => {\n      opts.onMode?.('ended');\n      const reason =\n        details.reason === 'error'\n          ? details.message || details.closeReason || 'error'\n          : details.reason;\n      opts.onEnd?.(reason);\n    },\n    onError: (message, context) => {\n      void context;\n      opts.onError?.(new Error(message || 'Unknown ElevenLabs error'));\n    },\n    onMessage: (msg) => {\n      const role: 'user' | 'agent' =\n        msg.role === 'user' || msg.role === 'agent'\n          ? msg.role\n          : msg.source === 'user'\n            ? 'user'\n            : 'agent';\n      const text = String(msg.message ?? '').trim();\n      if (!text) return;\n      const last = role === 'user' ? lastUserText : lastAgentText;\n      if (text === last) return;\n      if (role === 'user') lastUserText = text;\n      else lastAgentText = text;\n      opts.onTurn?.({ role, text, final: true });\n    },\n    onModeChange: (info) => opts.onMode?.(info.mode),\n  };\n\n  if (opts.signedUrl) sessionParams.signedUrl = opts.signedUrl;\n  else if (opts.agentId) sessionParams.agentId = opts.agentId;\n\n  const conv = await sdk.Conversation.startSession(sessionParams);\n\n  return {\n    end: () => conv.endSession(),\n  };\n}\n","/**\n * <hw-chat> Web Component.\n *\n * Owns the Shadow DOM, subscribes to a ChatController for state, and\n * re-renders on every state change. Event delegation (data-action attrs)\n * keeps handler plumbing simple — one click / keydown / drag listener\n * at the root, dispatching by action string.\n */\nimport type { AuthProvider, ChatConfig, HwChatOptions } from '../core/types.js';\nimport { ChatController, formatBytes, type Attachment, type ChatState, type Msg } from '../core/chat-controller.js';\nimport { styles } from './styles.js';\nimport { renderMarkdown } from './markdown.js';\nimport {\n  startVoiceSession,\n  type VoiceMode,\n  type VoiceProvider,\n  type VoiceSessionHandle,\n  type VoiceTurn,\n} from '../core/voice-driver.js';\nimport { fetchVoiceTicket } from '../core/api.js';\n\nconst TAG = 'hw-chat';\nconst DEFAULT_API_BASE = 'https://api.hostwebhook.com';\n\n// Conditional base class so this module loads cleanly in SSR contexts\n// (Next.js build workers, Astro / Nuxt / Remix prerender). The real\n// HTMLElement only exists in the browser; Node throws \"HTMLElement is\n// not defined\" before a framework can mark the importer client-only.\n// In Node the class is defined against a stub and never instantiated\n// because registerHwChat() returns early below.\nconst BaseElement: typeof HTMLElement =\n  typeof HTMLElement !== 'undefined'\n    ? HTMLElement\n    : (class {} as unknown as typeof HTMLElement);\n\nexport class HwChatElement extends BaseElement {\n  /** Programmatic auth provider. The React wrapper assigns this in a\n   *  useEffect, which fires AFTER `connectedCallback` already ran `boot()`,\n   *  so the setter has to forward late assignments to the live controller —\n   *  otherwise the AuthManager keeps the null snapshot it captured at boot\n   *  and every signed request goes out unauthenticated (401 from ingress). */\n  private _authProvider: AuthProvider | null = null;\n  get authProvider(): AuthProvider | null { return this._authProvider; }\n  set authProvider(provider: AuthProvider | null) {\n    this._authProvider = provider;\n    this.controller?.setAuthProvider(provider);\n  }\n\n  private root: ShadowRoot;\n  private controller: ChatController | null = null;\n  private unsubscribe: (() => void) | null = null;\n  private dragCounter = 0;\n  private initialized = false;\n  private systemDarkMq: MediaQueryList | null = null;\n  private systemDarkListener: ((e: MediaQueryListEvent) => void) | null = null;\n\n  // The textarea is kept alive across renders. innerHTML replacement\n  // destroys all children, which kills focus + selection no matter what you\n  // do afterwards, so we detach the textarea *before* the swap and slot it\n  // back in where the new HTML left a placeholder. Node identity is\n  // preserved → focus is preserved natively by the browser.\n  private textareaEl: HTMLTextAreaElement | null = null;\n  private rootListenersAttached = false;\n\n  static get observedAttributes(): string[] {\n    return [\n      'chat-id', 'api-base', 'auth-endpoint', 'theme', 'primary-color',\n      'preview-token', 'embedded',\n      'voice-agent-id', 'voice-provider', 'voice-sdk-url',\n    ];\n  }\n\n  /** Active voice session, if any. Mutually exclusive with text-mode\n   *  streaming — the SDK takes over audio capture while it lives. */\n  private voiceSession: VoiceSessionHandle | null = null;\n  private voiceMode: VoiceMode = 'idle';\n\n  private postMessageListener: ((e: MessageEvent) => void) | null = null;\n\n  constructor() {\n    super();\n    this.root = this.attachShadow({ mode: 'open' });\n  }\n\n  connectedCallback(): void {\n    if (this.initialized) return;\n    this.initialized = true;\n    void this.boot();\n  }\n\n  disconnectedCallback(): void {\n    this.unsubscribe?.();\n    this.unsubscribe = null;\n    // Tear down voice session before the element vanishes — leaking it\n    // keeps the mic on after the widget unmounts (visible bug on SPAs\n    // that route between pages while a call is active).\n    void this.endVoice('element_disconnected');\n    if (this.systemDarkMq && this.systemDarkListener) {\n      this.systemDarkMq.removeEventListener('change', this.systemDarkListener);\n    }\n    if (this.postMessageListener) {\n      window.removeEventListener('message', this.postMessageListener);\n      this.postMessageListener = null;\n    }\n  }\n\n  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n    if (!this.initialized) return;\n    if (oldValue === newValue) return;\n    if (name === 'chat-id') {\n      this.initialized = false;\n      this.controller?.stop();\n      this.unsubscribe?.();\n      this.connectedCallback();\n    }\n    if (name === 'theme' || name === 'primary-color') {\n      this.applyThemeVars();\n    }\n    if (name === 'preview-token') {\n      this.controller?.setPreviewToken(newValue);\n    }\n    if (name === 'embedded') {\n      // Header close button only renders when this flag flips — re-render\n      // against current state.\n      if (this.controller) this.render(this.controller.state);\n    }\n  }\n\n  private readOptions(): Required<Pick<HwChatOptions, 'chatId' | 'apiBase'>> & HwChatOptions {\n    return {\n      chatId: this.getAttribute('chat-id') ?? '',\n      apiBase: this.getAttribute('api-base') ?? DEFAULT_API_BASE,\n      authEndpoint: this.getAttribute('auth-endpoint') ?? undefined,\n      theme: (this.getAttribute('theme') as HwChatOptions['theme']) ?? undefined,\n      primaryColor: this.getAttribute('primary-color') ?? undefined,\n      authProvider: this.authProvider ?? undefined,\n      voiceAgentId: this.getAttribute('voice-agent-id') ?? undefined,\n      voiceProvider: (this.getAttribute('voice-provider') as VoiceProvider | null) ?? undefined,\n      voiceSdkUrl: this.getAttribute('voice-sdk-url') ?? undefined,\n    };\n  }\n\n  private async boot(): Promise<void> {\n    const opts = this.readOptions();\n    if (!opts.chatId) {\n      this.renderBootError('hw-chat is missing a chat-id attribute');\n      return;\n    }\n    this.controller = new ChatController({\n      chatId: opts.chatId,\n      apiBase: opts.apiBase,\n      authProvider: opts.authProvider ?? null,\n      authEndpoint: opts.authEndpoint ?? null,\n      themeOverride: opts.theme,\n      primaryColorOverride: opts.primaryColor,\n      previewToken: this.getAttribute('preview-token'),\n    });\n    this.unsubscribe = this.controller.subscribe((state) => this.render(state));\n    this.watchSystemTheme();\n    this.watchParentMessages();\n    this.applyThemeVars();\n    this.render(this.controller.state);\n    await this.controller.boot();\n  }\n\n  /** Dashboard preview iframe refreshes the short-lived preview token every\n   *  25 minutes over postMessage so long sessions don't expire. Same message\n   *  contract the dashboard's WidgetPreview component sends. */\n  private watchParentMessages(): void {\n    this.postMessageListener = (e: MessageEvent) => {\n      const data = e.data as { type?: string; token?: string } | null;\n      if (!data) return;\n      if (data.type === 'hw-chat-preview-token' && typeof data.token === 'string') {\n        this.controller?.setPreviewToken(data.token);\n        this.setAttribute('preview-token', data.token);\n      }\n    };\n    window.addEventListener('message', this.postMessageListener);\n  }\n\n  /* ── Theme resolution ──────────────────────────────────────────────── */\n\n  private watchSystemTheme(): void {\n    if (typeof window === 'undefined' || !window.matchMedia) return;\n    this.systemDarkMq = window.matchMedia('(prefers-color-scheme: dark)');\n    this.systemDarkListener = () => this.applyThemeVars();\n    this.systemDarkMq.addEventListener('change', this.systemDarkListener);\n  }\n\n  private applyThemeVars(): void {\n    const cfg = this.controller?.state.config ?? null;\n    const themeAttr = this.getAttribute('theme') as HwChatOptions['theme'] | null;\n    const mode = themeAttr ?? cfg?.theme.mode ?? 'dark';\n    const systemDark = this.systemDarkMq?.matches ?? true;\n    const isDark = mode === 'dark' || (mode === 'auto' && systemDark);\n    this.dataset.theme = isDark ? 'dark' : 'light';\n\n    const primary = this.getAttribute('primary-color') ?? cfg?.theme.primaryColor;\n    if (primary) {\n      this.style.setProperty('--hw-primary', primary);\n      this.style.setProperty('--hw-primary-rgb', hexToRgb(primary) ?? '124, 58, 237');\n    }\n  }\n\n  /* ── Render ──────────────────────────────────────────────────────────── */\n\n  private render(state: ChatState): void {\n    if (state.bootError) {\n      this.renderBootError(state.bootError);\n      return;\n    }\n    if (!state.config) {\n      this.root.innerHTML = `<style>${styles}</style><div class=\"shell\"><div class=\"boot-error\">Loading…</div></div>`;\n      return;\n    }\n\n    // Re-apply theme vars when config arrives (the config may carry theme info).\n    this.applyThemeVars();\n\n    const cfg = state.config;\n    const attachmentsEnabled = !!cfg.attachments?.enabled;\n    const hasUserTurn = state.messages.some((m) => m.role === 'user');\n    const embedded = this.hasAttribute('embedded');\n    // Attribute wins over config so per-page overrides still work for\n    // consumers that ship a custom voice agent on top of a generic\n    // chat trigger. Empty attribute → fall back to whatever the\n    // dashboard configured on the Chat Trigger.\n    const voiceAgentId =\n      this.getAttribute('voice-agent-id') ?? cfg.voiceAgentId ?? '';\n    const voiceMode = this.voiceMode;\n    const hasBlocking = state.attachments.some((a) => a.status !== 'clean');\n    const disableSend = state.streaming || (!state.input.trim() && state.attachments.length === 0) || hasBlocking || !state.sessionId;\n    const acceptMimes = (cfg.attachments?.allowedMimeTypes ?? []).join(',');\n\n    const html = `\n      <style>${styles}</style>\n      <div class=\"shell-wrap\">\n        ${state.dragActive && attachmentsEnabled ? dropOverlay() : ''}\n        <div class=\"shell\">\n          ${header(cfg, hasUserTurn, embedded, voiceAgentId, voiceMode)}\n          <div class=\"messages\" part=\"messages\">\n            ${state.messages.map((m, i) => messageHtml(m, i === state.messages.length - 1, state)).join('')}\n            ${state.error ? `<div class=\"error-box\">${escapeHtml(state.error)}</div>` : ''}\n          </div>\n          <div class=\"composer\">\n            <div class=\"composer-inner\">\n              ${state.attachments.length > 0 ? attachmentsStrip(state.attachments) : ''}\n              <div class=\"composer-row\">\n                ${attachmentsEnabled ? `\n                  <input type=\"file\" data-action=\"pick-files\" multiple accept=\"${escapeHtml(acceptMimes)}\" style=\"display:none\" />\n                  <button class=\"btn-icon\" data-action=\"open-picker\" ${state.streaming || state.attachments.length >= (cfg.attachments?.maxPerMessage ?? 5) ? 'disabled' : ''} title=\"Attach a file\" aria-label=\"Attach a file\">\n                    ${iconPaperclip()}\n                  </button>\n                ` : ''}\n                <span data-textarea-slot></span>\n                ${state.streaming\n                  ? `<button class=\"btn-send stop\" data-action=\"stop\" title=\"Stop\">${iconStop()}</button>`\n                  : `<button class=\"btn-send\" data-action=\"send\" ${disableSend ? 'disabled' : ''} title=\"Send\">${iconSend()}</button>`\n                }\n              </div>\n            </div>\n            <div class=\"footer\">Powered by HostWebhook</div>\n          </div>\n        </div>\n      </div>\n    `;\n\n    // Node identity alone isn't enough — removeChild fires blur\n    // synchronously, so the user loses focus the moment we detach. Capture\n    // focus + caret before the swap, then explicitly re-apply them after\n    // the textarea is back in the tree.\n    const hadFocus = !!this.textareaEl && this.root.activeElement === this.textareaEl;\n    const savedSelection: [number, number] | null =\n      hadFocus && this.textareaEl\n        ? [this.textareaEl.selectionStart ?? 0, this.textareaEl.selectionEnd ?? 0]\n        : null;\n\n    if (this.textareaEl?.parentNode) {\n      this.textareaEl.parentNode.removeChild(this.textareaEl);\n    }\n\n    this.root.innerHTML = html;\n\n    const slot = this.root.querySelector('[data-textarea-slot]');\n    if (slot) {\n      if (!this.textareaEl) {\n        this.textareaEl = this.createTextarea(cfg.placeholder || 'Type your message…');\n      }\n      // Keep disabled state current. Never overwrite the textarea's own\n      // value while it has focus — that's mid-typing and would move the\n      // caret out from under the user.\n      this.textareaEl.disabled = state.streaming;\n      if (!hadFocus && this.textareaEl.value !== state.input) {\n        this.textareaEl.value = state.input;\n      }\n      slot.replaceWith(this.textareaEl);\n\n      if (hadFocus) {\n        this.textareaEl.focus();\n        if (savedSelection) {\n          try { this.textareaEl.setSelectionRange(savedSelection[0], savedSelection[1]); } catch { /* ignore */ }\n        }\n      }\n    }\n\n    this.wireEvents();\n    this.autoresizeTextarea();\n    this.scrollToBottom();\n  }\n\n  /** Build the persistent textarea once. Listeners attached here survive\n   *  every subsequent render because the node itself is never destroyed. */\n  private createTextarea(placeholder: string): HTMLTextAreaElement {\n    const ta = document.createElement('textarea');\n    ta.className = 'textarea';\n    ta.setAttribute('data-role', 'input');\n    ta.placeholder = placeholder;\n    ta.rows = 1;\n    // Belt + suspenders alongside CSS min-height: 44px / max-height: 200px\n    // and the autoresize empty-short-circuit. Setting all three inline means\n    // the row's box reads correctly from the very first paint — bypasses\n    // any stale CSS bundle still cached in a long-lived iframe + sidesteps\n    // any host-page cascade pollution that might leak through Shadow DOM\n    // edge cases.\n    ta.style.height = '44px';\n    ta.style.minHeight = '44px';\n    ta.style.maxHeight = '200px';\n    const c = this.controller;\n    if (!c) return ta;\n    ta.addEventListener('input', () => {\n      c.setInput(ta.value);\n    });\n    ta.addEventListener('keydown', (e) => {\n      if (e.key === 'Enter' && !e.shiftKey) {\n        e.preventDefault();\n        void c.sendMessage(c.state.input);\n      }\n    });\n    return ta;\n  }\n\n  /* ── Voice ───────────────────────────────────────────────────────────── */\n\n  private async toggleVoice(): Promise<void> {\n    if (this.voiceSession) {\n      await this.endVoice('user_hangup');\n      return;\n    }\n    await this.startVoice();\n  }\n\n  private async startVoice(): Promise<void> {\n    const opts = this.readOptions();\n    const cfg = this.controller?.state.config;\n    const controller = this.controller;\n    // Attribute > config so consumers can override per-embed without\n    // re-saving the trigger. Same precedence as the render path.\n    const voiceAgentId = opts.voiceAgentId ?? cfg?.voiceAgentId ?? '';\n    // Cast through unknown because cfg.voiceProvider is typed with the\n    // empty-string fallback the API uses to mean \"not configured\", but\n    // the SDK enum doesn't carry that case.\n    const cfgProvider = cfg?.voiceProvider as string | undefined;\n    const voiceProvider =\n      opts.voiceProvider ??\n      (cfgProvider && cfgProvider !== '' ? (cfgProvider as VoiceProvider) : undefined) ??\n      'elevenlabs';\n    if (!voiceAgentId) return;\n    if (this.voiceSession) return;\n    if (!controller) return;\n\n    const sessionId = controller.state.sessionId;\n    if (!sessionId) {\n      this.controller?.appendMessage(\n        'assistant',\n        '⚠ Could not start voice call: chat session not ready yet. Send a message first or refresh.',\n      );\n      return;\n    }\n\n    this.setVoiceMode('connecting');\n    try {\n      // Mint the ticket via HW backend first. ElevenLabs ignores\n      // `connectionType: 'websocket'` for public agents (only agentId)\n      // and forces WebRTC/LiveKit, which times out against their older\n      // LiveKit cluster. Handing the SDK a signedUrl skips LiveKit\n      // entirely. Same path the dashboard's Test Agent + voice-widget\n      // <hw-voice> already use.\n      const ticket = await fetchVoiceTicket(\n        opts.apiBase,\n        opts.chatId,\n        sessionId,\n        controller.authManager,\n      );\n\n      // Provider mismatch shouldn't happen (backend reads voiceProvider\n      // off the same trigger config) but if it does, the discriminated\n      // union below would crash silently — fail loud instead.\n      if (ticket.provider !== voiceProvider) {\n        throw new Error(\n          `Voice provider mismatch — trigger says ${voiceProvider} but backend minted ${ticket.provider}.`,\n        );\n      }\n\n      this.voiceSession = await startVoiceSession({\n        provider: voiceProvider,\n        agentId: ticket.agentId,\n        signedUrl: ticket.provider === 'elevenlabs' ? ticket.signedUrl : undefined,\n        sdkUrl: opts.voiceSdkUrl,\n        onTurn: (turn: VoiceTurn) => {\n          // Pipe transcript turns into the message list so users see\n          // the conversation history alongside text-mode turns.\n          this.controller?.appendMessage(\n            turn.role === 'user' ? 'user' : 'assistant',\n            turn.text,\n          );\n        },\n        onMode: (mode) => this.setVoiceMode(mode),\n        onEnd: () => {\n          this.voiceSession = null;\n          this.setVoiceMode('idle');\n        },\n        onError: (err) => {\n          // Surface as a one-off \"system\" assistant message so the\n          // visitor sees what went wrong without us bolting a toast\n          // onto the widget. Error text is short by design.\n          this.controller?.appendMessage('assistant', `⚠ Voice error: ${err.message}`);\n          this.voiceSession = null;\n          this.setVoiceMode('idle');\n        },\n      });\n    } catch (err) {\n      this.controller?.appendMessage(\n        'assistant',\n        `⚠ Could not start voice call: ${(err as Error).message}`,\n      );\n      this.voiceSession = null;\n      this.setVoiceMode('idle');\n    }\n  }\n\n  private async endVoice(_reason: string): Promise<void> {\n    const session = this.voiceSession;\n    this.voiceSession = null;\n    this.setVoiceMode('idle');\n    try {\n      await session?.end();\n    } catch {\n      // SDK may already have torn down — silent failure is fine.\n    }\n  }\n\n  private setVoiceMode(mode: VoiceMode): void {\n    if (this.voiceMode === mode) return;\n    this.voiceMode = mode;\n    if (this.controller) this.render(this.controller.state);\n  }\n\n  private renderBootError(msg: string): void {\n    this.root.innerHTML = `\n      <style>${styles}</style>\n      <div class=\"shell-wrap\"><div class=\"shell\"><div class=\"boot-error\">\n        <strong>Chat unavailable</strong>${escapeHtml(msg)}\n      </div></div></div>\n    `;\n  }\n\n  /* ── Event wiring ─────────────────────────────────────────────────────── */\n\n  private wireEvents(): void {\n    const c = this.controller;\n    if (!c) return;\n\n    // Root-level listeners attach exactly once. Before this guard, every\n    // render appended another click listener and we'd balloon to hundreds\n    // after a chatty session.\n    this.wireRootListenersOnce();\n\n    const fileInput = this.root.querySelector('[data-action=\"pick-files\"]') as HTMLInputElement | null;\n    if (fileInput) {\n      fileInput.addEventListener('change', (e) => {\n        const files = (e.target as HTMLInputElement).files;\n        if (files) c.addFiles(files);\n        (e.target as HTMLInputElement).value = '';\n      });\n    }\n  }\n\n  private wireRootListenersOnce(): void {\n    if (this.rootListenersAttached) return;\n    this.rootListenersAttached = true;\n    const c = this.controller;\n    if (!c) return;\n\n    this.root.addEventListener('click', (e) => {\n      const target = (e.target as HTMLElement).closest('[data-action]');\n      if (!target) return;\n      const action = target.getAttribute('data-action');\n      switch (action) {\n        case 'send': {\n          void c.sendMessage(c.state.input);\n          break;\n        }\n        case 'stop': c.stop(); break;\n        case 'reset': {\n          if (confirm('Start a fresh chat? Current conversation will be cleared.')) c.reset();\n          break;\n        }\n        case 'open-picker': {\n          const input = this.root.querySelector('[data-action=\"pick-files\"]') as HTMLInputElement | null;\n          input?.click();\n          break;\n        }\n        case 'close-embed': {\n          try { window.parent.postMessage({ type: 'hw-chat-close' }, '*'); } catch { /* sandboxed */ }\n          break;\n        }\n        case 'remove-attachment': {\n          const id = target.getAttribute('data-client-id');\n          if (id) c.removeAttachment(id);\n          break;\n        }\n        case 'toggle-voice': {\n          void this.toggleVoice();\n          break;\n        }\n      }\n    });\n\n    // Textarea listeners live on the persistent textarea node itself —\n    // see createTextarea(). Not attached here because wireEvents runs per\n    // render and we'd duplicate.\n\n    // Drag / drop — attach on host element so overlay sees events regardless of nested child.\n    this.addEventListener('dragenter', (e) => {\n      if (!c.state.config?.attachments?.enabled) return;\n      e.preventDefault();\n      this.dragCounter++;\n      if (e.dataTransfer?.types.includes('Files')) c.setDragActive(true);\n    });\n    this.addEventListener('dragleave', () => {\n      this.dragCounter = Math.max(0, this.dragCounter - 1);\n      if (this.dragCounter === 0) c.setDragActive(false);\n    });\n    this.addEventListener('dragover', (e) => {\n      if (c.state.config?.attachments?.enabled) e.preventDefault();\n    });\n    this.addEventListener('drop', (e) => {\n      if (!c.state.config?.attachments?.enabled) return;\n      e.preventDefault();\n      this.dragCounter = 0;\n      c.setDragActive(false);\n      if (e.dataTransfer?.files?.length) c.addFiles(e.dataTransfer.files);\n    });\n  }\n\n  private autoresizeTextarea(): void {\n    const ta = this.root.querySelector('[data-role=\"input\"]') as HTMLTextAreaElement | null;\n    if (!ta) return;\n    // Empty textarea: always 44px (one line + padding, matches the buttons).\n    // Skip the scrollHeight dance entirely — on a freshly-mounted iframe the\n    // first layout pass can report stale scrollHeight values (seen as high\n    // as the parent container's height), which then cap at 200px and ruin\n    // the composer. Short-circuiting on empty avoids the trap completely.\n    if (!ta.value) {\n      ta.style.height = '44px';\n      return;\n    }\n    ta.style.height = 'auto';\n    ta.style.height = Math.min(Math.max(ta.scrollHeight, 44), 200) + 'px';\n  }\n\n  private scrollToBottom(): void {\n    const msgs = this.root.querySelector('.messages');\n    if (!msgs) return;\n    msgs.scrollTop = msgs.scrollHeight;\n  }\n}\n\n/* ── Templates ───────────────────────────────────────────────────────────── */\n\nfunction header(\n  cfg: ChatConfig,\n  hasUserTurn: boolean,\n  embedded: boolean,\n  voiceAgentId: string,\n  voiceMode: VoiceMode,\n): string {\n  const avatar = cfg.theme.avatarUrl;\n  const resetLabel = embedded ? 'Reset' : 'New chat';\n  const voiceActive = voiceMode !== 'idle' && voiceMode !== 'ended';\n  // Three states for the mic button: idle (start), connecting/listening\n  // (pulsing), speaking (filled). The data-attr drives the CSS so the\n  // shadow-DOM stylesheet can react without us re-rendering the SVG.\n  const voiceTitle = voiceActive ? 'End voice call' : 'Start voice call';\n  const voiceLabel = (() => {\n    switch (voiceMode) {\n      case 'connecting': return 'Connecting…';\n      case 'listening':  return 'Listening';\n      case 'speaking':   return 'Speaking';\n      default:           return '';\n    }\n  })();\n  return `\n    <div class=\"header\">\n      <div class=\"avatar\">\n        ${avatar ? `<img src=\"${escapeHtml(avatar)}\" alt=\"\" />` : iconChat()}\n      </div>\n      <div class=\"header-text\">\n        <div class=\"title\">${escapeHtml(cfg.title || 'Chat')}</div>\n        ${voiceLabel\n          ? `<div class=\"subtitle voice-status\">${escapeHtml(voiceLabel)}</div>`\n          : cfg.subtitle ? `<div class=\"subtitle\">${escapeHtml(cfg.subtitle)}</div>` : ''}\n      </div>\n      ${voiceAgentId ? `<button class=\"header-btn voice-btn\" data-action=\"toggle-voice\" data-voice-mode=\"${voiceMode}\" title=\"${voiceTitle}\" aria-label=\"${voiceTitle}\">${iconMic(voiceActive)}</button>` : ''}\n      ${hasUserTurn ? `<button class=\"header-btn\" data-action=\"reset\" title=\"Start a new conversation\">${escapeHtml(resetLabel)}</button>` : ''}\n      ${embedded ? `<button class=\"header-btn\" data-action=\"close-embed\" title=\"Close\" aria-label=\"Close\">${iconXSmall()}</button>` : ''}\n    </div>\n  `;\n}\n\nfunction messageHtml(m: Msg, isLast: boolean, state: ChatState): string {\n  if (m.role === 'user') {\n    // Attachments + bubble are direct flex children of .msg-row.user (column\n    // flex, right-aligned in CSS). The previous nested wrapper had its own\n    // max-width: 80% AND the bubble had max-width: 80% — browsers resolve\n    // nested percentage widths in shrink-to-fit containers to degenerate\n    // values, so \"hola\" wrapped as \"h / ol / a\" and longer text broke at\n    // arbitrary points with the bubble stretching full-width.\n    return `\n      <div class=\"msg\">\n        <div class=\"msg-row user\">\n          ${m.attachments && m.attachments.length > 0 ? `\n            <div class=\"msg-attachments\">\n              ${m.attachments.map((a) => `\n                <span class=\"msg-attach-chip\" title=\"${escapeHtml(a.originalName)} · ${formatBytes(a.size)}\">\n                  ${iconPaperclip()}<span class=\"name\">${escapeHtml(a.originalName)}</span>\n                </span>\n              `).join('')}\n            </div>\n          ` : ''}\n          ${m.content ? `<div class=\"bubble-user\">${escapeHtml(m.content)}</div>` : ''}\n        </div>\n      </div>\n    `;\n  }\n\n  const showTyping = isLast && state.streaming && !state.activeTool && !m.content;\n  const showCursor = isLast && state.streaming && !state.activeTool && m.content;\n  return `\n    <div class=\"msg\">\n      ${m.toolCalls && m.toolCalls.length > 0 ? `\n        <div class=\"tool-calls\">\n          ${m.toolCalls.map((c) => `\n            <span class=\"tool-pill ${c.running ? 'running' : 'done'}\">\n              ${c.running ? iconSpinner() : iconCheck()}\n              <span>${escapeHtml(c.name)}</span>\n            </span>\n          `).join('')}\n        </div>\n      ` : ''}\n      ${m.content ? `<div class=\"assistant\">${renderAssistantContent(m.content)}${showCursor ? '<span class=\"cursor\"></span>' : ''}</div>` : ''}\n      ${showTyping ? `<div class=\"typing\"><span></span><span></span><span></span></div>` : ''}\n    </div>\n  `;\n}\n\nfunction attachmentsStrip(list: Attachment[]): string {\n  return `\n    <div class=\"attachments-strip\">\n      ${list.map((a) => {\n        const isBad = a.status === 'infected' || a.status === 'scan_failed' || a.status === 'error';\n        const isWorking = a.status === 'uploading' || a.status === 'scanning';\n        const klass = isBad ? 'bad' : a.status === 'clean' ? 'ok' : 'neutral';\n        const label =\n          a.status === 'uploading' ? `Uploading ${a.progress}%`\n          : a.status === 'scanning' ? 'Scanning…'\n          : a.status === 'clean' ? 'Ready'\n          : a.status === 'infected' ? (a.error ? `Blocked: ${a.error}` : 'Blocked')\n          : a.status === 'scan_failed' ? (a.error ? `Scan failed: ${a.error}` : 'Scan failed')\n          : (a.error ? `Upload failed: ${a.error}` : 'Upload failed');\n        const titleAttr = a.error ? `${a.file.name}: ${a.error}` : `${a.file.name} · ${formatBytes(a.file.size)} · ${label}`;\n        return `\n          <span class=\"attach-chip ${klass}\" title=\"${escapeHtml(titleAttr)}\">\n            ${isWorking ? iconSpinner() : a.status === 'clean' ? iconCheck() : iconX()}\n            <span class=\"name\">${escapeHtml(a.file.name)}</span>\n            <span class=\"status\">· ${escapeHtml(label)}</span>\n            <button type=\"button\" class=\"remove\" data-action=\"remove-attachment\" data-client-id=\"${escapeHtml(a.clientId)}\" aria-label=\"Remove ${escapeHtml(a.file.name)}\">\n              ${iconXSmall()}\n            </button>\n          </span>\n        `;\n      }).join('')}\n    </div>\n  `;\n}\n\nfunction dropOverlay(): string {\n  return `\n    <div class=\"drop-overlay\">\n      <div class=\"drop-text\">\n        <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\">\n          <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z\" />\n        </svg>\n        <p>Drop to attach</p>\n      </div>\n    </div>\n  `;\n}\n\n/* ── Icons ───────────────────────────────────────────────────────────────── */\n\nfunction iconChat(): string {\n  return `<svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.184-4.183a1.14 1.14 0 01.778-.332 48.294 48.294 0 005.83-.498c1.585-.233 2.708-1.626 2.708-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z\" /></svg>`;\n}\nfunction iconPaperclip(): string {\n  return `<svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"1.8\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m18.375 12.739-7.693 7.693a4.5 4.5 0 0 1-6.364-6.364l10.94-10.94A3 3 0 0 1 19.5 7.372L8.552 18.32m.009-.01-.01.01m5.699-9.941-7.81 7.81a1.5 1.5 0 0 0 2.112 2.13\" /></svg>`;\n}\nfunction iconSend(): string {\n  return `<svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5\" /></svg>`;\n}\nfunction iconStop(): string { return `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"2\" /></svg>`; }\nfunction iconSpinner(): string { return `<svg class=\"spin\" viewBox=\"0 0 24 24\" fill=\"none\"><circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"4\"/><path fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"/></svg>`; }\nfunction iconCheck(): string { return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M4.5 12.75l6 6 9-13.5\"/></svg>`; }\nfunction iconX(): string { return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"/></svg>`; }\nfunction iconXSmall(): string { return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" style=\"width:10px;height:10px;\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"/></svg>`; }\n\nfunction iconMic(active: boolean): string {\n  // Active state shows a filled mic with a pulse ring; idle is the\n  // outline. The SVG fill switches based on `active` so the same\n  // markup serves both — CSS handles the pulse animation off the\n  // data-voice-mode attribute on the button.\n  if (active) {\n    return `<svg viewBox=\"0 0 24 24\" fill=\"currentColor\" style=\"width:14px;height:14px;\"><path d=\"M12 14a3 3 0 003-3V5a3 3 0 10-6 0v6a3 3 0 003 3zm5-3a5 5 0 01-10 0H5a7 7 0 006 6.93V21h2v-3.07A7 7 0 0019 11z\"/></svg>`;\n  }\n  return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" style=\"width:14px;height:14px;\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z\"/></svg>`;\n}\n\n/* ── Helpers ────────────────────────────────────────────────────────────── */\n\nfunction escapeHtml(s: string): string {\n  return s.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]!));\n}\n\nfunction renderAssistantContent(content: string): string {\n  return renderMarkdown(content);\n}\n\nfunction hexToRgb(hex: string): string | null {\n  const h = hex.replace('#', '').trim();\n  if (h.length !== 6) return null;\n  const n = parseInt(h, 16);\n  if (Number.isNaN(n)) return null;\n  return `${(n >> 16) & 0xff}, ${(n >> 8) & 0xff}, ${n & 0xff}`;\n}\n\n/** Register once. Calling define twice throws, so guard first. */\nexport function registerHwChat(): void {\n  if (typeof customElements === 'undefined') return;\n  if (customElements.get(TAG)) return;\n  customElements.define(TAG, HwChatElement);\n}\n\nregisterHwChat();\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["AuthManager","provider","endpoint","sessionId","now","myGen","tok","result","normalizeToken","res","raw","obj","sig","ts","fetchConfig","apiBase","chatId","res","requestUploadUrl","body","err","confirmUpload","fileId","pollFileStatus","fetchVoiceTicket","sessionId","auth","headers","token","errBody","ticket","openMessageStream","signal","previewToken","parseSseStream","body","reader","decoder","buffer","done","value","parts","part","frame","parseFrame","raw","eventMatch","dataMatch","data","ChatController","opts","AuthManager","fn","token","provider","endpoint","config","fetchConfig","sessionId","savedMessages","m","err","text","streaming","attachments","trimmed","ready","a","attachmentSnapshot","ctrl","res","openMessageStream","errBody","frame","parseSseStream","message","sid","active","role","content","files","cfg","allowed","maxFileSize","maxPerMessage","toAdd","file","mime","formatBytes","randomId","clientId","att","apiBase","chatId","fileId","uploadUrl","requestUploadUrl","resolve","reject","xhr","ev","body","confirmUpload","msg","scanning","scanStatus","scanMessage","pollFileStatus","patch","event","data","d","last","calls","idx","c","response","next","existing","buf","b","raw","parsed","styles","renderMarkdown","raw","codeBlocks","text","_m","lang","body","escapeAttr","escapeHtml","groupList","inner","label","url","pre","trail","clean","tail","p","isBlockLevel","i","marker","tag","lines","out","buffer","flush","b","line","m","s","c","DEFAULT_ELEVENLABS_SDK_URL","dynamicImport","override","u","startVoiceSession","opts","startElevenLabsSession","sdkUrl","sdk","err","msg","t","lastUserText","lastAgentText","sessionParams","details","reason","message","context","role","text","info","conv","TAG","DEFAULT_API_BASE","BaseElement","HwChatElement","provider","name","oldValue","newValue","opts","ChatController","state","e","data","cfg","mode","systemDark","isDark","primary","hexToRgb","styles","attachmentsEnabled","hasUserTurn","m","embedded","voiceAgentId","voiceMode","hasBlocking","a","disableSend","acceptMimes","html","dropOverlay","header","i","messageHtml","escapeHtml","attachmentsStrip","iconPaperclip","iconStop","iconSend","hadFocus","savedSelection","slot","placeholder","ta","c","controller","cfgProvider","voiceProvider","sessionId","ticket","fetchVoiceTicket","startVoiceSession","turn","err","_reason","session","msg","fileInput","files","target","id","msgs","avatar","resetLabel","voiceActive","voiceTitle","voiceLabel","iconChat","iconMic","iconXSmall","isLast","formatBytes","showTyping","showCursor","iconSpinner","iconCheck","renderAssistantContent","list","isBad","isWorking","klass","label","titleAttr","iconX","active","s","content","renderMarkdown","hex","h","n","registerHwChat"]}