@holaboss/client 0.2.0-beta.0 → 0.2.0-beta.10
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 +321 -19
- package/dist/index.d.ts +321 -19
- 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
|
-
`))
|
|
4
|
-
`)}:null},
|
|
2
|
+
var J=Object.defineProperty;var Ee=Object.getOwnPropertyDescriptor;var $e=Object.getOwnPropertyNames;var Oe=Object.prototype.hasOwnProperty;var _e=(e,t)=>{for(var r in t)J(e,r,{get:t[r],enumerable:!0})},Ue=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of $e(t))!Oe.call(e,s)&&s!==r&&J(e,s,{get:()=>t[s],enumerable:!(o=Ee(t,s))||o.enumerable});return e};var Fe=e=>Ue(J({},"__esModule",{value:!0}),e);var lt={};_e(lt,{Chat:()=>ke,GRID_COLS:()=>K,GRID_MAX_COLS:()=>Y,GRID_MAX_ROWS:()=>Q,HolaProvider:()=>xe,PortalConflict:()=>I,bandOfKind:()=>N,bandOfType:()=>Z,cellValue:()=>pe,createApiTransport:()=>he,createHolaClient:()=>Te,createPortalData:()=>Me,createWidgetTransport:()=>L,deliverableCellText:()=>le,deliverableStatus:()=>se,deriveFields:()=>q,footprintOf:()=>ee,footprintWith:()=>G,formatDeliverableDate:()=>me,groupDeliverableVersions:()=>B,inferSpec:()=>X,isDeliverableChecked:()=>ue,mergeMessage:()=>ge,normalizeDeliverableColType:()=>V,normalizeDeliverableItem:()=>j,parseDashboardSpec:()=>re,parseDeliverableItems:()=>oe,parseDeliverableTable:()=>ae,parseTableResponses:()=>de,placeWidgets:()=>te,publishedVersionOf:()=>ie,renderModel:()=>ne,setResponse:()=>ce,textDeltaOf:()=>H,thinkingDeltaOf:()=>M,toolEventOf:()=>W,useArtifactUrl:()=>$,useChat:()=>U,useHolaClient:()=>_,useThread:()=>Ae});module.exports=Fe(lt);var fe=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},H=e=>{let t=fe(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},M=e=>{let t=fe(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},W=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 Le=/^ /,R=e=>{if(e&&typeof e=="object"){let t=e,r=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:r,retryable:t.retryable}:{error:r}}return{error:"request_failed"}},Ne=e=>{let t="message",r=[];for(let o of e.split(`
|
|
3
|
+
`))o.startsWith(":")||(o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("data:")&&r.push(o.slice(5).replace(Le,"")));return r.length>0?{event:t,data:r.join(`
|
|
4
|
+
`)}:null},Ge=(e,t,r)=>{let o;try{o=JSON.parse(e.data)}catch{return}if(e.event==="session"){let s=o.sessionId;s&&(r(s),t.onSession?.(s));return}if(e.event==="turn_event"){t.onEvent?.(o);let s=H(o);s&&t.onText?.(s);let l=M(o);l&&t.onThinking?.(l);let n=W(o);n&&t.onTool?.(n);return}if(e.event==="chat_completed"){let s=o;r(s.sessionId),t.onComplete?.(s);return}e.event==="chat_error"&&t.onError?.(R(o))},h=async(e,t)=>{let r=await fetch(e,{headers:t});if(!r.ok)throw new Error(`hola: request failed (${r.status})`);return await r.json()},ye=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()},be=async(e,t,r,o)=>{let s=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:o});if(!(s.ok&&s.body)){t.onError?.({error:`stream_failed_${s.status}`});return}let l=s.body.getReader(),n=new TextDecoder,i="",a=()=>{let d=i.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;d!==-1;){let m=i.slice(0,d);i=i.slice(d+2);let p=Ne(m);p&&Ge(p,t,r),d=i.indexOf(`
|
|
7
7
|
|
|
8
|
-
`)}},i=async()=>{let l=await p.read();if(!l.done)return o+=r.decode(l.value,{stream:!0}),d(),i()};await i()};var ne=/\/+$/,E="/api/v1/end-user",j=e=>{let t=e.baseUrl.replace(ne,""),n=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await n()}`}),a=async p=>{let r=await fetch(`${t}${E}/chat`,{method:"POST",headers:await s(),body:JSON.stringify({message:p})});if(!r.ok)throw new Error(`hola: message failed (${r.status})`);return{reply:(await r.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>C(`${t}${E}/chat/info`,await s()),sendMessage:a,streamMessage:async(p,r)=>{try{let o=await a(p);r.onText?.(o.reply),r.onComplete?.(o)}catch(o){r.onError?.(T(o))}},subscribe:(p,r)=>{let o=r?.since??"0",d=!1,i,l=r?.intervalMs??3e3,f=async()=>{try{let c=await C(`${t}${E}/chat/thread?since=${encodeURIComponent(o)}`,await s());for(let y of c.messages)p.onMessage(y);o=c.cursor}catch(c){p.onError?.(T(c))}d||(i=setTimeout(f,l))},u=()=>{d=!0,i&&clearTimeout(i)};return r?.signal?.addEventListener("abort",u),i=setTimeout(f,0),u},requestHuman:async()=>{await fetch(`${t}${E}/chat/request-human`,{method:"POST",headers:await s(),body:"{}"})}}};var S=require("react"),k=(e,t)=>{let[n,s]=(0,S.useState)("");return(0,S.useEffect)(()=>{if(!e){s("");return}let a=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),p=URL.createObjectURL(new Blob([a],{type:t}));return s(p),()=>URL.revokeObjectURL(p)},[e,t]),n};var U=require("react");var $=require("react"),q=require("react/jsx-runtime"),z=(0,$.createContext)(null),J=({client:e,children:t})=>(0,q.jsx)(z.Provider,{value:e,children:t}),M=()=>{let e=(0,$.useContext)(z);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};var b=require("react"),A=e=>{let[t,n]=(0,b.useState)([]),[s,a]=(0,b.useState)(""),[p,r]=(0,b.useState)(!1),[o,d]=(0,b.useState)(null),i=(0,b.useRef)(0),l=(0,b.useCallback)(()=>{let u=i.current;return i.current+=1,u},[]);(0,b.useEffect)(()=>{let u=!0;return e.getInfo().then(c=>{u&&c.greeting&&n([{id:l(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,l]);let f=(0,b.useCallback)(()=>{let u=s.trim();if(!u||p)return;a(""),d(null),r(!0);let c=l(),y=l();n(m=>[...m,{id:c,role:"user",text:u},{id:y,role:"assistant",text:"",streaming:!0}]);let g=m=>{n(x=>x.map(_=>_.id===y?m(_):_))};e.streamMessage(u,{onText:m=>g(x=>({...x,text:x.text+m})),onComplete:m=>{g(x=>({...x,text:m.reply||x.text,artifacts:m.artifacts,streaming:!1})),r(!1)},onError:m=>{g(x=>({...x,streaming:!1})),d(m.error),r(!1)}}).catch(m=>{g(x=>({...x,streaming:!1})),d(m instanceof Error?m.message:"stream_failed"),r(!1)})},[s,p,e,l]);return{messages:t,input:s,setInput:a,send:f,sending:p,error:o}};var h=require("react/jsx-runtime"),H={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"}},ae=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),n=e.url??t;return n?e.mimeType.startsWith("image/")?(0,h.jsx)("a",{href:n,rel:"noopener noreferrer",target:"_blank",children:(0,h.jsx)("img",{alt:e.filename,src:n,style:H.image})}):(0,h.jsx)("a",{download:e.filename,href:n,style:H.link,children:e.filename}):null},B=()=>{let e=M(),{messages:t,input:n,setInput:s,send:a,sending:p,error:r}=A(e),o=(0,U.useRef)(null);(0,U.useEffect)(()=>{let i=o.current;i&&(i.scrollTop=i.scrollHeight)},[t]);let d=i=>{i.preventDefault(),a()};return(0,h.jsxs)("div",{style:H.root,children:[(0,h.jsx)("div",{ref:o,style:H.log,children:t.map(i=>(0,h.jsxs)("div",{style:i.role==="user"?H.user:H.assistant,children:[i.text||(i.streaming?"\u2026":""),i.artifacts&&i.artifacts.length>0?(0,h.jsx)("div",{style:H.artifacts,children:i.artifacts.map(l=>(0,h.jsx)(ae,{artifact:l},l.filename))}):null]},i.id))}),r?(0,h.jsx)("div",{style:H.error,children:r}):null,(0,h.jsxs)("form",{onSubmit:d,style:H.form,children:[(0,h.jsx)("input",{"aria-label":"Message",onChange:i=>s(i.target.value),placeholder:"Type a message\u2026",style:H.input,value:n}),(0,h.jsx)("button",{disabled:p||n.trim().length===0,style:H.button,type:"submit",children:"Send"})]})]})};var ie=/\/+$/,O=e=>{let t=e.sessionId,n=e.baseUrl.replace(ie,""),s=r=>`${n}/endpoint/${encodeURIComponent(e.token)}${r}`,a={"content-type":"application/json"},p=(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:()=>C(s("/config"),{}),sendMessage:async r=>{let o=await W(p("/message",r));return t=o.sessionId,o},streamMessage:(r,o,d)=>N(p("/message/stream",r),o,i=>{t=i},d),subscribe:(r,o)=>{let d=o?.since??"0",i=!1,l,f=o?.intervalMs??3e3,u=async()=>{let y=t;if(y)try{let g=await C(s(`/messages?session=${encodeURIComponent(y)}&since=${encodeURIComponent(d)}`),{});for(let m of g.messages)r.onMessage(m);d=g.cursor}catch(g){r.onError?.(T(g))}i||(l=setTimeout(u,f))},c=()=>{i=!0,l&&clearTimeout(l)};return o?.signal?.addEventListener("abort",c),l=setTimeout(u,0),c},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:a,body:JSON.stringify({sessionId:t})})}}};var G=e=>"transport"in e?e.transport:O(e);var le=/\/+$/,v="/api/v1/end-user";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 K=e=>{let t=e.baseUrl.replace(le,""),n=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await n()}`}),a=async(r,o)=>{try{let d=await fetch(`${t}${v}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(!d.ok){let i=await d.text().catch(()=>""),l=`${o.method} ${r} \u2192 ${String(d.status)}`;try{let f=JSON.parse(i);f.message&&(l=f.message)}catch{l=i?`${l}: ${i}`:l}throw T(new Error(l))}return await d.json()}catch(d){throw T(d)}},p=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await C(`${t}${v}/tables`,await s())).tables,listRows:async(r,o)=>await C(`${t}${v}${p(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:d}=await a(`${p(r)}/rows`,{method:"POST",body:{rows:[o]}}),i=d[0];if(!i)throw T(new Error("The row was not created."));return i},patchRow:async(r,o,d)=>(await a(`${p(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:d})).updated,deleteRow:async(r,o)=>(await a(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await C(`${t}${v}/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 d=new Uint8Array(await r.arrayBuffer());return await a("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:pe(d),...o===void 0?{}:{message:o}}})},fileUrl:r=>`${t}${v}/files/${encodeURIComponent(r)}/raw`,deleteFile:async r=>(await a(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted}};var w=require("react");function D(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 V=(e,t)=>{let[n,s]=(0,w.useState)([]),[a,p]=(0,w.useState)(""),[r,o]=(0,w.useState)(!1),[d,i]=(0,w.useState)(null),l=(0,w.useRef)(0),f=t?.pollMs??2e3;(0,w.useEffect)(()=>{let c=!0;return e.getInfo().then(y=>{c&&y.greeting&&s(g=>g.length===0?[{id:"greeting",cursor:"",author:"agent",text:y.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),(0,w.useEffect)(()=>e.subscribe?.({onMessage:c=>s(y=>D(y,c)),onError:c=>i(c.error)},{intervalMs:f}),[e,f]);let u=(0,w.useCallback)(()=>{let c=a.trim();if(!c||r)return;p(""),i(null),o(!0),l.current+=1;let y={id:`pending-${l.current}`,cursor:"",author:"client",text:c,pending:!0};s(g=>[...g,y]),e.sendMessage(c).then(()=>o(!1)).catch(g=>{i(g instanceof Error?g.message:"send_failed"),o(!1)})},[a,r,e]);return{messages:n,input:a,setInput:p,send:u,sending:r,error:d}};0&&(module.exports={Chat,HolaProvider,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,mergeMessage,textDeltaOf,thinkingDeltaOf,toolEventOf,useArtifactUrl,useChat,useHolaClient,useThread});
|
|
8
|
+
`)}},u=async()=>{let d=await l.read();if(!d.done)return i+=n.decode(d.value,{stream:!0}),a(),u()};await u()};var je=/\/+$/,A="/api/v1/end-user",he=e=>{let t=e.baseUrl.replace(je,""),r=()=>typeof e.token=="function"?e.token():e.token,o=n=>e.project?`${n}project=${encodeURIComponent(e.project)}`:"",s=async()=>({"content-type":"application/json",authorization:`Bearer ${await r()}`}),l=async n=>{let i=await fetch(`${t}${A}/chat${o("?")}`,{method:"POST",headers:await s(),body:JSON.stringify({message:n})});if(!i.ok)throw new Error(`hola: message failed (${i.status})`);return{reply:(await i.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>h(`${t}${A}/chat/info${o("?")}`,await s()),sendMessage:l,streamMessage:async(n,i)=>{try{let a=await l(n);i.onText?.(a.reply),i.onComplete?.(a)}catch(a){i.onError?.(R(a))}},subscribe:(n,i)=>{let a=i?.since??"0",u=!1,d,m=i?.intervalMs??3e3,p=async()=>{try{let f=await h(`${t}${A}/chat/thread?since=${encodeURIComponent(a)}${o("&")}`,await s());for(let g of f.messages)n.onMessage(g);a=f.cursor,n.onHealthy?.()}catch(f){n.onError?.(R(f))}u||(d=setTimeout(p,m))},c=()=>{u=!0,d&&clearTimeout(d)};return i?.signal?.addEventListener("abort",c),d=setTimeout(p,0),c},requestHuman:async()=>{await fetch(`${t}${A}/chat/request-human${o("?")}`,{method:"POST",headers:await s(),body:"{}"})}}};var E=require("react"),$=(e,t)=>{let[r,o]=(0,E.useState)("");return(0,E.useEffect)(()=>{if(!e){o("");return}let s=Uint8Array.from(atob(e),n=>n.charCodeAt(0)),l=URL.createObjectURL(new Blob([s],{type:t}));return o(l),()=>URL.revokeObjectURL(l)},[e,t]),r};var F=require("react");var O=require("react"),ve=require("react/jsx-runtime"),we=(0,O.createContext)(null),xe=({client:e,children:t})=>(0,ve.jsx)(we.Provider,{value:e,children:t}),_=()=>{let e=(0,O.useContext)(we);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};var w=require("react"),U=e=>{let[t,r]=(0,w.useState)([]),[o,s]=(0,w.useState)(""),[l,n]=(0,w.useState)(!1),[i,a]=(0,w.useState)(null),u=(0,w.useRef)(0),d=(0,w.useCallback)(()=>{let p=u.current;return u.current+=1,p},[]);(0,w.useEffect)(()=>{let p=!0;return e.getInfo().then(c=>{p&&c.greeting&&r([{id:d(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{p=!1}},[e,d]);let m=(0,w.useCallback)(()=>{let p=o.trim();if(!p||l)return;s(""),a(null),n(!0);let c=d(),f=d();r(y=>[...y,{id:c,role:"user",text:p},{id:f,role:"assistant",text:"",streaming:!0}]);let g=y=>{r(v=>v.map(z=>z.id===f?y(z):z))};e.streamMessage(p,{onText:y=>g(v=>({...v,text:v.text+y})),onComplete:y=>{g(v=>({...v,text:y.reply||v.text,artifacts:y.artifacts,streaming:!1})),n(!1)},onError:y=>{g(v=>({...v,streaming:!1})),a(y.error),n(!1)}}).catch(y=>{g(v=>({...v,streaming:!1})),a(y instanceof Error?y.message:"stream_failed"),n(!1)})},[o,l,e,d]);return{messages:t,input:o,setInput:s,send:m,sending:l,error:i}};var b=require("react/jsx-runtime"),k={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"}},Be=({artifact:e})=>{let t=$(e.dataBase64??"",e.mimeType),r=e.url??t;return r?e.mimeType.startsWith("image/")?(0,b.jsx)("a",{href:r,rel:"noopener noreferrer",target:"_blank",children:(0,b.jsx)("img",{alt:e.filename,src:r,style:k.image})}):(0,b.jsx)("a",{download:e.filename,href:r,style:k.link,children:e.filename}):null},ke=()=>{let e=_(),{messages:t,input:r,setInput:o,send:s,sending:l,error:n}=U(e),i=(0,F.useRef)(null);(0,F.useEffect)(()=>{let u=i.current;u&&(u.scrollTop=u.scrollHeight)},[t]);let a=u=>{u.preventDefault(),s()};return(0,b.jsxs)("div",{style:k.root,children:[(0,b.jsx)("div",{ref:i,style:k.log,children:t.map(u=>(0,b.jsxs)("div",{style:u.role==="user"?k.user:k.assistant,children:[u.text||(u.streaming?"\u2026":""),u.artifacts&&u.artifacts.length>0?(0,b.jsx)("div",{style:k.artifacts,children:u.artifacts.map(d=>(0,b.jsx)(Be,{artifact:d},d.filename))}):null]},u.id))}),n?(0,b.jsx)("div",{style:k.error,children:n}):null,(0,b.jsxs)("form",{onSubmit:a,style:k.form,children:[(0,b.jsx)("input",{"aria-label":"Message",onChange:u=>o(u.target.value),placeholder:"Type a message\u2026",style:k.input,value:r}),(0,b.jsx)("button",{disabled:l||r.trim().length===0,style:k.button,type:"submit",children:"Send"})]})]})};var Ve=/\/+$/,L=e=>{let t=e.sessionId,r=e.baseUrl.replace(Ve,""),o=n=>`${r}/endpoint/${encodeURIComponent(e.token)}${n}`,s={"content-type":"application/json"},l=(n,i)=>({url:o(n),headers:s,body:JSON.stringify({message:i,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>h(o("/config"),{}),sendMessage:async n=>{let i=await ye(l("/message",n));return t=i.sessionId,i},streamMessage:(n,i,a)=>be(l("/message/stream",n),i,u=>{t=u},a),subscribe:(n,i)=>{let a=i?.since??"0",u=!1,d,m=i?.intervalMs??3e3,p=async()=>{let f=t;if(f)try{let g=await h(o(`/messages?session=${encodeURIComponent(f)}&since=${encodeURIComponent(a)}`),{});for(let y of g.messages)n.onMessage(y);a=g.cursor,n.onHealthy?.()}catch(g){n.onError?.(R(g))}u||(d=setTimeout(p,m))},c=()=>{u=!0,d&&clearTimeout(d)};return i?.signal?.addEventListener("abort",c),d=setTimeout(p,0),c},requestHuman:async()=>{t&&await fetch(o("/request-human"),{method:"POST",headers:s,body:JSON.stringify({sessionId:t})})}}};var Te=e=>"transport"in e?e.transport:L(e);var ze=/^https?:\/\//i,Je=/^\d{4}-\d{2}-\d{2}(?:[T ]|$)/,qe=e=>e.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());function Xe(e){if(typeof e=="number")return"number";if(typeof e=="boolean")return"boolean";if(typeof e=="string"){if(ze.test(e))return"url";if(Je.test(e))return"date"}return"text"}function q(e){let t=[],r=new Map;for(let o of e)for(let[s,l]of Object.entries(o.data??{})){if(l==null)continue;let n=r.get(s);n||(n=new Map,r.set(s,n),t.push(s));let i=Xe(l);n.set(i,(n.get(i)??0)+1)}return t.map(o=>{let s=r.get(o)??new Map,l="text",n=-1;for(let[i,a]of s)a>n&&(n=a,l=i);return{key:o,label:qe(o),type:l}})}var Ke=e=>typeof e=="number"?e:Number(e)||0,Re=e=>/rate|position|rank|score|percent|avg/i.test(e),Ye=/posted|published|sent|completed|shipped|launched|_at|date/i,Qe=new Set(["service","order","end_user_id"]);function X(e,t){let r=e.filter(a=>!Qe.has(a.key)),o=r.filter(a=>a.type==="number").slice(0,3),s=r.find(a=>a.key==="status")??r.find(a=>a.type==="select"),l=r.find(a=>a.type==="date"&&Ye.test(a.key))??r.find(a=>a.type==="date"),n=r.filter(a=>a.type==="number"&&!Re(a.key)).map(a=>({field:a,total:t.reduce((u,d)=>u+Ke(d.data[a.key]),0)})).sort((a,u)=>u.total-a.total).find(a=>a.total>0)?.field??null,i=[{type:"stat",label:"Items",agg:"count"}];for(let a of o){let u=Re(a.key);i.push({type:"stat",metric:a.key,agg:u?"avg":"sum",label:u?`Avg ${a.label.toLowerCase()}`:a.label})}return l&&i.push({type:"line",x:l.key,y:n?.key,agg:"sum",title:n?`${n.label} by day`:"Items by day"}),s&&i.push({type:"donut",groupBy:s.key,title:s.label}),i.push({type:"table",columns:r.map(a=>a.key)}),{version:1,widgets:i}}var K=6,Y=6,Q=4,De={stat:{w:1,h:1},chart:{w:2,h:2},block:{w:6,h:4}},Ze=e=>Math.max(1,Math.min(6,Math.round(e))),et=e=>Math.max(1,Math.min(4,Math.round(e)));function N(e){return e==="stat"?"stat":e==="series"||e==="donut"?"chart":"block"}function Z(e){return e==="stat"?"stat":e==="line"||e==="bar"||e==="donut"?"chart":"block"}function ee(e){return De[e]}function G(e,t,r){let o=De[e];return{w:t===void 0?o.w:Ze(t),h:r===void 0?o.h:et(r)}}function te(e){let t=[],r=[],o=(n,i,a,u)=>{if(n<0||n+a>6)return!1;for(let d=0;d<u;d+=1){let m=r[i+d];if(m){for(let p=0;p<a;p+=1)if(m[n+p])return!1}}return!0},s=(n,i,a,u)=>{for(let d=0;d<u;d+=1){let m=r[i+d]??[];for(let p=0;p<a;p+=1)m[n+p]=!0;r[i+d]=m}},l=(n,i)=>{for(let a=0;;a+=1)for(let u=0;u+n<=6;u+=1)if(o(u,a,n,i))return{gx:u,gy:a}};for(let n of e){let i=G(N(n.kind),n.w,n.h),u=n.gx!==void 0&&n.gy!==void 0&&o(n.gx,n.gy,i.w,i.h)&&n.gx!==void 0&&n.gy!==void 0?{gx:n.gx,gy:n.gy}:l(i.w,i.h);s(u.gx,u.gy,i.w,i.h),t.push({widget:n,gx:u.gx,gy:u.gy,w:i.w,h:i.h})}return t}var Ce=new Set(["service","order","end_user_id"]),Se=e=>typeof e=="number"?e:Number(e)||0,Pe=e=>e==null?"":String(e);function Ie(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 r=t.reduce((o,s)=>o+s,0);return e==="avg"?Math.round(r/t.length):r}function tt(e,t,r,o){let s=new Map;for(let l of e){let n=new Date(Pe(l.data[t]));if(Number.isNaN(n.getTime()))continue;let i=n.toISOString().slice(0,10),a=s.get(i)??[];a.push(r?Se(l.data[r]):1),s.set(i,a)}return[...s.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:r?Ie(o,n):n.length}))}function nt(e,t){let r=new Map;for(let o of e){let s=Pe(o.data[t]).trim();s&&r.set(s,(r.get(s)??0)+1)}return[...r.entries()].sort((o,s)=>s[1]-o[1]).map(([o,s])=>({label:o,value:s}))}function rt(e,t,r,o){switch(e.type){case"stat":{let s=e.metric?t.get(e.metric):void 0;if(e.metric&&!s)return null;if(!s)return{kind:"stat",label:e.label??"Items",value:o.length};let l=o.map(n=>Se(n.data[s.key]));return{kind:"stat",label:e.label??s.label,value:Ie(e.agg??"sum",l)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let s=tt(o,e.x,e.y,e.agg??"sum");return s.length<2?null:{kind:"series",draw:e.type,title:e.title??"By day",points:s}}case"donut":{if(!t.has(e.groupBy))return null;let s=nt(o,e.groupBy);return s.length===0?null:{kind:"donut",title:e.title??t.get(e.groupBy)?.label??"Breakdown",points:s}}case"table":{let s=e.columns?e.columns.map(l=>t.get(l)).filter(l=>l!==void 0&&!Ce.has(l.key)):r;return s.length===0?null:{kind:"table",title:e.title,columns:s,rows:o}}case"text":return{kind:"text",title:e.title,body:e.body};default:return null}}function ne(e,t,r){let o=t.filter(n=>!Ce.has(n.key)),s=new Map(t.map(n=>[n.key,n])),l=[];for(let n of e.widgets){let i=rt(n,s,o,r);i&&l.push({...i,w:n.w,h:n.h,gx:n.gx,gy:n.gy})}return l}var ot=new Set(["sum","avg","count","min","max","last"]),He=e=>typeof e=="string"&&ot.has(e)?e:void 0,T=e=>typeof e=="string"&&e!==""?e:void 0,S=e=>typeof e=="number"&&e>=1?Math.min(6,Math.round(e)):void 0,P=e=>typeof e=="number"&&e>=1?Math.min(4,Math.round(e)):void 0,D=e=>typeof e=="number"&&e>=0?Math.round(e):void 0;function st(e){if(typeof e!="object"||e===null)return null;let t=e;switch(t.type){case"stat":return{type:"stat",label:T(t.label),metric:T(t.metric),agg:He(t.agg),w:S(t.w),h:P(t.h),gx:D(t.gx),gy:D(t.gy)};case"line":case"bar":{let r=T(t.x);return r?{type:t.type,title:T(t.title),x:r,y:T(t.y),agg:He(t.agg),w:S(t.w),h:P(t.h),gx:D(t.gx),gy:D(t.gy)}:null}case"donut":{let r=T(t.groupBy);return r?{type:"donut",title:T(t.title),groupBy:r,w:S(t.w),h:P(t.h),gx:D(t.gx),gy:D(t.gy)}:null}case"table":{let r=Array.isArray(t.columns)?t.columns.filter(o=>typeof o=="string"):void 0;return{type:"table",title:T(t.title),columns:r,w:S(t.w),h:P(t.h),gx:D(t.gx),gy:D(t.gy)}}case"text":{let r=T(t.body);return r?{type:"text",title:T(t.title),body:r,w:S(t.w),h:P(t.h),gx:D(t.gx),gy:D(t.gy)}:null}default:return null}}function re(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 r=t.widgets;if(!Array.isArray(r))return null;let o=r.map(st).filter(l=>l!==null);if(o.length===0)return null;let s=t.version;return{version:typeof s=="number"?s:1,title:T(t.title),widgets:o}}function j(e){if(typeof e=="string")return e?{kind:"file",value:e}:null;if(e&&typeof e=="object"){let t=e,r=typeof t.value=="string"?t.value:"",o=t.kind==="link"||t.kind==="text"||t.kind==="table"?t.kind:"file",s=typeof t.v=="number"&&t.v>=1?Math.floor(t.v):void 0;return r?{kind:o,value:r,...s?{v:s}:{}}:null}return null}function B(e){let t=new Map;for(let r of e){let o=r.v??1,s=t.get(o)??[];s.push(r),t.set(o,s)}return[...t.entries()].sort(([r],[o])=>r-o).map(([r,o])=>({v:r,items:o}))}function oe(e){let t=e.files;if(typeof t=="string"&&t.trim()!=="")try{let o=JSON.parse(t);if(Array.isArray(o))return o.map(j).filter(s=>s!==null)}catch{}let r=String(e.file??"");return r?[{kind:"file",value:r}]:[]}function se(e,t,r){let s=(B(e).find(n=>n.v===t)?.items??[]).filter(n=>n.kind==="file").map(n=>n.value),l=s.map(n=>r.get(n)??"");return s.length===0||l.some(n=>n==="")?"awaiting":l.some(n=>n==="revise")?"revise":"accepted"}function ie(e){return Number(e.published_version)||0}function V(e){return e==="number"||e==="date"||e==="boolean"||e==="select"||e==="url"?e:e==="link"?"url":e==="checkbox"?"boolean":"text"}function ae(e){try{let t=JSON.parse(e);if(Array.isArray(t.columns)&&Array.isArray(t.rows)&&t.columns.length){let r=t.columns.map((l,n)=>({key:typeof l.key=="string"&&l.key?l.key:typeof l.id=="string"&&l.id?l.id:`column_${n+1}`,label:typeof l.label=="string"?l.label:"",type:V(l.type),...l.clientEditable===!0?{clientEditable:!0}:{}})),o=t.rows.map((l,n)=>({id:typeof l.id=="string"&&l.id?l.id:`row_${n}`,cells:l.cells&&typeof l.cells=="object"?l.cells:{}})),s=typeof t.name=="string"?t.name:void 0;return{columns:r,rows:o,...s?{name:s}:{}}}}catch{}return null}function le(e){return e==null?"":String(e)}function ue(e){return e===!0||e==="true"||e==="1"||e==="yes"||e==="\u2713"}function de(e){if(typeof e!="string"||e.trim()==="")return{};try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{}return{}}function pe(e,t,r){if(e.clientEditable){let o=r[t.id];if(o&&e.key in o)return o[e.key]}return t.cells[e.key]}function ce(e,t,r,o){return{...e,[t]:{...e[t]??{},[r]:o}}}function me(e){let t=new Date(`${e}T00:00:00`);return Number.isNaN(t.getTime())?e:t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}var it=/\/+$/,C="/api/v1/end-user",I=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function at(e){let t="";for(let o=0;o<e.length;o+=32768)t+=String.fromCharCode(...e.subarray(o,o+32768));return btoa(t)}var Me=e=>{let t=e.baseUrl.replace(it,""),r=()=>typeof e.token=="function"?e.token():e.token,o=async()=>({"content-type":"application/json",authorization:`Bearer ${await r()}`}),s=async(n,i)=>{try{let a=await fetch(`${t}${C}${n}`,{method:i.method,headers:await o(),...i.body===void 0?{}:{body:JSON.stringify(i.body)}});if(a.status===409){let u=await a.json().catch(()=>({}));throw new I(u.message??"Someone else saved this first.")}if(!a.ok){let u=await a.text().catch(()=>""),d=`${i.method} ${n} \u2192 ${String(a.status)}`;try{let m=JSON.parse(u);m.message&&(d=m.message)}catch{d=u?`${d}: ${u}`:d}throw R(new Error(d))}return await a.json()}catch(a){throw R(a)}},l=n=>`/tables/${encodeURIComponent(n)}`;return{listTables:async()=>(await h(`${t}${C}/tables`,await o())).tables,listRows:async(n,i)=>await h(`${t}${C}${l(n)}/rows?limit=${String(i??100)}`,await o()),insertRow:async(n,i)=>{let{rowIds:a}=await s(`${l(n)}/rows`,{method:"POST",body:{rows:[i]}}),u=a[0];if(!u)throw R(new Error("The row was not created."));return u},patchRow:async(n,i,a)=>(await s(`${l(n)}/rows/${encodeURIComponent(i)}`,{method:"PATCH",body:a})).updated,deleteRow:async(n,i)=>(await s(`/${encodeURIComponent(n)}/${encodeURIComponent(i)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await h(`${t}${C}/tables/files/rows?limit=200`,await o())).rows.map(i=>({id:i.rowId,filename:String(i.data.filename??""),source:String(i.data.source??"client"),campaign:String(i.data.campaign??""),size:typeof i.data.size=="number"?i.data.size:0,createdAt:i.createdAt})),uploadFile:async(n,i)=>{let a=new Uint8Array(await n.arrayBuffer());return await s("/files",{method:"POST",body:{filename:n.name,mime:n.type||"application/octet-stream",data:at(a),...i===void 0?{}:{message:i}}})},fileLink:async n=>await h(`${t}${C}/files/${encodeURIComponent(n)}/url`,await o()),deleteFile:async n=>(await s(`/files/${encodeURIComponent(n)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await h(`${t}${C}/documents`,await o())).documents,readDocument:async n=>(await h(`${t}${C}/documents/${encodeURIComponent(n)}`,await o())).document,saveDocument:async(n,i)=>await s(`/documents/${encodeURIComponent(n)}`,{method:"PATCH",body:i})}};var x=require("react"),We="greeting";function ge(e,t){if(e.some(s=>s.id===t.id))return e;let r=e.filter(s=>s.id!==We);return[...t.author==="client"?r.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):r,t]}var Ae=(e,t)=>{let[r,o]=(0,x.useState)([]),[s,l]=(0,x.useState)(""),[n,i]=(0,x.useState)(!1),[a,u]=(0,x.useState)(null),d=(0,x.useRef)(0),m=t?.pollMs??2e3;(0,x.useEffect)(()=>{let c=!0;return e.getInfo().then(f=>{c&&f.greeting&&o(g=>g.length===0?[{id:We,cursor:"",author:"agent",text:f.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),(0,x.useEffect)(()=>e.subscribe?.({onMessage:c=>o(f=>ge(f,c)),onError:c=>u(c.error),onHealthy:()=>u(null)},{intervalMs:m}),[e,m]);let p=(0,x.useCallback)(()=>{let c=s.trim();if(!c||n)return;l(""),u(null),i(!0),d.current+=1;let f={id:`pending-${d.current}`,cursor:"",author:"client",text:c,pending:!0};o(g=>[...g,f]),e.sendMessage(c).then(()=>i(!1)).catch(g=>{u(g instanceof Error?g.message:"send_failed"),i(!1)})},[s,n,e]);return{messages:r,input:s,setInput:l,send:p,sending:n,error:a}};0&&(module.exports={Chat,GRID_COLS,GRID_MAX_COLS,GRID_MAX_ROWS,HolaProvider,PortalConflict,bandOfKind,bandOfType,cellValue,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,deliverableCellText,deliverableStatus,deriveFields,footprintOf,footprintWith,formatDeliverableDate,groupDeliverableVersions,inferSpec,isDeliverableChecked,mergeMessage,normalizeDeliverableColType,normalizeDeliverableItem,parseDashboardSpec,parseDeliverableItems,parseDeliverableTable,parseTableResponses,placeWidgets,publishedVersionOf,renderModel,setResponse,textDeltaOf,thinkingDeltaOf,toolEventOf,useArtifactUrl,useChat,useHolaClient,useThread});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/** A file the employee produced (image/video/audio/other). In-turn artifacts (a turn's
|
|
@@ -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. */
|
|
@@ -108,10 +111,19 @@ type ApiTransportConfig = {
|
|
|
108
111
|
/** End-user access token for the Authorization header. A string, or a getter so a
|
|
109
112
|
* refreshed token is used per request. */
|
|
110
113
|
token: string | (() => string | Promise<string>);
|
|
114
|
+
/**
|
|
115
|
+
* Which of the customer's projects this conversation is about.
|
|
116
|
+
*
|
|
117
|
+
* Omit for the general thread — the one running conversation with the company,
|
|
118
|
+
* and where everything that predates per-project threads still lives. The
|
|
119
|
+
* server checks the project belongs to this customer before it trusts it, so
|
|
120
|
+
* naming someone else's is a 404 rather than a way into their thread.
|
|
121
|
+
*/
|
|
122
|
+
project?: string;
|
|
111
123
|
};
|
|
112
124
|
/** The Bearer-authed transport for the signed-in end-user PORTAL (`/api/v1/end-user/*`). Same
|
|
113
125
|
* client surface as the anonymous widget transport — the token (not a path param) resolves the
|
|
114
|
-
* customer
|
|
126
|
+
* customer; an optional `project` picks which of their threads. The portal is buffered (no SSE) and one-thread-per-user,
|
|
115
127
|
* so `sendMessage` returns just the reply and everything out-of-turn — the AI's turns, a human's
|
|
116
128
|
* reply, an async artifact (video) — arrives via `subscribe`. */
|
|
117
129
|
declare const createApiTransport: (config: ApiTransportConfig) => HolaTransport;
|
|
@@ -122,7 +134,7 @@ declare const useArtifactUrl: (dataBase64: string, mimeType: string) => string;
|
|
|
122
134
|
|
|
123
135
|
/** A minimal, dependency-free chat surface for the employee. Drop it inside a
|
|
124
136
|
* `<HolaProvider>`. For custom UI, use `useChat(client)` directly. */
|
|
125
|
-
declare const Chat: () =>
|
|
137
|
+
declare const Chat: () => react.JSX.Element;
|
|
126
138
|
|
|
127
139
|
type WidgetTransportConfig = {
|
|
128
140
|
/** API origin that fronts the Holaboss employee endpoint, e.g. "https://api.holaos.ai". */
|
|
@@ -149,13 +161,6 @@ type CreateHolaClientOptions = WidgetTransportConfig | {
|
|
|
149
161
|
* agnostic — the current transport is an internal detail, not the SDK's identity. */
|
|
150
162
|
declare const createHolaClient: (options: CreateHolaClientOptions) => HolaClient;
|
|
151
163
|
|
|
152
|
-
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
153
|
-
declare const textDeltaOf: (event: unknown) => string | null;
|
|
154
|
-
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
155
|
-
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
156
|
-
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
157
|
-
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
158
|
-
|
|
159
164
|
/** A column, as the company defined it. */
|
|
160
165
|
type PortalField = {
|
|
161
166
|
key: string;
|
|
@@ -197,6 +202,29 @@ type PortalFile = {
|
|
|
197
202
|
size: number;
|
|
198
203
|
createdAt: string;
|
|
199
204
|
};
|
|
205
|
+
/** A document shared with this customer. The body arrives only on a read. */
|
|
206
|
+
type PortalDocumentSummary = {
|
|
207
|
+
documentId: string;
|
|
208
|
+
title: string;
|
|
209
|
+
version: number;
|
|
210
|
+
/** Whether the company opened this one for editing, not merely for reading. */
|
|
211
|
+
editable: boolean;
|
|
212
|
+
updatedAt: string;
|
|
213
|
+
};
|
|
214
|
+
type PortalDocument = PortalDocumentSummary & {
|
|
215
|
+
body: string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* A save that lost a race.
|
|
219
|
+
*
|
|
220
|
+
* Thrown rather than returned so it cannot be mistaken for success, and separate
|
|
221
|
+
* from a plain error because the caller should do something different: re-read,
|
|
222
|
+
* reapply, save again — never retry the same bytes, which would overwrite whoever
|
|
223
|
+
* got there first.
|
|
224
|
+
*/
|
|
225
|
+
declare class PortalConflict extends Error {
|
|
226
|
+
constructor(message: string);
|
|
227
|
+
}
|
|
200
228
|
type PortalData = {
|
|
201
229
|
/** The tables this customer can see. A table the company has not shared is
|
|
202
230
|
* absent, not empty — it answers as if it did not exist. */
|
|
@@ -212,8 +240,14 @@ type PortalData = {
|
|
|
212
240
|
insertRow(slug: string, row: Record<string, unknown>): Promise<string>;
|
|
213
241
|
/** Pass an empty string to clear a field. */
|
|
214
242
|
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>;
|
|
215
|
-
/**
|
|
216
|
-
*
|
|
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
|
+
*/
|
|
217
251
|
deleteRow(slug: string, rowId: string): Promise<number>;
|
|
218
252
|
/** Everything exchanged with this customer, newest first. */
|
|
219
253
|
listFiles(): Promise<PortalFile[]>;
|
|
@@ -222,19 +256,290 @@ type PortalData = {
|
|
|
222
256
|
uploadFile(file: File, message?: string): Promise<{
|
|
223
257
|
rowId: string;
|
|
224
258
|
}>;
|
|
225
|
-
/**
|
|
226
|
-
|
|
259
|
+
/**
|
|
260
|
+
* A link the browser can actually follow.
|
|
261
|
+
*
|
|
262
|
+
* Not a plain URL string: the download route needs the customer's token, and
|
|
263
|
+
* navigation from an `<a href>` carries no Authorization header — so a direct
|
|
264
|
+
* link is a guaranteed 401. This fetches the signed URL with the token, and
|
|
265
|
+
* what comes back needs no header of its own.
|
|
266
|
+
*/
|
|
267
|
+
fileLink(rowId: string): Promise<{
|
|
268
|
+
url: string;
|
|
269
|
+
filename: string;
|
|
270
|
+
}>;
|
|
227
271
|
/** Only files this customer sent, and only before they are filed. */
|
|
228
272
|
deleteFile(rowId: string): Promise<number>;
|
|
273
|
+
/** Documents the company shared with this customer. */
|
|
274
|
+
listDocuments(): Promise<PortalDocumentSummary[]>;
|
|
275
|
+
readDocument(documentId: string): Promise<PortalDocument>;
|
|
276
|
+
/**
|
|
277
|
+
* Save new text.
|
|
278
|
+
*
|
|
279
|
+
* `baseVersion` is the version that was open. Rejects with a PortalConflict if
|
|
280
|
+
* it has moved — do not retry with the same text: read again, put the change
|
|
281
|
+
* back on top of theirs, and save that.
|
|
282
|
+
*/
|
|
283
|
+
saveDocument(documentId: string, edit: {
|
|
284
|
+
body: string;
|
|
285
|
+
baseVersion: number;
|
|
286
|
+
note?: string;
|
|
287
|
+
}): Promise<{
|
|
288
|
+
version: number;
|
|
289
|
+
}>;
|
|
229
290
|
};
|
|
230
291
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
231
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
|
+
w?: number | undefined;
|
|
310
|
+
h?: number | undefined;
|
|
311
|
+
gx?: number | undefined;
|
|
312
|
+
gy?: number | undefined;
|
|
313
|
+
}
|
|
314
|
+
/** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
|
|
315
|
+
* rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
|
|
316
|
+
interface SeriesWidget {
|
|
317
|
+
type: "line" | "bar";
|
|
318
|
+
title?: string | undefined;
|
|
319
|
+
x: string;
|
|
320
|
+
y?: string | undefined;
|
|
321
|
+
agg?: Agg | undefined;
|
|
322
|
+
w?: number | undefined;
|
|
323
|
+
h?: number | undefined;
|
|
324
|
+
gx?: number | undefined;
|
|
325
|
+
gy?: number | undefined;
|
|
326
|
+
}
|
|
327
|
+
/** A breakdown: the count of rows per distinct value of `groupBy`. */
|
|
328
|
+
interface DonutWidget {
|
|
329
|
+
type: "donut";
|
|
330
|
+
title?: string | undefined;
|
|
331
|
+
groupBy: string;
|
|
332
|
+
w?: number | undefined;
|
|
333
|
+
h?: number | undefined;
|
|
334
|
+
gx?: number | undefined;
|
|
335
|
+
gy?: number | undefined;
|
|
336
|
+
}
|
|
337
|
+
/** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
|
|
338
|
+
interface TableWidget {
|
|
339
|
+
type: "table";
|
|
340
|
+
title?: string | undefined;
|
|
341
|
+
columns?: string[] | undefined;
|
|
342
|
+
w?: number | undefined;
|
|
343
|
+
h?: number | undefined;
|
|
344
|
+
gx?: number | undefined;
|
|
345
|
+
gy?: number | undefined;
|
|
346
|
+
}
|
|
347
|
+
/** Static prose — a note above or between the data widgets. */
|
|
348
|
+
interface TextWidget {
|
|
349
|
+
type: "text";
|
|
350
|
+
title?: string | undefined;
|
|
351
|
+
body: string;
|
|
352
|
+
w?: number | undefined;
|
|
353
|
+
h?: number | undefined;
|
|
354
|
+
gx?: number | undefined;
|
|
355
|
+
gy?: number | undefined;
|
|
356
|
+
}
|
|
357
|
+
type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
|
|
358
|
+
interface DashboardSpec {
|
|
359
|
+
version: number;
|
|
360
|
+
title?: string | undefined;
|
|
361
|
+
widgets: Widget[];
|
|
362
|
+
}
|
|
363
|
+
/** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
|
|
364
|
+
* for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
|
|
365
|
+
* caller falls back to inference instead of showing a blank dashboard. */
|
|
366
|
+
declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
|
|
367
|
+
|
|
368
|
+
declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
|
|
369
|
+
|
|
370
|
+
/** A point on a line/bar chart or a slice of a donut. */
|
|
371
|
+
interface Point {
|
|
372
|
+
label: string;
|
|
373
|
+
value: number;
|
|
374
|
+
}
|
|
375
|
+
/** A widget resolved against the data — everything a renderer needs, already computed. */
|
|
376
|
+
type ResolvedWidget = {
|
|
377
|
+
kind: "stat";
|
|
378
|
+
label: string;
|
|
379
|
+
value: number;
|
|
380
|
+
w?: number | undefined;
|
|
381
|
+
h?: number | undefined;
|
|
382
|
+
gx?: number | undefined;
|
|
383
|
+
gy?: number | undefined;
|
|
384
|
+
} | {
|
|
385
|
+
kind: "series";
|
|
386
|
+
draw: "line" | "bar";
|
|
387
|
+
title: string;
|
|
388
|
+
points: Point[];
|
|
389
|
+
w?: number | undefined;
|
|
390
|
+
h?: number | undefined;
|
|
391
|
+
gx?: number | undefined;
|
|
392
|
+
gy?: number | undefined;
|
|
393
|
+
} | {
|
|
394
|
+
kind: "donut";
|
|
395
|
+
title: string;
|
|
396
|
+
points: Point[];
|
|
397
|
+
w?: number | undefined;
|
|
398
|
+
h?: number | undefined;
|
|
399
|
+
gx?: number | undefined;
|
|
400
|
+
gy?: number | undefined;
|
|
401
|
+
} | {
|
|
402
|
+
kind: "table";
|
|
403
|
+
title?: string | undefined;
|
|
404
|
+
columns: PortalField[];
|
|
405
|
+
rows: PortalRow[];
|
|
406
|
+
w?: number | undefined;
|
|
407
|
+
h?: number | undefined;
|
|
408
|
+
gx?: number | undefined;
|
|
409
|
+
gy?: number | undefined;
|
|
410
|
+
} | {
|
|
411
|
+
kind: "text";
|
|
412
|
+
title?: string | undefined;
|
|
413
|
+
body: string;
|
|
414
|
+
w?: number | undefined;
|
|
415
|
+
h?: number | undefined;
|
|
416
|
+
gx?: number | undefined;
|
|
417
|
+
gy?: number | undefined;
|
|
418
|
+
};
|
|
419
|
+
/** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
|
|
420
|
+
* draw nothing. */
|
|
421
|
+
declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
|
|
422
|
+
|
|
423
|
+
type Band = "stat" | "chart" | "block";
|
|
424
|
+
type StatView = Extract<ResolvedWidget, {
|
|
425
|
+
kind: "stat";
|
|
426
|
+
}>;
|
|
427
|
+
type ChartView = Extract<ResolvedWidget, {
|
|
428
|
+
kind: "series" | "donut";
|
|
429
|
+
}>;
|
|
430
|
+
/** The dashboard grid is a fixed 6 columns wide, at every width. */
|
|
431
|
+
declare const GRID_COLS = 6;
|
|
432
|
+
declare const GRID_MAX_COLS = 6;
|
|
433
|
+
declare const GRID_MAX_ROWS = 4;
|
|
434
|
+
/** The band a resolved widget belongs to. */
|
|
435
|
+
declare function bandOfKind(kind: ResolvedWidget["kind"]): Band;
|
|
436
|
+
/** The band a spec widget TYPE belongs to — for the editor, which lays out widgets before they resolve
|
|
437
|
+
* (a half-configured widget still needs a cell). Mirrors bandOfKind. */
|
|
438
|
+
declare function bandOfType(type: WidgetType): Band;
|
|
439
|
+
/** A band's default cell size. */
|
|
440
|
+
declare function footprintOf(band: Band): {
|
|
441
|
+
w: number;
|
|
442
|
+
h: number;
|
|
443
|
+
};
|
|
444
|
+
/** A widget's effective cell size: its stored `w`×`h` (clamped to the grid), else the band default.
|
|
445
|
+
* The renderers and the editor both size cells through this, so a widget looks the same everywhere. */
|
|
446
|
+
declare function footprintWith(band: Band, w: number | undefined, h: number | undefined): {
|
|
447
|
+
w: number;
|
|
448
|
+
h: number;
|
|
449
|
+
};
|
|
450
|
+
/** A widget placed on the grid — the view-model plus its top-left cell and size in cells. */
|
|
451
|
+
interface PlacedWidget {
|
|
452
|
+
widget: ResolvedWidget;
|
|
453
|
+
gx: number;
|
|
454
|
+
gy: number;
|
|
455
|
+
w: number;
|
|
456
|
+
h: number;
|
|
457
|
+
}
|
|
458
|
+
/** Place every widget on the fixed grid: honor its stored `gx`/`gy` when set (and free), else auto-
|
|
459
|
+
* place it first-fit (top-to-bottom, left-to-right) at its footprint. Deterministic + collision-free,
|
|
460
|
+
* so a legacy spec (no positions) and a positioned one both render stably, and a stale position that
|
|
461
|
+
* now overlaps (e.g. after a neighbour grew) falls back to a free slot instead of stacking. */
|
|
462
|
+
declare function placeWidgets(resolved: ResolvedWidget[]): PlacedWidget[];
|
|
463
|
+
|
|
464
|
+
type DeliverableItemKind = "file" | "link" | "text" | "table";
|
|
465
|
+
type DeliverableItem = {
|
|
466
|
+
kind: DeliverableItemKind;
|
|
467
|
+
value: string;
|
|
468
|
+
/** Version this item was delivered in, numbered from 1 (absent = v1). */
|
|
469
|
+
v?: number;
|
|
470
|
+
};
|
|
471
|
+
/** One delivered version — a bundle of items sent together, numbered from 1. */
|
|
472
|
+
type DeliverableVersion = {
|
|
473
|
+
v: number;
|
|
474
|
+
items: DeliverableItem[];
|
|
475
|
+
};
|
|
476
|
+
declare function normalizeDeliverableItem(x: unknown): DeliverableItem | null;
|
|
477
|
+
declare function groupDeliverableVersions(items: DeliverableItem[]): DeliverableVersion[];
|
|
478
|
+
/** Parse a deliverable row's items from its `data`. Tolerant of the legacy single `file` field and of
|
|
479
|
+
* bare-string refs; returns [] when there is nothing. */
|
|
480
|
+
declare function parseDeliverableItems(data: Record<string, unknown>): DeliverableItem[];
|
|
481
|
+
type DeliverableReviewState = "awaiting" | "accepted" | "revise";
|
|
482
|
+
declare function deliverableStatus(items: DeliverableItem[], publishedVersion: number, verdictById: Map<string, string>): DeliverableReviewState;
|
|
483
|
+
/** The version the client currently sees — the EXPLICIT `published_version` pointer the operator set
|
|
484
|
+
* (0 = nothing published). No `done → latest` fallback: that followed the latest version, so a new
|
|
485
|
+
* version the team delivered would auto-appear to the client before it was published. */
|
|
486
|
+
declare function publishedVersionOf(data: Record<string, unknown>): number;
|
|
487
|
+
|
|
488
|
+
/** A column type. `select` is an open enum edited as text; the rest drive how a cell reads. */
|
|
489
|
+
type DeliverableColType = "text" | "number" | "url" | "date" | "boolean" | "select";
|
|
490
|
+
type DeliverableColumn = {
|
|
491
|
+
key: string;
|
|
492
|
+
label: string;
|
|
493
|
+
type: DeliverableColType;
|
|
494
|
+
/** The customer may fill this column in from their portal — their values live in the deliverable's
|
|
495
|
+
* `responses`, layered over the operator's cells. Absent = read-only (the default). */
|
|
496
|
+
clientEditable?: boolean;
|
|
497
|
+
};
|
|
498
|
+
type DeliverableRow = {
|
|
499
|
+
id: string;
|
|
500
|
+
cells: Record<string, unknown>;
|
|
501
|
+
};
|
|
502
|
+
type DeliverableTable = {
|
|
503
|
+
/** The operator's name for this table (e.g. "Twitter influencers"). Optional for older payloads. */
|
|
504
|
+
name?: string;
|
|
505
|
+
columns: DeliverableColumn[];
|
|
506
|
+
rows: DeliverableRow[];
|
|
507
|
+
};
|
|
508
|
+
/** Legacy tables used `link`/`checkbox`; the current vocabulary is `url`/`boolean`. */
|
|
509
|
+
declare function normalizeDeliverableColType(t: unknown): DeliverableColType;
|
|
510
|
+
/** Parse a table item's JSON `value` into the grid model, tolerating the legacy id-keyed shape (its
|
|
511
|
+
* columns carried `id` not `key`, so a legacy column keeps its cells by keying on that id). Returns
|
|
512
|
+
* null when `value` is not a table payload. */
|
|
513
|
+
declare function parseDeliverableTable(value: string): DeliverableTable | null;
|
|
514
|
+
/** A cell value as display text (a stored null/undefined reads as empty). */
|
|
515
|
+
declare function deliverableCellText(v: unknown): string;
|
|
516
|
+
/** Whether a `boolean` cell is checked — tolerant of a real boolean or the string forms it was stored
|
|
517
|
+
* as across versions. */
|
|
518
|
+
declare function isDeliverableChecked(v: unknown): boolean;
|
|
519
|
+
type TableResponses = Record<string, Record<string, unknown>>;
|
|
520
|
+
/** Parse a deliverable's `responses` field (client_tasks.responses) into the rowId→colKey→value map. */
|
|
521
|
+
declare function parseTableResponses(value: unknown): TableResponses;
|
|
522
|
+
/** The value to SHOW for a cell: the customer's response on a client-editable column, else the
|
|
523
|
+
* operator's authored value. */
|
|
524
|
+
declare function cellValue(col: DeliverableColumn, row: DeliverableRow, responses: TableResponses): unknown;
|
|
525
|
+
/** Merge one client cell edit into a responses map (immutably), for writing back to `responses`. */
|
|
526
|
+
declare function setResponse(responses: TableResponses, rowId: string, colKey: string, value: unknown): TableResponses;
|
|
527
|
+
/** A `date` cell stores YYYY-MM-DD; format it as e.g. "Aug 18, 2026" (raw string if unparseable). */
|
|
528
|
+
declare function formatDeliverableDate(v: string): string;
|
|
529
|
+
|
|
530
|
+
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
531
|
+
declare const textDeltaOf: (event: unknown) => string | null;
|
|
532
|
+
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
533
|
+
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
534
|
+
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
535
|
+
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
536
|
+
|
|
232
537
|
/** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
|
|
233
538
|
* Create the client once (e.g. `useMemo`) so its session persists across renders. */
|
|
234
539
|
declare const HolaProvider: ({ client, children, }: {
|
|
235
540
|
client: HolaClient;
|
|
236
541
|
children: ReactNode;
|
|
237
|
-
}) =>
|
|
542
|
+
}) => react.JSX.Element;
|
|
238
543
|
declare const useHolaClient: () => HolaClient;
|
|
239
544
|
|
|
240
545
|
type ChatMessage = {
|
|
@@ -268,9 +573,6 @@ type UseThread = {
|
|
|
268
573
|
sending: boolean;
|
|
269
574
|
error: string | null;
|
|
270
575
|
};
|
|
271
|
-
/** Merge one incoming thread message: dedupe by id, and replace a pending optimistic echo of the
|
|
272
|
-
* visitor's own send (same text) with the authoritative one. Pure — the hook's core, so the
|
|
273
|
-
* merge rule is unit-checkable without React. */
|
|
274
576
|
declare function mergeMessage(list: ThreadMessage[], incoming: HolaMessage): ThreadMessage[];
|
|
275
577
|
/** A poll-based UNIFIED thread: the whole conversation — visitor, agent, and HUMAN turns — as one
|
|
276
578
|
* live list via the receive channel (`client.subscribe`). Unlike `useChat` (streaming, one turn
|
|
@@ -281,4 +583,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
281
583
|
pollMs?: number;
|
|
282
584
|
}) => UseThread;
|
|
283
585
|
|
|
284
|
-
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, type PortalData, 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 };
|
|
586
|
+
export { type Agg, type ApiTransportConfig, type Band, type ChartView, Chat, type ChatMessage, type CreateHolaClientOptions, type DashboardSpec, type DeliverableColType, type DeliverableColumn, type DeliverableItem, type DeliverableItemKind, type DeliverableReviewState, type DeliverableRow, type DeliverableTable, type DeliverableVersion, type DonutWidget, GRID_COLS, GRID_MAX_COLS, GRID_MAX_ROWS, 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 PlacedWidget, type Point, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ResolvedWidget, type SeriesWidget, type StatView, type StatWidget, type TableResponses, type TableWidget, type TextWidget, type ThreadMessage, type UseChat, type UseThread, type Widget, type WidgetTransportConfig, type WidgetType, bandOfKind, bandOfType, cellValue, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, deliverableCellText, deliverableStatus, deriveFields, footprintOf, footprintWith, formatDeliverableDate, groupDeliverableVersions, inferSpec, isDeliverableChecked, mergeMessage, normalizeDeliverableColType, normalizeDeliverableItem, parseDashboardSpec, parseDeliverableItems, parseDeliverableTable, parseTableResponses, placeWidgets, publishedVersionOf, renderModel, setResponse, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/** A file the employee produced (image/video/audio/other). In-turn artifacts (a turn's
|
|
@@ -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. */
|
|
@@ -108,10 +111,19 @@ type ApiTransportConfig = {
|
|
|
108
111
|
/** End-user access token for the Authorization header. A string, or a getter so a
|
|
109
112
|
* refreshed token is used per request. */
|
|
110
113
|
token: string | (() => string | Promise<string>);
|
|
114
|
+
/**
|
|
115
|
+
* Which of the customer's projects this conversation is about.
|
|
116
|
+
*
|
|
117
|
+
* Omit for the general thread — the one running conversation with the company,
|
|
118
|
+
* and where everything that predates per-project threads still lives. The
|
|
119
|
+
* server checks the project belongs to this customer before it trusts it, so
|
|
120
|
+
* naming someone else's is a 404 rather than a way into their thread.
|
|
121
|
+
*/
|
|
122
|
+
project?: string;
|
|
111
123
|
};
|
|
112
124
|
/** The Bearer-authed transport for the signed-in end-user PORTAL (`/api/v1/end-user/*`). Same
|
|
113
125
|
* client surface as the anonymous widget transport — the token (not a path param) resolves the
|
|
114
|
-
* customer
|
|
126
|
+
* customer; an optional `project` picks which of their threads. The portal is buffered (no SSE) and one-thread-per-user,
|
|
115
127
|
* so `sendMessage` returns just the reply and everything out-of-turn — the AI's turns, a human's
|
|
116
128
|
* reply, an async artifact (video) — arrives via `subscribe`. */
|
|
117
129
|
declare const createApiTransport: (config: ApiTransportConfig) => HolaTransport;
|
|
@@ -122,7 +134,7 @@ declare const useArtifactUrl: (dataBase64: string, mimeType: string) => string;
|
|
|
122
134
|
|
|
123
135
|
/** A minimal, dependency-free chat surface for the employee. Drop it inside a
|
|
124
136
|
* `<HolaProvider>`. For custom UI, use `useChat(client)` directly. */
|
|
125
|
-
declare const Chat: () =>
|
|
137
|
+
declare const Chat: () => react.JSX.Element;
|
|
126
138
|
|
|
127
139
|
type WidgetTransportConfig = {
|
|
128
140
|
/** API origin that fronts the Holaboss employee endpoint, e.g. "https://api.holaos.ai". */
|
|
@@ -149,13 +161,6 @@ type CreateHolaClientOptions = WidgetTransportConfig | {
|
|
|
149
161
|
* agnostic — the current transport is an internal detail, not the SDK's identity. */
|
|
150
162
|
declare const createHolaClient: (options: CreateHolaClientOptions) => HolaClient;
|
|
151
163
|
|
|
152
|
-
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
153
|
-
declare const textDeltaOf: (event: unknown) => string | null;
|
|
154
|
-
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
155
|
-
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
156
|
-
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
157
|
-
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
158
|
-
|
|
159
164
|
/** A column, as the company defined it. */
|
|
160
165
|
type PortalField = {
|
|
161
166
|
key: string;
|
|
@@ -197,6 +202,29 @@ type PortalFile = {
|
|
|
197
202
|
size: number;
|
|
198
203
|
createdAt: string;
|
|
199
204
|
};
|
|
205
|
+
/** A document shared with this customer. The body arrives only on a read. */
|
|
206
|
+
type PortalDocumentSummary = {
|
|
207
|
+
documentId: string;
|
|
208
|
+
title: string;
|
|
209
|
+
version: number;
|
|
210
|
+
/** Whether the company opened this one for editing, not merely for reading. */
|
|
211
|
+
editable: boolean;
|
|
212
|
+
updatedAt: string;
|
|
213
|
+
};
|
|
214
|
+
type PortalDocument = PortalDocumentSummary & {
|
|
215
|
+
body: string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* A save that lost a race.
|
|
219
|
+
*
|
|
220
|
+
* Thrown rather than returned so it cannot be mistaken for success, and separate
|
|
221
|
+
* from a plain error because the caller should do something different: re-read,
|
|
222
|
+
* reapply, save again — never retry the same bytes, which would overwrite whoever
|
|
223
|
+
* got there first.
|
|
224
|
+
*/
|
|
225
|
+
declare class PortalConflict extends Error {
|
|
226
|
+
constructor(message: string);
|
|
227
|
+
}
|
|
200
228
|
type PortalData = {
|
|
201
229
|
/** The tables this customer can see. A table the company has not shared is
|
|
202
230
|
* absent, not empty — it answers as if it did not exist. */
|
|
@@ -212,8 +240,14 @@ type PortalData = {
|
|
|
212
240
|
insertRow(slug: string, row: Record<string, unknown>): Promise<string>;
|
|
213
241
|
/** Pass an empty string to clear a field. */
|
|
214
242
|
patchRow(slug: string, rowId: string, patch: Record<string, unknown>): Promise<number>;
|
|
215
|
-
/**
|
|
216
|
-
*
|
|
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
|
+
*/
|
|
217
251
|
deleteRow(slug: string, rowId: string): Promise<number>;
|
|
218
252
|
/** Everything exchanged with this customer, newest first. */
|
|
219
253
|
listFiles(): Promise<PortalFile[]>;
|
|
@@ -222,19 +256,290 @@ type PortalData = {
|
|
|
222
256
|
uploadFile(file: File, message?: string): Promise<{
|
|
223
257
|
rowId: string;
|
|
224
258
|
}>;
|
|
225
|
-
/**
|
|
226
|
-
|
|
259
|
+
/**
|
|
260
|
+
* A link the browser can actually follow.
|
|
261
|
+
*
|
|
262
|
+
* Not a plain URL string: the download route needs the customer's token, and
|
|
263
|
+
* navigation from an `<a href>` carries no Authorization header — so a direct
|
|
264
|
+
* link is a guaranteed 401. This fetches the signed URL with the token, and
|
|
265
|
+
* what comes back needs no header of its own.
|
|
266
|
+
*/
|
|
267
|
+
fileLink(rowId: string): Promise<{
|
|
268
|
+
url: string;
|
|
269
|
+
filename: string;
|
|
270
|
+
}>;
|
|
227
271
|
/** Only files this customer sent, and only before they are filed. */
|
|
228
272
|
deleteFile(rowId: string): Promise<number>;
|
|
273
|
+
/** Documents the company shared with this customer. */
|
|
274
|
+
listDocuments(): Promise<PortalDocumentSummary[]>;
|
|
275
|
+
readDocument(documentId: string): Promise<PortalDocument>;
|
|
276
|
+
/**
|
|
277
|
+
* Save new text.
|
|
278
|
+
*
|
|
279
|
+
* `baseVersion` is the version that was open. Rejects with a PortalConflict if
|
|
280
|
+
* it has moved — do not retry with the same text: read again, put the change
|
|
281
|
+
* back on top of theirs, and save that.
|
|
282
|
+
*/
|
|
283
|
+
saveDocument(documentId: string, edit: {
|
|
284
|
+
body: string;
|
|
285
|
+
baseVersion: number;
|
|
286
|
+
note?: string;
|
|
287
|
+
}): Promise<{
|
|
288
|
+
version: number;
|
|
289
|
+
}>;
|
|
229
290
|
};
|
|
230
291
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
231
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
|
+
w?: number | undefined;
|
|
310
|
+
h?: number | undefined;
|
|
311
|
+
gx?: number | undefined;
|
|
312
|
+
gy?: number | undefined;
|
|
313
|
+
}
|
|
314
|
+
/** A time series: `y` (a numeric column) aggregated per day of `x` (a date column); omit `y` to count
|
|
315
|
+
* rows per day. `line` and `bar` differ only in how the renderer draws the same points. */
|
|
316
|
+
interface SeriesWidget {
|
|
317
|
+
type: "line" | "bar";
|
|
318
|
+
title?: string | undefined;
|
|
319
|
+
x: string;
|
|
320
|
+
y?: string | undefined;
|
|
321
|
+
agg?: Agg | undefined;
|
|
322
|
+
w?: number | undefined;
|
|
323
|
+
h?: number | undefined;
|
|
324
|
+
gx?: number | undefined;
|
|
325
|
+
gy?: number | undefined;
|
|
326
|
+
}
|
|
327
|
+
/** A breakdown: the count of rows per distinct value of `groupBy`. */
|
|
328
|
+
interface DonutWidget {
|
|
329
|
+
type: "donut";
|
|
330
|
+
title?: string | undefined;
|
|
331
|
+
groupBy: string;
|
|
332
|
+
w?: number | undefined;
|
|
333
|
+
h?: number | undefined;
|
|
334
|
+
gx?: number | undefined;
|
|
335
|
+
gy?: number | undefined;
|
|
336
|
+
}
|
|
337
|
+
/** A row table: `columns` (by key, in order) or every non-plumbing column when omitted. */
|
|
338
|
+
interface TableWidget {
|
|
339
|
+
type: "table";
|
|
340
|
+
title?: string | undefined;
|
|
341
|
+
columns?: string[] | undefined;
|
|
342
|
+
w?: number | undefined;
|
|
343
|
+
h?: number | undefined;
|
|
344
|
+
gx?: number | undefined;
|
|
345
|
+
gy?: number | undefined;
|
|
346
|
+
}
|
|
347
|
+
/** Static prose — a note above or between the data widgets. */
|
|
348
|
+
interface TextWidget {
|
|
349
|
+
type: "text";
|
|
350
|
+
title?: string | undefined;
|
|
351
|
+
body: string;
|
|
352
|
+
w?: number | undefined;
|
|
353
|
+
h?: number | undefined;
|
|
354
|
+
gx?: number | undefined;
|
|
355
|
+
gy?: number | undefined;
|
|
356
|
+
}
|
|
357
|
+
type Widget = StatWidget | SeriesWidget | DonutWidget | TableWidget | TextWidget;
|
|
358
|
+
interface DashboardSpec {
|
|
359
|
+
version: number;
|
|
360
|
+
title?: string | undefined;
|
|
361
|
+
widgets: Widget[];
|
|
362
|
+
}
|
|
363
|
+
/** Parse a service's stored dashboard spec (a JSON string or an already-parsed object). Returns null
|
|
364
|
+
* for anything that isn't a usable spec — empty, absent, garbled, or with no valid widgets — so the
|
|
365
|
+
* caller falls back to inference instead of showing a blank dashboard. */
|
|
366
|
+
declare function parseDashboardSpec(raw: unknown): DashboardSpec | null;
|
|
367
|
+
|
|
368
|
+
declare function inferSpec(fields: PortalField[], rows: PortalRow[]): DashboardSpec;
|
|
369
|
+
|
|
370
|
+
/** A point on a line/bar chart or a slice of a donut. */
|
|
371
|
+
interface Point {
|
|
372
|
+
label: string;
|
|
373
|
+
value: number;
|
|
374
|
+
}
|
|
375
|
+
/** A widget resolved against the data — everything a renderer needs, already computed. */
|
|
376
|
+
type ResolvedWidget = {
|
|
377
|
+
kind: "stat";
|
|
378
|
+
label: string;
|
|
379
|
+
value: number;
|
|
380
|
+
w?: number | undefined;
|
|
381
|
+
h?: number | undefined;
|
|
382
|
+
gx?: number | undefined;
|
|
383
|
+
gy?: number | undefined;
|
|
384
|
+
} | {
|
|
385
|
+
kind: "series";
|
|
386
|
+
draw: "line" | "bar";
|
|
387
|
+
title: string;
|
|
388
|
+
points: Point[];
|
|
389
|
+
w?: number | undefined;
|
|
390
|
+
h?: number | undefined;
|
|
391
|
+
gx?: number | undefined;
|
|
392
|
+
gy?: number | undefined;
|
|
393
|
+
} | {
|
|
394
|
+
kind: "donut";
|
|
395
|
+
title: string;
|
|
396
|
+
points: Point[];
|
|
397
|
+
w?: number | undefined;
|
|
398
|
+
h?: number | undefined;
|
|
399
|
+
gx?: number | undefined;
|
|
400
|
+
gy?: number | undefined;
|
|
401
|
+
} | {
|
|
402
|
+
kind: "table";
|
|
403
|
+
title?: string | undefined;
|
|
404
|
+
columns: PortalField[];
|
|
405
|
+
rows: PortalRow[];
|
|
406
|
+
w?: number | undefined;
|
|
407
|
+
h?: number | undefined;
|
|
408
|
+
gx?: number | undefined;
|
|
409
|
+
gy?: number | undefined;
|
|
410
|
+
} | {
|
|
411
|
+
kind: "text";
|
|
412
|
+
title?: string | undefined;
|
|
413
|
+
body: string;
|
|
414
|
+
w?: number | undefined;
|
|
415
|
+
h?: number | undefined;
|
|
416
|
+
gx?: number | undefined;
|
|
417
|
+
gy?: number | undefined;
|
|
418
|
+
};
|
|
419
|
+
/** Resolve a whole spec against the data. The result is render-ready and free of widgets that would
|
|
420
|
+
* draw nothing. */
|
|
421
|
+
declare function renderModel(spec: DashboardSpec, fields: PortalField[], rows: PortalRow[]): ResolvedWidget[];
|
|
422
|
+
|
|
423
|
+
type Band = "stat" | "chart" | "block";
|
|
424
|
+
type StatView = Extract<ResolvedWidget, {
|
|
425
|
+
kind: "stat";
|
|
426
|
+
}>;
|
|
427
|
+
type ChartView = Extract<ResolvedWidget, {
|
|
428
|
+
kind: "series" | "donut";
|
|
429
|
+
}>;
|
|
430
|
+
/** The dashboard grid is a fixed 6 columns wide, at every width. */
|
|
431
|
+
declare const GRID_COLS = 6;
|
|
432
|
+
declare const GRID_MAX_COLS = 6;
|
|
433
|
+
declare const GRID_MAX_ROWS = 4;
|
|
434
|
+
/** The band a resolved widget belongs to. */
|
|
435
|
+
declare function bandOfKind(kind: ResolvedWidget["kind"]): Band;
|
|
436
|
+
/** The band a spec widget TYPE belongs to — for the editor, which lays out widgets before they resolve
|
|
437
|
+
* (a half-configured widget still needs a cell). Mirrors bandOfKind. */
|
|
438
|
+
declare function bandOfType(type: WidgetType): Band;
|
|
439
|
+
/** A band's default cell size. */
|
|
440
|
+
declare function footprintOf(band: Band): {
|
|
441
|
+
w: number;
|
|
442
|
+
h: number;
|
|
443
|
+
};
|
|
444
|
+
/** A widget's effective cell size: its stored `w`×`h` (clamped to the grid), else the band default.
|
|
445
|
+
* The renderers and the editor both size cells through this, so a widget looks the same everywhere. */
|
|
446
|
+
declare function footprintWith(band: Band, w: number | undefined, h: number | undefined): {
|
|
447
|
+
w: number;
|
|
448
|
+
h: number;
|
|
449
|
+
};
|
|
450
|
+
/** A widget placed on the grid — the view-model plus its top-left cell and size in cells. */
|
|
451
|
+
interface PlacedWidget {
|
|
452
|
+
widget: ResolvedWidget;
|
|
453
|
+
gx: number;
|
|
454
|
+
gy: number;
|
|
455
|
+
w: number;
|
|
456
|
+
h: number;
|
|
457
|
+
}
|
|
458
|
+
/** Place every widget on the fixed grid: honor its stored `gx`/`gy` when set (and free), else auto-
|
|
459
|
+
* place it first-fit (top-to-bottom, left-to-right) at its footprint. Deterministic + collision-free,
|
|
460
|
+
* so a legacy spec (no positions) and a positioned one both render stably, and a stale position that
|
|
461
|
+
* now overlaps (e.g. after a neighbour grew) falls back to a free slot instead of stacking. */
|
|
462
|
+
declare function placeWidgets(resolved: ResolvedWidget[]): PlacedWidget[];
|
|
463
|
+
|
|
464
|
+
type DeliverableItemKind = "file" | "link" | "text" | "table";
|
|
465
|
+
type DeliverableItem = {
|
|
466
|
+
kind: DeliverableItemKind;
|
|
467
|
+
value: string;
|
|
468
|
+
/** Version this item was delivered in, numbered from 1 (absent = v1). */
|
|
469
|
+
v?: number;
|
|
470
|
+
};
|
|
471
|
+
/** One delivered version — a bundle of items sent together, numbered from 1. */
|
|
472
|
+
type DeliverableVersion = {
|
|
473
|
+
v: number;
|
|
474
|
+
items: DeliverableItem[];
|
|
475
|
+
};
|
|
476
|
+
declare function normalizeDeliverableItem(x: unknown): DeliverableItem | null;
|
|
477
|
+
declare function groupDeliverableVersions(items: DeliverableItem[]): DeliverableVersion[];
|
|
478
|
+
/** Parse a deliverable row's items from its `data`. Tolerant of the legacy single `file` field and of
|
|
479
|
+
* bare-string refs; returns [] when there is nothing. */
|
|
480
|
+
declare function parseDeliverableItems(data: Record<string, unknown>): DeliverableItem[];
|
|
481
|
+
type DeliverableReviewState = "awaiting" | "accepted" | "revise";
|
|
482
|
+
declare function deliverableStatus(items: DeliverableItem[], publishedVersion: number, verdictById: Map<string, string>): DeliverableReviewState;
|
|
483
|
+
/** The version the client currently sees — the EXPLICIT `published_version` pointer the operator set
|
|
484
|
+
* (0 = nothing published). No `done → latest` fallback: that followed the latest version, so a new
|
|
485
|
+
* version the team delivered would auto-appear to the client before it was published. */
|
|
486
|
+
declare function publishedVersionOf(data: Record<string, unknown>): number;
|
|
487
|
+
|
|
488
|
+
/** A column type. `select` is an open enum edited as text; the rest drive how a cell reads. */
|
|
489
|
+
type DeliverableColType = "text" | "number" | "url" | "date" | "boolean" | "select";
|
|
490
|
+
type DeliverableColumn = {
|
|
491
|
+
key: string;
|
|
492
|
+
label: string;
|
|
493
|
+
type: DeliverableColType;
|
|
494
|
+
/** The customer may fill this column in from their portal — their values live in the deliverable's
|
|
495
|
+
* `responses`, layered over the operator's cells. Absent = read-only (the default). */
|
|
496
|
+
clientEditable?: boolean;
|
|
497
|
+
};
|
|
498
|
+
type DeliverableRow = {
|
|
499
|
+
id: string;
|
|
500
|
+
cells: Record<string, unknown>;
|
|
501
|
+
};
|
|
502
|
+
type DeliverableTable = {
|
|
503
|
+
/** The operator's name for this table (e.g. "Twitter influencers"). Optional for older payloads. */
|
|
504
|
+
name?: string;
|
|
505
|
+
columns: DeliverableColumn[];
|
|
506
|
+
rows: DeliverableRow[];
|
|
507
|
+
};
|
|
508
|
+
/** Legacy tables used `link`/`checkbox`; the current vocabulary is `url`/`boolean`. */
|
|
509
|
+
declare function normalizeDeliverableColType(t: unknown): DeliverableColType;
|
|
510
|
+
/** Parse a table item's JSON `value` into the grid model, tolerating the legacy id-keyed shape (its
|
|
511
|
+
* columns carried `id` not `key`, so a legacy column keeps its cells by keying on that id). Returns
|
|
512
|
+
* null when `value` is not a table payload. */
|
|
513
|
+
declare function parseDeliverableTable(value: string): DeliverableTable | null;
|
|
514
|
+
/** A cell value as display text (a stored null/undefined reads as empty). */
|
|
515
|
+
declare function deliverableCellText(v: unknown): string;
|
|
516
|
+
/** Whether a `boolean` cell is checked — tolerant of a real boolean or the string forms it was stored
|
|
517
|
+
* as across versions. */
|
|
518
|
+
declare function isDeliverableChecked(v: unknown): boolean;
|
|
519
|
+
type TableResponses = Record<string, Record<string, unknown>>;
|
|
520
|
+
/** Parse a deliverable's `responses` field (client_tasks.responses) into the rowId→colKey→value map. */
|
|
521
|
+
declare function parseTableResponses(value: unknown): TableResponses;
|
|
522
|
+
/** The value to SHOW for a cell: the customer's response on a client-editable column, else the
|
|
523
|
+
* operator's authored value. */
|
|
524
|
+
declare function cellValue(col: DeliverableColumn, row: DeliverableRow, responses: TableResponses): unknown;
|
|
525
|
+
/** Merge one client cell edit into a responses map (immutably), for writing back to `responses`. */
|
|
526
|
+
declare function setResponse(responses: TableResponses, rowId: string, colKey: string, value: unknown): TableResponses;
|
|
527
|
+
/** A `date` cell stores YYYY-MM-DD; format it as e.g. "Aug 18, 2026" (raw string if unparseable). */
|
|
528
|
+
declare function formatDeliverableDate(v: string): string;
|
|
529
|
+
|
|
530
|
+
/** Extract a streamed text delta from a raw pi turn event, or null. */
|
|
531
|
+
declare const textDeltaOf: (event: unknown) => string | null;
|
|
532
|
+
/** Extract a streamed reasoning delta from a raw pi turn event, or null. */
|
|
533
|
+
declare const thinkingDeltaOf: (event: unknown) => string | null;
|
|
534
|
+
/** Extract a tool-execution start/end from a raw pi turn event, or null. */
|
|
535
|
+
declare const toolEventOf: (event: unknown) => HolaToolEvent | null;
|
|
536
|
+
|
|
232
537
|
/** Provides a `HolaClient` (from `createHolaClient`) to `useChat` / `<Chat/>`.
|
|
233
538
|
* Create the client once (e.g. `useMemo`) so its session persists across renders. */
|
|
234
539
|
declare const HolaProvider: ({ client, children, }: {
|
|
235
540
|
client: HolaClient;
|
|
236
541
|
children: ReactNode;
|
|
237
|
-
}) =>
|
|
542
|
+
}) => react.JSX.Element;
|
|
238
543
|
declare const useHolaClient: () => HolaClient;
|
|
239
544
|
|
|
240
545
|
type ChatMessage = {
|
|
@@ -268,9 +573,6 @@ type UseThread = {
|
|
|
268
573
|
sending: boolean;
|
|
269
574
|
error: string | null;
|
|
270
575
|
};
|
|
271
|
-
/** Merge one incoming thread message: dedupe by id, and replace a pending optimistic echo of the
|
|
272
|
-
* visitor's own send (same text) with the authoritative one. Pure — the hook's core, so the
|
|
273
|
-
* merge rule is unit-checkable without React. */
|
|
274
576
|
declare function mergeMessage(list: ThreadMessage[], incoming: HolaMessage): ThreadMessage[];
|
|
275
577
|
/** A poll-based UNIFIED thread: the whole conversation — visitor, agent, and HUMAN turns — as one
|
|
276
578
|
* live list via the receive channel (`client.subscribe`). Unlike `useChat` (streaming, one turn
|
|
@@ -281,4 +583,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
281
583
|
pollMs?: number;
|
|
282
584
|
}) => UseThread;
|
|
283
585
|
|
|
284
|
-
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, type PortalData, 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 };
|
|
586
|
+
export { type Agg, type ApiTransportConfig, type Band, type ChartView, Chat, type ChatMessage, type CreateHolaClientOptions, type DashboardSpec, type DeliverableColType, type DeliverableColumn, type DeliverableItem, type DeliverableItemKind, type DeliverableReviewState, type DeliverableRow, type DeliverableTable, type DeliverableVersion, type DonutWidget, GRID_COLS, GRID_MAX_COLS, GRID_MAX_ROWS, 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 PlacedWidget, type Point, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ResolvedWidget, type SeriesWidget, type StatView, type StatWidget, type TableResponses, type TableWidget, type TextWidget, type ThreadMessage, type UseChat, type UseThread, type Widget, type WidgetTransportConfig, type WidgetType, bandOfKind, bandOfType, cellValue, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, deliverableCellText, deliverableStatus, deriveFields, footprintOf, footprintWith, formatDeliverableDate, groupDeliverableVersions, inferSpec, isDeliverableChecked, mergeMessage, normalizeDeliverableColType, normalizeDeliverableItem, parseDashboardSpec, parseDeliverableItems, parseDeliverableTable, parseTableResponses, placeWidgets, publishedVersionOf, renderModel, setResponse, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
var
|
|
3
|
-
`))
|
|
4
|
-
`)}:null},
|
|
2
|
+
var V=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},W=e=>{let t=V(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},A=e=>{let t=V(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 Ce=/^ /,v=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"}},Se=e=>{let t="message",o=[];for(let r of e.split(`
|
|
3
|
+
`))r.startsWith(":")||(r.startsWith("event:")?t=r.slice(6).trim():r.startsWith("data:")&&o.push(r.slice(5).replace(Ce,"")));return o.length>0?{event:t,data:o.join(`
|
|
4
|
+
`)}:null},Pe=(e,t,o)=>{let r;try{r=JSON.parse(e.data)}catch{return}if(e.event==="session"){let s=r.sessionId;s&&(o(s),t.onSession?.(s));return}if(e.event==="turn_event"){t.onEvent?.(r);let s=W(r);s&&t.onText?.(s);let l=A(r);l&&t.onThinking?.(l);let n=E(r);n&&t.onTool?.(n);return}if(e.event==="chat_completed"){let s=r;o(s.sessionId),t.onComplete?.(s);return}e.event==="chat_error"&&t.onError?.(v(r))},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()},z=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()},J=async(e,t,o,r)=>{let s=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:r});if(!(s.ok&&s.body)){t.onError?.({error:`stream_failed_${s.status}`});return}let l=s.body.getReader(),n=new TextDecoder,i="",a=()=>{let d=i.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;d!==-1;){let m=i.slice(0,d);i=i.slice(d+2);let p=Se(m);p&&Pe(p,t,o),d=i.indexOf(`
|
|
7
7
|
|
|
8
|
-
`)}},n=async()=>{let l=await p.read();if(!l.done)return o+=r.decode(l.value,{stream:!0}),d(),n()};await n()};var q=/\/+$/,C="/api/v1/end-user",B=e=>{let t=e.baseUrl.replace(q,""),a=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),i=async p=>{let r=await fetch(`${t}${C}/chat`,{method:"POST",headers:await s(),body:JSON.stringify({message:p})});if(!r.ok)throw new Error(`hola: message failed (${r.status})`);return{reply:(await r.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>x(`${t}${C}/chat/info`,await s()),sendMessage:i,streamMessage:async(p,r)=>{try{let o=await i(p);r.onText?.(o.reply),r.onComplete?.(o)}catch(o){r.onError?.(w(o))}},subscribe:(p,r)=>{let o=r?.since??"0",d=!1,n,l=r?.intervalMs??3e3,f=async()=>{try{let c=await x(`${t}${C}/chat/thread?since=${encodeURIComponent(o)}`,await s());for(let y of c.messages)p.onMessage(y);o=c.cursor}catch(c){p.onError?.(w(c))}d||(n=setTimeout(f,l))},u=()=>{d=!0,n&&clearTimeout(n)};return r?.signal?.addEventListener("abort",u),n=setTimeout(f,0),u},requestHuman:async()=>{await fetch(`${t}${C}/chat/request-human`,{method:"POST",headers:await s(),body:"{}"})}}};import{useEffect as G,useState as K}from"react";var k=(e,t)=>{let[a,s]=K("");return G(()=>{if(!e){s("");return}let i=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),p=URL.createObjectURL(new Blob([i],{type:t}));return s(p),()=>URL.revokeObjectURL(p)},[e,t]),a};import{useEffect as te,useRef as re}from"react";import{createContext as V,useContext as Y}from"react";import{jsx as X}from"react/jsx-runtime";var D=V(null),Q=({client:e,children:t})=>X(D.Provider,{value:e,children:t}),$=()=>{let e=Y(D);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as L,useEffect as Z,useRef as ee,useState as v}from"react";var M=e=>{let[t,a]=v([]),[s,i]=v(""),[p,r]=v(!1),[o,d]=v(null),n=ee(0),l=L(()=>{let u=n.current;return n.current+=1,u},[]);Z(()=>{let u=!0;return e.getInfo().then(c=>{u&&c.greeting&&a([{id:l(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,l]);let f=L(()=>{let u=s.trim();if(!u||p)return;i(""),d(null),r(!0);let c=l(),y=l();a(m=>[...m,{id:c,role:"user",text:u},{id:y,role:"assistant",text:"",streaming:!0}]);let g=m=>{a(h=>h.map(I=>I.id===y?m(I):I))};e.streamMessage(u,{onText:m=>g(h=>({...h,text:h.text+m})),onComplete:m=>{g(h=>({...h,text:m.reply||h.text,artifacts:m.artifacts,streaming:!1})),r(!1)},onError:m=>{g(h=>({...h,streaming:!1})),d(m.error),r(!1)}}).catch(m=>{g(h=>({...h,streaming:!1})),d(m instanceof Error?m.message:"stream_failed"),r(!1)})},[s,p,e,l]);return{messages:t,input:s,setInput:i,send:f,sending:p,error:o}};import{jsx as H,jsxs as A}from"react/jsx-runtime";var b={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"}},oe=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),a=e.url??t;return a?e.mimeType.startsWith("image/")?H("a",{href:a,rel:"noopener noreferrer",target:"_blank",children:H("img",{alt:e.filename,src:a,style:b.image})}):H("a",{download:e.filename,href:a,style:b.link,children:e.filename}):null},se=()=>{let e=$(),{messages:t,input:a,setInput:s,send:i,sending:p,error:r}=M(e),o=re(null);te(()=>{let n=o.current;n&&(n.scrollTop=n.scrollHeight)},[t]);let d=n=>{n.preventDefault(),i()};return A("div",{style:b.root,children:[H("div",{ref:o,style:b.log,children:t.map(n=>A("div",{style:n.role==="user"?b.user:b.assistant,children:[n.text||(n.streaming?"\u2026":""),n.artifacts&&n.artifacts.length>0?H("div",{style:b.artifacts,children:n.artifacts.map(l=>H(oe,{artifact:l},l.filename))}):null]},n.id))}),r?H("div",{style:b.error,children:r}):null,A("form",{onSubmit:d,style:b.form,children:[H("input",{"aria-label":"Message",onChange:n=>s(n.target.value),placeholder:"Type a message\u2026",style:b.input,value:a}),H("button",{disabled:p||a.trim().length===0,style:b.button,type:"submit",children:"Send"})]})]})};var ne=/\/+$/,U=e=>{let t=e.sessionId,a=e.baseUrl.replace(ne,""),s=r=>`${a}/endpoint/${encodeURIComponent(e.token)}${r}`,i={"content-type":"application/json"},p=(r,o)=>({url:s(r),headers:i,body:JSON.stringify({message:o,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>x(s("/config"),{}),sendMessage:async r=>{let o=await _(p("/message",r));return t=o.sessionId,o},streamMessage:(r,o,d)=>F(p("/message/stream",r),o,n=>{t=n},d),subscribe:(r,o)=>{let d=o?.since??"0",n=!1,l,f=o?.intervalMs??3e3,u=async()=>{let y=t;if(y)try{let g=await x(s(`/messages?session=${encodeURIComponent(y)}&since=${encodeURIComponent(d)}`),{});for(let m of g.messages)r.onMessage(m);d=g.cursor}catch(g){r.onError?.(w(g))}n||(l=setTimeout(u,f))},c=()=>{n=!0,l&&clearTimeout(l)};return o?.signal?.addEventListener("abort",c),l=setTimeout(u,0),c},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:i,body:JSON.stringify({sessionId:t})})}}};var ae=e=>"transport"in e?e.transport:U(e);var ie=/\/+$/,T="/api/v1/end-user";function le(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(ie,""),a=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),i=async(r,o)=>{try{let d=await fetch(`${t}${T}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(!d.ok){let n=await d.text().catch(()=>""),l=`${o.method} ${r} \u2192 ${String(d.status)}`;try{let f=JSON.parse(n);f.message&&(l=f.message)}catch{l=n?`${l}: ${n}`:l}throw w(new Error(l))}return await d.json()}catch(d){throw w(d)}},p=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await x(`${t}${T}/tables`,await s())).tables,listRows:async(r,o)=>await x(`${t}${T}${p(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:d}=await i(`${p(r)}/rows`,{method:"POST",body:{rows:[o]}}),n=d[0];if(!n)throw w(new Error("The row was not created."));return n},patchRow:async(r,o,d)=>(await i(`${p(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:d})).updated,deleteRow:async(r,o)=>(await i(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await x(`${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 d=new Uint8Array(await r.arrayBuffer());return await i("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:le(d),...o===void 0?{}:{message:o}}})},fileUrl:r=>`${t}${T}/files/${encodeURIComponent(r)}/raw`,deleteFile:async r=>(await i(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted}};import{useCallback as de,useEffect as W,useRef as ce,useState as P}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 ue=(e,t)=>{let[a,s]=P([]),[i,p]=P(""),[r,o]=P(!1),[d,n]=P(null),l=ce(0),f=t?.pollMs??2e3;W(()=>{let c=!0;return e.getInfo().then(y=>{c&&y.greeting&&s(g=>g.length===0?[{id:"greeting",cursor:"",author:"agent",text:y.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),W(()=>e.subscribe?.({onMessage:c=>s(y=>N(y,c)),onError:c=>n(c.error)},{intervalMs:f}),[e,f]);let u=de(()=>{let c=i.trim();if(!c||r)return;p(""),n(null),o(!0),l.current+=1;let y={id:`pending-${l.current}`,cursor:"",author:"client",text:c,pending:!0};s(g=>[...g,y]),e.sendMessage(c).then(()=>o(!1)).catch(g=>{n(g instanceof Error?g.message:"send_failed"),o(!1)})},[i,r,e]);return{messages:a,input:i,setInput:p,send:u,sending:r,error:d}};export{se as Chat,Q as HolaProvider,B as createApiTransport,ae as createHolaClient,pe as createPortalData,U as createWidgetTransport,N as mergeMessage,R as textDeltaOf,E as thinkingDeltaOf,S as toolEventOf,k as useArtifactUrl,M as useChat,$ as useHolaClient,ue as useThread};
|
|
8
|
+
`)}},u=async()=>{let d=await l.read();if(!d.done)return i+=n.decode(d.value,{stream:!0}),a(),u()};await u()};var Ie=/\/+$/,S="/api/v1/end-user",He=e=>{let t=e.baseUrl.replace(Ie,""),o=()=>typeof e.token=="function"?e.token():e.token,r=n=>e.project?`${n}project=${encodeURIComponent(e.project)}`:"",s=async()=>({"content-type":"application/json",authorization:`Bearer ${await o()}`}),l=async n=>{let i=await fetch(`${t}${S}/chat${r("?")}`,{method:"POST",headers:await s(),body:JSON.stringify({message:n})});if(!i.ok)throw new Error(`hola: message failed (${i.status})`);return{reply:(await i.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>b(`${t}${S}/chat/info${r("?")}`,await s()),sendMessage:l,streamMessage:async(n,i)=>{try{let a=await l(n);i.onText?.(a.reply),i.onComplete?.(a)}catch(a){i.onError?.(v(a))}},subscribe:(n,i)=>{let a=i?.since??"0",u=!1,d,m=i?.intervalMs??3e3,p=async()=>{try{let f=await b(`${t}${S}/chat/thread?since=${encodeURIComponent(a)}${r("&")}`,await s());for(let g of f.messages)n.onMessage(g);a=f.cursor,n.onHealthy?.()}catch(f){n.onError?.(v(f))}u||(d=setTimeout(p,m))},c=()=>{u=!0,d&&clearTimeout(d)};return i?.signal?.addEventListener("abort",c),d=setTimeout(p,0),c},requestHuman:async()=>{await fetch(`${t}${S}/chat/request-human${r("?")}`,{method:"POST",headers:await s(),body:"{}"})}}};import{useEffect as Me,useState as We}from"react";var $=(e,t)=>{let[o,r]=We("");return Me(()=>{if(!e){r("");return}let s=Uint8Array.from(atob(e),n=>n.charCodeAt(0)),l=URL.createObjectURL(new Blob([s],{type:t}));return r(l),()=>URL.revokeObjectURL(l)},[e,t]),o};import{useEffect as Fe,useRef as Le}from"react";import{createContext as Ae,useContext as Ee}from"react";import{jsx as Oe}from"react/jsx-runtime";var q=Ae(null),$e=({client:e,children:t})=>Oe(q.Provider,{value:e,children:t}),O=()=>{let e=Ee(q);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as X,useEffect as _e,useRef as Ue,useState as P}from"react";var _=e=>{let[t,o]=P([]),[r,s]=P(""),[l,n]=P(!1),[i,a]=P(null),u=Ue(0),d=X(()=>{let p=u.current;return u.current+=1,p},[]);_e(()=>{let p=!0;return e.getInfo().then(c=>{p&&c.greeting&&o([{id:d(),role:"assistant",text:c.greeting}])}).catch(()=>{}),()=>{p=!1}},[e,d]);let m=X(()=>{let p=r.trim();if(!p||l)return;s(""),a(null),n(!0);let c=d(),f=d();o(y=>[...y,{id:c,role:"user",text:p},{id:f,role:"assistant",text:"",streaming:!0}]);let g=y=>{o(h=>h.map(M=>M.id===f?y(M):M))};e.streamMessage(p,{onText:y=>g(h=>({...h,text:h.text+y})),onComplete:y=>{g(h=>({...h,text:y.reply||h.text,artifacts:y.artifacts,streaming:!1})),n(!1)},onError:y=>{g(h=>({...h,streaming:!1})),a(y.error),n(!1)}}).catch(y=>{g(h=>({...h,streaming:!1})),a(y instanceof Error?y.message:"stream_failed"),n(!1)})},[r,l,e,d]);return{messages:t,input:r,setInput:s,send:m,sending:l,error:i}};import{jsx as T,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=$(e.dataBase64??"",e.mimeType),o=e.url??t;return o?e.mimeType.startsWith("image/")?T("a",{href:o,rel:"noopener noreferrer",target:"_blank",children:T("img",{alt:e.filename,src:o,style:w.image})}):T("a",{download:e.filename,href:o,style:w.link,children:e.filename}):null},Ge=()=>{let e=O(),{messages:t,input:o,setInput:r,send:s,sending:l,error:n}=_(e),i=Le(null);Fe(()=>{let u=i.current;u&&(u.scrollTop=u.scrollHeight)},[t]);let a=u=>{u.preventDefault(),s()};return U("div",{style:w.root,children:[T("div",{ref:i,style:w.log,children:t.map(u=>U("div",{style:u.role==="user"?w.user:w.assistant,children:[u.text||(u.streaming?"\u2026":""),u.artifacts&&u.artifacts.length>0?T("div",{style:w.artifacts,children:u.artifacts.map(d=>T(Ne,{artifact:d},d.filename))}):null]},u.id))}),n?T("div",{style:w.error,children:n}):null,U("form",{onSubmit:a,style:w.form,children:[T("input",{"aria-label":"Message",onChange:u=>r(u.target.value),placeholder:"Type a message\u2026",style:w.input,value:o}),T("button",{disabled:l||o.trim().length===0,style:w.button,type:"submit",children:"Send"})]})]})};var je=/\/+$/,F=e=>{let t=e.sessionId,o=e.baseUrl.replace(je,""),r=n=>`${o}/endpoint/${encodeURIComponent(e.token)}${n}`,s={"content-type":"application/json"},l=(n,i)=>({url:r(n),headers:s,body:JSON.stringify({message:i,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>b(r("/config"),{}),sendMessage:async n=>{let i=await z(l("/message",n));return t=i.sessionId,i},streamMessage:(n,i,a)=>J(l("/message/stream",n),i,u=>{t=u},a),subscribe:(n,i)=>{let a=i?.since??"0",u=!1,d,m=i?.intervalMs??3e3,p=async()=>{let f=t;if(f)try{let g=await b(r(`/messages?session=${encodeURIComponent(f)}&since=${encodeURIComponent(a)}`),{});for(let y of g.messages)n.onMessage(y);a=g.cursor,n.onHealthy?.()}catch(g){n.onError?.(v(g))}u||(d=setTimeout(p,m))},c=()=>{u=!0,d&&clearTimeout(d)};return i?.signal?.addEventListener("abort",c),d=setTimeout(p,0),c},requestHuman:async()=>{t&&await fetch(r("/request-human"),{method:"POST",headers:s,body:JSON.stringify({sessionId:t})})}}};var Be=e=>"transport"in e?e.transport:F(e);var Ve=/^https?:\/\//i,ze=/^\d{4}-\d{2}-\d{2}(?:[T ]|$)/,Je=e=>e.replace(/_/g," ").replace(/\b\w/g,t=>t.toUpperCase());function qe(e){if(typeof e=="number")return"number";if(typeof e=="boolean")return"boolean";if(typeof e=="string"){if(Ve.test(e))return"url";if(ze.test(e))return"date"}return"text"}function K(e){let t=[],o=new Map;for(let r of e)for(let[s,l]of Object.entries(r.data??{})){if(l==null)continue;let n=o.get(s);n||(n=new Map,o.set(s,n),t.push(s));let i=qe(l);n.set(i,(n.get(i)??0)+1)}return t.map(r=>{let s=o.get(r)??new Map,l="text",n=-1;for(let[i,a]of s)a>n&&(n=a,l=i);return{key:r,label:Je(r),type:l}})}var Xe=e=>typeof e=="number"?e:Number(e)||0,Y=e=>/rate|position|rank|score|percent|avg/i.test(e),Ke=/posted|published|sent|completed|shipped|launched|_at|date/i,Ye=new Set(["service","order","end_user_id"]);function Q(e,t){let o=e.filter(a=>!Ye.has(a.key)),r=o.filter(a=>a.type==="number").slice(0,3),s=o.find(a=>a.key==="status")??o.find(a=>a.type==="select"),l=o.find(a=>a.type==="date"&&Ke.test(a.key))??o.find(a=>a.type==="date"),n=o.filter(a=>a.type==="number"&&!Y(a.key)).map(a=>({field:a,total:t.reduce((u,d)=>u+Xe(d.data[a.key]),0)})).sort((a,u)=>u.total-a.total).find(a=>a.total>0)?.field??null,i=[{type:"stat",label:"Items",agg:"count"}];for(let a of r){let u=Y(a.key);i.push({type:"stat",metric:a.key,agg:u?"avg":"sum",label:u?`Avg ${a.label.toLowerCase()}`:a.label})}return l&&i.push({type:"line",x:l.key,y:n?.key,agg:"sum",title:n?`${n.label} by day`:"Items by day"}),s&&i.push({type:"donut",groupBy:s.key,title:s.label}),i.push({type:"table",columns:o.map(a=>a.key)}),{version:1,widgets:i}}var Z=6,ee=6,te=4,ne={stat:{w:1,h:1},chart:{w:2,h:2},block:{w:6,h:4}},Qe=e=>Math.max(1,Math.min(6,Math.round(e))),Ze=e=>Math.max(1,Math.min(4,Math.round(e)));function L(e){return e==="stat"?"stat":e==="series"||e==="donut"?"chart":"block"}function re(e){return e==="stat"?"stat":e==="line"||e==="bar"||e==="donut"?"chart":"block"}function oe(e){return ne[e]}function N(e,t,o){let r=ne[e];return{w:t===void 0?r.w:Qe(t),h:o===void 0?r.h:Ze(o)}}function se(e){let t=[],o=[],r=(n,i,a,u)=>{if(n<0||n+a>6)return!1;for(let d=0;d<u;d+=1){let m=o[i+d];if(m){for(let p=0;p<a;p+=1)if(m[n+p])return!1}}return!0},s=(n,i,a,u)=>{for(let d=0;d<u;d+=1){let m=o[i+d]??[];for(let p=0;p<a;p+=1)m[n+p]=!0;o[i+d]=m}},l=(n,i)=>{for(let a=0;;a+=1)for(let u=0;u+n<=6;u+=1)if(r(u,a,n,i))return{gx:u,gy:a}};for(let n of e){let i=N(L(n.kind),n.w,n.h),u=n.gx!==void 0&&n.gy!==void 0&&r(n.gx,n.gy,i.w,i.h)&&n.gx!==void 0&&n.gy!==void 0?{gx:n.gx,gy:n.gy}:l(i.w,i.h);s(u.gx,u.gy,i.w,i.h),t.push({widget:n,gx:u.gx,gy:u.gy,w:i.w,h:i.h})}return t}var ie=new Set(["service","order","end_user_id"]),ae=e=>typeof e=="number"?e:Number(e)||0,le=e=>e==null?"":String(e);function ue(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((r,s)=>r+s,0);return e==="avg"?Math.round(o/t.length):o}function et(e,t,o,r){let s=new Map;for(let l of e){let n=new Date(le(l.data[t]));if(Number.isNaN(n.getTime()))continue;let i=n.toISOString().slice(0,10),a=s.get(i)??[];a.push(o?ae(l.data[o]):1),s.set(i,a)}return[...s.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?ue(r,n):n.length}))}function tt(e,t){let o=new Map;for(let r of e){let s=le(r.data[t]).trim();s&&o.set(s,(o.get(s)??0)+1)}return[...o.entries()].sort((r,s)=>s[1]-r[1]).map(([r,s])=>({label:r,value:s}))}function nt(e,t,o,r){switch(e.type){case"stat":{let s=e.metric?t.get(e.metric):void 0;if(e.metric&&!s)return null;if(!s)return{kind:"stat",label:e.label??"Items",value:r.length};let l=r.map(n=>ae(n.data[s.key]));return{kind:"stat",label:e.label??s.label,value:ue(e.agg??"sum",l)}}case"line":case"bar":{if(!t.has(e.x)||e.y&&!t.has(e.y))return null;let s=et(r,e.x,e.y,e.agg??"sum");return s.length<2?null:{kind:"series",draw:e.type,title:e.title??"By day",points:s}}case"donut":{if(!t.has(e.groupBy))return null;let s=tt(r,e.groupBy);return s.length===0?null:{kind:"donut",title:e.title??t.get(e.groupBy)?.label??"Breakdown",points:s}}case"table":{let s=e.columns?e.columns.map(l=>t.get(l)).filter(l=>l!==void 0&&!ie.has(l.key)):o;return s.length===0?null:{kind:"table",title:e.title,columns:s,rows:r}}case"text":return{kind:"text",title:e.title,body:e.body};default:return null}}function de(e,t,o){let r=t.filter(n=>!ie.has(n.key)),s=new Map(t.map(n=>[n.key,n])),l=[];for(let n of e.widgets){let i=nt(n,s,r,o);i&&l.push({...i,w:n.w,h:n.h,gx:n.gx,gy:n.gy})}return l}var rt=new Set(["sum","avg","count","min","max","last"]),pe=e=>typeof e=="string"&&rt.has(e)?e:void 0,x=e=>typeof e=="string"&&e!==""?e:void 0,D=e=>typeof e=="number"&&e>=1?Math.min(6,Math.round(e)):void 0,C=e=>typeof e=="number"&&e>=1?Math.min(4,Math.round(e)):void 0,k=e=>typeof e=="number"&&e>=0?Math.round(e):void 0;function ot(e){if(typeof e!="object"||e===null)return null;let t=e;switch(t.type){case"stat":return{type:"stat",label:x(t.label),metric:x(t.metric),agg:pe(t.agg),w:D(t.w),h:C(t.h),gx:k(t.gx),gy:k(t.gy)};case"line":case"bar":{let o=x(t.x);return o?{type:t.type,title:x(t.title),x:o,y:x(t.y),agg:pe(t.agg),w:D(t.w),h:C(t.h),gx:k(t.gx),gy:k(t.gy)}:null}case"donut":{let o=x(t.groupBy);return o?{type:"donut",title:x(t.title),groupBy:o,w:D(t.w),h:C(t.h),gx:k(t.gx),gy:k(t.gy)}:null}case"table":{let o=Array.isArray(t.columns)?t.columns.filter(r=>typeof r=="string"):void 0;return{type:"table",title:x(t.title),columns:o,w:D(t.w),h:C(t.h),gx:k(t.gx),gy:k(t.gy)}}case"text":{let o=x(t.body);return o?{type:"text",title:x(t.title),body:o,w:D(t.w),h:C(t.h),gx:k(t.gx),gy:k(t.gy)}:null}default:return null}}function ce(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 r=o.map(ot).filter(l=>l!==null);if(r.length===0)return null;let s=t.version;return{version:typeof s=="number"?s:1,title:x(t.title),widgets:r}}function G(e){if(typeof e=="string")return e?{kind:"file",value:e}:null;if(e&&typeof e=="object"){let t=e,o=typeof t.value=="string"?t.value:"",r=t.kind==="link"||t.kind==="text"||t.kind==="table"?t.kind:"file",s=typeof t.v=="number"&&t.v>=1?Math.floor(t.v):void 0;return o?{kind:r,value:o,...s?{v:s}:{}}:null}return null}function j(e){let t=new Map;for(let o of e){let r=o.v??1,s=t.get(r)??[];s.push(o),t.set(r,s)}return[...t.entries()].sort(([o],[r])=>o-r).map(([o,r])=>({v:o,items:r}))}function me(e){let t=e.files;if(typeof t=="string"&&t.trim()!=="")try{let r=JSON.parse(t);if(Array.isArray(r))return r.map(G).filter(s=>s!==null)}catch{}let o=String(e.file??"");return o?[{kind:"file",value:o}]:[]}function ge(e,t,o){let s=(j(e).find(n=>n.v===t)?.items??[]).filter(n=>n.kind==="file").map(n=>n.value),l=s.map(n=>o.get(n)??"");return s.length===0||l.some(n=>n==="")?"awaiting":l.some(n=>n==="revise")?"revise":"accepted"}function fe(e){return Number(e.published_version)||0}function B(e){return e==="number"||e==="date"||e==="boolean"||e==="select"||e==="url"?e:e==="link"?"url":e==="checkbox"?"boolean":"text"}function ye(e){try{let t=JSON.parse(e);if(Array.isArray(t.columns)&&Array.isArray(t.rows)&&t.columns.length){let o=t.columns.map((l,n)=>({key:typeof l.key=="string"&&l.key?l.key:typeof l.id=="string"&&l.id?l.id:`column_${n+1}`,label:typeof l.label=="string"?l.label:"",type:B(l.type),...l.clientEditable===!0?{clientEditable:!0}:{}})),r=t.rows.map((l,n)=>({id:typeof l.id=="string"&&l.id?l.id:`row_${n}`,cells:l.cells&&typeof l.cells=="object"?l.cells:{}})),s=typeof t.name=="string"?t.name:void 0;return{columns:o,rows:r,...s?{name:s}:{}}}}catch{}return null}function be(e){return e==null?"":String(e)}function he(e){return e===!0||e==="true"||e==="1"||e==="yes"||e==="\u2713"}function we(e){if(typeof e!="string"||e.trim()==="")return{};try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t))return t}catch{}return{}}function xe(e,t,o){if(e.clientEditable){let r=o[t.id];if(r&&e.key in r)return r[e.key]}return t.cells[e.key]}function ve(e,t,o,r){return{...e,[t]:{...e[t]??{},[o]:r}}}function ke(e){let t=new Date(`${e}T00:00:00`);return Number.isNaN(t.getTime())?e:t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}var st=/\/+$/,R="/api/v1/end-user",I=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function it(e){let t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}var at=e=>{let t=e.baseUrl.replace(st,""),o=()=>typeof e.token=="function"?e.token():e.token,r=async()=>({"content-type":"application/json",authorization:`Bearer ${await o()}`}),s=async(n,i)=>{try{let a=await fetch(`${t}${R}${n}`,{method:i.method,headers:await r(),...i.body===void 0?{}:{body:JSON.stringify(i.body)}});if(a.status===409){let u=await a.json().catch(()=>({}));throw new I(u.message??"Someone else saved this first.")}if(!a.ok){let u=await a.text().catch(()=>""),d=`${i.method} ${n} \u2192 ${String(a.status)}`;try{let m=JSON.parse(u);m.message&&(d=m.message)}catch{d=u?`${d}: ${u}`:d}throw v(new Error(d))}return await a.json()}catch(a){throw v(a)}},l=n=>`/tables/${encodeURIComponent(n)}`;return{listTables:async()=>(await b(`${t}${R}/tables`,await r())).tables,listRows:async(n,i)=>await b(`${t}${R}${l(n)}/rows?limit=${String(i??100)}`,await r()),insertRow:async(n,i)=>{let{rowIds:a}=await s(`${l(n)}/rows`,{method:"POST",body:{rows:[i]}}),u=a[0];if(!u)throw v(new Error("The row was not created."));return u},patchRow:async(n,i,a)=>(await s(`${l(n)}/rows/${encodeURIComponent(i)}`,{method:"PATCH",body:a})).updated,deleteRow:async(n,i)=>(await s(`/${encodeURIComponent(n)}/${encodeURIComponent(i)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await b(`${t}${R}/tables/files/rows?limit=200`,await r())).rows.map(i=>({id:i.rowId,filename:String(i.data.filename??""),source:String(i.data.source??"client"),campaign:String(i.data.campaign??""),size:typeof i.data.size=="number"?i.data.size:0,createdAt:i.createdAt})),uploadFile:async(n,i)=>{let a=new Uint8Array(await n.arrayBuffer());return await s("/files",{method:"POST",body:{filename:n.name,mime:n.type||"application/octet-stream",data:it(a),...i===void 0?{}:{message:i}}})},fileLink:async n=>await b(`${t}${R}/files/${encodeURIComponent(n)}/url`,await r()),deleteFile:async n=>(await s(`/files/${encodeURIComponent(n)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await b(`${t}${R}/documents`,await r())).documents,readDocument:async n=>(await b(`${t}${R}/documents/${encodeURIComponent(n)}`,await r())).document,saveDocument:async(n,i)=>await s(`/documents/${encodeURIComponent(n)}`,{method:"PATCH",body:i})}};import{useCallback as lt,useEffect as Te,useRef as ut,useState as H}from"react";var Re="greeting";function De(e,t){if(e.some(s=>s.id===t.id))return e;let o=e.filter(s=>s.id!==Re);return[...t.author==="client"?o.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):o,t]}var dt=(e,t)=>{let[o,r]=H([]),[s,l]=H(""),[n,i]=H(!1),[a,u]=H(null),d=ut(0),m=t?.pollMs??2e3;Te(()=>{let c=!0;return e.getInfo().then(f=>{c&&f.greeting&&r(g=>g.length===0?[{id:Re,cursor:"",author:"agent",text:f.greeting}]:g)}).catch(()=>{}),()=>{c=!1}},[e]),Te(()=>e.subscribe?.({onMessage:c=>r(f=>De(f,c)),onError:c=>u(c.error),onHealthy:()=>u(null)},{intervalMs:m}),[e,m]);let p=lt(()=>{let c=s.trim();if(!c||n)return;l(""),u(null),i(!0),d.current+=1;let f={id:`pending-${d.current}`,cursor:"",author:"client",text:c,pending:!0};r(g=>[...g,f]),e.sendMessage(c).then(()=>i(!1)).catch(g=>{u(g instanceof Error?g.message:"send_failed"),i(!1)})},[s,n,e]);return{messages:o,input:s,setInput:l,send:p,sending:n,error:a}};export{Ge as Chat,Z as GRID_COLS,ee as GRID_MAX_COLS,te as GRID_MAX_ROWS,$e as HolaProvider,I as PortalConflict,L as bandOfKind,re as bandOfType,xe as cellValue,He as createApiTransport,Be as createHolaClient,at as createPortalData,F as createWidgetTransport,be as deliverableCellText,ge as deliverableStatus,K as deriveFields,oe as footprintOf,N as footprintWith,ke as formatDeliverableDate,j as groupDeliverableVersions,Q as inferSpec,he as isDeliverableChecked,De as mergeMessage,B as normalizeDeliverableColType,G as normalizeDeliverableItem,ce as parseDashboardSpec,me as parseDeliverableItems,ye as parseDeliverableTable,we as parseTableResponses,se as placeWidgets,fe as publishedVersionOf,de as renderModel,ve as setResponse,W as textDeltaOf,A as thinkingDeltaOf,E as toolEventOf,$ as useArtifactUrl,_ as useChat,O as useHolaClient,dt 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.10",
|
|
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",
|