@holaboss/client 0.2.0-beta.3 → 0.2.0-beta.5
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 +5 -5
- package/dist/index.d.cts +102 -13
- package/dist/index.d.ts +102 -13
- package/dist/index.js +5 -5
- package/package.json +6 -4
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
var
|
|
3
|
-
`))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&
|
|
4
|
-
`)}:null},
|
|
2
|
+
var _=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var de=Object.getOwnPropertyNames;var ue=Object.prototype.hasOwnProperty;var ce=(e,t)=>{for(var o in t)_(e,o,{get:t[o],enumerable:!0})},pe=(e,t,o,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of de(t))!ue.call(e,r)&&r!==o&&_(e,r,{get:()=>t[r],enumerable:!(s=le(t,r))||s.enumerable});return e};var ge=e=>pe(_({},"__esModule",{value:!0}),e);var Ee={};ce(Ee,{Chat:()=>X,HolaProvider:()=>Y,PortalConflict:()=>S,createApiTransport:()=>q,createHolaClient:()=>Z,createPortalData:()=>se,createWidgetTransport:()=>O,inferSpec:()=>N,mergeMessage:()=>B,parseDashboardSpec:()=>j,renderModel:()=>L,textDeltaOf:()=>v,thinkingDeltaOf:()=>R,toolEventOf:()=>I,useArtifactUrl:()=>A,useChat:()=>D,useHolaClient:()=>M,useThread:()=>ie});module.exports=ge(Ee);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 me=/^ /,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"}},fe=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(me,"")));return o.length>0?{event:t,data:o.join(`
|
|
4
|
+
`)}:null},ye=(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 d=R(s);d&&t.onThinking?.(d);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()},z=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 d=r.body.getReader(),n=new TextDecoder,a="",i=()=>{let u=a.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;u!==-1;){let b=a.slice(0,u);a=a.slice(u+2);let p=fe(b);p&&ye(p,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 ae=/\/+$/,S="/api/v1/end-user",z=e=>{let t=e.baseUrl.replace(ae,""),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"),B=require("react/jsx-runtime"),J=(0,M.createContext)(null),q=({client:e,children:t})=>(0,B.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"}},ie=({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},G=()=>{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)(ie,{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 le=/\/+$/,O=e=>{let t=e.sessionId,a=e.baseUrl.replace(le,""),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 ce=/\/+$/,P="/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 V=e=>{let t=e.baseUrl.replace(ce,""),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:de(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");function L(e,t){return e.some(s=>s.id===t.id)?e:[...t.author==="client"?e.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):e,t]}var Y=(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:"greeting",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
|
+
`)}},l=async()=>{let u=await d.read();if(!u.done)return a+=n.decode(u.value,{stream:!0}),i(),l()};await l()};var be=/\/+$/,E="/api/v1/end-user",q=e=>{let t=e.baseUrl.replace(be,""),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()}`}),d=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:d,streamMessage:async(n,a)=>{try{let i=await d(n);a.onText?.(i.reply),a.onComplete?.(i)}catch(i){a.onError?.(k(i))}},subscribe:(n,a)=>{let i=a?.since??"0",l=!1,u,b=a?.intervalMs??3e3,p=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))}l||(u=setTimeout(p,b))},c=()=>{l=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",c),u=setTimeout(p,0),c},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)),d=URL.createObjectURL(new Blob([r],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),o};var U=require("react");var W=require("react"),Q=require("react/jsx-runtime"),V=(0,W.createContext)(null),Y=({client:e,children:t})=>(0,Q.jsx)(V.Provider,{value:e,children:t}),M=()=>{let e=(0,W.useContext)(V);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)(""),[d,n]=(0,x.useState)(!1),[a,i]=(0,x.useState)(null),l=(0,x.useRef)(0),u=(0,x.useCallback)(()=>{let p=l.current;return l.current+=1,p},[]);(0,x.useEffect)(()=>{let p=!0;return e.getInfo().then(c=>{p&&c.greeting&&o([{id:u(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{p=!1}},[e,u]);let b=(0,x.useCallback)(()=>{let p=s.trim();if(!p||d)return;r(""),i(null),n(!0);let c=u(),m=u();o(f=>[...f,{id:c,role:"user",text:p},{id:m,role:"assistant",text:"",streaming:!0}]);let g=f=>{o(T=>T.map(F=>F.id===m?f(F):F))};e.streamMessage(p,{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,d,e,u]);return{messages:t,input:s,setInput:r,send:b,sending:d,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"}},he=({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},X=()=>{let e=M(),{messages:t,input:o,setInput:s,send:r,sending:d,error:n}=D(e),a=(0,U.useRef)(null);(0,U.useEffect)(()=>{let l=a.current;l&&(l.scrollTop=l.scrollHeight)},[t]);let i=l=>{l.preventDefault(),r()};return(0,y.jsxs)("div",{style:P.root,children:[(0,y.jsx)("div",{ref:a,style:P.log,children:t.map(l=>(0,y.jsxs)("div",{style:l.role==="user"?P.user:P.assistant,children:[l.text||(l.streaming?"\u2026":""),l.artifacts&&l.artifacts.length>0?(0,y.jsx)("div",{style:P.artifacts,children:l.artifacts.map(u=>(0,y.jsx)(he,{artifact:u},u.filename))}):null]},l.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:l=>s(l.target.value),placeholder:"Type a message\u2026",style:P.input,value:o}),(0,y.jsx)("button",{disabled:d||o.trim().length===0,style:P.button,type:"submit",children:"Send"})]})]})};var xe=/\/+$/,O=e=>{let t=e.sessionId,o=e.baseUrl.replace(xe,""),s=n=>`${o}/endpoint/${encodeURIComponent(e.token)}${n}`,r={"content-type":"application/json"},d=(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(d("/message",n));return t=a.sessionId,a},streamMessage:(n,a,i)=>z(d("/message/stream",n),a,l=>{t=l},i),subscribe:(n,a)=>{let i=a?.since??"0",l=!1,u,b=a?.intervalMs??3e3,p=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))}l||(u=setTimeout(p,b))},c=()=>{l=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",c),u=setTimeout(p,0),c},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:r,body:JSON.stringify({sessionId:t})})}}};var Z=e=>"transport"in e?e.transport:O(e);var we=e=>typeof e=="number"?e:Number(e)||0,K=e=>/rate|position|rank|score|percent|avg/i.test(e),Te=/posted|published|sent|completed|shipped|launched|_at|date/i,Pe=new Set(["service","order","end_user_id"]);function N(e,t){let o=e.filter(i=>!Pe.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"),d=o.find(i=>i.type==="date"&&Te.test(i.key))??o.find(i=>i.type==="date"),n=o.filter(i=>i.type==="number"&&!K(i.key)).map(i=>({field:i,total:t.reduce((l,u)=>l+we(u.data[i.key]),0)})).sort((i,l)=>l.total-i.total).find(i=>i.total>0)?.field??null,a=[{type:"stat",label:"Items",agg:"count"}];for(let i of s){let l=K(i.key);a.push({type:"stat",metric:i.key,agg:l?"avg":"sum",label:l?`Avg ${i.label.toLowerCase()}`:i.label})}return d&&a.push({type:"line",x:d.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 ee=new Set(["service","order","end_user_id"]),te=e=>typeof e=="number"?e:Number(e)||0,re=e=>e==null?"":String(e);function ne(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 He(e,t,o,s){let r=new Map;for(let d of e){let n=new Date(re(d.data[t]));if(Number.isNaN(n.getTime()))continue;let a=n.toISOString().slice(0,10),i=r.get(a)??[];i.push(o?te(d.data[o]):1),r.set(a,i)}return[...r.entries()].sort((d,n)=>d[0]<n[0]?-1:1).map(([d,n])=>({label:new Date(`${d}T00:00:00`).toLocaleDateString(void 0,{month:"short",day:"numeric"}),value:o?ne(s,n):n.length}))}function ke(e,t){let o=new Map;for(let s of e){let r=re(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 Ce(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 d=s.map(n=>te(n.data[r.key]));return{kind:"stat",label:e.label??r.label,value:ne(e.agg??"sum",d)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let r=He(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=ke(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(d=>t.get(d)).filter(d=>d!==void 0&&!ee.has(d.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 L(e,t,o){let s=t.filter(n=>!ee.has(n.key)),r=new Map(t.map(n=>[n.key,n])),d=[];for(let n of e.widgets){let a=Ce(n,r,s,o);a&&d.push(a)}return d}var Se=new Set(["sum","avg","count","min","max","last"]),oe=e=>typeof e=="string"&&Se.has(e)?e:void 0,H=e=>typeof e=="string"&&e!==""?e:void 0;function ve(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:oe(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:oe(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 j(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(ve).filter(d=>d!==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 Re=/\/+$/,C="/api/v1/end-user",S=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function Ie(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var se=e=>{let t=e.baseUrl.replace(Re,""),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 l=await i.json().catch(()=>({}));throw new S(l.message??"Someone else saved this first.")}if(!i.ok){let l=await i.text().catch(()=>""),u=`${a.method} ${n} \u2192 ${String(i.status)}`;try{let b=JSON.parse(l);b.message&&(u=b.message)}catch{u=l?`${u}: ${l}`:u}throw k(new Error(u))}return await i.json()}catch(i){throw k(i)}},d=n=>`/tables/${encodeURIComponent(n)}`;return{listTables:async()=>(await h(`${t}${C}/tables`,await s())).tables,listRows:async(n,a)=>await h(`${t}${C}${d(n)}/rows?limit=${String(a??100)}`,await s()),insertRow:async(n,a)=>{let{rowIds:i}=await r(`${d(n)}/rows`,{method:"POST",body:{rows:[a]}}),l=i[0];if(!l)throw k(new Error("The row was not created."));return l},patchRow:async(n,a,i)=>(await r(`${d(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:Ie(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"),ae="greeting";function B(e,t){if(e.some(r=>r.id===t.id))return e;let o=e.filter(r=>r.id!==ae);return[...t.author==="client"?o.filter(r=>!(r.pending&&r.author==="client"&&r.text===t.text)):o,t]}var ie=(e,t)=>{let[o,s]=(0,w.useState)([]),[r,d]=(0,w.useState)(""),[n,a]=(0,w.useState)(!1),[i,l]=(0,w.useState)(null),u=(0,w.useRef)(0),b=t?.pollMs??2e3;(0,w.useEffect)(()=>{let c=!0;return e.getInfo().then(m=>{c&&m.greeting&&s(g=>g.length===0?[{id:ae,cursor:"",author:"agent",text:m.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),(0,w.useEffect)(()=>e.subscribe?.({onMessage:c=>s(m=>B(m,c)),onError:c=>l(c.error),onHealthy:()=>l(null)},{intervalMs:b}),[e,b]);let p=(0,w.useCallback)(()=>{let c=r.trim();if(!c||n)return;d(""),l(null),a(!0),u.current+=1;let m={id:`pending-${u.current}`,cursor:"",author:"client",text:c,pending:!0};s(g=>[...g,m]),e.sendMessage(c).then(()=>a(!1)).catch(g=>{l(g instanceof Error?g.message:"send_failed"),a(!1)})},[r,n,e]);return{messages:o,input:r,setInput:d,send:p,sending:n,error:i}};0&&(module.exports={Chat,HolaProvider,PortalConflict,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,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;
|
|
@@ -244,8 +240,14 @@ type PortalData = {
|
|
|
244
240
|
insertRow(slug: string, row: Record<string, unknown>): Promise<string>;
|
|
245
241
|
/** Pass an empty string to clear a field. */
|
|
246
242
|
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>;
|
|
247
|
-
/**
|
|
248
|
-
*
|
|
243
|
+
/**
|
|
244
|
+
* Only what the company allows a customer to delete, and only when nothing
|
|
245
|
+
* depends on it. Rejects with the reason otherwise.
|
|
246
|
+
*
|
|
247
|
+
* Genuinely any table: which ones are deletable, and what holds a row back, is
|
|
248
|
+
* declared in the company's own schema rather than known to the platform. This
|
|
249
|
+
* used to look general and answer for two hard-coded slugs.
|
|
250
|
+
*/
|
|
249
251
|
deleteRow(slug: string, rowId: string): Promise<number>;
|
|
250
252
|
/** Everything exchanged with this customer, newest first. */
|
|
251
253
|
listFiles(): Promise<PortalFile[]>;
|
|
@@ -288,6 +290,96 @@ type PortalData = {
|
|
|
288
290
|
};
|
|
289
291
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
290
292
|
|
|
293
|
+
/** How a metric column is reduced to a single number. */
|
|
294
|
+
type Agg = "sum" | "avg" | "count" | "min" | "max" | "last";
|
|
295
|
+
type WidgetType = "stat" | "line" | "bar" | "donut" | "table" | "text";
|
|
296
|
+
/** A headline number: one metric reduced by `agg` (omit `metric` to count the rows). */
|
|
297
|
+
interface StatWidget {
|
|
298
|
+
type: "stat";
|
|
299
|
+
label?: string | undefined;
|
|
300
|
+
metric?: string | undefined;
|
|
301
|
+
agg?: Agg | undefined;
|
|
302
|
+
}
|
|
303
|
+
/** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
|
|
304
|
+
* rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
|
|
305
|
+
interface SeriesWidget {
|
|
306
|
+
type: "line" | "bar";
|
|
307
|
+
title?: string | undefined;
|
|
308
|
+
x: string;
|
|
309
|
+
y?: string | undefined;
|
|
310
|
+
agg?: Agg | undefined;
|
|
311
|
+
}
|
|
312
|
+
/** A breakdown: the count of rows per distinct value of `groupBy`. */
|
|
313
|
+
interface DonutWidget {
|
|
314
|
+
type: "donut";
|
|
315
|
+
title?: string | undefined;
|
|
316
|
+
groupBy: string;
|
|
317
|
+
}
|
|
318
|
+
/** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
|
|
319
|
+
interface TableWidget {
|
|
320
|
+
type: "table";
|
|
321
|
+
title?: string | undefined;
|
|
322
|
+
columns?: string[] | undefined;
|
|
323
|
+
}
|
|
324
|
+
/** Static prose — a note above or between the data widgets. */
|
|
325
|
+
interface TextWidget {
|
|
326
|
+
type: "text";
|
|
327
|
+
title?: string | undefined;
|
|
328
|
+
body: string;
|
|
329
|
+
}
|
|
330
|
+
type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
|
|
331
|
+
interface DashboardSpec {
|
|
332
|
+
version: number;
|
|
333
|
+
title?: string | undefined;
|
|
334
|
+
widgets: Widget[];
|
|
335
|
+
}
|
|
336
|
+
/** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
|
|
337
|
+
* for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
|
|
338
|
+
* caller falls back to inference instead of showing a blank dashboard. */
|
|
339
|
+
declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
|
|
340
|
+
|
|
341
|
+
declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
|
|
342
|
+
|
|
343
|
+
/** A point on a line/bar chart or a slice of a donut. */
|
|
344
|
+
interface Point {
|
|
345
|
+
label: string;
|
|
346
|
+
value: number;
|
|
347
|
+
}
|
|
348
|
+
/** A widget resolved against the data — everything a renderer needs, already computed. */
|
|
349
|
+
type ResolvedWidget = {
|
|
350
|
+
kind: "stat";
|
|
351
|
+
label: string;
|
|
352
|
+
value: number;
|
|
353
|
+
} | {
|
|
354
|
+
kind: "series";
|
|
355
|
+
draw: "line" | "bar";
|
|
356
|
+
title: string;
|
|
357
|
+
points: Point[];
|
|
358
|
+
} | {
|
|
359
|
+
kind: "donut";
|
|
360
|
+
title: string;
|
|
361
|
+
points: Point[];
|
|
362
|
+
} | {
|
|
363
|
+
kind: "table";
|
|
364
|
+
title?: string | undefined;
|
|
365
|
+
columns: PortalField[];
|
|
366
|
+
rows: PortalRow[];
|
|
367
|
+
} | {
|
|
368
|
+
kind: "text";
|
|
369
|
+
title?: string | undefined;
|
|
370
|
+
body: string;
|
|
371
|
+
};
|
|
372
|
+
/** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
|
|
373
|
+
* draw nothing. */
|
|
374
|
+
declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
|
|
375
|
+
|
|
376
|
+
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
377
|
+
declare const textDeltaOf: (event: unknown) => string | null;
|
|
378
|
+
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
379
|
+
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
380
|
+
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
381
|
+
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
382
|
+
|
|
291
383
|
/** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
|
|
292
384
|
* Create the client once (e.g. `useMemo`) so its session persists across renders. */
|
|
293
385
|
declare const HolaProvider: ({ client, children, }: {
|
|
@@ -327,9 +419,6 @@ type UseThread = {
|
|
|
327
419
|
sending: boolean;
|
|
328
420
|
error: string | null;
|
|
329
421
|
};
|
|
330
|
-
/** Merge one incoming thread message: dedupe by id, and replace a pending optimistic echo of the
|
|
331
|
-
* visitor's own send (same text) with the authoritative one. Pure — the hook's core, so the
|
|
332
|
-
* merge rule is unit-checkable without React. */
|
|
333
422
|
declare function mergeMessage(list: ThreadMessage[], incoming: HolaMessage): ThreadMessage[];
|
|
334
423
|
/** A poll-based UNIFIED thread: the whole conversation — visitor, agent, and HUMAN turns — as one
|
|
335
424
|
* live list via the receive channel (`client.subscribe`). Unlike `useChat` (streaming, one turn
|
|
@@ -340,4 +429,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
340
429
|
pollMs?: number;
|
|
341
430
|
}) => UseThread;
|
|
342
431
|
|
|
343
|
-
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 };
|
|
432
|
+
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, 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;
|
|
@@ -244,8 +240,14 @@ type PortalData = {
|
|
|
244
240
|
insertRow(slug: string, row: Record<string, unknown>): Promise<string>;
|
|
245
241
|
/** Pass an empty string to clear a field. */
|
|
246
242
|
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>;
|
|
247
|
-
/**
|
|
248
|
-
*
|
|
243
|
+
/**
|
|
244
|
+
* Only what the company allows a customer to delete, and only when nothing
|
|
245
|
+
* depends on it. Rejects with the reason otherwise.
|
|
246
|
+
*
|
|
247
|
+
* Genuinely any table: which ones are deletable, and what holds a row back, is
|
|
248
|
+
* declared in the company's own schema rather than known to the platform. This
|
|
249
|
+
* used to look general and answer for two hard-coded slugs.
|
|
250
|
+
*/
|
|
249
251
|
deleteRow(slug: string, rowId: string): Promise<number>;
|
|
250
252
|
/** Everything exchanged with this customer, newest first. */
|
|
251
253
|
listFiles(): Promise<PortalFile[]>;
|
|
@@ -288,6 +290,96 @@ type PortalData = {
|
|
|
288
290
|
};
|
|
289
291
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
290
292
|
|
|
293
|
+
/** How a metric column is reduced to a single number. */
|
|
294
|
+
type Agg = "sum" | "avg" | "count" | "min" | "max" | "last";
|
|
295
|
+
type WidgetType = "stat" | "line" | "bar" | "donut" | "table" | "text";
|
|
296
|
+
/** A headline number: one metric reduced by `agg` (omit `metric` to count the rows). */
|
|
297
|
+
interface StatWidget {
|
|
298
|
+
type: "stat";
|
|
299
|
+
label?: string | undefined;
|
|
300
|
+
metric?: string | undefined;
|
|
301
|
+
agg?: Agg | undefined;
|
|
302
|
+
}
|
|
303
|
+
/** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
|
|
304
|
+
* rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
|
|
305
|
+
interface SeriesWidget {
|
|
306
|
+
type: "line" | "bar";
|
|
307
|
+
title?: string | undefined;
|
|
308
|
+
x: string;
|
|
309
|
+
y?: string | undefined;
|
|
310
|
+
agg?: Agg | undefined;
|
|
311
|
+
}
|
|
312
|
+
/** A breakdown: the count of rows per distinct value of `groupBy`. */
|
|
313
|
+
interface DonutWidget {
|
|
314
|
+
type: "donut";
|
|
315
|
+
title?: string | undefined;
|
|
316
|
+
groupBy: string;
|
|
317
|
+
}
|
|
318
|
+
/** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
|
|
319
|
+
interface TableWidget {
|
|
320
|
+
type: "table";
|
|
321
|
+
title?: string | undefined;
|
|
322
|
+
columns?: string[] | undefined;
|
|
323
|
+
}
|
|
324
|
+
/** Static prose — a note above or between the data widgets. */
|
|
325
|
+
interface TextWidget {
|
|
326
|
+
type: "text";
|
|
327
|
+
title?: string | undefined;
|
|
328
|
+
body: string;
|
|
329
|
+
}
|
|
330
|
+
type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
|
|
331
|
+
interface DashboardSpec {
|
|
332
|
+
version: number;
|
|
333
|
+
title?: string | undefined;
|
|
334
|
+
widgets: Widget[];
|
|
335
|
+
}
|
|
336
|
+
/** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
|
|
337
|
+
* for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
|
|
338
|
+
* caller falls back to inference instead of showing a blank dashboard. */
|
|
339
|
+
declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
|
|
340
|
+
|
|
341
|
+
declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
|
|
342
|
+
|
|
343
|
+
/** A point on a line/bar chart or a slice of a donut. */
|
|
344
|
+
interface Point {
|
|
345
|
+
label: string;
|
|
346
|
+
value: number;
|
|
347
|
+
}
|
|
348
|
+
/** A widget resolved against the data — everything a renderer needs, already computed. */
|
|
349
|
+
type ResolvedWidget = {
|
|
350
|
+
kind: "stat";
|
|
351
|
+
label: string;
|
|
352
|
+
value: number;
|
|
353
|
+
} | {
|
|
354
|
+
kind: "series";
|
|
355
|
+
draw: "line" | "bar";
|
|
356
|
+
title: string;
|
|
357
|
+
points: Point[];
|
|
358
|
+
} | {
|
|
359
|
+
kind: "donut";
|
|
360
|
+
title: string;
|
|
361
|
+
points: Point[];
|
|
362
|
+
} | {
|
|
363
|
+
kind: "table";
|
|
364
|
+
title?: string | undefined;
|
|
365
|
+
columns: PortalField[];
|
|
366
|
+
rows: PortalRow[];
|
|
367
|
+
} | {
|
|
368
|
+
kind: "text";
|
|
369
|
+
title?: string | undefined;
|
|
370
|
+
body: string;
|
|
371
|
+
};
|
|
372
|
+
/** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
|
|
373
|
+
* draw nothing. */
|
|
374
|
+
declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
|
|
375
|
+
|
|
376
|
+
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
377
|
+
declare const textDeltaOf: (event: unknown) => string | null;
|
|
378
|
+
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
379
|
+
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
380
|
+
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
381
|
+
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
382
|
+
|
|
291
383
|
/** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
|
|
292
384
|
* Create the client once (e.g. `useMemo`) so its session persists across renders. */
|
|
293
385
|
declare const HolaProvider: ({ client, children, }: {
|
|
@@ -327,9 +419,6 @@ type UseThread = {
|
|
|
327
419
|
sending: boolean;
|
|
328
420
|
error: string | null;
|
|
329
421
|
};
|
|
330
|
-
/** Merge one incoming thread message: dedupe by id, and replace a pending optimistic echo of the
|
|
331
|
-
* visitor's own send (same text) with the authoritative one. Pure — the hook's core, so the
|
|
332
|
-
* merge rule is unit-checkable without React. */
|
|
333
422
|
declare function mergeMessage(list: ThreadMessage[], incoming: HolaMessage): ThreadMessage[];
|
|
334
423
|
/** A poll-based UNIFIED thread: the whole conversation — visitor, agent, and HUMAN turns — as one
|
|
335
424
|
* live list via the receive channel (`client.subscribe`). Unlike `useChat` (streaming, one turn
|
|
@@ -340,4 +429,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
340
429
|
pollMs?: number;
|
|
341
430
|
}) => UseThread;
|
|
342
431
|
|
|
343
|
-
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 };
|
|
432
|
+
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, 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},
|
|
3
|
-
`))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&
|
|
4
|
-
`)}:null},
|
|
2
|
+
var O=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},I=e=>{let t=O(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},E=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 ee=/^ /,T=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"}},te=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(ee,"")));return o.length>0?{event:t,data:o.join(`
|
|
4
|
+
`)}:null},re=(e,t,o)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let n=s.sessionId;n&&(o(n),t.onSession?.(n));return}if(e.event==="turn_event"){t.onEvent?.(s);let n=I(s);n&&t.onText?.(n);let d=E(s);d&&t.onThinking?.(d);let r=$(s);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let n=s;o(n.sessionId),t.onComplete?.(n);return}e.event==="chat_error"&&t.onError?.(T(s))},b=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()},F=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,o,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,a="",i=()=>{let u=a.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;u!==-1;){let y=a.slice(0,u);a=a.slice(u+2);let p=te(y);p&&re(p,t,o),u=a.indexOf(`
|
|
7
7
|
|
|
8
|
-
`)}},n=async()=>{let c=await d.read();if(!c.done)return o+=r.decode(c.value,{stream:!0}),l(),n()};await n()};var B=/\/+$/,C="/api/v1/end-user",G=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)}`:"",a=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),d=async r=>{let o=await fetch(`${t}${C}/chat${s("?")}`,{method:"POST",headers:await a(),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 a()),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",n=!1,c,y=o?.intervalMs??3e3,u=async()=>{try{let g=await b(`${t}${C}/chat/thread?since=${encodeURIComponent(l)}${s("&")}`,await a());for(let m of g.messages)r.onMessage(m);l=g.cursor}catch(g){r.onError?.(x(g))}n||(c=setTimeout(u,y))},p=()=>{n=!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 a(),body:"{}"})}}};import{useEffect as K,useState as V}from"react";var k=(e,t)=>{let[i,s]=V("");return K(()=>{if(!e){s("");return}let a=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),d=URL.createObjectURL(new Blob([a],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),i};import{useEffect as re,useRef as oe}from"react";import{createContext as Y,useContext as Q}from"react";import{jsx as Z}from"react/jsx-runtime";var L=Y(null),X=({client:e,children:t})=>Z(L.Provider,{value:e,children:t}),M=()=>{let e=Q(L);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as W,useEffect as ee,useRef as te,useState as P}from"react";var A=e=>{let[t,i]=P([]),[s,a]=P(""),[d,r]=P(!1),[o,l]=P(null),n=te(0),c=W(()=>{let u=n.current;return n.current+=1,u},[]);ee(()=>{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;a(""),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:a,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"}},se=({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},ne=()=>{let e=M(),{messages:t,input:i,setInput:s,send:a,sending:d,error:r}=A(e),o=oe(null);re(()=>{let n=o.current;n&&(n.scrollTop=n.scrollHeight)},[t]);let l=n=>{n.preventDefault(),a()};return U("div",{style:w.root,children:[H("div",{ref:o,style:w.log,children:t.map(n=>U("div",{style:n.role==="user"?w.user:w.assistant,children:[n.text||(n.streaming?"\u2026":""),n.artifacts&&n.artifacts.length>0?H("div",{style:w.artifacts,children:n.artifacts.map(c=>H(se,{artifact:c},c.filename))}):null]},n.id))}),r?H("div",{style:w.error,children:r}):null,U("form",{onSubmit:l,style:w.form,children:[H("input",{"aria-label":"Message",onChange:n=>s(n.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 ae=/\/+$/,D=e=>{let t=e.sessionId,i=e.baseUrl.replace(ae,""),s=r=>`${i}/endpoint/${encodeURIComponent(e.token)}${r}`,a={"content-type":"application/json"},d=(r,o)=>({url:s(r),headers:a,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,n=>{t=n},l),subscribe:(r,o)=>{let l=o?.since??"0",n=!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))}n||(c=setTimeout(u,y))},p=()=>{n=!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:a,body:JSON.stringify({sessionId:t})})}}};var ie=e=>"transport"in e?e.transport:D(e);var le=/\/+$/,T="/api/v1/end-user",v=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function ce(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var de=e=>{let t=e.baseUrl.replace(le,""),i=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),a=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 n=await l.json().catch(()=>({}));throw new v(n.message??"Someone else saved this first.")}if(!l.ok){let n=await l.text().catch(()=>""),c=`${o.method} ${r} \u2192 ${String(l.status)}`;try{let y=JSON.parse(n);y.message&&(c=y.message)}catch{c=n?`${c}: ${n}`: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 a(`${d(r)}/rows`,{method:"POST",body:{rows:[o]}}),n=l[0];if(!n)throw x(new Error("The row was not created."));return n},patchRow:async(r,o,l)=>(await a(`${d(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:l})).updated,deleteRow:async(r,o)=>(await a(`/${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 a("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:ce(l),...o===void 0?{}:{message:o}}})},fileLink:async r=>await b(`${t}${T}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await a(`/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 a(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:o})}};import{useCallback as pe,useEffect as j,useRef as ue,useState as I}from"react";function N(e,t){return e.some(s=>s.id===t.id)?e:[...t.author==="client"?e.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):e,t]}var me=(e,t)=>{let[i,s]=I([]),[a,d]=I(""),[r,o]=I(!1),[l,n]=I(null),c=ue(0),y=t?.pollMs??2e3;j(()=>{let p=!0;return e.getInfo().then(g=>{p&&g.greeting&&s(m=>m.length===0?[{id:"greeting",cursor:"",author:"agent",text:g.greeting}]:m)}).catch(()=>{}),()=>{p=!1}},[e]),j(()=>e.subscribe?.({onMessage:p=>s(g=>N(g,p)),onError:p=>n(p.error)},{intervalMs:y}),[e,y]);let u=pe(()=>{let p=a.trim();if(!p||r)return;d(""),n(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=>{n(m instanceof Error?m.message:"send_failed"),o(!1)})},[a,r,e]);return{messages:i,input:a,setInput:d,send:u,sending:r,error:l}};export{ne as Chat,X as HolaProvider,v as PortalConflict,G as createApiTransport,ie as createHolaClient,de as createPortalData,D as createWidgetTransport,N as mergeMessage,E as textDeltaOf,S as thinkingDeltaOf,$ as toolEventOf,k as useArtifactUrl,A as useChat,M as useHolaClient,me as useThread};
|
|
8
|
+
`)}},l=async()=>{let u=await d.read();if(!u.done)return a+=r.decode(u.value,{stream:!0}),i(),l()};await l()};var ne=/\/+$/,k="/api/v1/end-user",oe=e=>{let t=e.baseUrl.replace(ne,""),o=()=>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 o()}`}),d=async r=>{let a=await fetch(`${t}${k}/chat${s("?")}`,{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${s("?")}`,await n()),sendMessage:d,streamMessage:async(r,a)=>{try{let i=await d(r);a.onText?.(i.reply),a.onComplete?.(i)}catch(i){a.onError?.(T(i))}},subscribe:(r,a)=>{let i=a?.since??"0",l=!1,u,y=a?.intervalMs??3e3,p=async()=>{try{let m=await b(`${t}${k}/chat/thread?since=${encodeURIComponent(i)}${s("&")}`,await n());for(let g of m.messages)r.onMessage(g);i=m.cursor,r.onHealthy?.()}catch(m){r.onError?.(T(m))}l||(u=setTimeout(p,y))},c=()=>{l=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",c),u=setTimeout(p,0),c},requestHuman:async()=>{await fetch(`${t}${k}/chat/request-human${s("?")}`,{method:"POST",headers:await n(),body:"{}"})}}};import{useEffect as se,useState as ae}from"react";var A=(e,t)=>{let[o,s]=ae("");return se(()=>{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]),o};import{useEffect as ge,useRef as me}from"react";import{createContext as ie,useContext as le}from"react";import{jsx as ue}from"react/jsx-runtime";var N=ie(null),de=({client:e,children:t})=>ue(N.Provider,{value:e,children:t}),W=()=>{let e=le(N);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as L,useEffect as ce,useRef as pe,useState as C}from"react";var M=e=>{let[t,o]=C([]),[s,n]=C(""),[d,r]=C(!1),[a,i]=C(null),l=pe(0),u=L(()=>{let p=l.current;return l.current+=1,p},[]);ce(()=>{let p=!0;return e.getInfo().then(c=>{p&&c.greeting&&o([{id:u(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{p=!1}},[e,u]);let y=L(()=>{let p=s.trim();if(!p||d)return;n(""),i(null),r(!0);let c=u(),m=u();o(f=>[...f,{id:c,role:"user",text:p},{id:m,role:"assistant",text:"",streaming:!0}]);let g=f=>{o(h=>h.map(R=>R.id===m?f(R):R))};e.streamMessage(p,{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)})},[s,d,e,u]);return{messages:t,input:s,setInput:n,send:y,sending:d,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"}},fe=({artifact:e})=>{let t=A(e.dataBase64??"",e.mimeType),o=e.url??t;return o?e.mimeType.startsWith("image/")?P("a",{href:o,rel:"noopener noreferrer",target:"_blank",children:P("img",{alt:e.filename,src:o,style:x.image})}):P("a",{download:e.filename,href:o,style:x.link,children:e.filename}):null},ye=()=>{let e=W(),{messages:t,input:o,setInput:s,send:n,sending:d,error:r}=M(e),a=me(null);ge(()=>{let l=a.current;l&&(l.scrollTop=l.scrollHeight)},[t]);let i=l=>{l.preventDefault(),n()};return D("div",{style:x.root,children:[P("div",{ref:a,style:x.log,children:t.map(l=>D("div",{style:l.role==="user"?x.user:x.assistant,children:[l.text||(l.streaming?"\u2026":""),l.artifacts&&l.artifacts.length>0?P("div",{style:x.artifacts,children:l.artifacts.map(u=>P(fe,{artifact:u},u.filename))}):null]},l.id))}),r?P("div",{style:x.error,children:r}):null,D("form",{onSubmit:i,style:x.form,children:[P("input",{"aria-label":"Message",onChange:l=>s(l.target.value),placeholder:"Type a message\u2026",style:x.input,value:o}),P("button",{disabled:d||o.trim().length===0,style:x.button,type:"submit",children:"Send"})]})]})};var be=/\/+$/,U=e=>{let t=e.sessionId,o=e.baseUrl.replace(be,""),s=r=>`${o}/endpoint/${encodeURIComponent(e.token)}${r}`,n={"content-type":"application/json"},d=(r,a)=>({url:s(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(s("/config"),{}),sendMessage:async r=>{let a=await F(d("/message",r));return t=a.sessionId,a},streamMessage:(r,a,i)=>_(d("/message/stream",r),a,l=>{t=l},i),subscribe:(r,a)=>{let i=a?.since??"0",l=!1,u,y=a?.intervalMs??3e3,p=async()=>{let m=t;if(m)try{let g=await b(s(`/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))}l||(u=setTimeout(p,y))},c=()=>{l=!0,u&&clearTimeout(u)};return a?.signal?.addEventListener("abort",c),u=setTimeout(p,0),c},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:n,body:JSON.stringify({sessionId:t})})}}};var he=e=>"transport"in e?e.transport:U(e);var xe=e=>typeof e=="number"?e:Number(e)||0,j=e=>/rate|position|rank|score|percent|avg/i.test(e),we=/posted|published|sent|completed|shipped|launched|_at|date/i,Te=new Set(["service","order","end_user_id"]);function B(e,t){let o=e.filter(i=>!Te.has(i.key)),s=o.filter(i=>i.type==="number").slice(0,3),n=o.find(i=>i.key==="status")??o.find(i=>i.type==="select"),d=o.find(i=>i.type==="date"&&we.test(i.key))??o.find(i=>i.type==="date"),r=o.filter(i=>i.type==="number"&&!j(i.key)).map(i=>({field:i,total:t.reduce((l,u)=>l+xe(u.data[i.key]),0)})).sort((i,l)=>l.total-i.total).find(i=>i.total>0)?.field??null,a=[{type:"stat",label:"Items",agg:"count"}];for(let i of s){let l=j(i.key);a.push({type:"stat",metric:i.key,agg:l?"avg":"sum",label:l?`Avg ${i.label.toLowerCase()}`:i.label})}return d&&a.push({type:"line",x:d.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:o.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,z=e=>e==null?"":String(e);function q(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,n)=>s+n,0);return e==="avg"?Math.round(o/t.length):o}function Pe(e,t,o,s){let n=new Map;for(let d of e){let r=new Date(z(d.data[t]));if(Number.isNaN(r.getTime()))continue;let a=r.toISOString().slice(0,10),i=n.get(a)??[];i.push(o?J(d.data[o]):1),n.set(a,i)}return[...n.entries()].sort((d,r)=>d[0]<r[0]?-1:1).map(([d,r])=>({label:new Date(`${d}T00:00:00`).toLocaleDateString(void 0,{month:"short",day:"numeric"}),value:o?q(s,r):r.length}))}function He(e,t){let o=new Map;for(let s of e){let n=z(s.data[t]).trim();n&&o.set(n,(o.get(n)??0)+1)}return[...o.entries()].sort((s,n)=>n[1]-s[1]).map(([s,n])=>({label:s,value:n}))}function ke(e,t,o,s){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:s.length};let d=s.map(r=>J(r.data[n.key]));return{kind:"stat",label:e.label??n.label,value:q(e.agg??"sum",d)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let n=Pe(s,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=He(s,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(d=>t.get(d)).filter(d=>d!==void 0&&!G.has(d.key)):o;return n.length===0?null:{kind:"table",title:e.title,columns:n,rows:s}}case"text":return{kind:"text",title:e.title,body:e.body};default:return null}}function V(e,t,o){let s=t.filter(r=>!G.has(r.key)),n=new Map(t.map(r=>[r.key,r])),d=[];for(let r of e.widgets){let a=ke(r,n,s,o);a&&d.push(a)}return d}var Ce=new Set(["sum","avg","count","min","max","last"]),Y=e=>typeof e=="string"&&Ce.has(e)?e:void 0,w=e=>typeof e=="string"&&e!==""?e:void 0;function Se(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:Y(t.agg)};case"line":case"bar":{let o=w(t.x);return o?{type:t.type,title:w(t.title),x:o,y:w(t.y),agg:Y(t.agg)}:null}case"donut":{let o=w(t.groupBy);return o?{type:"donut",title:w(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:w(t.title),columns:o}}case"text":{let o=w(t.body);return o?{type:"text",title:w(t.title),body:o}:null}default:return null}}function Q(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(Se).filter(d=>d!==null);if(s.length===0)return null;let n=t.version;return{version:typeof n=="number"?n:1,title:w(t.title),widgets:s}}var ve=/\/+$/,H="/api/v1/end-user",S=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function Re(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var Ie=e=>{let t=e.baseUrl.replace(ve,""),o=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await o()}`}),n=async(r,a)=>{try{let i=await fetch(`${t}${H}${r}`,{method:a.method,headers:await s(),...a.body===void 0?{}:{body:JSON.stringify(a.body)}});if(i.status===409){let l=await i.json().catch(()=>({}));throw new S(l.message??"Someone else saved this first.")}if(!i.ok){let l=await i.text().catch(()=>""),u=`${a.method} ${r} \u2192 ${String(i.status)}`;try{let y=JSON.parse(l);y.message&&(u=y.message)}catch{u=l?`${u}: ${l}`:u}throw T(new Error(u))}return await i.json()}catch(i){throw T(i)}},d=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await b(`${t}${H}/tables`,await s())).tables,listRows:async(r,a)=>await b(`${t}${H}${d(r)}/rows?limit=${String(a??100)}`,await s()),insertRow:async(r,a)=>{let{rowIds:i}=await n(`${d(r)}/rows`,{method:"POST",body:{rows:[a]}}),l=i[0];if(!l)throw T(new Error("The row was not created."));return l},patchRow:async(r,a,i)=>(await n(`${d(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 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(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:Re(i),...a===void 0?{}:{message:a}}})},fileLink:async r=>await b(`${t}${H}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await n(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await b(`${t}${H}/documents`,await s())).documents,readDocument:async r=>(await b(`${t}${H}/documents/${encodeURIComponent(r)}`,await s())).document,saveDocument:async(r,a)=>await n(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:a})}};import{useCallback as Ee,useEffect as X,useRef as $e,useState as v}from"react";var Z="greeting";function K(e,t){if(e.some(n=>n.id===t.id))return e;let o=e.filter(n=>n.id!==Z);return[...t.author==="client"?o.filter(n=>!(n.pending&&n.author==="client"&&n.text===t.text)):o,t]}var Ae=(e,t)=>{let[o,s]=v([]),[n,d]=v(""),[r,a]=v(!1),[i,l]=v(null),u=$e(0),y=t?.pollMs??2e3;X(()=>{let c=!0;return e.getInfo().then(m=>{c&&m.greeting&&s(g=>g.length===0?[{id:Z,cursor:"",author:"agent",text:m.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),X(()=>e.subscribe?.({onMessage:c=>s(m=>K(m,c)),onError:c=>l(c.error),onHealthy:()=>l(null)},{intervalMs:y}),[e,y]);let p=Ee(()=>{let c=n.trim();if(!c||r)return;d(""),l(null),a(!0),u.current+=1;let m={id:`pending-${u.current}`,cursor:"",author:"client",text:c,pending:!0};s(g=>[...g,m]),e.sendMessage(c).then(()=>a(!1)).catch(g=>{l(g instanceof Error?g.message:"send_failed"),a(!1)})},[n,r,e]);return{messages:o,input:n,setInput:d,send:p,sending:r,error:i}};export{ye as Chat,de as HolaProvider,S as PortalConflict,oe as createApiTransport,he as createHolaClient,Ie as createPortalData,U as createWidgetTransport,B as inferSpec,K as mergeMessage,Q as parseDashboardSpec,V as renderModel,I as textDeltaOf,E as thinkingDeltaOf,$ as toolEventOf,A as useArtifactUrl,M as useChat,W as useHolaClient,Ae as useThread};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holaboss/client",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
4
|
-
"description": "Injectable client SDK for embedding a Holaboss AI employee (chat today;
|
|
3
|
+
"version": "0.2.0-beta.5",
|
|
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",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
22
|
"build": "tsup",
|
|
23
23
|
"check-types": "tsc --noEmit",
|
|
24
|
-
"prepublishOnly": "bun run build"
|
|
24
|
+
"prepublishOnly": "bun run build",
|
|
25
|
+
"test": "vitest run"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
28
|
"react": ">=18"
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"@types/react-dom": "^19.1.9",
|
|
32
33
|
"tsconfig": "workspace:*",
|
|
33
34
|
"tsup": "^8.3.5",
|
|
34
|
-
"typescript": "catalog:"
|
|
35
|
+
"typescript": "catalog:",
|
|
36
|
+
"vitest": "^4.1.0"
|
|
35
37
|
},
|
|
36
38
|
"keywords": [
|
|
37
39
|
"ai",
|