@holaboss/client 0.2.0-beta.4 → 0.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use client'
2
- var F=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var ee=Object.prototype.hasOwnProperty;var te=(e,t)=>{for(var a in t)F(e,a,{get:t[a],enumerable:!0})},re=(e,t,a,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Z(t))!ee.call(e,n)&&n!==a&&F(e,n,{get:()=>t[n],enumerable:!(s=X(t,n))||s.enumerable});return e};var oe=e=>re(F({},"__esModule",{value:!0}),e);var ue={};te(ue,{Chat:()=>B,HolaProvider:()=>q,PortalConflict:()=>v,createApiTransport:()=>z,createHolaClient:()=>K,createPortalData:()=>V,createWidgetTransport:()=>O,mergeMessage:()=>L,textDeltaOf:()=>I,thinkingDeltaOf:()=>R,toolEventOf:()=>E,useArtifactUrl:()=>k,useChat:()=>U,useHolaClient:()=>A,useThread:()=>Q});module.exports=oe(ue);var W=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},I=e=>{let t=W(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},R=e=>{let t=W(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},E=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var se=/^ /,C=e=>{if(e&&typeof e=="object"){let t=e,a=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:a,retryable:t.retryable}:{error:a}}return{error:"request_failed"}},ne=e=>{let t="message",a=[];for(let s of e.split(`
3
- `))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&a.push(s.slice(5).replace(se,"")));return a.length>0?{event:t,data:a.join(`
4
- `)}:null},ae=(e,t,a)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let n=s.sessionId;n&&(a(n),t.onSession?.(n));return}if(e.event==="turn_event"){t.onEvent?.(s);let n=I(s);n&&t.onText?.(n);let d=R(s);d&&t.onThinking?.(d);let r=E(s);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let n=s;a(n.sessionId),t.onComplete?.(n);return}e.event==="chat_error"&&t.onError?.(C(s))},h=async(e,t)=>{let a=await fetch(e,{headers:t});if(!a.ok)throw new Error(`hola: request failed (${a.status})`);return await a.json()},j=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},N=async(e,t,a,s)=>{let n=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:s});if(!(n.ok&&n.body)){t.onError?.({error:`stream_failed_${n.status}`});return}let d=n.body.getReader(),r=new TextDecoder,o="",l=()=>{let c=o.indexOf(`
2
+ var _=Object.defineProperty;var de=Object.getOwnPropertyDescriptor;var ue=Object.getOwnPropertyNames;var pe=Object.prototype.hasOwnProperty;var ce=(e,t)=>{for(var o in t)_(e,o,{get:t[o],enumerable:!0})},ge=(e,t,o,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ue(t))!pe.call(e,r)&&r!==o&&_(e,r,{get:()=>t[r],enumerable:!(s=de(t,r))||s.enumerable});return e};var me=e=>ge(_({},"__esModule",{value:!0}),e);var De={};ce(De,{Chat:()=>Z,HolaProvider:()=>Q,PortalConflict:()=>S,createApiTransport:()=>V,createHolaClient:()=>K,createPortalData:()=>ae,createWidgetTransport:()=>F,deriveFields:()=>N,inferSpec:()=>L,mergeMessage:()=>z,parseDashboardSpec:()=>B,renderModel:()=>j,textDeltaOf:()=>v,thinkingDeltaOf:()=>R,toolEventOf:()=>I,useArtifactUrl:()=>A,useChat:()=>D,useHolaClient:()=>W,useThread:()=>le});module.exports=me(De);var G=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},v=e=>{let t=G(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},R=e=>{let t=G(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},I=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var fe=/^ /,k=e=>{if(e&&typeof e=="object"){let t=e,o=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:o,retryable:t.retryable}:{error:o}}return{error:"request_failed"}},ye=e=>{let t="message",o=[];for(let s of e.split(`
3
+ `))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&o.push(s.slice(5).replace(fe,"")));return o.length>0?{event:t,data:o.join(`
4
+ `)}:null},be=(e,t,o)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let r=s.sessionId;r&&(o(r),t.onSession?.(r));return}if(e.event==="turn_event"){t.onEvent?.(s);let r=v(s);r&&t.onText?.(r);let l=R(s);l&&t.onThinking?.(l);let n=I(s);n&&t.onTool?.(n);return}if(e.event==="chat_completed"){let r=s;o(r.sessionId),t.onComplete?.(r);return}e.event==="chat_error"&&t.onError?.(k(s))},h=async(e,t)=>{let o=await fetch(e,{headers:t});if(!o.ok)throw new Error(`hola: request failed (${o.status})`);return await o.json()},J=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},q=async(e,t,o,s)=>{let r=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:s});if(!(r.ok&&r.body)){t.onError?.({error:`stream_failed_${r.status}`});return}let l=r.body.getReader(),n=new TextDecoder,a="",i=()=>{let u=a.indexOf(`
5
5
 
6
- `);for(;c!==-1;){let b=o.slice(0,c);o=o.slice(c+2);let u=ne(b);u&&ae(u,t,a),c=o.indexOf(`
6
+ `);for(;u!==-1;){let b=a.slice(0,u);a=a.slice(u+2);let c=ye(b);c&&be(c,t,o),u=a.indexOf(`
7
7
 
8
- `)}},i=async()=>{let c=await d.read();if(!c.done)return o+=r.decode(c.value,{stream:!0}),l(),i()};await i()};var ie=/\/+$/,S="/api/v1/end-user",z=e=>{let t=e.baseUrl.replace(ie,""),a=()=>typeof e.token=="function"?e.token():e.token,s=r=>e.project?`${r}project=${encodeURIComponent(e.project)}`:"",n=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),d=async r=>{let o=await fetch(`${t}${S}/chat${s("?")}`,{method:"POST",headers:await n(),body:JSON.stringify({message:r})});if(!o.ok)throw new Error(`hola: message failed (${o.status})`);return{reply:(await o.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>h(`${t}${S}/chat/info${s("?")}`,await n()),sendMessage:d,streamMessage:async(r,o)=>{try{let l=await d(r);o.onText?.(l.reply),o.onComplete?.(l)}catch(l){o.onError?.(C(l))}},subscribe:(r,o)=>{let l=o?.since??"0",i=!1,c,b=o?.intervalMs??3e3,u=async()=>{try{let g=await h(`${t}${S}/chat/thread?since=${encodeURIComponent(l)}${s("&")}`,await n());for(let m of g.messages)r.onMessage(m);l=g.cursor}catch(g){r.onError?.(C(g))}i||(c=setTimeout(u,b))},p=()=>{i=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{await fetch(`${t}${S}/chat/request-human${s("?")}`,{method:"POST",headers:await n(),body:"{}"})}}};var $=require("react"),k=(e,t)=>{let[a,s]=(0,$.useState)("");return(0,$.useEffect)(()=>{if(!e){s("");return}let n=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),d=URL.createObjectURL(new Blob([n],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),a};var D=require("react");var M=require("react"),G=require("react/jsx-runtime"),J=(0,M.createContext)(null),q=({client:e,children:t})=>(0,G.jsx)(J.Provider,{value:e,children:t}),A=()=>{let e=(0,M.useContext)(J);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};var w=require("react"),U=e=>{let[t,a]=(0,w.useState)([]),[s,n]=(0,w.useState)(""),[d,r]=(0,w.useState)(!1),[o,l]=(0,w.useState)(null),i=(0,w.useRef)(0),c=(0,w.useCallback)(()=>{let u=i.current;return i.current+=1,u},[]);(0,w.useEffect)(()=>{let u=!0;return e.getInfo().then(p=>{u&&p.greeting&&a([{id:c(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,c]);let b=(0,w.useCallback)(()=>{let u=s.trim();if(!u||d)return;n(""),l(null),r(!0);let p=c(),g=c();a(f=>[...f,{id:p,role:"user",text:u},{id:g,role:"assistant",text:"",streaming:!0}]);let m=f=>{a(H=>H.map(_=>_.id===g?f(_):_))};e.streamMessage(u,{onText:f=>m(H=>({...H,text:H.text+f})),onComplete:f=>{m(H=>({...H,text:f.reply||H.text,artifacts:f.artifacts,streaming:!1})),r(!1)},onError:f=>{m(H=>({...H,streaming:!1})),l(f.error),r(!1)}}).catch(f=>{m(H=>({...H,streaming:!1})),l(f instanceof Error?f.message:"stream_failed"),r(!1)})},[s,d,e,c]);return{messages:t,input:s,setInput:n,send:b,sending:d,error:o}};var y=require("react/jsx-runtime"),T={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},le=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),a=e.url??t;return a?e.mimeType.startsWith("image/")?(0,y.jsx)("a",{href:a,rel:"noopener noreferrer",target:"_blank",children:(0,y.jsx)("img",{alt:e.filename,src:a,style:T.image})}):(0,y.jsx)("a",{download:e.filename,href:a,style:T.link,children:e.filename}):null},B=()=>{let e=A(),{messages:t,input:a,setInput:s,send:n,sending:d,error:r}=U(e),o=(0,D.useRef)(null);(0,D.useEffect)(()=>{let i=o.current;i&&(i.scrollTop=i.scrollHeight)},[t]);let l=i=>{i.preventDefault(),n()};return(0,y.jsxs)("div",{style:T.root,children:[(0,y.jsx)("div",{ref:o,style:T.log,children:t.map(i=>(0,y.jsxs)("div",{style:i.role==="user"?T.user:T.assistant,children:[i.text||(i.streaming?"\u2026":""),i.artifacts&&i.artifacts.length>0?(0,y.jsx)("div",{style:T.artifacts,children:i.artifacts.map(c=>(0,y.jsx)(le,{artifact:c},c.filename))}):null]},i.id))}),r?(0,y.jsx)("div",{style:T.error,children:r}):null,(0,y.jsxs)("form",{onSubmit:l,style:T.form,children:[(0,y.jsx)("input",{"aria-label":"Message",onChange:i=>s(i.target.value),placeholder:"Type a message\u2026",style:T.input,value:a}),(0,y.jsx)("button",{disabled:d||a.trim().length===0,style:T.button,type:"submit",children:"Send"})]})]})};var ce=/\/+$/,O=e=>{let t=e.sessionId,a=e.baseUrl.replace(ce,""),s=r=>`${a}/endpoint/${encodeURIComponent(e.token)}${r}`,n={"content-type":"application/json"},d=(r,o)=>({url:s(r),headers:n,body:JSON.stringify({message:o,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>h(s("/config"),{}),sendMessage:async r=>{let o=await j(d("/message",r));return t=o.sessionId,o},streamMessage:(r,o,l)=>N(d("/message/stream",r),o,i=>{t=i},l),subscribe:(r,o)=>{let l=o?.since??"0",i=!1,c,b=o?.intervalMs??3e3,u=async()=>{let g=t;if(g)try{let m=await h(s(`/messages?session=${encodeURIComponent(g)}&since=${encodeURIComponent(l)}`),{});for(let f of m.messages)r.onMessage(f);l=m.cursor}catch(m){r.onError?.(C(m))}i||(c=setTimeout(u,b))},p=()=>{i=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:n,body:JSON.stringify({sessionId:t})})}}};var K=e=>"transport"in e?e.transport:O(e);var de=/\/+$/,P="/api/v1/end-user",v=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function pe(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var V=e=>{let t=e.baseUrl.replace(de,""),a=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),n=async(r,o)=>{try{let l=await fetch(`${t}${P}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(l.status===409){let i=await l.json().catch(()=>({}));throw new v(i.message??"Someone else saved this first.")}if(!l.ok){let i=await l.text().catch(()=>""),c=`${o.method} ${r} \u2192 ${String(l.status)}`;try{let b=JSON.parse(i);b.message&&(c=b.message)}catch{c=i?`${c}: ${i}`:c}throw C(new Error(c))}return await l.json()}catch(l){throw C(l)}},d=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await h(`${t}${P}/tables`,await s())).tables,listRows:async(r,o)=>await h(`${t}${P}${d(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:l}=await n(`${d(r)}/rows`,{method:"POST",body:{rows:[o]}}),i=l[0];if(!i)throw C(new Error("The row was not created."));return i},patchRow:async(r,o,l)=>(await n(`${d(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:l})).updated,deleteRow:async(r,o)=>(await n(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await h(`${t}${P}/tables/files/rows?limit=200`,await s())).rows.map(o=>({id:o.rowId,filename:String(o.data.filename??""),source:String(o.data.source??"client"),campaign:String(o.data.campaign??""),size:typeof o.data.size=="number"?o.data.size:0,createdAt:o.createdAt})),uploadFile:async(r,o)=>{let l=new Uint8Array(await r.arrayBuffer());return await n("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:pe(l),...o===void 0?{}:{message:o}}})},fileLink:async r=>await h(`${t}${P}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await n(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await h(`${t}${P}/documents`,await s())).documents,readDocument:async r=>(await h(`${t}${P}/documents/${encodeURIComponent(r)}`,await s())).document,saveDocument:async(r,o)=>await n(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:o})}};var x=require("react"),Y="greeting";function L(e,t){if(e.some(n=>n.id===t.id))return e;let a=e.filter(n=>n.id!==Y);return[...t.author==="client"?a.filter(n=>!(n.pending&&n.author==="client"&&n.text===t.text)):a,t]}var Q=(e,t)=>{let[a,s]=(0,x.useState)([]),[n,d]=(0,x.useState)(""),[r,o]=(0,x.useState)(!1),[l,i]=(0,x.useState)(null),c=(0,x.useRef)(0),b=t?.pollMs??2e3;(0,x.useEffect)(()=>{let p=!0;return e.getInfo().then(g=>{p&&g.greeting&&s(m=>m.length===0?[{id:Y,cursor:"",author:"agent",text:g.greeting}]:m)}).catch(()=>{}),()=>{p=!1}},[e]),(0,x.useEffect)(()=>e.subscribe?.({onMessage:p=>s(g=>L(g,p)),onError:p=>i(p.error)},{intervalMs:b}),[e,b]);let u=(0,x.useCallback)(()=>{let p=n.trim();if(!p||r)return;d(""),i(null),o(!0),c.current+=1;let g={id:`pending-${c.current}`,cursor:"",author:"client",text:p,pending:!0};s(m=>[...m,g]),e.sendMessage(p).then(()=>o(!1)).catch(m=>{i(m instanceof Error?m.message:"send_failed"),o(!1)})},[n,r,e]);return{messages:a,input:n,setInput:d,send:u,sending:r,error:l}};0&&(module.exports={Chat,HolaProvider,PortalConflict,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,mergeMessage,textDeltaOf,thinkingDeltaOf,toolEventOf,useArtifactUrl,useChat,useHolaClient,useThread});
8
+ `)}},d=async()=>{let u=await l.read();if(!u.done)return a+=n.decode(u.value,{stream:!0}),i(),d()};await d()};var he=/\/+$/,E="/api/v1/end-user",V=e=>{let t=e.baseUrl.replace(he,""),o=()=>typeof e.token=="function"?e.token():e.token,s=n=>e.project?`${n}project=${encodeURIComponent(e.project)}`:"",r=async()=>({"content-type":"application/json",authorization:`Bearer ${await o()}`}),l=async n=>{let a=await fetch(`${t}${E}/chat${s("?")}`,{method:"POST",headers:await r(),body:JSON.stringify({message:n})});if(!a.ok)throw new Error(`hola: message failed (${a.status})`);return{reply:(await a.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>h(`${t}${E}/chat/info${s("?")}`,await r()),sendMessage:l,streamMessage:async(n,a)=>{try{let i=await l(n);a.onText?.(i.reply),a.onComplete?.(i)}catch(i){a.onError?.(k(i))}},subscribe:(n,a)=>{let i=a?.since??"0",d=!1,u,b=a?.intervalMs??3e3,c=async()=>{try{let m=await h(`${t}${E}/chat/thread?since=${encodeURIComponent(i)}${s("&")}`,await r());for(let g of m.messages)n.onMessage(g);i=m.cursor,n.onHealthy?.()}catch(m){n.onError?.(k(m))}d||(u=setTimeout(c,b))},p=()=>{d=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",p),u=setTimeout(c,0),p},requestHuman:async()=>{await fetch(`${t}${E}/chat/request-human${s("?")}`,{method:"POST",headers:await r(),body:"{}"})}}};var $=require("react"),A=(e,t)=>{let[o,s]=(0,$.useState)("");return(0,$.useEffect)(()=>{if(!e){s("");return}let r=Uint8Array.from(atob(e),n=>n.charCodeAt(0)),l=URL.createObjectURL(new Blob([r],{type:t}));return s(l),()=>URL.revokeObjectURL(l)},[e,t]),o};var U=require("react");var M=require("react"),X=require("react/jsx-runtime"),Y=(0,M.createContext)(null),Q=({client:e,children:t})=>(0,X.jsx)(Y.Provider,{value:e,children:t}),W=()=>{let e=(0,M.useContext)(Y);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};var x=require("react"),D=e=>{let[t,o]=(0,x.useState)([]),[s,r]=(0,x.useState)(""),[l,n]=(0,x.useState)(!1),[a,i]=(0,x.useState)(null),d=(0,x.useRef)(0),u=(0,x.useCallback)(()=>{let c=d.current;return d.current+=1,c},[]);(0,x.useEffect)(()=>{let c=!0;return e.getInfo().then(p=>{c&&p.greeting&&o([{id:u(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{c=!1}},[e,u]);let b=(0,x.useCallback)(()=>{let c=s.trim();if(!c||l)return;r(""),i(null),n(!0);let p=u(),m=u();o(f=>[...f,{id:p,role:"user",text:c},{id:m,role:"assistant",text:"",streaming:!0}]);let g=f=>{o(T=>T.map(O=>O.id===m?f(O):O))};e.streamMessage(c,{onText:f=>g(T=>({...T,text:T.text+f})),onComplete:f=>{g(T=>({...T,text:f.reply||T.text,artifacts:f.artifacts,streaming:!1})),n(!1)},onError:f=>{g(T=>({...T,streaming:!1})),i(f.error),n(!1)}}).catch(f=>{g(T=>({...T,streaming:!1})),i(f instanceof Error?f.message:"stream_failed"),n(!1)})},[s,l,e,u]);return{messages:t,input:s,setInput:r,send:b,sending:l,error:a}};var y=require("react/jsx-runtime"),P={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},xe=({artifact:e})=>{let t=A(e.dataBase64??"",e.mimeType),o=e.url??t;return o?e.mimeType.startsWith("image/")?(0,y.jsx)("a",{href:o,rel:"noopener noreferrer",target:"_blank",children:(0,y.jsx)("img",{alt:e.filename,src:o,style:P.image})}):(0,y.jsx)("a",{download:e.filename,href:o,style:P.link,children:e.filename}):null},Z=()=>{let e=W(),{messages:t,input:o,setInput:s,send:r,sending:l,error:n}=D(e),a=(0,U.useRef)(null);(0,U.useEffect)(()=>{let d=a.current;d&&(d.scrollTop=d.scrollHeight)},[t]);let i=d=>{d.preventDefault(),r()};return(0,y.jsxs)("div",{style:P.root,children:[(0,y.jsx)("div",{ref:a,style:P.log,children:t.map(d=>(0,y.jsxs)("div",{style:d.role==="user"?P.user:P.assistant,children:[d.text||(d.streaming?"\u2026":""),d.artifacts&&d.artifacts.length>0?(0,y.jsx)("div",{style:P.artifacts,children:d.artifacts.map(u=>(0,y.jsx)(xe,{artifact:u},u.filename))}):null]},d.id))}),n?(0,y.jsx)("div",{style:P.error,children:n}):null,(0,y.jsxs)("form",{onSubmit:i,style:P.form,children:[(0,y.jsx)("input",{"aria-label":"Message",onChange:d=>s(d.target.value),placeholder:"Type a message\u2026",style:P.input,value:o}),(0,y.jsx)("button",{disabled:l||o.trim().length===0,style:P.button,type:"submit",children:"Send"})]})]})};var we=/\/+$/,F=e=>{let t=e.sessionId,o=e.baseUrl.replace(we,""),s=n=>`${o}/endpoint/${encodeURIComponent(e.token)}${n}`,r={"content-type":"application/json"},l=(n,a)=>({url:s(n),headers:r,body:JSON.stringify({message:a,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>h(s("/config"),{}),sendMessage:async n=>{let a=await J(l("/message",n));return t=a.sessionId,a},streamMessage:(n,a,i)=>q(l("/message/stream",n),a,d=>{t=d},i),subscribe:(n,a)=>{let i=a?.since??"0",d=!1,u,b=a?.intervalMs??3e3,c=async()=>{let m=t;if(m)try{let g=await h(s(`/messages?session=${encodeURIComponent(m)}&since=${encodeURIComponent(i)}`),{});for(let f of g.messages)n.onMessage(f);i=g.cursor,n.onHealthy?.()}catch(g){n.onError?.(k(g))}d||(u=setTimeout(c,b))},p=()=>{d=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",p),u=setTimeout(c,0),p},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:r,body:JSON.stringify({sessionId:t})})}}};var K=e=>"transport"in e?e.transport:F(e);var Te=/^https?:\/\//i,Pe=/^\d{4}-\d{2}-\d{2}(?:[T ]|$)/,He=e=>e.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());function ke(e){if(typeof e=="number")return"number";if(typeof e=="boolean")return"boolean";if(typeof e=="string"){if(Te.test(e))return"url";if(Pe.test(e))return"date"}return"text"}function N(e){let t=[],o=new Map;for(let s of e)for(let[r,l]of Object.entries(s.data??{})){if(l==null)continue;let n=o.get(r);n||(n=new Map,o.set(r,n),t.push(r));let a=ke(l);n.set(a,(n.get(a)??0)+1)}return t.map(s=>{let r=o.get(s)??new Map,l="text",n=-1;for(let[a,i]of r)i>n&&(n=i,l=a);return{key:s,label:He(s),type:l}})}var Ce=e=>typeof e=="number"?e:Number(e)||0,ee=e=>/rate|position|rank|score|percent|avg/i.test(e),Se=/posted|published|sent|completed|shipped|launched|_at|date/i,ve=new Set(["service","order","end_user_id"]);function L(e,t){let o=e.filter(i=>!ve.has(i.key)),s=o.filter(i=>i.type==="number").slice(0,3),r=o.find(i=>i.key==="status")??o.find(i=>i.type==="select"),l=o.find(i=>i.type==="date"&&Se.test(i.key))??o.find(i=>i.type==="date"),n=o.filter(i=>i.type==="number"&&!ee(i.key)).map(i=>({field:i,total:t.reduce((d,u)=>d+Ce(u.data[i.key]),0)})).sort((i,d)=>d.total-i.total).find(i=>i.total>0)?.field??null,a=[{type:"stat",label:"Items",agg:"count"}];for(let i of s){let d=ee(i.key);a.push({type:"stat",metric:i.key,agg:d?"avg":"sum",label:d?`Avg ${i.label.toLowerCase()}`:i.label})}return l&&a.push({type:"line",x:l.key,y:n?.key,agg:"sum",title:n?`${n.label} by day`:"Items by day"}),r&&a.push({type:"donut",groupBy:r.key,title:r.label}),a.push({type:"table",columns:o.map(i=>i.key)}),{version:1,widgets:a}}var te=new Set(["service","order","end_user_id"]),re=e=>typeof e=="number"?e:Number(e)||0,ne=e=>e==null?"":String(e);function oe(e,t){if(e==="count")return t.length;if(t.length===0)return 0;if(e==="last")return t[t.length-1]??0;if(e==="min")return Math.min(...t);if(e==="max")return Math.max(...t);let o=t.reduce((s,r)=>s+r,0);return e==="avg"?Math.round(o/t.length):o}function Re(e,t,o,s){let r=new Map;for(let l of e){let n=new Date(ne(l.data[t]));if(Number.isNaN(n.getTime()))continue;let a=n.toISOString().slice(0,10),i=r.get(a)??[];i.push(o?re(l.data[o]):1),r.set(a,i)}return[...r.entries()].sort((l,n)=>l[0]<n[0]?-1:1).map(([l,n])=>({label:new Date(`${l}T00:00:00`).toLocaleDateString(void 0,{month:"short",day:"numeric"}),value:o?oe(s,n):n.length}))}function Ie(e,t){let o=new Map;for(let s of e){let r=ne(s.data[t]).trim();r&&o.set(r,(o.get(r)??0)+1)}return[...o.entries()].sort((s,r)=>r[1]-s[1]).map(([s,r])=>({label:s,value:r}))}function Ee(e,t,o,s){switch(e.type){case"stat":{let r=e.metric?t.get(e.metric):void 0;if(e.metric&&!r)return null;if(!r)return{kind:"stat",label:e.label??"Items",value:s.length};let l=s.map(n=>re(n.data[r.key]));return{kind:"stat",label:e.label??r.label,value:oe(e.agg??"sum",l)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let r=Re(s,e.x,e.y,e.agg??"sum");return r.length<2?null:{kind:"series",draw:e.type,title:e.title??"By day",points:r}}case"donut":{if(!t.has(e.groupBy))return null;let r=Ie(s,e.groupBy);return r.length===0?null:{kind:"donut",title:e.title??t.get(e.groupBy)?.label??"Breakdown",points:r}}case"table":{let r=e.columns?e.columns.map(l=>t.get(l)).filter(l=>l!==void 0&&!te.has(l.key)):o;return r.length===0?null:{kind:"table",title:e.title,columns:r,rows:s}}case"text":return{kind:"text",title:e.title,body:e.body};default:return null}}function j(e,t,o){let s=t.filter(n=>!te.has(n.key)),r=new Map(t.map(n=>[n.key,n])),l=[];for(let n of e.widgets){let a=Ee(n,r,s,o);a&&l.push(a)}return l}var $e=new Set(["sum","avg","count","min","max","last"]),se=e=>typeof e=="string"&&$e.has(e)?e:void 0,H=e=>typeof e=="string"&&e!==""?e:void 0;function Ae(e){if(typeof e!="object"||e===null)return null;let t=e;switch(t.type){case"stat":return{type:"stat",label:H(t.label),metric:H(t.metric),agg:se(t.agg)};case"line":case"bar":{let o=H(t.x);return o?{type:t.type,title:H(t.title),x:o,y:H(t.y),agg:se(t.agg)}:null}case"donut":{let o=H(t.groupBy);return o?{type:"donut",title:H(t.title),groupBy:o}:null}case"table":{let o=Array.isArray(t.columns)?t.columns.filter(s=>typeof s=="string"):void 0;return{type:"table",title:H(t.title),columns:o}}case"text":{let o=H(t.body);return o?{type:"text",title:H(t.title),body:o}:null}default:return null}}function B(e){let t=e;if(typeof e=="string"){if(e.trim()==="")return null;try{t=JSON.parse(e)}catch{return null}}if(typeof t!="object"||t===null)return null;let o=t.widgets;if(!Array.isArray(o))return null;let s=o.map(Ae).filter(l=>l!==null);if(s.length===0)return null;let r=t.version;return{version:typeof r=="number"?r:1,title:H(t.title),widgets:s}}var Me=/\/+$/,C="/api/v1/end-user",S=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function We(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var ae=e=>{let t=e.baseUrl.replace(Me,""),o=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await o()}`}),r=async(n,a)=>{try{let i=await fetch(`${t}${C}${n}`,{method:a.method,headers:await s(),...a.body===void 0?{}:{body:JSON.stringify(a.body)}});if(i.status===409){let d=await i.json().catch(()=>({}));throw new S(d.message??"Someone else saved this first.")}if(!i.ok){let d=await i.text().catch(()=>""),u=`${a.method} ${n} \u2192 ${String(i.status)}`;try{let b=JSON.parse(d);b.message&&(u=b.message)}catch{u=d?`${u}: ${d}`:u}throw k(new Error(u))}return await i.json()}catch(i){throw k(i)}},l=n=>`/tables/${encodeURIComponent(n)}`;return{listTables:async()=>(await h(`${t}${C}/tables`,await s())).tables,listRows:async(n,a)=>await h(`${t}${C}${l(n)}/rows?limit=${String(a??100)}`,await s()),insertRow:async(n,a)=>{let{rowIds:i}=await r(`${l(n)}/rows`,{method:"POST",body:{rows:[a]}}),d=i[0];if(!d)throw k(new Error("The row was not created."));return d},patchRow:async(n,a,i)=>(await r(`${l(n)}/rows/${encodeURIComponent(a)}`,{method:"PATCH",body:i})).updated,deleteRow:async(n,a)=>(await r(`/${encodeURIComponent(n)}/${encodeURIComponent(a)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await h(`${t}${C}/tables/files/rows?limit=200`,await s())).rows.map(a=>({id:a.rowId,filename:String(a.data.filename??""),source:String(a.data.source??"client"),campaign:String(a.data.campaign??""),size:typeof a.data.size=="number"?a.data.size:0,createdAt:a.createdAt})),uploadFile:async(n,a)=>{let i=new Uint8Array(await n.arrayBuffer());return await r("/files",{method:"POST",body:{filename:n.name,mime:n.type||"application/octet-stream",data:We(i),...a===void 0?{}:{message:a}}})},fileLink:async n=>await h(`${t}${C}/files/${encodeURIComponent(n)}/url`,await s()),deleteFile:async n=>(await r(`/files/${encodeURIComponent(n)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await h(`${t}${C}/documents`,await s())).documents,readDocument:async n=>(await h(`${t}${C}/documents/${encodeURIComponent(n)}`,await s())).document,saveDocument:async(n,a)=>await r(`/documents/${encodeURIComponent(n)}`,{method:"PATCH",body:a})}};var w=require("react"),ie="greeting";function z(e,t){if(e.some(r=>r.id===t.id))return e;let o=e.filter(r=>r.id!==ie);return[...t.author==="client"?o.filter(r=>!(r.pending&&r.author==="client"&&r.text===t.text)):o,t]}var le=(e,t)=>{let[o,s]=(0,w.useState)([]),[r,l]=(0,w.useState)(""),[n,a]=(0,w.useState)(!1),[i,d]=(0,w.useState)(null),u=(0,w.useRef)(0),b=t?.pollMs??2e3;(0,w.useEffect)(()=>{let p=!0;return e.getInfo().then(m=>{p&&m.greeting&&s(g=>g.length===0?[{id:ie,cursor:"",author:"agent",text:m.greeting}]:g)}).catch(()=>{}),()=>{p=!1}},[e]),(0,w.useEffect)(()=>e.subscribe?.({onMessage:p=>s(m=>z(m,p)),onError:p=>d(p.error),onHealthy:()=>d(null)},{intervalMs:b}),[e,b]);let c=(0,w.useCallback)(()=>{let p=r.trim();if(!p||n)return;l(""),d(null),a(!0),u.current+=1;let m={id:`pending-${u.current}`,cursor:"",author:"client",text:p,pending:!0};s(g=>[...g,m]),e.sendMessage(p).then(()=>a(!1)).catch(g=>{d(g instanceof Error?g.message:"send_failed"),a(!1)})},[r,n,e]);return{messages:o,input:r,setInput:l,send:c,sending:n,error:i}};0&&(module.exports={Chat,HolaProvider,PortalConflict,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,deriveFields,inferSpec,mergeMessage,parseDashboardSpec,renderModel,textDeltaOf,thinkingDeltaOf,toolEventOf,useArtifactUrl,useChat,useHolaClient,useThread});
package/dist/index.d.cts CHANGED
@@ -61,6 +61,9 @@ type HolaMessage = {
61
61
  type HolaSubscribeHandlers = {
62
62
  onMessage: (message: HolaMessage) => void;
63
63
  onError?: (error: HolaError) => void;
64
+ /** A poll completed cleanly. Fires every successful cycle (even with no new messages) so a
65
+ * consumer can clear a stale error — the receive channel retries, so one blip is not an outage. */
66
+ onHealthy?: () => void;
64
67
  };
65
68
  type HolaSubscribeOptions = {
66
69
  /** Resume from this cursor; omit to read the thread from the start. */
@@ -158,13 +161,6 @@ type CreateHolaClientOptions = WidgetTransportConfig | {
158
161
  * agnostic — the current transport is an internal detail, not the SDK's identity. */
159
162
  declare const createHolaClient: (options: CreateHolaClientOptions) => HolaClient;
160
163
 
161
- /** Extract a streamed text delta from a raw pi turn event, or null. */
162
- declare const textDeltaOf: (event: unknown) => string | null;
163
- /** Extract a streamed reasoning delta from a raw pi turn event, or null. */
164
- declare const thinkingDeltaOf: (event: unknown) => string | null;
165
- /** Extract a tool-execution start/end from a raw pi turn event, or null. */
166
- declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
167
-
168
164
  /** A column, as the company defined it. */
169
165
  type PortalField = {
170
166
  key: string;
@@ -294,6 +290,103 @@ type PortalData = {
294
290
  };
295
291
  declare const createPortalData: (config: ApiTransportConfig) => PortalData;
296
292
 
293
+ /** Build a `PortalField[]` from a slice's rows, typed by the dominant JSON value type per key.
294
+ * `select` is never inferred (it needs an option set only a template carries) — such columns fall
295
+ * back to `text`. Returns [] for an empty slice. */
296
+ declare function deriveFields(rows: readonly {
297
+ data: Record<string, unknown>;
298
+ }[]): PortalField[];
299
+
300
+ /** How a metric column is reduced to a single number. */
301
+ type Agg = "sum" | "avg" | "count" | "min" | "max" | "last";
302
+ type WidgetType = "stat" | "line" | "bar" | "donut" | "table" | "text";
303
+ /** A headline number: one metric reduced by `agg` (omit `metric` to count the rows). */
304
+ interface StatWidget {
305
+ type: "stat";
306
+ label?: string | undefined;
307
+ metric?: string | undefined;
308
+ agg?: Agg | undefined;
309
+ }
310
+ /** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
311
+ * rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
312
+ interface SeriesWidget {
313
+ type: "line" | "bar";
314
+ title?: string | undefined;
315
+ x: string;
316
+ y?: string | undefined;
317
+ agg?: Agg | undefined;
318
+ }
319
+ /** A breakdown: the count of rows per distinct value of `groupBy`. */
320
+ interface DonutWidget {
321
+ type: "donut";
322
+ title?: string | undefined;
323
+ groupBy: string;
324
+ }
325
+ /** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
326
+ interface TableWidget {
327
+ type: "table";
328
+ title?: string | undefined;
329
+ columns?: string[] | undefined;
330
+ }
331
+ /** Static prose — a note above or between the data widgets. */
332
+ interface TextWidget {
333
+ type: "text";
334
+ title?: string | undefined;
335
+ body: string;
336
+ }
337
+ type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
338
+ interface DashboardSpec {
339
+ version: number;
340
+ title?: string | undefined;
341
+ widgets: Widget[];
342
+ }
343
+ /** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
344
+ * for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
345
+ * caller falls back to inference instead of showing a blank dashboard. */
346
+ declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
347
+
348
+ declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
349
+
350
+ /** A point on a line/bar chart or a slice of a donut. */
351
+ interface Point {
352
+ label: string;
353
+ value: number;
354
+ }
355
+ /** A widget resolved against the data — everything a renderer needs, already computed. */
356
+ type ResolvedWidget = {
357
+ kind: "stat";
358
+ label: string;
359
+ value: number;
360
+ } | {
361
+ kind: "series";
362
+ draw: "line" | "bar";
363
+ title: string;
364
+ points: Point[];
365
+ } | {
366
+ kind: "donut";
367
+ title: string;
368
+ points: Point[];
369
+ } | {
370
+ kind: "table";
371
+ title?: string | undefined;
372
+ columns: PortalField[];
373
+ rows: PortalRow[];
374
+ } | {
375
+ kind: "text";
376
+ title?: string | undefined;
377
+ body: string;
378
+ };
379
+ /** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
380
+ * draw nothing. */
381
+ declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
382
+
383
+ /** Extract a streamed text delta from a raw pi turn event, or null. */
384
+ declare const textDeltaOf: (event: unknown) => string | null;
385
+ /** Extract a streamed reasoning delta from a raw pi turn event, or null. */
386
+ declare const thinkingDeltaOf: (event: unknown) => string | null;
387
+ /** Extract a tool-execution start/end from a raw pi turn event, or null. */
388
+ declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
389
+
297
390
  /** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
298
391
  * Create the client once (e.g. `useMemo`) so its session persists across renders. */
299
392
  declare const HolaProvider: ({ client, children, }: {
@@ -343,4 +436,4 @@ declare const useThread: (client: HolaClient, options?: {
343
436
  pollMs?: number;
344
437
  }) => UseThread;
345
438
 
346
- export { type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ThreadMessage, type UseChat, type UseThread, type WidgetTransportConfig, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, mergeMessage, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
439
+ export { type Agg, type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type DashboardSpec, type DonutWidget, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, type Point, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ResolvedWidget, type SeriesWidget, type StatWidget, type TableWidget, type TextWidget, type ThreadMessage, type UseChat, type UseThread, type Widget, type WidgetTransportConfig, type WidgetType, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, deriveFields, inferSpec, mergeMessage, parseDashboardSpec, renderModel, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
package/dist/index.d.ts CHANGED
@@ -61,6 +61,9 @@ type HolaMessage = {
61
61
  type HolaSubscribeHandlers = {
62
62
  onMessage: (message: HolaMessage) => void;
63
63
  onError?: (error: HolaError) => void;
64
+ /** A poll completed cleanly. Fires every successful cycle (even with no new messages) so a
65
+ * consumer can clear a stale error — the receive channel retries, so one blip is not an outage. */
66
+ onHealthy?: () => void;
64
67
  };
65
68
  type HolaSubscribeOptions = {
66
69
  /** Resume from this cursor; omit to read the thread from the start. */
@@ -158,13 +161,6 @@ type CreateHolaClientOptions = WidgetTransportConfig | {
158
161
  * agnostic — the current transport is an internal detail, not the SDK's identity. */
159
162
  declare const createHolaClient: (options: CreateHolaClientOptions) => HolaClient;
160
163
 
161
- /** Extract a streamed text delta from a raw pi turn event, or null. */
162
- declare const textDeltaOf: (event: unknown) => string | null;
163
- /** Extract a streamed reasoning delta from a raw pi turn event, or null. */
164
- declare const thinkingDeltaOf: (event: unknown) => string | null;
165
- /** Extract a tool-execution start/end from a raw pi turn event, or null. */
166
- declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
167
-
168
164
  /** A column, as the company defined it. */
169
165
  type PortalField = {
170
166
  key: string;
@@ -294,6 +290,103 @@ type PortalData = {
294
290
  };
295
291
  declare const createPortalData: (config: ApiTransportConfig) => PortalData;
296
292
 
293
+ /** Build a `PortalField[]` from a slice's rows, typed by the dominant JSON value type per key.
294
+ * `select` is never inferred (it needs an option set only a template carries) — such columns fall
295
+ * back to `text`. Returns [] for an empty slice. */
296
+ declare function deriveFields(rows: readonly {
297
+ data: Record<string, unknown>;
298
+ }[]): PortalField[];
299
+
300
+ /** How a metric column is reduced to a single number. */
301
+ type Agg = "sum" | "avg" | "count" | "min" | "max" | "last";
302
+ type WidgetType = "stat" | "line" | "bar" | "donut" | "table" | "text";
303
+ /** A headline number: one metric reduced by `agg` (omit `metric` to count the rows). */
304
+ interface StatWidget {
305
+ type: "stat";
306
+ label?: string | undefined;
307
+ metric?: string | undefined;
308
+ agg?: Agg | undefined;
309
+ }
310
+ /** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
311
+ * rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
312
+ interface SeriesWidget {
313
+ type: "line" | "bar";
314
+ title?: string | undefined;
315
+ x: string;
316
+ y?: string | undefined;
317
+ agg?: Agg | undefined;
318
+ }
319
+ /** A breakdown: the count of rows per distinct value of `groupBy`. */
320
+ interface DonutWidget {
321
+ type: "donut";
322
+ title?: string | undefined;
323
+ groupBy: string;
324
+ }
325
+ /** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
326
+ interface TableWidget {
327
+ type: "table";
328
+ title?: string | undefined;
329
+ columns?: string[] | undefined;
330
+ }
331
+ /** Static prose — a note above or between the data widgets. */
332
+ interface TextWidget {
333
+ type: "text";
334
+ title?: string | undefined;
335
+ body: string;
336
+ }
337
+ type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
338
+ interface DashboardSpec {
339
+ version: number;
340
+ title?: string | undefined;
341
+ widgets: Widget[];
342
+ }
343
+ /** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
344
+ * for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
345
+ * caller falls back to inference instead of showing a blank dashboard. */
346
+ declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
347
+
348
+ declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
349
+
350
+ /** A point on a line/bar chart or a slice of a donut. */
351
+ interface Point {
352
+ label: string;
353
+ value: number;
354
+ }
355
+ /** A widget resolved against the data — everything a renderer needs, already computed. */
356
+ type ResolvedWidget = {
357
+ kind: "stat";
358
+ label: string;
359
+ value: number;
360
+ } | {
361
+ kind: "series";
362
+ draw: "line" | "bar";
363
+ title: string;
364
+ points: Point[];
365
+ } | {
366
+ kind: "donut";
367
+ title: string;
368
+ points: Point[];
369
+ } | {
370
+ kind: "table";
371
+ title?: string | undefined;
372
+ columns: PortalField[];
373
+ rows: PortalRow[];
374
+ } | {
375
+ kind: "text";
376
+ title?: string | undefined;
377
+ body: string;
378
+ };
379
+ /** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
380
+ * draw nothing. */
381
+ declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
382
+
383
+ /** Extract a streamed text delta from a raw pi turn event, or null. */
384
+ declare const textDeltaOf: (event: unknown) => string | null;
385
+ /** Extract a streamed reasoning delta from a raw pi turn event, or null. */
386
+ declare const thinkingDeltaOf: (event: unknown) => string | null;
387
+ /** Extract a tool-execution start/end from a raw pi turn event, or null. */
388
+ declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
389
+
297
390
  /** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
298
391
  * Create the client once (e.g. `useMemo`) so its session persists across renders. */
299
392
  declare const HolaProvider: ({ client, children, }: {
@@ -343,4 +436,4 @@ declare const useThread: (client: HolaClient, options?: {
343
436
  pollMs?: number;
344
437
  }) => UseThread;
345
438
 
346
- export { type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ThreadMessage, type UseChat, type UseThread, type WidgetTransportConfig, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, mergeMessage, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
439
+ export { type Agg, type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type DashboardSpec, type DonutWidget, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, type Point, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ResolvedWidget, type SeriesWidget, type StatWidget, type TableWidget, type TextWidget, type ThreadMessage, type UseChat, type UseThread, type Widget, type WidgetTransportConfig, type WidgetType, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, deriveFields, inferSpec, mergeMessage, parseDashboardSpec, renderModel, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  'use client'
2
- var O=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},E=e=>{let t=O(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},S=e=>{let t=O(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},$=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var J=/^ /,x=e=>{if(e&&typeof e=="object"){let t=e,i=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:i,retryable:t.retryable}:{error:i}}return{error:"request_failed"}},q=e=>{let t="message",i=[];for(let s of e.split(`
3
- `))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&i.push(s.slice(5).replace(J,"")));return i.length>0?{event:t,data:i.join(`
4
- `)}:null},G=(e,t,i)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let n=s.sessionId;n&&(i(n),t.onSession?.(n));return}if(e.event==="turn_event"){t.onEvent?.(s);let n=E(s);n&&t.onText?.(n);let d=S(s);d&&t.onThinking?.(d);let r=$(s);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let n=s;i(n.sessionId),t.onComplete?.(n);return}e.event==="chat_error"&&t.onError?.(x(s))},b=async(e,t)=>{let i=await fetch(e,{headers:t});if(!i.ok)throw new Error(`hola: request failed (${i.status})`);return await i.json()},_=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},F=async(e,t,i,s)=>{let n=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:s});if(!(n.ok&&n.body)){t.onError?.({error:`stream_failed_${n.status}`});return}let d=n.body.getReader(),r=new TextDecoder,o="",l=()=>{let c=o.indexOf(`
2
+ var F=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},I=e=>{let t=F(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},E=e=>{let t=F(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},$=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var te=/^ /,T=e=>{if(e&&typeof e=="object"){let t=e,s=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:s,retryable:t.retryable}:{error:s}}return{error:"request_failed"}},re=e=>{let t="message",s=[];for(let o of e.split(`
3
+ `))o.startsWith(":")||(o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("data:")&&s.push(o.slice(5).replace(te,"")));return s.length>0?{event:t,data:s.join(`
4
+ `)}:null},ne=(e,t,s)=>{let o;try{o=JSON.parse(e.data)}catch{return}if(e.event==="session"){let n=o.sessionId;n&&(s(n),t.onSession?.(n));return}if(e.event==="turn_event"){t.onEvent?.(o);let n=I(o);n&&t.onText?.(n);let l=E(o);l&&t.onThinking?.(l);let r=$(o);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let n=o;s(n.sessionId),t.onComplete?.(n);return}e.event==="chat_error"&&t.onError?.(T(o))},b=async(e,t)=>{let s=await fetch(e,{headers:t});if(!s.ok)throw new Error(`hola: request failed (${s.status})`);return await s.json()},O=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},_=async(e,t,s,o)=>{let n=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:o});if(!(n.ok&&n.body)){t.onError?.({error:`stream_failed_${n.status}`});return}let l=n.body.getReader(),r=new TextDecoder,a="",i=()=>{let u=a.indexOf(`
5
5
 
6
- `);for(;c!==-1;){let y=o.slice(0,c);o=o.slice(c+2);let u=q(y);u&&G(u,t,i),c=o.indexOf(`
6
+ `);for(;u!==-1;){let y=a.slice(0,u);a=a.slice(u+2);let c=re(y);c&&ne(c,t,s),u=a.indexOf(`
7
7
 
8
- `)}},a=async()=>{let c=await d.read();if(!c.done)return o+=r.decode(c.value,{stream:!0}),l(),a()};await a()};var B=/\/+$/,C="/api/v1/end-user",K=e=>{let t=e.baseUrl.replace(B,""),i=()=>typeof e.token=="function"?e.token():e.token,s=r=>e.project?`${r}project=${encodeURIComponent(e.project)}`:"",n=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),d=async r=>{let o=await fetch(`${t}${C}/chat${s("?")}`,{method:"POST",headers:await n(),body:JSON.stringify({message:r})});if(!o.ok)throw new Error(`hola: message failed (${o.status})`);return{reply:(await o.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>b(`${t}${C}/chat/info${s("?")}`,await n()),sendMessage:d,streamMessage:async(r,o)=>{try{let l=await d(r);o.onText?.(l.reply),o.onComplete?.(l)}catch(l){o.onError?.(x(l))}},subscribe:(r,o)=>{let l=o?.since??"0",a=!1,c,y=o?.intervalMs??3e3,u=async()=>{try{let g=await b(`${t}${C}/chat/thread?since=${encodeURIComponent(l)}${s("&")}`,await n());for(let m of g.messages)r.onMessage(m);l=g.cursor}catch(g){r.onError?.(x(g))}a||(c=setTimeout(u,y))},p=()=>{a=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{await fetch(`${t}${C}/chat/request-human${s("?")}`,{method:"POST",headers:await n(),body:"{}"})}}};import{useEffect as V,useState as Y}from"react";var k=(e,t)=>{let[i,s]=Y("");return V(()=>{if(!e){s("");return}let n=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),d=URL.createObjectURL(new Blob([n],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),i};import{useEffect as oe,useRef as se}from"react";import{createContext as Q,useContext as X}from"react";import{jsx as ee}from"react/jsx-runtime";var L=Q(null),Z=({client:e,children:t})=>ee(L.Provider,{value:e,children:t}),M=()=>{let e=X(L);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as W,useEffect as te,useRef as re,useState as P}from"react";var A=e=>{let[t,i]=P([]),[s,n]=P(""),[d,r]=P(!1),[o,l]=P(null),a=re(0),c=W(()=>{let u=a.current;return a.current+=1,u},[]);te(()=>{let u=!0;return e.getInfo().then(p=>{u&&p.greeting&&i([{id:c(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,c]);let y=W(()=>{let u=s.trim();if(!u||d)return;n(""),l(null),r(!0);let p=c(),g=c();i(f=>[...f,{id:p,role:"user",text:u},{id:g,role:"assistant",text:"",streaming:!0}]);let m=f=>{i(h=>h.map(R=>R.id===g?f(R):R))};e.streamMessage(u,{onText:f=>m(h=>({...h,text:h.text+f})),onComplete:f=>{m(h=>({...h,text:f.reply||h.text,artifacts:f.artifacts,streaming:!1})),r(!1)},onError:f=>{m(h=>({...h,streaming:!1})),l(f.error),r(!1)}}).catch(f=>{m(h=>({...h,streaming:!1})),l(f instanceof Error?f.message:"stream_failed"),r(!1)})},[s,d,e,c]);return{messages:t,input:s,setInput:n,send:y,sending:d,error:o}};import{jsx as H,jsxs as U}from"react/jsx-runtime";var w={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},ne=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),i=e.url??t;return i?e.mimeType.startsWith("image/")?H("a",{href:i,rel:"noopener noreferrer",target:"_blank",children:H("img",{alt:e.filename,src:i,style:w.image})}):H("a",{download:e.filename,href:i,style:w.link,children:e.filename}):null},ae=()=>{let e=M(),{messages:t,input:i,setInput:s,send:n,sending:d,error:r}=A(e),o=se(null);oe(()=>{let a=o.current;a&&(a.scrollTop=a.scrollHeight)},[t]);let l=a=>{a.preventDefault(),n()};return U("div",{style:w.root,children:[H("div",{ref:o,style:w.log,children:t.map(a=>U("div",{style:a.role==="user"?w.user:w.assistant,children:[a.text||(a.streaming?"\u2026":""),a.artifacts&&a.artifacts.length>0?H("div",{style:w.artifacts,children:a.artifacts.map(c=>H(ne,{artifact:c},c.filename))}):null]},a.id))}),r?H("div",{style:w.error,children:r}):null,U("form",{onSubmit:l,style:w.form,children:[H("input",{"aria-label":"Message",onChange:a=>s(a.target.value),placeholder:"Type a message\u2026",style:w.input,value:i}),H("button",{disabled:d||i.trim().length===0,style:w.button,type:"submit",children:"Send"})]})]})};var ie=/\/+$/,D=e=>{let t=e.sessionId,i=e.baseUrl.replace(ie,""),s=r=>`${i}/endpoint/${encodeURIComponent(e.token)}${r}`,n={"content-type":"application/json"},d=(r,o)=>({url:s(r),headers:n,body:JSON.stringify({message:o,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>b(s("/config"),{}),sendMessage:async r=>{let o=await _(d("/message",r));return t=o.sessionId,o},streamMessage:(r,o,l)=>F(d("/message/stream",r),o,a=>{t=a},l),subscribe:(r,o)=>{let l=o?.since??"0",a=!1,c,y=o?.intervalMs??3e3,u=async()=>{let g=t;if(g)try{let m=await b(s(`/messages?session=${encodeURIComponent(g)}&since=${encodeURIComponent(l)}`),{});for(let f of m.messages)r.onMessage(f);l=m.cursor}catch(m){r.onError?.(x(m))}a||(c=setTimeout(u,y))},p=()=>{a=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:n,body:JSON.stringify({sessionId:t})})}}};var le=e=>"transport"in e?e.transport:D(e);var ce=/\/+$/,T="/api/v1/end-user",v=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function de(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var pe=e=>{let t=e.baseUrl.replace(ce,""),i=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),n=async(r,o)=>{try{let l=await fetch(`${t}${T}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(l.status===409){let a=await l.json().catch(()=>({}));throw new v(a.message??"Someone else saved this first.")}if(!l.ok){let a=await l.text().catch(()=>""),c=`${o.method} ${r} \u2192 ${String(l.status)}`;try{let y=JSON.parse(a);y.message&&(c=y.message)}catch{c=a?`${c}: ${a}`:c}throw x(new Error(c))}return await l.json()}catch(l){throw x(l)}},d=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await b(`${t}${T}/tables`,await s())).tables,listRows:async(r,o)=>await b(`${t}${T}${d(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:l}=await n(`${d(r)}/rows`,{method:"POST",body:{rows:[o]}}),a=l[0];if(!a)throw x(new Error("The row was not created."));return a},patchRow:async(r,o,l)=>(await n(`${d(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:l})).updated,deleteRow:async(r,o)=>(await n(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await b(`${t}${T}/tables/files/rows?limit=200`,await s())).rows.map(o=>({id:o.rowId,filename:String(o.data.filename??""),source:String(o.data.source??"client"),campaign:String(o.data.campaign??""),size:typeof o.data.size=="number"?o.data.size:0,createdAt:o.createdAt})),uploadFile:async(r,o)=>{let l=new Uint8Array(await r.arrayBuffer());return await n("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:de(l),...o===void 0?{}:{message:o}}})},fileLink:async r=>await b(`${t}${T}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await n(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await b(`${t}${T}/documents`,await s())).documents,readDocument:async r=>(await b(`${t}${T}/documents/${encodeURIComponent(r)}`,await s())).document,saveDocument:async(r,o)=>await n(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:o})}};import{useCallback as ue,useEffect as j,useRef as me,useState as I}from"react";var N="greeting";function z(e,t){if(e.some(n=>n.id===t.id))return e;let i=e.filter(n=>n.id!==N);return[...t.author==="client"?i.filter(n=>!(n.pending&&n.author==="client"&&n.text===t.text)):i,t]}var ge=(e,t)=>{let[i,s]=I([]),[n,d]=I(""),[r,o]=I(!1),[l,a]=I(null),c=me(0),y=t?.pollMs??2e3;j(()=>{let p=!0;return e.getInfo().then(g=>{p&&g.greeting&&s(m=>m.length===0?[{id:N,cursor:"",author:"agent",text:g.greeting}]:m)}).catch(()=>{}),()=>{p=!1}},[e]),j(()=>e.subscribe?.({onMessage:p=>s(g=>z(g,p)),onError:p=>a(p.error)},{intervalMs:y}),[e,y]);let u=ue(()=>{let p=n.trim();if(!p||r)return;d(""),a(null),o(!0),c.current+=1;let g={id:`pending-${c.current}`,cursor:"",author:"client",text:p,pending:!0};s(m=>[...m,g]),e.sendMessage(p).then(()=>o(!1)).catch(m=>{a(m instanceof Error?m.message:"send_failed"),o(!1)})},[n,r,e]);return{messages:i,input:n,setInput:d,send:u,sending:r,error:l}};export{ae as Chat,Z as HolaProvider,v as PortalConflict,K as createApiTransport,le as createHolaClient,pe as createPortalData,D as createWidgetTransport,z as mergeMessage,E as textDeltaOf,S as thinkingDeltaOf,$ as toolEventOf,k as useArtifactUrl,A as useChat,M as useHolaClient,ge as useThread};
8
+ `)}},d=async()=>{let u=await l.read();if(!u.done)return a+=r.decode(u.value,{stream:!0}),i(),d()};await d()};var oe=/\/+$/,k="/api/v1/end-user",se=e=>{let t=e.baseUrl.replace(oe,""),s=()=>typeof e.token=="function"?e.token():e.token,o=r=>e.project?`${r}project=${encodeURIComponent(e.project)}`:"",n=async()=>({"content-type":"application/json",authorization:`Bearer ${await s()}`}),l=async r=>{let a=await fetch(`${t}${k}/chat${o("?")}`,{method:"POST",headers:await n(),body:JSON.stringify({message:r})});if(!a.ok)throw new Error(`hola: message failed (${a.status})`);return{reply:(await a.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>b(`${t}${k}/chat/info${o("?")}`,await n()),sendMessage:l,streamMessage:async(r,a)=>{try{let i=await l(r);a.onText?.(i.reply),a.onComplete?.(i)}catch(i){a.onError?.(T(i))}},subscribe:(r,a)=>{let i=a?.since??"0",d=!1,u,y=a?.intervalMs??3e3,c=async()=>{try{let m=await b(`${t}${k}/chat/thread?since=${encodeURIComponent(i)}${o("&")}`,await n());for(let g of m.messages)r.onMessage(g);i=m.cursor,r.onHealthy?.()}catch(m){r.onError?.(T(m))}d||(u=setTimeout(c,y))},p=()=>{d=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",p),u=setTimeout(c,0),p},requestHuman:async()=>{await fetch(`${t}${k}/chat/request-human${o("?")}`,{method:"POST",headers:await n(),body:"{}"})}}};import{useEffect as ae,useState as ie}from"react";var A=(e,t)=>{let[s,o]=ie("");return ae(()=>{if(!e){o("");return}let n=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),l=URL.createObjectURL(new Blob([n],{type:t}));return o(l),()=>URL.revokeObjectURL(l)},[e,t]),s};import{useEffect as me,useRef as fe}from"react";import{createContext as le,useContext as de}from"react";import{jsx as pe}from"react/jsx-runtime";var N=le(null),ue=({client:e,children:t})=>pe(N.Provider,{value:e,children:t}),M=()=>{let e=de(N);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as L,useEffect as ce,useRef as ge,useState as C}from"react";var W=e=>{let[t,s]=C([]),[o,n]=C(""),[l,r]=C(!1),[a,i]=C(null),d=ge(0),u=L(()=>{let c=d.current;return d.current+=1,c},[]);ce(()=>{let c=!0;return e.getInfo().then(p=>{c&&p.greeting&&s([{id:u(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{c=!1}},[e,u]);let y=L(()=>{let c=o.trim();if(!c||l)return;n(""),i(null),r(!0);let p=u(),m=u();s(f=>[...f,{id:p,role:"user",text:c},{id:m,role:"assistant",text:"",streaming:!0}]);let g=f=>{s(h=>h.map(R=>R.id===m?f(R):R))};e.streamMessage(c,{onText:f=>g(h=>({...h,text:h.text+f})),onComplete:f=>{g(h=>({...h,text:f.reply||h.text,artifacts:f.artifacts,streaming:!1})),r(!1)},onError:f=>{g(h=>({...h,streaming:!1})),i(f.error),r(!1)}}).catch(f=>{g(h=>({...h,streaming:!1})),i(f instanceof Error?f.message:"stream_failed"),r(!1)})},[o,l,e,u]);return{messages:t,input:o,setInput:n,send:y,sending:l,error:a}};import{jsx as P,jsxs as D}from"react/jsx-runtime";var x={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},ye=({artifact:e})=>{let t=A(e.dataBase64??"",e.mimeType),s=e.url??t;return s?e.mimeType.startsWith("image/")?P("a",{href:s,rel:"noopener noreferrer",target:"_blank",children:P("img",{alt:e.filename,src:s,style:x.image})}):P("a",{download:e.filename,href:s,style:x.link,children:e.filename}):null},be=()=>{let e=M(),{messages:t,input:s,setInput:o,send:n,sending:l,error:r}=W(e),a=fe(null);me(()=>{let d=a.current;d&&(d.scrollTop=d.scrollHeight)},[t]);let i=d=>{d.preventDefault(),n()};return D("div",{style:x.root,children:[P("div",{ref:a,style:x.log,children:t.map(d=>D("div",{style:d.role==="user"?x.user:x.assistant,children:[d.text||(d.streaming?"\u2026":""),d.artifacts&&d.artifacts.length>0?P("div",{style:x.artifacts,children:d.artifacts.map(u=>P(ye,{artifact:u},u.filename))}):null]},d.id))}),r?P("div",{style:x.error,children:r}):null,D("form",{onSubmit:i,style:x.form,children:[P("input",{"aria-label":"Message",onChange:d=>o(d.target.value),placeholder:"Type a message\u2026",style:x.input,value:s}),P("button",{disabled:l||s.trim().length===0,style:x.button,type:"submit",children:"Send"})]})]})};var he=/\/+$/,U=e=>{let t=e.sessionId,s=e.baseUrl.replace(he,""),o=r=>`${s}/endpoint/${encodeURIComponent(e.token)}${r}`,n={"content-type":"application/json"},l=(r,a)=>({url:o(r),headers:n,body:JSON.stringify({message:a,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>b(o("/config"),{}),sendMessage:async r=>{let a=await O(l("/message",r));return t=a.sessionId,a},streamMessage:(r,a,i)=>_(l("/message/stream",r),a,d=>{t=d},i),subscribe:(r,a)=>{let i=a?.since??"0",d=!1,u,y=a?.intervalMs??3e3,c=async()=>{let m=t;if(m)try{let g=await b(o(`/messages?session=${encodeURIComponent(m)}&since=${encodeURIComponent(i)}`),{});for(let f of g.messages)r.onMessage(f);i=g.cursor,r.onHealthy?.()}catch(g){r.onError?.(T(g))}d||(u=setTimeout(c,y))},p=()=>{d=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",p),u=setTimeout(c,0),p},requestHuman:async()=>{t&&await fetch(o("/request-human"),{method:"POST",headers:n,body:JSON.stringify({sessionId:t})})}}};var xe=e=>"transport"in e?e.transport:U(e);var we=/^https?:\/\//i,Te=/^\d{4}-\d{2}-\d{2}(?:[T ]|$)/,Pe=e=>e.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());function He(e){if(typeof e=="number")return"number";if(typeof e=="boolean")return"boolean";if(typeof e=="string"){if(we.test(e))return"url";if(Te.test(e))return"date"}return"text"}function j(e){let t=[],s=new Map;for(let o of e)for(let[n,l]of Object.entries(o.data??{})){if(l==null)continue;let r=s.get(n);r||(r=new Map,s.set(n,r),t.push(n));let a=He(l);r.set(a,(r.get(a)??0)+1)}return t.map(o=>{let n=s.get(o)??new Map,l="text",r=-1;for(let[a,i]of n)i>r&&(r=i,l=a);return{key:o,label:Pe(o),type:l}})}var ke=e=>typeof e=="number"?e:Number(e)||0,B=e=>/rate|position|rank|score|percent|avg/i.test(e),Ce=/posted|published|sent|completed|shipped|launched|_at|date/i,Se=new Set(["service","order","end_user_id"]);function z(e,t){let s=e.filter(i=>!Se.has(i.key)),o=s.filter(i=>i.type==="number").slice(0,3),n=s.find(i=>i.key==="status")??s.find(i=>i.type==="select"),l=s.find(i=>i.type==="date"&&Ce.test(i.key))??s.find(i=>i.type==="date"),r=s.filter(i=>i.type==="number"&&!B(i.key)).map(i=>({field:i,total:t.reduce((d,u)=>d+ke(u.data[i.key]),0)})).sort((i,d)=>d.total-i.total).find(i=>i.total>0)?.field??null,a=[{type:"stat",label:"Items",agg:"count"}];for(let i of o){let d=B(i.key);a.push({type:"stat",metric:i.key,agg:d?"avg":"sum",label:d?`Avg ${i.label.toLowerCase()}`:i.label})}return l&&a.push({type:"line",x:l.key,y:r?.key,agg:"sum",title:r?`${r.label} by day`:"Items by day"}),n&&a.push({type:"donut",groupBy:n.key,title:n.label}),a.push({type:"table",columns:s.map(i=>i.key)}),{version:1,widgets:a}}var G=new Set(["service","order","end_user_id"]),J=e=>typeof e=="number"?e:Number(e)||0,q=e=>e==null?"":String(e);function V(e,t){if(e==="count")return t.length;if(t.length===0)return 0;if(e==="last")return t[t.length-1]??0;if(e==="min")return Math.min(...t);if(e==="max")return Math.max(...t);let s=t.reduce((o,n)=>o+n,0);return e==="avg"?Math.round(s/t.length):s}function ve(e,t,s,o){let n=new Map;for(let l of e){let r=new Date(q(l.data[t]));if(Number.isNaN(r.getTime()))continue;let a=r.toISOString().slice(0,10),i=n.get(a)??[];i.push(s?J(l.data[s]):1),n.set(a,i)}return[...n.entries()].sort((l,r)=>l[0]<r[0]?-1:1).map(([l,r])=>({label:new Date(`${l}T00:00:00`).toLocaleDateString(void 0,{month:"short",day:"numeric"}),value:s?V(o,r):r.length}))}function Re(e,t){let s=new Map;for(let o of e){let n=q(o.data[t]).trim();n&&s.set(n,(s.get(n)??0)+1)}return[...s.entries()].sort((o,n)=>n[1]-o[1]).map(([o,n])=>({label:o,value:n}))}function Ie(e,t,s,o){switch(e.type){case"stat":{let n=e.metric?t.get(e.metric):void 0;if(e.metric&&!n)return null;if(!n)return{kind:"stat",label:e.label??"Items",value:o.length};let l=o.map(r=>J(r.data[n.key]));return{kind:"stat",label:e.label??n.label,value:V(e.agg??"sum",l)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let n=ve(o,e.x,e.y,e.agg??"sum");return n.length<2?null:{kind:"series",draw:e.type,title:e.title??"By day",points:n}}case"donut":{if(!t.has(e.groupBy))return null;let n=Re(o,e.groupBy);return n.length===0?null:{kind:"donut",title:e.title??t.get(e.groupBy)?.label??"Breakdown",points:n}}case"table":{let n=e.columns?e.columns.map(l=>t.get(l)).filter(l=>l!==void 0&&!G.has(l.key)):s;return n.length===0?null:{kind:"table",title:e.title,columns:n,rows:o}}case"text":return{kind:"text",title:e.title,body:e.body};default:return null}}function Y(e,t,s){let o=t.filter(r=>!G.has(r.key)),n=new Map(t.map(r=>[r.key,r])),l=[];for(let r of e.widgets){let a=Ie(r,n,o,s);a&&l.push(a)}return l}var Ee=new Set(["sum","avg","count","min","max","last"]),Q=e=>typeof e=="string"&&Ee.has(e)?e:void 0,w=e=>typeof e=="string"&&e!==""?e:void 0;function $e(e){if(typeof e!="object"||e===null)return null;let t=e;switch(t.type){case"stat":return{type:"stat",label:w(t.label),metric:w(t.metric),agg:Q(t.agg)};case"line":case"bar":{let s=w(t.x);return s?{type:t.type,title:w(t.title),x:s,y:w(t.y),agg:Q(t.agg)}:null}case"donut":{let s=w(t.groupBy);return s?{type:"donut",title:w(t.title),groupBy:s}:null}case"table":{let s=Array.isArray(t.columns)?t.columns.filter(o=>typeof o=="string"):void 0;return{type:"table",title:w(t.title),columns:s}}case"text":{let s=w(t.body);return s?{type:"text",title:w(t.title),body:s}:null}default:return null}}function X(e){let t=e;if(typeof e=="string"){if(e.trim()==="")return null;try{t=JSON.parse(e)}catch{return null}}if(typeof t!="object"||t===null)return null;let s=t.widgets;if(!Array.isArray(s))return null;let o=s.map($e).filter(l=>l!==null);if(o.length===0)return null;let n=t.version;return{version:typeof n=="number"?n:1,title:w(t.title),widgets:o}}var Ae=/\/+$/,H="/api/v1/end-user",S=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function Me(e){let t="";for(let o=0;o<e.length;o+=32768)t+=String.fromCharCode(...e.subarray(o,o+32768));return btoa(t)}var We=e=>{let t=e.baseUrl.replace(Ae,""),s=()=>typeof e.token=="function"?e.token():e.token,o=async()=>({"content-type":"application/json",authorization:`Bearer ${await s()}`}),n=async(r,a)=>{try{let i=await fetch(`${t}${H}${r}`,{method:a.method,headers:await o(),...a.body===void 0?{}:{body:JSON.stringify(a.body)}});if(i.status===409){let d=await i.json().catch(()=>({}));throw new S(d.message??"Someone else saved this first.")}if(!i.ok){let d=await i.text().catch(()=>""),u=`${a.method} ${r} \u2192 ${String(i.status)}`;try{let y=JSON.parse(d);y.message&&(u=y.message)}catch{u=d?`${u}: ${d}`:u}throw T(new Error(u))}return await i.json()}catch(i){throw T(i)}},l=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await b(`${t}${H}/tables`,await o())).tables,listRows:async(r,a)=>await b(`${t}${H}${l(r)}/rows?limit=${String(a??100)}`,await o()),insertRow:async(r,a)=>{let{rowIds:i}=await n(`${l(r)}/rows`,{method:"POST",body:{rows:[a]}}),d=i[0];if(!d)throw T(new Error("The row was not created."));return d},patchRow:async(r,a,i)=>(await n(`${l(r)}/rows/${encodeURIComponent(a)}`,{method:"PATCH",body:i})).updated,deleteRow:async(r,a)=>(await n(`/${encodeURIComponent(r)}/${encodeURIComponent(a)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await b(`${t}${H}/tables/files/rows?limit=200`,await o())).rows.map(a=>({id:a.rowId,filename:String(a.data.filename??""),source:String(a.data.source??"client"),campaign:String(a.data.campaign??""),size:typeof a.data.size=="number"?a.data.size:0,createdAt:a.createdAt})),uploadFile:async(r,a)=>{let i=new Uint8Array(await r.arrayBuffer());return await n("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:Me(i),...a===void 0?{}:{message:a}}})},fileLink:async r=>await b(`${t}${H}/files/${encodeURIComponent(r)}/url`,await o()),deleteFile:async r=>(await n(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await b(`${t}${H}/documents`,await o())).documents,readDocument:async r=>(await b(`${t}${H}/documents/${encodeURIComponent(r)}`,await o())).document,saveDocument:async(r,a)=>await n(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:a})}};import{useCallback as De,useEffect as Z,useRef as Ue,useState as v}from"react";var K="greeting";function ee(e,t){if(e.some(n=>n.id===t.id))return e;let s=e.filter(n=>n.id!==K);return[...t.author==="client"?s.filter(n=>!(n.pending&&n.author==="client"&&n.text===t.text)):s,t]}var Fe=(e,t)=>{let[s,o]=v([]),[n,l]=v(""),[r,a]=v(!1),[i,d]=v(null),u=Ue(0),y=t?.pollMs??2e3;Z(()=>{let p=!0;return e.getInfo().then(m=>{p&&m.greeting&&o(g=>g.length===0?[{id:K,cursor:"",author:"agent",text:m.greeting}]:g)}).catch(()=>{}),()=>{p=!1}},[e]),Z(()=>e.subscribe?.({onMessage:p=>o(m=>ee(m,p)),onError:p=>d(p.error),onHealthy:()=>d(null)},{intervalMs:y}),[e,y]);let c=De(()=>{let p=n.trim();if(!p||r)return;l(""),d(null),a(!0),u.current+=1;let m={id:`pending-${u.current}`,cursor:"",author:"client",text:p,pending:!0};o(g=>[...g,m]),e.sendMessage(p).then(()=>a(!1)).catch(g=>{d(g instanceof Error?g.message:"send_failed"),a(!1)})},[n,r,e]);return{messages:s,input:n,setInput:l,send:c,sending:r,error:i}};export{be as Chat,ue as HolaProvider,S as PortalConflict,se as createApiTransport,xe as createHolaClient,We as createPortalData,U as createWidgetTransport,j as deriveFields,z as inferSpec,ee as mergeMessage,X as parseDashboardSpec,Y as renderModel,I as textDeltaOf,E as thinkingDeltaOf,$ as toolEventOf,A as useArtifactUrl,W as useChat,M as useHolaClient,Fe as useThread};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@holaboss/client",
3
- "version": "0.2.0-beta.4",
4
- "description": "Injectable client SDK for embedding a Holaboss AI employee (chat today; dashboard + artifacts next) into your own web app.",
3
+ "version": "0.2.0-beta.6",
4
+ "description": "Injectable client SDK for embedding a Holaboss AI employee (chat + configurable dashboards today; artifacts next) into your own web app.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",