@miiflow/assistant-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +563 -0
  3. package/dist/WelcomeScreen-CsFaFNcu.d.mts +246 -0
  4. package/dist/WelcomeScreen-TrcbOYob.d.ts +246 -0
  5. package/dist/avatar-D5eHcfjf.d.mts +109 -0
  6. package/dist/avatar-DftdWqSs.d.ts +109 -0
  7. package/dist/branding-SzYU4ncD.d.mts +18 -0
  8. package/dist/branding-SzYU4ncD.d.ts +18 -0
  9. package/dist/chunk-3E2HG62U.mjs +2 -0
  10. package/dist/chunk-3E2HG62U.mjs.map +1 -0
  11. package/dist/chunk-3ERHTQXR.js +2 -0
  12. package/dist/chunk-3ERHTQXR.js.map +1 -0
  13. package/dist/chunk-3GQNGDXX.mjs +22 -0
  14. package/dist/chunk-3GQNGDXX.mjs.map +1 -0
  15. package/dist/chunk-3KB4JYSQ.js +2 -0
  16. package/dist/chunk-3KB4JYSQ.js.map +1 -0
  17. package/dist/chunk-BA3VCHRC.js +22 -0
  18. package/dist/chunk-BA3VCHRC.js.map +1 -0
  19. package/dist/chunk-CRNBTU42.mjs +2 -0
  20. package/dist/chunk-CRNBTU42.mjs.map +1 -0
  21. package/dist/chunk-KPGHBLGY.mjs +2 -0
  22. package/dist/chunk-KPGHBLGY.mjs.map +1 -0
  23. package/dist/chunk-LJQHWCUK.js +2 -0
  24. package/dist/chunk-LJQHWCUK.js.map +1 -0
  25. package/dist/chunk-MFCWFFJV.mjs +2 -0
  26. package/dist/chunk-MFCWFFJV.mjs.map +1 -0
  27. package/dist/chunk-NSTK5EUQ.js +2 -0
  28. package/dist/chunk-NSTK5EUQ.js.map +1 -0
  29. package/dist/chunk-OCKHJ4WO.js +2 -0
  30. package/dist/chunk-OCKHJ4WO.js.map +1 -0
  31. package/dist/chunk-RTT6LULU.mjs +2 -0
  32. package/dist/chunk-RTT6LULU.mjs.map +1 -0
  33. package/dist/client/index.d.mts +249 -0
  34. package/dist/client/index.d.ts +249 -0
  35. package/dist/client/index.js +9 -0
  36. package/dist/client/index.js.map +1 -0
  37. package/dist/client/index.mjs +9 -0
  38. package/dist/client/index.mjs.map +1 -0
  39. package/dist/context/index.d.mts +43 -0
  40. package/dist/context/index.d.ts +43 -0
  41. package/dist/context/index.js +2 -0
  42. package/dist/context/index.js.map +1 -0
  43. package/dist/context/index.mjs +2 -0
  44. package/dist/context/index.mjs.map +1 -0
  45. package/dist/hooks/index.d.mts +109 -0
  46. package/dist/hooks/index.d.ts +109 -0
  47. package/dist/hooks/index.js +2 -0
  48. package/dist/hooks/index.js.map +1 -0
  49. package/dist/hooks/index.mjs +2 -0
  50. package/dist/hooks/index.mjs.map +1 -0
  51. package/dist/index.d.mts +157 -0
  52. package/dist/index.d.ts +157 -0
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/index.mjs +2 -0
  56. package/dist/index.mjs.map +1 -0
  57. package/dist/message-B21_kqE2.d.ts +78 -0
  58. package/dist/message-ufYsvKXP.d.mts +78 -0
  59. package/dist/primitives/index.d.mts +86 -0
  60. package/dist/primitives/index.d.ts +86 -0
  61. package/dist/primitives/index.js +2 -0
  62. package/dist/primitives/index.js.map +1 -0
  63. package/dist/primitives/index.mjs +2 -0
  64. package/dist/primitives/index.mjs.map +1 -0
  65. package/dist/streaming-CF63E6iS.d.mts +426 -0
  66. package/dist/streaming-CF63E6iS.d.ts +426 -0
  67. package/dist/styled/index.d.mts +477 -0
  68. package/dist/styled/index.d.ts +477 -0
  69. package/dist/styled/index.js +2 -0
  70. package/dist/styled/index.js.map +1 -0
  71. package/dist/styled/index.mjs +2 -0
  72. package/dist/styled/index.mjs.map +1 -0
  73. package/dist/styles-no-preflight.css +1 -0
  74. package/dist/styles.css +1 -0
  75. package/package.json +100 -0
@@ -0,0 +1,2 @@
1
+ import {useMemo}from'react';function i(e){let n=e.trim();if(n.startsWith("#")){let r=n.slice(1);if(r.length===3)return [parseInt(r[0]+r[0],16),parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16)];if(r.length>=6)return [parseInt(r.slice(0,2),16),parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16)]}let t=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function c(e,n,t){let[r,a,u]=[e/255,n/255,t/255].map(s=>s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4));return .2126*r+.7152*a+.0722*u}function o(e){let n=i(e);return n&&c(...n)>.179?"#1D2033":"#ffffff"}function p(e,n){return useMemo(()=>{if(!e&&!n?.iconColor)return {};let t={},r=e?.backgroundBubbleColor||n?.iconColor;return r&&(t["--chat-primary"]=r,t["--chat-user-message-bg"]=r,t["--chat-user-message-text"]=o(r)),e?.headerBackgroundColor&&(t["--chat-header-bg"]=e.headerBackgroundColor),e?.messageFontSize&&(t["--chat-message-font-size"]=`${e.messageFontSize}px`),t},[e,n?.iconColor])}export{o as a,p as b};//# sourceMappingURL=chunk-CRNBTU42.mjs.map
2
+ //# sourceMappingURL=chunk-CRNBTU42.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/color-contrast.ts","../src/hooks/use-branding-css-vars.ts"],"names":["parseColor","color","hex","h","rgbMatch","relativeLuminance","r","g","b","rs","gs","bs","c","getContrastTextColor","bgColor","rgb","useBrandingCSSVars","branding","overrides","useMemo","vars","primaryColor"],"mappings":"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","file":"chunk-CRNBTU42.mjs","sourcesContent":["/**\n * Parse a CSS color string (hex, rgb, rgba) into [r, g, b] values.\n * Returns null if the color cannot be parsed.\n */\nfunction parseColor(color: string): [number, number, number] | null {\n // Hex: #RGB, #RRGGBB\n const hex = color.trim();\n if (hex.startsWith(\"#\")) {\n const h = hex.slice(1);\n if (h.length === 3) {\n return [\n parseInt(h[0] + h[0], 16),\n parseInt(h[1] + h[1], 16),\n parseInt(h[2] + h[2], 16),\n ];\n }\n if (h.length >= 6) {\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n }\n }\n\n // rgb(r, g, b) or rgba(r, g, b, a)\n const rgbMatch = color.match(\n /rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})/\n );\n if (rgbMatch) {\n return [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];\n }\n\n return null;\n}\n\n/**\n * Compute the relative luminance of an sRGB color per WCAG 2.0.\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n */\nfunction relativeLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)\n );\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Given a background color string, return the best contrast text color.\n * Returns \"#ffffff\" for dark backgrounds and \"#1D2033\" for light backgrounds.\n * Falls back to \"#ffffff\" if the color cannot be parsed.\n */\nexport function getContrastTextColor(bgColor: string): string {\n const rgb = parseColor(bgColor);\n if (!rgb) return \"#ffffff\";\n\n const lum = relativeLuminance(...rgb);\n // WCAG threshold: luminance > 0.179 means the background is \"light\"\n return lum > 0.179 ? \"#1D2033\" : \"#ffffff\";\n}\n","import { useMemo } from \"react\";\nimport type { BrandingData } from \"../types/branding\";\nimport { getContrastTextColor } from \"../utils/color-contrast\";\n\n/**\n * Converts BrandingData into CSS custom properties for chat-ui theming.\n * Returns a CSSProperties object that can be spread onto a container element.\n */\nexport function useBrandingCSSVars(\n branding: BrandingData | null | undefined,\n overrides?: { iconColor?: string }\n): React.CSSProperties {\n return useMemo(() => {\n if (!branding && !overrides?.iconColor) return {};\n\n const vars: Record<string, string> = {};\n\n const primaryColor =\n branding?.backgroundBubbleColor || overrides?.iconColor;\n if (primaryColor) {\n vars[\"--chat-primary\"] = primaryColor;\n vars[\"--chat-user-message-bg\"] = primaryColor;\n vars[\"--chat-user-message-text\"] = getContrastTextColor(primaryColor);\n }\n\n if (branding?.headerBackgroundColor) {\n vars[\"--chat-header-bg\"] = branding.headerBackgroundColor;\n }\n\n if (branding?.messageFontSize) {\n vars[\"--chat-message-font-size\"] = `${branding.messageFontSize}px`;\n }\n\n return vars as React.CSSProperties;\n }, [branding, overrides?.iconColor]);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import {useRef,useCallback,useEffect,useState}from'react';function O({enabled:l=true,threshold:a=100,smooth:h=true}={}){let o=useRef(null),c=useRef(true),u=useCallback(()=>{let e=o.current;if(!e)return;let{scrollTop:t,scrollHeight:d,clientHeight:g}=e,i=d-t-g;c.current=i<=a;},[a]),n=useCallback((e=false)=>{let t=o.current;t&&(e||c.current)&&t.scrollTo({top:t.scrollHeight,behavior:h?"smooth":"auto"});},[h]);return useEffect(()=>{l&&n();},[l,n]),useEffect(()=>{let e=o.current;if(!e||!l)return;let t=new MutationObserver(()=>{n();});return t.observe(e,{childList:true,subtree:true,characterData:true}),()=>t.disconnect()},[l,n]),useEffect(()=>{let e=o.current;if(e)return e.addEventListener("scroll",u,{passive:true}),()=>e.removeEventListener("scroll",u)},[u]),{containerRef:o,scrollToBottom:n,isAtBottom:c.current}}function k({onSubmit:l,disabled:a=false,maxFileSize:h=10*1024*1024,allowedFileTypes:o}){let[c,u]=useState(""),[n,e]=useState([]),[t,d]=useState(false),[g,i]=useState(null),A=useRef(null),C=useCallback(r=>{u(r),i(null);},[]),F=useCallback(r=>{let m=Array.from(r),v=[];for(let s of m){if(s.size>h){i(`File "${s.name}" exceeds maximum size`);continue}if(o&&!o.includes(s.type)){i(`File type "${s.type}" is not allowed`);continue}v.push(s);}e(s=>[...s,...v]);},[h,o]),T=useCallback(r=>{e(m=>m.filter((v,s)=>s!==r));},[]),b=useCallback(async()=>{let r=c.trim();if(!(!r&&n.length===0||a||t)){d(true),i(null);try{await l(r,n.length>0?n:void 0),u(""),e([]),A.current?.focus();}catch(m){i(m instanceof Error?m.message:"Failed to send message");}finally{d(false);}}},[c,n,a,t,l]),x=useCallback(r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),b());},[b]),L=useCallback(()=>{u(""),e([]),i(null);},[]),M=(c.trim()||n.length>0)&&!a&&!t;return {content:c,attachments:n,isSubmitting:t,error:g,canSubmit:M,inputRef:A,handleContentChange:C,handleAddAttachment:F,handleRemoveAttachment:T,handleSubmit:b,handleKeyDown:x,clear:L}}export{O as a,k as b};//# sourceMappingURL=chunk-KPGHBLGY.mjs.map
2
+ //# sourceMappingURL=chunk-KPGHBLGY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-auto-scroll.ts","../src/hooks/use-message-composer.ts"],"names":["useAutoScroll","enabled","threshold","smooth","containerRef","useRef","shouldScrollRef","handleScroll","useCallback","container","scrollTop","scrollHeight","clientHeight","distanceFromBottom","scrollToBottom","force","useEffect","observer","useMessageComposer","onSubmit","disabled","maxFileSize","allowedFileTypes","content","setContent","useState","attachments","setAttachments","isSubmitting","setIsSubmitting","error","setError","inputRef","handleContentChange","value","handleAddAttachment","files","fileArray","validFiles","file","prev","handleRemoveAttachment","index","_","i","handleSubmit","trimmedContent","err","handleKeyDown","e","clear","canSubmit"],"mappings":"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","file":"chunk-KPGHBLGY.mjs","sourcesContent":["import { useEffect, useRef, useCallback } from \"react\";\n\ninterface UseAutoScrollOptions {\n /** Whether auto-scroll is enabled */\n enabled?: boolean;\n /** Threshold from bottom to trigger auto-scroll (pixels) */\n threshold?: number;\n /** Smooth scroll behavior */\n smooth?: boolean;\n}\n\n/**\n * Hook to automatically scroll to the bottom of a container\n * when new content is added, unless the user has scrolled up.\n */\nexport function useAutoScroll<T extends HTMLElement>({\n enabled = true,\n threshold = 100,\n smooth = true,\n}: UseAutoScrollOptions = {}) {\n const containerRef = useRef<T>(null);\n const shouldScrollRef = useRef(true);\n\n // Track if user has scrolled away from bottom\n const handleScroll = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const { scrollTop, scrollHeight, clientHeight } = container;\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\n // User is near bottom, enable auto-scroll\n shouldScrollRef.current = distanceFromBottom <= threshold;\n }, [threshold]);\n\n // Scroll to bottom\n const scrollToBottom = useCallback(\n (force = false) => {\n const container = containerRef.current;\n if (!container) return;\n\n if (force || shouldScrollRef.current) {\n container.scrollTo({\n top: container.scrollHeight,\n behavior: smooth ? \"smooth\" : \"auto\",\n });\n }\n },\n [smooth]\n );\n\n // Auto-scroll when enabled changes or container updates\n useEffect(() => {\n if (enabled) {\n scrollToBottom();\n }\n }, [enabled, scrollToBottom]);\n\n // Watch for DOM mutations (new messages, streaming content)\n useEffect(() => {\n const container = containerRef.current;\n if (!container || !enabled) return;\n\n const observer = new MutationObserver(() => {\n scrollToBottom();\n });\n\n observer.observe(container, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n // Attach scroll listener\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n container.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => container.removeEventListener(\"scroll\", handleScroll);\n }, [handleScroll]);\n\n return {\n containerRef,\n scrollToBottom,\n isAtBottom: shouldScrollRef.current,\n };\n}\n","import { useState, useCallback, useRef, type KeyboardEvent } from \"react\";\n\ninterface UseMessageComposerOptions {\n /** Callback when message is submitted */\n onSubmit: (content: string, attachments?: File[]) => Promise<void>;\n /** Whether submission is disabled */\n disabled?: boolean;\n /** Maximum file size in bytes */\n maxFileSize?: number;\n /** Allowed file types (MIME types) */\n allowedFileTypes?: string[];\n}\n\n/**\n * Hook to manage message composer state and behavior.\n */\nexport function useMessageComposer({\n onSubmit,\n disabled = false,\n maxFileSize = 10 * 1024 * 1024, // 10MB\n allowedFileTypes,\n}: UseMessageComposerOptions) {\n const [content, setContent] = useState(\"\");\n const [attachments, setAttachments] = useState<File[]>([]);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n const handleContentChange = useCallback((value: string) => {\n setContent(value);\n setError(null);\n }, []);\n\n const handleAddAttachment = useCallback(\n (files: FileList | File[]) => {\n const fileArray = Array.from(files);\n const validFiles: File[] = [];\n\n for (const file of fileArray) {\n // Check file size\n if (file.size > maxFileSize) {\n setError(`File \"${file.name}\" exceeds maximum size`);\n continue;\n }\n\n // Check file type\n if (allowedFileTypes && !allowedFileTypes.includes(file.type)) {\n setError(`File type \"${file.type}\" is not allowed`);\n continue;\n }\n\n validFiles.push(file);\n }\n\n setAttachments((prev) => [...prev, ...validFiles]);\n },\n [maxFileSize, allowedFileTypes]\n );\n\n const handleRemoveAttachment = useCallback((index: number) => {\n setAttachments((prev) => prev.filter((_, i) => i !== index));\n }, []);\n\n const handleSubmit = useCallback(async () => {\n const trimmedContent = content.trim();\n if ((!trimmedContent && attachments.length === 0) || disabled || isSubmitting) {\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n await onSubmit(trimmedContent, attachments.length > 0 ? attachments : undefined);\n setContent(\"\");\n setAttachments([]);\n inputRef.current?.focus();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to send message\");\n } finally {\n setIsSubmitting(false);\n }\n }, [content, attachments, disabled, isSubmitting, onSubmit]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n // Submit on Enter without Shift\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit]\n );\n\n const clear = useCallback(() => {\n setContent(\"\");\n setAttachments([]);\n setError(null);\n }, []);\n\n const canSubmit = (content.trim() || attachments.length > 0) && !disabled && !isSubmitting;\n\n return {\n content,\n attachments,\n isSubmitting,\n error,\n canSubmit,\n inputRef,\n handleContentChange,\n handleAddAttachment,\n handleRemoveAttachment,\n handleSubmit,\n handleKeyDown,\n clear,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react');function O({enabled:l=true,threshold:a=100,smooth:h=true}={}){let o=react.useRef(null),c=react.useRef(true),u=react.useCallback(()=>{let e=o.current;if(!e)return;let{scrollTop:t,scrollHeight:d,clientHeight:g}=e,i=d-t-g;c.current=i<=a;},[a]),n=react.useCallback((e=false)=>{let t=o.current;t&&(e||c.current)&&t.scrollTo({top:t.scrollHeight,behavior:h?"smooth":"auto"});},[h]);return react.useEffect(()=>{l&&n();},[l,n]),react.useEffect(()=>{let e=o.current;if(!e||!l)return;let t=new MutationObserver(()=>{n();});return t.observe(e,{childList:true,subtree:true,characterData:true}),()=>t.disconnect()},[l,n]),react.useEffect(()=>{let e=o.current;if(e)return e.addEventListener("scroll",u,{passive:true}),()=>e.removeEventListener("scroll",u)},[u]),{containerRef:o,scrollToBottom:n,isAtBottom:c.current}}function k({onSubmit:l,disabled:a=false,maxFileSize:h=10*1024*1024,allowedFileTypes:o}){let[c,u]=react.useState(""),[n,e]=react.useState([]),[t,d]=react.useState(false),[g,i]=react.useState(null),A=react.useRef(null),C=react.useCallback(r=>{u(r),i(null);},[]),F=react.useCallback(r=>{let m=Array.from(r),v=[];for(let s of m){if(s.size>h){i(`File "${s.name}" exceeds maximum size`);continue}if(o&&!o.includes(s.type)){i(`File type "${s.type}" is not allowed`);continue}v.push(s);}e(s=>[...s,...v]);},[h,o]),T=react.useCallback(r=>{e(m=>m.filter((v,s)=>s!==r));},[]),b=react.useCallback(async()=>{let r=c.trim();if(!(!r&&n.length===0||a||t)){d(true),i(null);try{await l(r,n.length>0?n:void 0),u(""),e([]),A.current?.focus();}catch(m){i(m instanceof Error?m.message:"Failed to send message");}finally{d(false);}}},[c,n,a,t,l]),x=react.useCallback(r=>{r.key==="Enter"&&!r.shiftKey&&(r.preventDefault(),b());},[b]),L=react.useCallback(()=>{u(""),e([]),i(null);},[]),M=(c.trim()||n.length>0)&&!a&&!t;return {content:c,attachments:n,isSubmitting:t,error:g,canSubmit:M,inputRef:A,handleContentChange:C,handleAddAttachment:F,handleRemoveAttachment:T,handleSubmit:b,handleKeyDown:x,clear:L}}exports.a=O;exports.b=k;//# sourceMappingURL=chunk-LJQHWCUK.js.map
2
+ //# sourceMappingURL=chunk-LJQHWCUK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/use-auto-scroll.ts","../src/hooks/use-message-composer.ts"],"names":["useAutoScroll","enabled","threshold","smooth","containerRef","useRef","shouldScrollRef","handleScroll","useCallback","container","scrollTop","scrollHeight","clientHeight","distanceFromBottom","scrollToBottom","force","useEffect","observer","useMessageComposer","onSubmit","disabled","maxFileSize","allowedFileTypes","content","setContent","useState","attachments","setAttachments","isSubmitting","setIsSubmitting","error","setError","inputRef","handleContentChange","value","handleAddAttachment","files","fileArray","validFiles","file","prev","handleRemoveAttachment","index","_","i","handleSubmit","trimmedContent","err","handleKeyDown","e","clear","canSubmit"],"mappings":"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","file":"chunk-LJQHWCUK.js","sourcesContent":["import { useEffect, useRef, useCallback } from \"react\";\n\ninterface UseAutoScrollOptions {\n /** Whether auto-scroll is enabled */\n enabled?: boolean;\n /** Threshold from bottom to trigger auto-scroll (pixels) */\n threshold?: number;\n /** Smooth scroll behavior */\n smooth?: boolean;\n}\n\n/**\n * Hook to automatically scroll to the bottom of a container\n * when new content is added, unless the user has scrolled up.\n */\nexport function useAutoScroll<T extends HTMLElement>({\n enabled = true,\n threshold = 100,\n smooth = true,\n}: UseAutoScrollOptions = {}) {\n const containerRef = useRef<T>(null);\n const shouldScrollRef = useRef(true);\n\n // Track if user has scrolled away from bottom\n const handleScroll = useCallback(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const { scrollTop, scrollHeight, clientHeight } = container;\n const distanceFromBottom = scrollHeight - scrollTop - clientHeight;\n\n // User is near bottom, enable auto-scroll\n shouldScrollRef.current = distanceFromBottom <= threshold;\n }, [threshold]);\n\n // Scroll to bottom\n const scrollToBottom = useCallback(\n (force = false) => {\n const container = containerRef.current;\n if (!container) return;\n\n if (force || shouldScrollRef.current) {\n container.scrollTo({\n top: container.scrollHeight,\n behavior: smooth ? \"smooth\" : \"auto\",\n });\n }\n },\n [smooth]\n );\n\n // Auto-scroll when enabled changes or container updates\n useEffect(() => {\n if (enabled) {\n scrollToBottom();\n }\n }, [enabled, scrollToBottom]);\n\n // Watch for DOM mutations (new messages, streaming content)\n useEffect(() => {\n const container = containerRef.current;\n if (!container || !enabled) return;\n\n const observer = new MutationObserver(() => {\n scrollToBottom();\n });\n\n observer.observe(container, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => observer.disconnect();\n }, [enabled, scrollToBottom]);\n\n // Attach scroll listener\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n container.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => container.removeEventListener(\"scroll\", handleScroll);\n }, [handleScroll]);\n\n return {\n containerRef,\n scrollToBottom,\n isAtBottom: shouldScrollRef.current,\n };\n}\n","import { useState, useCallback, useRef, type KeyboardEvent } from \"react\";\n\ninterface UseMessageComposerOptions {\n /** Callback when message is submitted */\n onSubmit: (content: string, attachments?: File[]) => Promise<void>;\n /** Whether submission is disabled */\n disabled?: boolean;\n /** Maximum file size in bytes */\n maxFileSize?: number;\n /** Allowed file types (MIME types) */\n allowedFileTypes?: string[];\n}\n\n/**\n * Hook to manage message composer state and behavior.\n */\nexport function useMessageComposer({\n onSubmit,\n disabled = false,\n maxFileSize = 10 * 1024 * 1024, // 10MB\n allowedFileTypes,\n}: UseMessageComposerOptions) {\n const [content, setContent] = useState(\"\");\n const [attachments, setAttachments] = useState<File[]>([]);\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const inputRef = useRef<HTMLTextAreaElement>(null);\n\n const handleContentChange = useCallback((value: string) => {\n setContent(value);\n setError(null);\n }, []);\n\n const handleAddAttachment = useCallback(\n (files: FileList | File[]) => {\n const fileArray = Array.from(files);\n const validFiles: File[] = [];\n\n for (const file of fileArray) {\n // Check file size\n if (file.size > maxFileSize) {\n setError(`File \"${file.name}\" exceeds maximum size`);\n continue;\n }\n\n // Check file type\n if (allowedFileTypes && !allowedFileTypes.includes(file.type)) {\n setError(`File type \"${file.type}\" is not allowed`);\n continue;\n }\n\n validFiles.push(file);\n }\n\n setAttachments((prev) => [...prev, ...validFiles]);\n },\n [maxFileSize, allowedFileTypes]\n );\n\n const handleRemoveAttachment = useCallback((index: number) => {\n setAttachments((prev) => prev.filter((_, i) => i !== index));\n }, []);\n\n const handleSubmit = useCallback(async () => {\n const trimmedContent = content.trim();\n if ((!trimmedContent && attachments.length === 0) || disabled || isSubmitting) {\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n await onSubmit(trimmedContent, attachments.length > 0 ? attachments : undefined);\n setContent(\"\");\n setAttachments([]);\n inputRef.current?.focus();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Failed to send message\");\n } finally {\n setIsSubmitting(false);\n }\n }, [content, attachments, disabled, isSubmitting, onSubmit]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLTextAreaElement>) => {\n // Submit on Enter without Shift\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSubmit();\n }\n },\n [handleSubmit]\n );\n\n const clear = useCallback(() => {\n setContent(\"\");\n setAttachments([]);\n setError(null);\n }, []);\n\n const canSubmit = (content.trim() || attachments.length > 0) && !disabled && !isSubmitting;\n\n return {\n content,\n attachments,\n isSubmitting,\n error,\n canSubmit,\n inputRef,\n handleContentChange,\n handleAddAttachment,\n handleRemoveAttachment,\n handleSubmit,\n handleKeyDown,\n clear,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import {a,b}from'./chunk-KPGHBLGY.mjs';import {createContext,forwardRef,useContext}from'react';import {jsx,Fragment,jsxs}from'react/jsx-runtime';var m=createContext(null);function d(){let e=useContext(m);if(!e)throw new Error("useMessage must be used within a Message component");return e}var v=forwardRef(({message:e,viewerRole:t="user",children:s,...o},r)=>{let n=(e.participant?.role||"").toLowerCase()===(t||"").toLowerCase(),i=e.isStreaming??false;return jsx(m.Provider,{value:{message:e,isViewer:n,isStreaming:i},children:jsx("div",{ref:r,"data-role":e.participant?.role,"data-viewer":n,"data-streaming":i,...o,children:s})})});v.displayName="Message";var S=forwardRef(({children:e,...t},s)=>{let{message:o}=d();return jsx("div",{ref:s,...t,children:e??o.textContent})});S.displayName="MessageContent";var L=forwardRef(({format:e,...t},s)=>{let{message:o}=d(),r=typeof o.createdAt=="string"?new Date(o.createdAt):o.createdAt,n=e?e(r):r.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:true});return jsx("span",{ref:s,...t,children:n})});L.displayName="MessageTimestamp";var h=forwardRef(({children:e,autoScroll:t=true,scrollThreshold:s=100,...o},r)=>{let{containerRef:n,scrollToBottom:i}=a({enabled:t,threshold:s});return jsx("div",{ref:p=>{n.current=p,typeof r=="function"?r(p):r&&(r.current=p);},"data-scroll-to-bottom":i,...o,children:e})});h.displayName="MessageList";var f=createContext(null);function M(){let e=useContext(f);if(!e)throw new Error("useComposer must be used within a MessageComposer component");return e}var P=forwardRef(({onSubmit:e,disabled:t=false,children:s,className:o},r)=>{let n=b({onSubmit:e,disabled:t}),i=p=>{p.preventDefault(),n.handleSubmit();},a={...n,canSubmit:!!n.canSubmit};return jsx(f.Provider,{value:a,children:jsx("form",{ref:r,className:o,onSubmit:i,"data-submitting":n.isSubmitting,"data-can-submit":n.canSubmit,children:s})})});P.displayName="MessageComposer";var H=forwardRef((e,t)=>{let{content:s,handleContentChange:o,handleKeyDown:r,inputRef:n}=M();return jsx("textarea",{ref:a=>{n.current=a,typeof t=="function"?t(a):t&&(t.current=a);},value:s,onChange:a=>o(a.target.value),onKeyDown:r,...e})});H.displayName="ComposerInput";var R=forwardRef(({children:e,disabled:t,...s},o)=>{let{canSubmit:r,isSubmitting:n}=M();return jsx("button",{ref:o,type:"submit",disabled:t||!r,"data-submitting":n,...s,children:e})});R.displayName="ComposerSubmit";function q(e){let t=e.trim().split(/\s+/);return t.length===1?t[0].charAt(0).toUpperCase():(t[0].charAt(0)+t[t.length-1].charAt(0)).toUpperCase()}var E=forwardRef(({name:e,src:t,alt:s,role:o,fallback:r,children:n,...i},a)=>{let p=e?q(e):null;return jsx("div",{ref:a,"data-role":o,...i,children:n??jsx(Fragment,{children:t?jsx("img",{src:t,alt:s??e??o??"Avatar",style:{width:"100%",height:"100%"}}):r??p??o?.charAt(0).toUpperCase()})})});E.displayName="Avatar";var N=forwardRef(({content:e,isStreaming:t=false,showCursor:s=true,cursor:o,children:r,...n},i)=>jsxs("div",{ref:i,"data-streaming":t,...n,children:[r??e,t&&s&&(o??jsx("span",{"aria-hidden":"true",style:{display:"inline-block",width:"2px",height:"1em",backgroundColor:"currentColor",marginLeft:"2px",verticalAlign:"text-bottom",animation:"blink 1s step-end infinite"}}))]}));N.displayName="StreamingText";var C=createContext(null);function D(){let e=useContext(C);if(!e)throw new Error("useSuggestedActions must be used within a SuggestedActions component");return e}var B=forwardRef(({actions:e,onSelect:t,children:s,className:o},r)=>e.length===0?null:jsx(C.Provider,{value:{actions:e,onSelect:t},children:jsx("div",{ref:r,role:"group","aria-label":"Suggested actions",className:o,children:s})}));B.displayName="SuggestedActions";var I=forwardRef(({action:e,children:t,onClick:s,...o},r)=>{let{onSelect:n}=D();return jsx("button",{ref:r,type:"button",onClick:a=>{s?.(a),a.defaultPrevented||n(e);},...o,children:t??e.label})});I.displayName="ActionButton";var k=forwardRef(({children:e,dotCount:t=3,...s},o)=>jsx("div",{ref:o,role:"status","aria-label":"Assistant is typing",...s,children:e??jsx("span",{"aria-hidden":"true",children:Array.from({length:t}).map((r,n)=>jsx("span",{style:{display:"inline-block",width:"6px",height:"6px",borderRadius:"50%",backgroundColor:"currentColor",marginRight:n<t-1?"4px":0,animation:"typing 1.4s infinite ease-in-out",animationDelay:`${n*.2}s`}},n))})}));k.displayName="TypingIndicator";export{m as a,d as b,v as c,S as d,L as e,h as f,f as g,M as h,P as i,H as j,R as k,E as l,N as m,C as n,D as o,B as p,I as q,k as r};//# sourceMappingURL=chunk-MFCWFFJV.mjs.map
2
+ //# sourceMappingURL=chunk-MFCWFFJV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/primitives/message.tsx","../src/primitives/message-list.tsx","../src/primitives/message-composer.tsx","../src/primitives/avatar.tsx","../src/primitives/streaming-text.tsx","../src/primitives/suggested-actions.tsx","../src/primitives/typing-indicator.tsx"],"names":["MessageContext","createContext","useMessage","context","useContext","Message","forwardRef","message","viewerRole","children","props","ref","isViewer","isStreaming","jsx","MessageContent","MessageTimestamp","format","date","formatted","MessageList","autoScroll","scrollThreshold","containerRef","scrollToBottom","useAutoScroll","node","ComposerContext","useComposer","MessageComposer","onSubmit","disabled","className","composer","useMessageComposer","handleFormSubmit","e","contextValue","ComposerInput","content","handleContentChange","handleKeyDown","inputRef","ComposerSubmit","canSubmit","isSubmitting","getInitials","name","parts","Avatar","src","alt","role","fallback","initials","Fragment","StreamingText","showCursor","cursor","jsxs","SuggestedActionsContext","useSuggestedActions","SuggestedActions","actions","onSelect","ActionButton","action","onClick","TypingIndicator","dotCount","_","i"],"mappings":"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","file":"chunk-MFCWFFJV.mjs","sourcesContent":["import { createContext, forwardRef, useContext, type HTMLAttributes, type ReactNode } from \"react\";\nimport type { MessageData, ParticipantRole } from \"../types\";\n\ninterface MessageContextValue {\n\tmessage: MessageData;\n\tisViewer: boolean;\n\tisStreaming: boolean;\n}\n\nconst MessageContext = createContext<MessageContextValue | null>(null);\n\n/**\n * Hook to access the current message context.\n * Must be used within a Message component.\n */\nexport function useMessage() {\n\tconst context = useContext(MessageContext);\n\tif (!context) {\n\t\tthrow new Error(\"useMessage must be used within a Message component\");\n\t}\n\treturn context;\n}\n\nexport interface MessageProps extends HTMLAttributes<HTMLDivElement> {\n\t/** The message data */\n\tmessage: MessageData;\n\t/** The viewer's role (used to determine alignment) */\n\tviewerRole?: ParticipantRole;\n\t/** Children to render inside the message */\n\tchildren: ReactNode;\n}\n\n/**\n * Headless Message primitive.\n * Provides message context to children and data attributes for styling.\n */\nexport const Message = forwardRef<HTMLDivElement, MessageProps>(\n\t({ message, viewerRole = \"user\", children, ...props }, ref) => {\n\t\t// Case-insensitive comparison for role matching\n\t\tconst isViewer = (message.participant?.role || \"\").toLowerCase() === (viewerRole || \"\").toLowerCase();\n\t\tconst isStreaming = message.isStreaming ?? false;\n\n\t\treturn (\n\t\t\t<MessageContext.Provider value={{ message, isViewer, isStreaming }}>\n\t\t\t\t<div\n\t\t\t\t\tref={ref}\n\t\t\t\t\tdata-role={message.participant?.role}\n\t\t\t\t\tdata-viewer={isViewer}\n\t\t\t\t\tdata-streaming={isStreaming}\n\t\t\t\t\t{...props}>\n\t\t\t\t\t{children}\n\t\t\t\t</div>\n\t\t\t</MessageContext.Provider>\n\t\t);\n\t},\n);\n\nMessage.displayName = \"Message\";\n\nexport interface MessageContentProps extends HTMLAttributes<HTMLDivElement> {\n\t/** Custom content to render instead of message text */\n\tchildren?: ReactNode;\n}\n\n/**\n * Renders the message content.\n * By default renders the message's textContent.\n */\nexport const MessageContent = forwardRef<HTMLDivElement, MessageContentProps>(({ children, ...props }, ref) => {\n\tconst { message } = useMessage();\n\n\treturn (\n\t\t<div ref={ref} {...props}>\n\t\t\t{children ?? message.textContent}\n\t\t</div>\n\t);\n});\n\nMessageContent.displayName = \"MessageContent\";\n\nexport interface MessageTimestampProps extends HTMLAttributes<HTMLSpanElement> {\n\t/** Custom date formatter */\n\tformat?: (date: Date) => string;\n}\n\n/**\n * Renders the message timestamp.\n */\nexport const MessageTimestamp = forwardRef<HTMLSpanElement, MessageTimestampProps>(({ format, ...props }, ref) => {\n\tconst { message } = useMessage();\n\tconst date = typeof message.createdAt === \"string\" ? new Date(message.createdAt) : message.createdAt;\n\n\tconst formatted = format\n\t\t? format(date)\n\t\t: date.toLocaleTimeString(\"en-US\", {\n\t\t\t\thour: \"numeric\",\n\t\t\t\tminute: \"2-digit\",\n\t\t\t\thour12: true,\n\t\t\t});\n\n\treturn (\n\t\t<span ref={ref} {...props}>\n\t\t\t{formatted}\n\t\t</span>\n\t);\n});\n\nMessageTimestamp.displayName = \"MessageTimestamp\";\n\nexport { MessageContext };\n","import { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useAutoScroll } from \"../hooks/use-auto-scroll\";\n\nexport interface MessageListProps extends HTMLAttributes<HTMLDivElement> {\n\t/** Messages to render */\n\tchildren: ReactNode;\n\t/** Whether to auto-scroll to bottom on new messages */\n\tautoScroll?: boolean;\n\t/** Threshold from bottom to trigger auto-scroll (pixels) */\n\tscrollThreshold?: number;\n}\n\n/**\n * Headless MessageList primitive.\n * Provides a scrollable container with auto-scroll behavior.\n */\nexport const MessageList = forwardRef<HTMLDivElement, MessageListProps>(\n\t({ children, autoScroll = true, scrollThreshold = 100, ...props }, ref) => {\n\t\tconst { containerRef, scrollToBottom } = useAutoScroll<HTMLDivElement>({\n\t\t\tenabled: autoScroll,\n\t\t\tthreshold: scrollThreshold,\n\t\t});\n\n\t\t// Merge refs\n\t\tconst mergedRef = (node: HTMLDivElement | null) => {\n\t\t\t(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node;\n\t\t\tif (typeof ref === \"function\") {\n\t\t\t\tref(node);\n\t\t\t} else if (ref) {\n\t\t\t\tref.current = node;\n\t\t\t}\n\t\t};\n\n\t\treturn (\n\t\t\t<div ref={mergedRef} data-scroll-to-bottom={scrollToBottom} {...props}>\n\t\t\t\t{children}\n\t\t\t</div>\n\t\t);\n\t},\n);\n\nMessageList.displayName = \"MessageList\";\n","import {\n createContext,\n useContext,\n forwardRef,\n type ReactNode,\n type TextareaHTMLAttributes,\n type ButtonHTMLAttributes,\n} from \"react\";\nimport { useMessageComposer } from \"../hooks/use-message-composer\";\n\ninterface ComposerContextValue {\n content: string;\n isSubmitting: boolean;\n canSubmit: boolean;\n error: string | null;\n handleContentChange: (value: string) => void;\n handleSubmit: () => Promise<void>;\n handleKeyDown: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;\n inputRef: React.RefObject<HTMLTextAreaElement | null>;\n}\n\nconst ComposerContext = createContext<ComposerContextValue | null>(null);\n\n/**\n * Hook to access the composer context.\n * Must be used within a MessageComposer component.\n */\nexport function useComposer() {\n const context = useContext(ComposerContext);\n if (!context) {\n throw new Error(\"useComposer must be used within a MessageComposer component\");\n }\n return context;\n}\n\nexport interface MessageComposerProps {\n /** Callback when message is submitted */\n onSubmit: (content: string, attachments?: File[]) => Promise<void>;\n /** Whether the composer is disabled */\n disabled?: boolean;\n /** Children to render inside the composer */\n children: ReactNode;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Headless MessageComposer primitive.\n * Provides composer state and behavior to children.\n */\nexport const MessageComposer = forwardRef<HTMLFormElement, MessageComposerProps>(\n ({ onSubmit, disabled = false, children, className }, ref) => {\n const composer = useMessageComposer({ onSubmit, disabled });\n\n const handleFormSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n composer.handleSubmit();\n };\n\n const contextValue: ComposerContextValue = {\n ...composer,\n canSubmit: Boolean(composer.canSubmit),\n };\n\n return (\n <ComposerContext.Provider value={contextValue}>\n <form\n ref={ref}\n className={className}\n onSubmit={handleFormSubmit}\n data-submitting={composer.isSubmitting}\n data-can-submit={composer.canSubmit}\n >\n {children}\n </form>\n </ComposerContext.Provider>\n );\n }\n);\n\nMessageComposer.displayName = \"MessageComposer\";\n\nexport interface ComposerInputProps\n extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"onChange\" | \"value\"> {}\n\n/**\n * Text input for the message composer.\n */\nexport const ComposerInput = forwardRef<HTMLTextAreaElement, ComposerInputProps>(\n (props, ref) => {\n const { content, handleContentChange, handleKeyDown, inputRef } = useComposer();\n\n // Merge refs\n const mergedRef = (node: HTMLTextAreaElement | null) => {\n (inputRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = node;\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n };\n\n return (\n <textarea\n ref={mergedRef}\n value={content}\n onChange={(e) => handleContentChange(e.target.value)}\n onKeyDown={handleKeyDown}\n {...props}\n />\n );\n }\n);\n\nComposerInput.displayName = \"ComposerInput\";\n\nexport interface ComposerSubmitProps extends ButtonHTMLAttributes<HTMLButtonElement> {}\n\n/**\n * Submit button for the message composer.\n */\nexport const ComposerSubmit = forwardRef<HTMLButtonElement, ComposerSubmitProps>(\n ({ children, disabled, ...props }, ref) => {\n const { canSubmit, isSubmitting } = useComposer();\n\n return (\n <button\n ref={ref}\n type=\"submit\"\n disabled={disabled || !canSubmit}\n data-submitting={isSubmitting}\n {...props}\n >\n {children}\n </button>\n );\n }\n);\n\nComposerSubmit.displayName = \"ComposerSubmit\";\n\nexport { ComposerContext };\n","import { forwardRef, type HTMLAttributes } from \"react\";\nimport type { ParticipantRole } from \"../types\";\n\nexport interface AvatarProps extends HTMLAttributes<HTMLDivElement> {\n /** Name for fallback initials */\n name?: string;\n /** Image URL */\n src?: string;\n /** Alt text for image */\n alt?: string;\n /** Participant role for styling */\n role?: ParticipantRole;\n /** Fallback content when no image or name */\n fallback?: React.ReactNode;\n}\n\n/**\n * Get initials from a name.\n */\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/);\n if (parts.length === 1) {\n return parts[0].charAt(0).toUpperCase();\n }\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n}\n\n/**\n * Headless Avatar primitive.\n * Renders an image or fallback initials.\n */\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>(\n ({ name, src, alt, role, fallback, children, ...props }, ref) => {\n const initials = name ? getInitials(name) : null;\n const altText = alt ?? name ?? role ?? \"Avatar\";\n\n return (\n <div ref={ref} data-role={role} {...props}>\n {children ?? (\n <>\n {src ? (\n <img src={src} alt={altText} style={{ width: \"100%\", height: \"100%\" }} />\n ) : (\n fallback ?? initials ?? role?.charAt(0).toUpperCase()\n )}\n </>\n )}\n </div>\n );\n }\n);\n\nAvatar.displayName = \"Avatar\";\n","import { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nexport interface StreamingTextProps extends HTMLAttributes<HTMLDivElement> {\n /** The content to display */\n content: string;\n /** Whether the text is currently streaming */\n isStreaming?: boolean;\n /** Show a cursor indicator while streaming */\n showCursor?: boolean;\n /** Custom cursor element */\n cursor?: ReactNode;\n /** Children override (takes precedence over content) */\n children?: ReactNode;\n}\n\n/**\n * Headless StreamingText primitive.\n * Renders text with streaming indicator support.\n */\nexport const StreamingText = forwardRef<HTMLDivElement, StreamingTextProps>(\n (\n { content, isStreaming = false, showCursor = true, cursor, children, ...props },\n ref\n ) => {\n const defaultCursor = (\n <span\n aria-hidden=\"true\"\n style={{\n display: \"inline-block\",\n width: \"2px\",\n height: \"1em\",\n backgroundColor: \"currentColor\",\n marginLeft: \"2px\",\n verticalAlign: \"text-bottom\",\n animation: \"blink 1s step-end infinite\",\n }}\n />\n );\n\n return (\n <div ref={ref} data-streaming={isStreaming} {...props}>\n {children ?? content}\n {isStreaming && showCursor && (cursor ?? defaultCursor)}\n </div>\n );\n }\n);\n\nStreamingText.displayName = \"StreamingText\";\n","import {\n createContext,\n useContext,\n forwardRef,\n type ReactNode,\n type ButtonHTMLAttributes,\n} from \"react\";\nimport type { SuggestedAction } from \"../types\";\n\ninterface SuggestedActionsContextValue {\n actions: SuggestedAction[];\n onSelect: (action: SuggestedAction) => void;\n}\n\nconst SuggestedActionsContext = createContext<SuggestedActionsContextValue | null>(null);\n\n/**\n * Hook to access suggested actions context.\n */\nexport function useSuggestedActions() {\n const context = useContext(SuggestedActionsContext);\n if (!context) {\n throw new Error(\n \"useSuggestedActions must be used within a SuggestedActions component\"\n );\n }\n return context;\n}\n\nexport interface SuggestedActionsProps {\n /** List of suggested actions */\n actions: SuggestedAction[];\n /** Callback when an action is selected */\n onSelect: (action: SuggestedAction) => void;\n /** Children to render */\n children: ReactNode;\n /** Additional CSS classes */\n className?: string;\n}\n\n/**\n * Headless SuggestedActions primitive.\n * Provides context for rendering action buttons.\n */\nexport const SuggestedActions = forwardRef<HTMLDivElement, SuggestedActionsProps>(\n ({ actions, onSelect, children, className }, ref) => {\n if (actions.length === 0) {\n return null;\n }\n\n return (\n <SuggestedActionsContext.Provider value={{ actions, onSelect }}>\n <div ref={ref} role=\"group\" aria-label=\"Suggested actions\" className={className}>\n {children}\n </div>\n </SuggestedActionsContext.Provider>\n );\n }\n);\n\nSuggestedActions.displayName = \"SuggestedActions\";\n\nexport interface ActionButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** The action this button represents */\n action: SuggestedAction;\n /** Children to render inside button */\n children?: ReactNode;\n}\n\n/**\n * Button for a suggested action.\n */\nexport const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>(\n ({ action, children, onClick, ...props }, ref) => {\n const { onSelect } = useSuggestedActions();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n if (!e.defaultPrevented) {\n onSelect(action);\n }\n };\n\n return (\n <button ref={ref} type=\"button\" onClick={handleClick} {...props}>\n {children ?? action.label}\n </button>\n );\n }\n);\n\nActionButton.displayName = \"ActionButton\";\n\nexport { SuggestedActionsContext };\n","import { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nexport interface TypingIndicatorProps extends HTMLAttributes<HTMLDivElement> {\n /** Custom content to show while typing */\n children?: ReactNode;\n /** Number of dots to show */\n dotCount?: number;\n}\n\n/**\n * Headless TypingIndicator primitive.\n * Shows animated dots or custom content.\n */\nexport const TypingIndicator = forwardRef<HTMLDivElement, TypingIndicatorProps>(\n ({ children, dotCount = 3, ...props }, ref) => {\n return (\n <div ref={ref} role=\"status\" aria-label=\"Assistant is typing\" {...props}>\n {children ?? (\n <span aria-hidden=\"true\">\n {Array.from({ length: dotCount }).map((_, i) => (\n <span\n key={i}\n style={{\n display: \"inline-block\",\n width: \"6px\",\n height: \"6px\",\n borderRadius: \"50%\",\n backgroundColor: \"currentColor\",\n marginRight: i < dotCount - 1 ? \"4px\" : 0,\n animation: `typing 1.4s infinite ease-in-out`,\n animationDelay: `${i * 0.2}s`,\n }}\n />\n ))}\n </span>\n )}\n </div>\n );\n }\n);\n\nTypingIndicator.displayName = \"TypingIndicator\";\n"]}
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react');function i(e){let n=e.trim();if(n.startsWith("#")){let r=n.slice(1);if(r.length===3)return [parseInt(r[0]+r[0],16),parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16)];if(r.length>=6)return [parseInt(r.slice(0,2),16),parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16)]}let t=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/);return t?[Number(t[1]),Number(t[2]),Number(t[3])]:null}function c(e,n,t){let[r,a,u]=[e/255,n/255,t/255].map(s=>s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4));return .2126*r+.7152*a+.0722*u}function o(e){let n=i(e);return n&&c(...n)>.179?"#1D2033":"#ffffff"}function p(e,n){return react.useMemo(()=>{if(!e&&!n?.iconColor)return {};let t={},r=e?.backgroundBubbleColor||n?.iconColor;return r&&(t["--chat-primary"]=r,t["--chat-user-message-bg"]=r,t["--chat-user-message-text"]=o(r)),e?.headerBackgroundColor&&(t["--chat-header-bg"]=e.headerBackgroundColor),e?.messageFontSize&&(t["--chat-message-font-size"]=`${e.messageFontSize}px`),t},[e,n?.iconColor])}exports.a=o;exports.b=p;//# sourceMappingURL=chunk-NSTK5EUQ.js.map
2
+ //# sourceMappingURL=chunk-NSTK5EUQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/color-contrast.ts","../src/hooks/use-branding-css-vars.ts"],"names":["parseColor","color","hex","h","rgbMatch","relativeLuminance","r","g","b","rs","gs","bs","c","getContrastTextColor","bgColor","rgb","useBrandingCSSVars","branding","overrides","useMemo","vars","primaryColor"],"mappings":"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","file":"chunk-NSTK5EUQ.js","sourcesContent":["/**\n * Parse a CSS color string (hex, rgb, rgba) into [r, g, b] values.\n * Returns null if the color cannot be parsed.\n */\nfunction parseColor(color: string): [number, number, number] | null {\n // Hex: #RGB, #RRGGBB\n const hex = color.trim();\n if (hex.startsWith(\"#\")) {\n const h = hex.slice(1);\n if (h.length === 3) {\n return [\n parseInt(h[0] + h[0], 16),\n parseInt(h[1] + h[1], 16),\n parseInt(h[2] + h[2], 16),\n ];\n }\n if (h.length >= 6) {\n return [\n parseInt(h.slice(0, 2), 16),\n parseInt(h.slice(2, 4), 16),\n parseInt(h.slice(4, 6), 16),\n ];\n }\n }\n\n // rgb(r, g, b) or rgba(r, g, b, a)\n const rgbMatch = color.match(\n /rgba?\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})/\n );\n if (rgbMatch) {\n return [Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3])];\n }\n\n return null;\n}\n\n/**\n * Compute the relative luminance of an sRGB color per WCAG 2.0.\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n */\nfunction relativeLuminance(r: number, g: number, b: number): number {\n const [rs, gs, bs] = [r / 255, g / 255, b / 255].map((c) =>\n c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)\n );\n return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;\n}\n\n/**\n * Given a background color string, return the best contrast text color.\n * Returns \"#ffffff\" for dark backgrounds and \"#1D2033\" for light backgrounds.\n * Falls back to \"#ffffff\" if the color cannot be parsed.\n */\nexport function getContrastTextColor(bgColor: string): string {\n const rgb = parseColor(bgColor);\n if (!rgb) return \"#ffffff\";\n\n const lum = relativeLuminance(...rgb);\n // WCAG threshold: luminance > 0.179 means the background is \"light\"\n return lum > 0.179 ? \"#1D2033\" : \"#ffffff\";\n}\n","import { useMemo } from \"react\";\nimport type { BrandingData } from \"../types/branding\";\nimport { getContrastTextColor } from \"../utils/color-contrast\";\n\n/**\n * Converts BrandingData into CSS custom properties for chat-ui theming.\n * Returns a CSSProperties object that can be spread onto a container element.\n */\nexport function useBrandingCSSVars(\n branding: BrandingData | null | undefined,\n overrides?: { iconColor?: string }\n): React.CSSProperties {\n return useMemo(() => {\n if (!branding && !overrides?.iconColor) return {};\n\n const vars: Record<string, string> = {};\n\n const primaryColor =\n branding?.backgroundBubbleColor || overrides?.iconColor;\n if (primaryColor) {\n vars[\"--chat-primary\"] = primaryColor;\n vars[\"--chat-user-message-bg\"] = primaryColor;\n vars[\"--chat-user-message-text\"] = getContrastTextColor(primaryColor);\n }\n\n if (branding?.headerBackgroundColor) {\n vars[\"--chat-header-bg\"] = branding.headerBackgroundColor;\n }\n\n if (branding?.messageFontSize) {\n vars[\"--chat-message-font-size\"] = `${branding.messageFontSize}px`;\n }\n\n return vars as React.CSSProperties;\n }, [branding, overrides?.iconColor]);\n}\n"]}
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var e=react.createContext(null);function P({children:t,messages:o,isStreaming:n=false,streamingMessageId:a=null,viewerRole:i="user",onSendMessage:r,onStopStreaming:s,onRetryLastMessage:l,customData:C,onVisualizationAction:u}){let c=react.useCallback(async(h,v)=>{await r(h,v);},[r]),d=react.useMemo(()=>({messages:o,isStreaming:n,streamingMessageId:a,viewerRole:i,sendMessage:c,stopStreaming:s,retryLastMessage:l,customData:C,onVisualizationAction:u}),[o,n,a,i,c,s,l,C,u]);return jsxRuntime.jsx(e.Provider,{value:d,children:t})}function f(){let t=react.useContext(e);if(!t)throw new Error("useChatContext must be used within a ChatProvider");return t}exports.a=e;exports.b=P;exports.c=f;//# sourceMappingURL=chunk-OCKHJ4WO.js.map
2
+ //# sourceMappingURL=chunk-OCKHJ4WO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/ChatProvider.tsx"],"names":["ChatContext","createContext","ChatProvider","children","messages","isStreaming","streamingMessageId","viewerRole","onSendMessage","onStopStreaming","onRetryLastMessage","customData","onVisualizationAction","sendMessage","useCallback","content","attachments","value","useMemo","jsx","useChatContext","context","useContext"],"mappings":"gFA8BA,IAAMA,CAAAA,CAAcC,mBAAAA,CAAuC,IAAI,EAexD,SAASC,CAAAA,CAAa,CAC3B,QAAA,CAAAC,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,WAAA,CAAAC,CAAAA,CAAc,KAAA,CACd,kBAAA,CAAAC,CAAAA,CAAqB,IAAA,CACrB,UAAA,CAAAC,CAAAA,CAAa,MAAA,CACb,aAAA,CAAAC,CAAAA,CACA,eAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CACF,CAAA,CAAsB,CACpB,IAAMC,CAAAA,CAAcC,iBAAAA,CAClB,MAAOC,CAAAA,CAAiBC,CAAAA,GAAyB,CAC/C,MAAMR,CAAAA,CAAcO,CAAAA,CAASC,CAAW,EAC1C,CAAA,CACA,CAACR,CAAa,CAChB,CAAA,CAEMS,CAAAA,CAAQC,aAAAA,CACZ,KAAO,CACL,QAAA,CAAAd,CAAAA,CACA,WAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,WAAA,CAAAM,CAAAA,CACA,aAAA,CAAeJ,CAAAA,CACf,gBAAA,CAAkBC,CAAAA,CAClB,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CACF,CAAA,CAAA,CACA,CACER,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAM,CAAAA,CACAJ,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CACF,CACF,CAAA,CAEA,OAAOO,cAAAA,CAACnB,CAAAA,CAAY,QAAA,CAAZ,CAAqB,KAAA,CAAOiB,CAAAA,CAAQ,QAAA,CAAAd,CAAAA,CAAS,CACvD,CAEO,SAASiB,CAAAA,EAAiB,CAC/B,IAAMC,CAAAA,CAAUC,gBAAAA,CAAWtB,CAAW,CAAA,CACtC,GAAI,CAACqB,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,mDAAmD,CAAA,CAErE,OAAOA,CACT","file":"chunk-OCKHJ4WO.js","sourcesContent":["import {\n createContext,\n useContext,\n useCallback,\n useMemo,\n type ReactNode,\n} from \"react\";\nimport type { ChatMessage, ParticipantRole, VisualizationActionEvent } from \"../types\";\n\nexport interface ChatContextValue {\n /** List of messages in the conversation */\n messages: ChatMessage[];\n /** Whether a message is currently being streamed */\n isStreaming: boolean;\n /** ID of the message currently being streamed */\n streamingMessageId: string | null;\n /** The viewer's role (determines message alignment) */\n viewerRole: ParticipantRole;\n /** Send a new message */\n sendMessage: (content: string, attachments?: File[]) => Promise<void>;\n /** Stop the current streaming response */\n stopStreaming?: () => void;\n /** Retry the last failed message */\n retryLastMessage?: () => Promise<void>;\n /** Custom data passed through context */\n customData?: Record<string, unknown>;\n /** Callback when user interacts with a visualization (form submit, card action, etc.) */\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n}\n\nconst ChatContext = createContext<ChatContextValue | null>(null);\n\nexport interface ChatProviderProps {\n children: ReactNode;\n messages: ChatMessage[];\n isStreaming?: boolean;\n streamingMessageId?: string | null;\n viewerRole?: ParticipantRole;\n onSendMessage: (content: string, attachments?: File[]) => Promise<void>;\n onStopStreaming?: () => void;\n onRetryLastMessage?: () => Promise<void>;\n customData?: Record<string, unknown>;\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n}\n\nexport function ChatProvider({\n children,\n messages,\n isStreaming = false,\n streamingMessageId = null,\n viewerRole = \"user\",\n onSendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n}: ChatProviderProps) {\n const sendMessage = useCallback(\n async (content: string, attachments?: File[]) => {\n await onSendMessage(content, attachments);\n },\n [onSendMessage]\n );\n\n const value = useMemo<ChatContextValue>(\n () => ({\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n stopStreaming: onStopStreaming,\n retryLastMessage: onRetryLastMessage,\n customData,\n onVisualizationAction,\n }),\n [\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n ]\n );\n\n return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;\n}\n\nexport function useChatContext() {\n const context = useContext(ChatContext);\n if (!context) {\n throw new Error(\"useChatContext must be used within a ChatProvider\");\n }\n return context;\n}\n\nexport { ChatContext };\n"]}
@@ -0,0 +1,2 @@
1
+ import {createContext,useCallback,useMemo,useContext}from'react';import {jsx}from'react/jsx-runtime';var e=createContext(null);function P({children:t,messages:o,isStreaming:n=false,streamingMessageId:a=null,viewerRole:i="user",onSendMessage:r,onStopStreaming:s,onRetryLastMessage:l,customData:C,onVisualizationAction:u}){let c=useCallback(async(h,v)=>{await r(h,v);},[r]),d=useMemo(()=>({messages:o,isStreaming:n,streamingMessageId:a,viewerRole:i,sendMessage:c,stopStreaming:s,retryLastMessage:l,customData:C,onVisualizationAction:u}),[o,n,a,i,c,s,l,C,u]);return jsx(e.Provider,{value:d,children:t})}function f(){let t=useContext(e);if(!t)throw new Error("useChatContext must be used within a ChatProvider");return t}export{e as a,P as b,f as c};//# sourceMappingURL=chunk-RTT6LULU.mjs.map
2
+ //# sourceMappingURL=chunk-RTT6LULU.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context/ChatProvider.tsx"],"names":["ChatContext","createContext","ChatProvider","children","messages","isStreaming","streamingMessageId","viewerRole","onSendMessage","onStopStreaming","onRetryLastMessage","customData","onVisualizationAction","sendMessage","useCallback","content","attachments","value","useMemo","jsx","useChatContext","context","useContext"],"mappings":"qGA8BA,IAAMA,CAAAA,CAAcC,aAAAA,CAAuC,IAAI,EAexD,SAASC,CAAAA,CAAa,CAC3B,QAAA,CAAAC,CAAAA,CACA,QAAA,CAAAC,CAAAA,CACA,WAAA,CAAAC,CAAAA,CAAc,KAAA,CACd,kBAAA,CAAAC,CAAAA,CAAqB,IAAA,CACrB,UAAA,CAAAC,CAAAA,CAAa,MAAA,CACb,aAAA,CAAAC,CAAAA,CACA,eAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CACF,CAAA,CAAsB,CACpB,IAAMC,CAAAA,CAAcC,WAAAA,CAClB,MAAOC,CAAAA,CAAiBC,CAAAA,GAAyB,CAC/C,MAAMR,CAAAA,CAAcO,CAAAA,CAASC,CAAW,EAC1C,CAAA,CACA,CAACR,CAAa,CAChB,CAAA,CAEMS,CAAAA,CAAQC,OAAAA,CACZ,KAAO,CACL,QAAA,CAAAd,CAAAA,CACA,WAAA,CAAAC,CAAAA,CACA,kBAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,WAAA,CAAAM,CAAAA,CACA,aAAA,CAAeJ,CAAAA,CACf,gBAAA,CAAkBC,CAAAA,CAClB,UAAA,CAAAC,CAAAA,CACA,qBAAA,CAAAC,CACF,CAAA,CAAA,CACA,CACER,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAM,CAAAA,CACAJ,CAAAA,CACAC,CAAAA,CACAC,CAAAA,CACAC,CACF,CACF,CAAA,CAEA,OAAOO,GAAAA,CAACnB,CAAAA,CAAY,QAAA,CAAZ,CAAqB,KAAA,CAAOiB,CAAAA,CAAQ,QAAA,CAAAd,CAAAA,CAAS,CACvD,CAEO,SAASiB,CAAAA,EAAiB,CAC/B,IAAMC,CAAAA,CAAUC,UAAAA,CAAWtB,CAAW,CAAA,CACtC,GAAI,CAACqB,CAAAA,CACH,MAAM,IAAI,KAAA,CAAM,mDAAmD,CAAA,CAErE,OAAOA,CACT","file":"chunk-RTT6LULU.mjs","sourcesContent":["import {\n createContext,\n useContext,\n useCallback,\n useMemo,\n type ReactNode,\n} from \"react\";\nimport type { ChatMessage, ParticipantRole, VisualizationActionEvent } from \"../types\";\n\nexport interface ChatContextValue {\n /** List of messages in the conversation */\n messages: ChatMessage[];\n /** Whether a message is currently being streamed */\n isStreaming: boolean;\n /** ID of the message currently being streamed */\n streamingMessageId: string | null;\n /** The viewer's role (determines message alignment) */\n viewerRole: ParticipantRole;\n /** Send a new message */\n sendMessage: (content: string, attachments?: File[]) => Promise<void>;\n /** Stop the current streaming response */\n stopStreaming?: () => void;\n /** Retry the last failed message */\n retryLastMessage?: () => Promise<void>;\n /** Custom data passed through context */\n customData?: Record<string, unknown>;\n /** Callback when user interacts with a visualization (form submit, card action, etc.) */\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n}\n\nconst ChatContext = createContext<ChatContextValue | null>(null);\n\nexport interface ChatProviderProps {\n children: ReactNode;\n messages: ChatMessage[];\n isStreaming?: boolean;\n streamingMessageId?: string | null;\n viewerRole?: ParticipantRole;\n onSendMessage: (content: string, attachments?: File[]) => Promise<void>;\n onStopStreaming?: () => void;\n onRetryLastMessage?: () => Promise<void>;\n customData?: Record<string, unknown>;\n onVisualizationAction?: (event: VisualizationActionEvent) => void;\n}\n\nexport function ChatProvider({\n children,\n messages,\n isStreaming = false,\n streamingMessageId = null,\n viewerRole = \"user\",\n onSendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n}: ChatProviderProps) {\n const sendMessage = useCallback(\n async (content: string, attachments?: File[]) => {\n await onSendMessage(content, attachments);\n },\n [onSendMessage]\n );\n\n const value = useMemo<ChatContextValue>(\n () => ({\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n stopStreaming: onStopStreaming,\n retryLastMessage: onRetryLastMessage,\n customData,\n onVisualizationAction,\n }),\n [\n messages,\n isStreaming,\n streamingMessageId,\n viewerRole,\n sendMessage,\n onStopStreaming,\n onRetryLastMessage,\n customData,\n onVisualizationAction,\n ]\n );\n\n return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;\n}\n\nexport function useChatContext() {\n const context = useContext(ChatContext);\n if (!context) {\n throw new Error(\"useChatContext must be used within a ChatProvider\");\n }\n return context;\n}\n\nexport { ChatContext };\n"]}
@@ -0,0 +1,249 @@
1
+ import { C as ChatMessage } from '../message-ufYsvKXP.mjs';
2
+ import { B as BrandingData } from '../branding-SzYU4ncD.mjs';
3
+ import '../streaming-CF63E6iS.mjs';
4
+
5
+ /**
6
+ * Types for the Miiflow chat client (transport layer).
7
+ */
8
+
9
+ interface MiiflowChatConfig {
10
+ /** Public API key for authentication */
11
+ publicKey: string;
12
+ /** Assistant ID to connect to */
13
+ assistantId: string;
14
+ /** Optional user ID for identity */
15
+ userId?: string;
16
+ /** Optional user display name */
17
+ userName?: string;
18
+ /** Optional user email */
19
+ userEmail?: string;
20
+ /** Optional user metadata (JSON string) */
21
+ userMetadata?: string;
22
+ /** HMAC for identity verification */
23
+ hmac?: string;
24
+ /** Timestamp for HMAC verification */
25
+ timestamp?: string;
26
+ /** Override base URL (auto-detected from bundleUrl otherwise) */
27
+ baseUrl?: string;
28
+ /** Bundle URL used for dev detection (internal, set by script embed) */
29
+ bundleUrl?: string;
30
+ /** Response timeout in milliseconds (default: 60000) */
31
+ responseTimeout?: number;
32
+ /** WebSocket URL for bidirectional events (tool invocations). Auto-derived from baseUrl if not set. */
33
+ webSocketUrl?: string;
34
+ /** Fallback for tool invocations not handled locally (multi-widget routing) */
35
+ onToolInvocationFallback?: (invocation: ToolInvocationRequest) => Promise<boolean>;
36
+ /** Callback fired when a user message is created (for widget event emission) */
37
+ onUserMessageCreated?: (message: {
38
+ id: string;
39
+ content: string;
40
+ }) => void;
41
+ /** Callback fired when an assistant message stream completes (for widget event emission) */
42
+ onAssistantMessageComplete?: (message: {
43
+ id: string;
44
+ content: string;
45
+ }) => void;
46
+ }
47
+ interface EmbedSessionBranding {
48
+ show_header?: boolean;
49
+ custom_name?: string;
50
+ welcome_message?: string;
51
+ chatbox_placeholder?: string;
52
+ background_bubble_color?: string;
53
+ header_background_color?: string;
54
+ message_font_size?: number;
55
+ rotating_placeholders?: string[];
56
+ preset_questions?: string[];
57
+ assistant_avatar?: string;
58
+ chatbot_logo?: string;
59
+ }
60
+ interface EmbedSessionConfig {
61
+ assistant_id: string;
62
+ assistant_name: string;
63
+ assistant_description: string;
64
+ thread_id: string;
65
+ branding?: EmbedSessionBranding;
66
+ }
67
+ interface EmbedSession {
68
+ token: string;
69
+ config: EmbedSessionConfig;
70
+ session_id: string;
71
+ }
72
+ interface MiiflowChatResult {
73
+ /** Chat messages in ChatMessage format (directly compatible with ChatProvider) */
74
+ messages: ChatMessage[];
75
+ /** Whether a response is currently streaming */
76
+ isStreaming: boolean;
77
+ /** ID of the message being streamed */
78
+ streamingMessageId: string | null;
79
+ /** Send a message to the assistant, optionally with attachment IDs */
80
+ sendMessage: (content: string, attachmentIds?: string[]) => Promise<void>;
81
+ /** Upload a file and return its attachment ID */
82
+ uploadFile: (file: File) => Promise<string>;
83
+ /** Remove uploaded attachment metadata (call when user removes attachment before sending) */
84
+ removeUploadedAttachment: (attachmentId: string) => void;
85
+ /** Current session data */
86
+ session: EmbedSession | null;
87
+ /** Whether the session is still initializing */
88
+ loading: boolean;
89
+ /** Error message if initialization or sending failed */
90
+ error: string | null;
91
+ /** Branding data from the session, mapped to BrandingData */
92
+ branding: BrandingData | null;
93
+ /** CSS custom properties derived from branding (spread onto container) */
94
+ brandingCSSVars: React.CSSProperties;
95
+ /** Start a new thread, returns the new thread ID */
96
+ startNewThread: () => Promise<string>;
97
+ /** Register a client-side tool */
98
+ registerTool: (tool: ClientToolDefinition) => Promise<void>;
99
+ /** Register multiple client-side tools */
100
+ registerTools: (tools: ClientToolDefinition[]) => Promise<void>;
101
+ /** Send a system event (invisible to chat, processed by assistant) */
102
+ sendSystemEvent: (event: SystemEvent) => Promise<void>;
103
+ /** Execute a client tool invocation (called when backend invokes a registered tool). Returns true if handled. */
104
+ handleToolInvocation: (invocation: ToolInvocationRequest) => Promise<boolean>;
105
+ /** Update the session externally (e.g. after token refresh) */
106
+ updateSession: (session: EmbedSession) => void;
107
+ }
108
+ interface JSONSchemaProperty {
109
+ type: "string" | "number" | "integer" | "boolean" | "array" | "object" | "null";
110
+ description?: string;
111
+ enum?: Array<string | number | boolean>;
112
+ items?: JSONSchemaProperty;
113
+ properties?: Record<string, JSONSchemaProperty>;
114
+ required?: string[];
115
+ default?: unknown;
116
+ }
117
+ interface JSONSchemaObject {
118
+ type: "object";
119
+ properties: Record<string, JSONSchemaProperty>;
120
+ required?: string[];
121
+ additionalProperties?: boolean | JSONSchemaProperty;
122
+ description?: string;
123
+ }
124
+ type ToolHandler = (params: Record<string, unknown>) => Promise<unknown>;
125
+ interface ClientToolDefinition {
126
+ /** Tool name (alphanumeric + underscore, must start with letter/underscore) */
127
+ name: string;
128
+ /** Description of what the tool does (shown to LLM) */
129
+ description: string;
130
+ /** JSON Schema defining the tool's parameters */
131
+ parameters: JSONSchemaObject;
132
+ /** Handler function to execute when LLM calls this tool */
133
+ handler: ToolHandler;
134
+ }
135
+ interface ToolInvocationRequest {
136
+ invocation_id: string;
137
+ tool_name: string;
138
+ parameters: Record<string, unknown>;
139
+ }
140
+ interface ToolExecutionResult {
141
+ invocation_id: string;
142
+ result?: unknown;
143
+ error?: string;
144
+ }
145
+ interface SystemEvent {
146
+ action: string;
147
+ description: string;
148
+ followUpInstruction: string;
149
+ metadata?: Record<string, unknown>;
150
+ }
151
+ type WidgetEventType = "widget_ready" | "widget_opened" | "widget_closed" | "widget_reconnected" | "session_start" | "session_end" | "thread_started" | "new_message" | "message_sent" | "message_received" | "error" | "load_failed" | "assistant_response_timeout";
152
+ type WidgetEventPayload = Record<string, unknown> | undefined;
153
+ type WidgetEventCallback = (type: WidgetEventType, payload?: WidgetEventPayload) => void;
154
+
155
+ /**
156
+ * useMiiflowChat - React hook for connecting to Miiflow's embedded chat API.
157
+ *
158
+ * Handles session init, SSE streaming, message management, tool registration,
159
+ * and branding. Returns a shape directly compatible with ChatProvider props.
160
+ */
161
+
162
+ declare function useMiiflowChat(config: MiiflowChatConfig): MiiflowChatResult;
163
+
164
+ /**
165
+ * Determine the backend base URL from config.
166
+ */
167
+ declare function getBackendBaseUrl(config: MiiflowChatConfig): string;
168
+ /**
169
+ * Get or create a persistent anonymous user ID stored in localStorage.
170
+ */
171
+ declare function getOrCreateUserId(): string;
172
+ /**
173
+ * Initialize an embed session by calling the backend init endpoint.
174
+ */
175
+ declare function initSession(config: MiiflowChatConfig): Promise<EmbedSession>;
176
+ /**
177
+ * Create a new thread for the current session.
178
+ */
179
+ declare function createThread(config: MiiflowChatConfig, session: EmbedSession): Promise<{
180
+ threadId: string;
181
+ token?: string;
182
+ }>;
183
+ /**
184
+ * Update user data for the current session.
185
+ */
186
+ declare function updateUser(config: MiiflowChatConfig, session: EmbedSession, userData: {
187
+ user_id?: string;
188
+ name?: string;
189
+ email?: string;
190
+ }): Promise<void>;
191
+ /**
192
+ * Upload a file attachment via REST endpoint.
193
+ * Returns the attachment ID for use in sendMessage.
194
+ */
195
+ declare function uploadFile(config: MiiflowChatConfig, session: EmbedSession, file: File): Promise<string>;
196
+ /**
197
+ * Send a system event to the backend (invisible to chat, processed by assistant).
198
+ */
199
+ declare function sendSystemEvent(config: MiiflowChatConfig, session: EmbedSession, systemEvent: SystemEvent): Promise<void>;
200
+ /**
201
+ * Send a tool execution result back to the backend.
202
+ */
203
+ declare function sendToolResult(config: MiiflowChatConfig, session: EmbedSession, result: ToolExecutionResult): Promise<void>;
204
+ /**
205
+ * Register tool definitions with the backend.
206
+ */
207
+ declare function registerToolsOnBackend(config: MiiflowChatConfig, session: EmbedSession, toolDefinitions: Array<Omit<ClientToolDefinition, "handler">>): Promise<void>;
208
+
209
+ /**
210
+ * Utility functions for parsing and managing JWT tokens.
211
+ * Used for proactive token refresh before expiration.
212
+ */
213
+ /**
214
+ * Parse the expiry time from a JWT token.
215
+ * Returns the expiry timestamp in milliseconds, or null if parsing fails.
216
+ */
217
+ declare function parseTokenExpiry(token: string): number | null;
218
+ /**
219
+ * Check if a token is expiring soon (within the given threshold).
220
+ */
221
+ declare function isTokenExpiringSoon(token: string, thresholdMs: number): boolean;
222
+ /**
223
+ * Check if a token has already expired.
224
+ */
225
+ declare function isTokenExpired(token: string): boolean;
226
+ /**
227
+ * Get the time remaining until token expiry in milliseconds.
228
+ */
229
+ declare function getTimeUntilExpiry(token: string): number;
230
+
231
+ /**
232
+ * Client-side validator for tool definitions.
233
+ * Validates tool definitions before they're sent to the backend.
234
+ */
235
+
236
+ declare class ToolValidationError extends Error {
237
+ constructor(message: string);
238
+ }
239
+ /**
240
+ * Validates a client tool definition.
241
+ * Throws ToolValidationError if validation fails.
242
+ */
243
+ declare function validateToolDefinition(tool: ClientToolDefinition): void;
244
+ /**
245
+ * Strips the handler function from a tool definition for sending to backend.
246
+ */
247
+ declare function serializeToolDefinition(tool: ClientToolDefinition): Omit<ClientToolDefinition, "handler">;
248
+
249
+ export { type ClientToolDefinition, type EmbedSession, type EmbedSessionBranding, type EmbedSessionConfig, type JSONSchemaObject, type JSONSchemaProperty, type MiiflowChatConfig, type MiiflowChatResult, type SystemEvent, type ToolExecutionResult, type ToolHandler, type ToolInvocationRequest, ToolValidationError, type WidgetEventCallback, type WidgetEventPayload, type WidgetEventType, createThread, getBackendBaseUrl, getOrCreateUserId, getTimeUntilExpiry, initSession, isTokenExpired, isTokenExpiringSoon, parseTokenExpiry, registerToolsOnBackend, sendSystemEvent, sendToolResult, serializeToolDefinition, updateUser, uploadFile, useMiiflowChat, validateToolDefinition };