@holaboss/client 0.2.0-beta.1 → 0.2.0-beta.3
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 +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +5 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
var F=Object.defineProperty;var
|
|
3
|
-
`))
|
|
4
|
-
`)}:null},
|
|
2
|
+
var F=Object.defineProperty;var Q=Object.getOwnPropertyDescriptor;var X=Object.getOwnPropertyNames;var Z=Object.prototype.hasOwnProperty;var ee=(e,t)=>{for(var a in t)F(e,a,{get:t[a],enumerable:!0})},te=(e,t,a,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of X(t))!Z.call(e,n)&&n!==a&&F(e,n,{get:()=>t[n],enumerable:!(s=Q(t,n))||s.enumerable});return e};var re=e=>te(F({},"__esModule",{value:!0}),e);var pe={};ee(pe,{Chat:()=>G,HolaProvider:()=>q,PortalConflict:()=>v,createApiTransport:()=>z,createHolaClient:()=>K,createPortalData:()=>V,createWidgetTransport:()=>O,mergeMessage:()=>L,textDeltaOf:()=>I,thinkingDeltaOf:()=>R,toolEventOf:()=>E,useArtifactUrl:()=>k,useChat:()=>U,useHolaClient:()=>A,useThread:()=>Y});module.exports=re(pe);var W=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},I=e=>{let t=W(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},R=e=>{let t=W(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},E=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var oe=/^ /,C=e=>{if(e&&typeof e=="object"){let t=e,a=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:a,retryable:t.retryable}:{error:a}}return{error:"request_failed"}},se=e=>{let t="message",a=[];for(let s of e.split(`
|
|
3
|
+
`))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&a.push(s.slice(5).replace(oe,"")));return a.length>0?{event:t,data:a.join(`
|
|
4
|
+
`)}:null},ne=(e,t,a)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let n=s.sessionId;n&&(a(n),t.onSession?.(n));return}if(e.event==="turn_event"){t.onEvent?.(s);let n=I(s);n&&t.onText?.(n);let d=R(s);d&&t.onThinking?.(d);let r=E(s);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let n=s;a(n.sessionId),t.onComplete?.(n);return}e.event==="chat_error"&&t.onError?.(C(s))},h=async(e,t)=>{let a=await fetch(e,{headers:t});if(!a.ok)throw new Error(`hola: request failed (${a.status})`);return await a.json()},j=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},N=async(e,t,a,s)=>{let n=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:s});if(!(n.ok&&n.body)){t.onError?.({error:`stream_failed_${n.status}`});return}let d=n.body.getReader(),r=new TextDecoder,o="",l=()=>{let c=o.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;c!==-1;){let b=o.slice(0,c);o=o.slice(c+2);let u=se(b);u&&ne(u,t,a),c=o.indexOf(`
|
|
7
7
|
|
|
8
|
-
`)}},i=async()=>{let
|
|
8
|
+
`)}},i=async()=>{let c=await d.read();if(!c.done)return o+=r.decode(c.value,{stream:!0}),l(),i()};await i()};var ae=/\/+$/,S="/api/v1/end-user",z=e=>{let t=e.baseUrl.replace(ae,""),a=()=>typeof e.token=="function"?e.token():e.token,s=r=>e.project?`${r}project=${encodeURIComponent(e.project)}`:"",n=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),d=async r=>{let o=await fetch(`${t}${S}/chat${s("?")}`,{method:"POST",headers:await n(),body:JSON.stringify({message:r})});if(!o.ok)throw new Error(`hola: message failed (${o.status})`);return{reply:(await o.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>h(`${t}${S}/chat/info${s("?")}`,await n()),sendMessage:d,streamMessage:async(r,o)=>{try{let l=await d(r);o.onText?.(l.reply),o.onComplete?.(l)}catch(l){o.onError?.(C(l))}},subscribe:(r,o)=>{let l=o?.since??"0",i=!1,c,b=o?.intervalMs??3e3,u=async()=>{try{let g=await h(`${t}${S}/chat/thread?since=${encodeURIComponent(l)}${s("&")}`,await n());for(let m of g.messages)r.onMessage(m);l=g.cursor}catch(g){r.onError?.(C(g))}i||(c=setTimeout(u,b))},p=()=>{i=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{await fetch(`${t}${S}/chat/request-human${s("?")}`,{method:"POST",headers:await n(),body:"{}"})}}};var $=require("react"),k=(e,t)=>{let[a,s]=(0,$.useState)("");return(0,$.useEffect)(()=>{if(!e){s("");return}let n=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),d=URL.createObjectURL(new Blob([n],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),a};var D=require("react");var M=require("react"),B=require("react/jsx-runtime"),J=(0,M.createContext)(null),q=({client:e,children:t})=>(0,B.jsx)(J.Provider,{value:e,children:t}),A=()=>{let e=(0,M.useContext)(J);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};var w=require("react"),U=e=>{let[t,a]=(0,w.useState)([]),[s,n]=(0,w.useState)(""),[d,r]=(0,w.useState)(!1),[o,l]=(0,w.useState)(null),i=(0,w.useRef)(0),c=(0,w.useCallback)(()=>{let u=i.current;return i.current+=1,u},[]);(0,w.useEffect)(()=>{let u=!0;return e.getInfo().then(p=>{u&&p.greeting&&a([{id:c(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,c]);let b=(0,w.useCallback)(()=>{let u=s.trim();if(!u||d)return;n(""),l(null),r(!0);let p=c(),g=c();a(f=>[...f,{id:p,role:"user",text:u},{id:g,role:"assistant",text:"",streaming:!0}]);let m=f=>{a(H=>H.map(_=>_.id===g?f(_):_))};e.streamMessage(u,{onText:f=>m(H=>({...H,text:H.text+f})),onComplete:f=>{m(H=>({...H,text:f.reply||H.text,artifacts:f.artifacts,streaming:!1})),r(!1)},onError:f=>{m(H=>({...H,streaming:!1})),l(f.error),r(!1)}}).catch(f=>{m(H=>({...H,streaming:!1})),l(f instanceof Error?f.message:"stream_failed"),r(!1)})},[s,d,e,c]);return{messages:t,input:s,setInput:n,send:b,sending:d,error:o}};var y=require("react/jsx-runtime"),T={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},ie=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),a=e.url??t;return a?e.mimeType.startsWith("image/")?(0,y.jsx)("a",{href:a,rel:"noopener noreferrer",target:"_blank",children:(0,y.jsx)("img",{alt:e.filename,src:a,style:T.image})}):(0,y.jsx)("a",{download:e.filename,href:a,style:T.link,children:e.filename}):null},G=()=>{let e=A(),{messages:t,input:a,setInput:s,send:n,sending:d,error:r}=U(e),o=(0,D.useRef)(null);(0,D.useEffect)(()=>{let i=o.current;i&&(i.scrollTop=i.scrollHeight)},[t]);let l=i=>{i.preventDefault(),n()};return(0,y.jsxs)("div",{style:T.root,children:[(0,y.jsx)("div",{ref:o,style:T.log,children:t.map(i=>(0,y.jsxs)("div",{style:i.role==="user"?T.user:T.assistant,children:[i.text||(i.streaming?"\u2026":""),i.artifacts&&i.artifacts.length>0?(0,y.jsx)("div",{style:T.artifacts,children:i.artifacts.map(c=>(0,y.jsx)(ie,{artifact:c},c.filename))}):null]},i.id))}),r?(0,y.jsx)("div",{style:T.error,children:r}):null,(0,y.jsxs)("form",{onSubmit:l,style:T.form,children:[(0,y.jsx)("input",{"aria-label":"Message",onChange:i=>s(i.target.value),placeholder:"Type a message\u2026",style:T.input,value:a}),(0,y.jsx)("button",{disabled:d||a.trim().length===0,style:T.button,type:"submit",children:"Send"})]})]})};var le=/\/+$/,O=e=>{let t=e.sessionId,a=e.baseUrl.replace(le,""),s=r=>`${a}/endpoint/${encodeURIComponent(e.token)}${r}`,n={"content-type":"application/json"},d=(r,o)=>({url:s(r),headers:n,body:JSON.stringify({message:o,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>h(s("/config"),{}),sendMessage:async r=>{let o=await j(d("/message",r));return t=o.sessionId,o},streamMessage:(r,o,l)=>N(d("/message/stream",r),o,i=>{t=i},l),subscribe:(r,o)=>{let l=o?.since??"0",i=!1,c,b=o?.intervalMs??3e3,u=async()=>{let g=t;if(g)try{let m=await h(s(`/messages?session=${encodeURIComponent(g)}&since=${encodeURIComponent(l)}`),{});for(let f of m.messages)r.onMessage(f);l=m.cursor}catch(m){r.onError?.(C(m))}i||(c=setTimeout(u,b))},p=()=>{i=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:n,body:JSON.stringify({sessionId:t})})}}};var K=e=>"transport"in e?e.transport:O(e);var ce=/\/+$/,P="/api/v1/end-user",v=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function de(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var V=e=>{let t=e.baseUrl.replace(ce,""),a=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await a()}`}),n=async(r,o)=>{try{let l=await fetch(`${t}${P}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(l.status===409){let i=await l.json().catch(()=>({}));throw new v(i.message??"Someone else saved this first.")}if(!l.ok){let i=await l.text().catch(()=>""),c=`${o.method} ${r} \u2192 ${String(l.status)}`;try{let b=JSON.parse(i);b.message&&(c=b.message)}catch{c=i?`${c}: ${i}`:c}throw C(new Error(c))}return await l.json()}catch(l){throw C(l)}},d=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await h(`${t}${P}/tables`,await s())).tables,listRows:async(r,o)=>await h(`${t}${P}${d(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:l}=await n(`${d(r)}/rows`,{method:"POST",body:{rows:[o]}}),i=l[0];if(!i)throw C(new Error("The row was not created."));return i},patchRow:async(r,o,l)=>(await n(`${d(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:l})).updated,deleteRow:async(r,o)=>(await n(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await h(`${t}${P}/tables/files/rows?limit=200`,await s())).rows.map(o=>({id:o.rowId,filename:String(o.data.filename??""),source:String(o.data.source??"client"),campaign:String(o.data.campaign??""),size:typeof o.data.size=="number"?o.data.size:0,createdAt:o.createdAt})),uploadFile:async(r,o)=>{let l=new Uint8Array(await r.arrayBuffer());return await n("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:de(l),...o===void 0?{}:{message:o}}})},fileLink:async r=>await h(`${t}${P}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await n(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await h(`${t}${P}/documents`,await s())).documents,readDocument:async r=>(await h(`${t}${P}/documents/${encodeURIComponent(r)}`,await s())).document,saveDocument:async(r,o)=>await n(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:o})}};var x=require("react");function L(e,t){return e.some(s=>s.id===t.id)?e:[...t.author==="client"?e.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):e,t]}var Y=(e,t)=>{let[a,s]=(0,x.useState)([]),[n,d]=(0,x.useState)(""),[r,o]=(0,x.useState)(!1),[l,i]=(0,x.useState)(null),c=(0,x.useRef)(0),b=t?.pollMs??2e3;(0,x.useEffect)(()=>{let p=!0;return e.getInfo().then(g=>{p&&g.greeting&&s(m=>m.length===0?[{id:"greeting",cursor:"",author:"agent",text:g.greeting}]:m)}).catch(()=>{}),()=>{p=!1}},[e]),(0,x.useEffect)(()=>e.subscribe?.({onMessage:p=>s(g=>L(g,p)),onError:p=>i(p.error)},{intervalMs:b}),[e,b]);let u=(0,x.useCallback)(()=>{let p=n.trim();if(!p||r)return;d(""),i(null),o(!0),c.current+=1;let g={id:`pending-${c.current}`,cursor:"",author:"client",text:p,pending:!0};s(m=>[...m,g]),e.sendMessage(p).then(()=>o(!1)).catch(m=>{i(m instanceof Error?m.message:"send_failed"),o(!1)})},[n,r,e]);return{messages:a,input:n,setInput:d,send:u,sending:r,error:l}};0&&(module.exports={Chat,HolaProvider,PortalConflict,createApiTransport,createHolaClient,createPortalData,createWidgetTransport,mergeMessage,textDeltaOf,thinkingDeltaOf,toolEventOf,useArtifactUrl,useChat,useHolaClient,useThread});
|
package/dist/index.d.cts
CHANGED
|
@@ -108,10 +108,19 @@ type ApiTransportConfig = {
|
|
|
108
108
|
/** End-user access token for the Authorization header. A string, or a getter so a
|
|
109
109
|
* refreshed token is used per request. */
|
|
110
110
|
token: string | (() => string | Promise<string>);
|
|
111
|
+
/**
|
|
112
|
+
* Which of the customer's projects this conversation is about.
|
|
113
|
+
*
|
|
114
|
+
* Omit for the general thread — the one running conversation with the company,
|
|
115
|
+
* and where everything that predates per-project threads still lives. The
|
|
116
|
+
* server checks the project belongs to this customer before it trusts it, so
|
|
117
|
+
* naming someone else's is a 404 rather than a way into their thread.
|
|
118
|
+
*/
|
|
119
|
+
project?: string;
|
|
111
120
|
};
|
|
112
121
|
/** The Bearer-authed transport for the signed-in end-user PORTAL (`/api/v1/end-user/*`). Same
|
|
113
122
|
* client surface as the anonymous widget transport — the token (not a path param) resolves the
|
|
114
|
-
* customer
|
|
123
|
+
* customer; an optional `project` picks which of their threads. The portal is buffered (no SSE) and one-thread-per-user,
|
|
115
124
|
* so `sendMessage` returns just the reply and everything out-of-turn — the AI's turns, a human's
|
|
116
125
|
* reply, an async artifact (video) — arrives via `subscribe`. */
|
|
117
126
|
declare const createApiTransport: (config: ApiTransportConfig) => HolaTransport;
|
|
@@ -197,6 +206,29 @@ type PortalFile = {
|
|
|
197
206
|
size: number;
|
|
198
207
|
createdAt: string;
|
|
199
208
|
};
|
|
209
|
+
/** A document shared with this customer. The body arrives only on a read. */
|
|
210
|
+
type PortalDocumentSummary = {
|
|
211
|
+
documentId: string;
|
|
212
|
+
title: string;
|
|
213
|
+
version: number;
|
|
214
|
+
/** Whether the company opened this one for editing, not merely for reading. */
|
|
215
|
+
editable: boolean;
|
|
216
|
+
updatedAt: string;
|
|
217
|
+
};
|
|
218
|
+
type PortalDocument = PortalDocumentSummary & {
|
|
219
|
+
body: string;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* A save that lost a race.
|
|
223
|
+
*
|
|
224
|
+
* Thrown rather than returned so it cannot be mistaken for success, and separate
|
|
225
|
+
* from a plain error because the caller should do something different: re-read,
|
|
226
|
+
* reapply, save again — never retry the same bytes, which would overwrite whoever
|
|
227
|
+
* got there first.
|
|
228
|
+
*/
|
|
229
|
+
declare class PortalConflict extends Error {
|
|
230
|
+
constructor(message: string);
|
|
231
|
+
}
|
|
200
232
|
type PortalData = {
|
|
201
233
|
/** The tables this customer can see. A table the company has not shared is
|
|
202
234
|
* absent, not empty — it answers as if it did not exist. */
|
|
@@ -236,6 +268,23 @@ type PortalData = {
|
|
|
236
268
|
}>;
|
|
237
269
|
/** Only files this customer sent, and only before they are filed. */
|
|
238
270
|
deleteFile(rowId: string): Promise<number>;
|
|
271
|
+
/** Documents the company shared with this customer. */
|
|
272
|
+
listDocuments(): Promise<PortalDocumentSummary[]>;
|
|
273
|
+
readDocument(documentId: string): Promise<PortalDocument>;
|
|
274
|
+
/**
|
|
275
|
+
* Save new text.
|
|
276
|
+
*
|
|
277
|
+
* `baseVersion` is the version that was open. Rejects with a PortalConflict if
|
|
278
|
+
* it has moved — do not retry with the same text: read again, put the change
|
|
279
|
+
* back on top of theirs, and save that.
|
|
280
|
+
*/
|
|
281
|
+
saveDocument(documentId: string, edit: {
|
|
282
|
+
body: string;
|
|
283
|
+
baseVersion: number;
|
|
284
|
+
note?: string;
|
|
285
|
+
}): Promise<{
|
|
286
|
+
version: number;
|
|
287
|
+
}>;
|
|
239
288
|
};
|
|
240
289
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
241
290
|
|
|
@@ -291,4 +340,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
291
340
|
pollMs?: number;
|
|
292
341
|
}) => UseThread;
|
|
293
342
|
|
|
294
|
-
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 };
|
|
343
|
+
export { type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ThreadMessage, type UseChat, type UseThread, type WidgetTransportConfig, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, mergeMessage, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
|
package/dist/index.d.ts
CHANGED
|
@@ -108,10 +108,19 @@ type ApiTransportConfig = {
|
|
|
108
108
|
/** End-user access token for the Authorization header. A string, or a getter so a
|
|
109
109
|
* refreshed token is used per request. */
|
|
110
110
|
token: string | (() => string | Promise<string>);
|
|
111
|
+
/**
|
|
112
|
+
* Which of the customer's projects this conversation is about.
|
|
113
|
+
*
|
|
114
|
+
* Omit for the general thread — the one running conversation with the company,
|
|
115
|
+
* and where everything that predates per-project threads still lives. The
|
|
116
|
+
* server checks the project belongs to this customer before it trusts it, so
|
|
117
|
+
* naming someone else's is a 404 rather than a way into their thread.
|
|
118
|
+
*/
|
|
119
|
+
project?: string;
|
|
111
120
|
};
|
|
112
121
|
/** The Bearer-authed transport for the signed-in end-user PORTAL (`/api/v1/end-user/*`). Same
|
|
113
122
|
* client surface as the anonymous widget transport — the token (not a path param) resolves the
|
|
114
|
-
* customer
|
|
123
|
+
* customer; an optional `project` picks which of their threads. The portal is buffered (no SSE) and one-thread-per-user,
|
|
115
124
|
* so `sendMessage` returns just the reply and everything out-of-turn — the AI's turns, a human's
|
|
116
125
|
* reply, an async artifact (video) — arrives via `subscribe`. */
|
|
117
126
|
declare const createApiTransport: (config: ApiTransportConfig) => HolaTransport;
|
|
@@ -197,6 +206,29 @@ type PortalFile = {
|
|
|
197
206
|
size: number;
|
|
198
207
|
createdAt: string;
|
|
199
208
|
};
|
|
209
|
+
/** A document shared with this customer. The body arrives only on a read. */
|
|
210
|
+
type PortalDocumentSummary = {
|
|
211
|
+
documentId: string;
|
|
212
|
+
title: string;
|
|
213
|
+
version: number;
|
|
214
|
+
/** Whether the company opened this one for editing, not merely for reading. */
|
|
215
|
+
editable: boolean;
|
|
216
|
+
updatedAt: string;
|
|
217
|
+
};
|
|
218
|
+
type PortalDocument = PortalDocumentSummary & {
|
|
219
|
+
body: string;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* A save that lost a race.
|
|
223
|
+
*
|
|
224
|
+
* Thrown rather than returned so it cannot be mistaken for success, and separate
|
|
225
|
+
* from a plain error because the caller should do something different: re-read,
|
|
226
|
+
* reapply, save again — never retry the same bytes, which would overwrite whoever
|
|
227
|
+
* got there first.
|
|
228
|
+
*/
|
|
229
|
+
declare class PortalConflict extends Error {
|
|
230
|
+
constructor(message: string);
|
|
231
|
+
}
|
|
200
232
|
type PortalData = {
|
|
201
233
|
/** The tables this customer can see. A table the company has not shared is
|
|
202
234
|
* absent, not empty — it answers as if it did not exist. */
|
|
@@ -236,6 +268,23 @@ type PortalData = {
|
|
|
236
268
|
}>;
|
|
237
269
|
/** Only files this customer sent, and only before they are filed. */
|
|
238
270
|
deleteFile(rowId: string): Promise<number>;
|
|
271
|
+
/** Documents the company shared with this customer. */
|
|
272
|
+
listDocuments(): Promise<PortalDocumentSummary[]>;
|
|
273
|
+
readDocument(documentId: string): Promise<PortalDocument>;
|
|
274
|
+
/**
|
|
275
|
+
* Save new text.
|
|
276
|
+
*
|
|
277
|
+
* `baseVersion` is the version that was open. Rejects with a PortalConflict if
|
|
278
|
+
* it has moved — do not retry with the same text: read again, put the change
|
|
279
|
+
* back on top of theirs, and save that.
|
|
280
|
+
*/
|
|
281
|
+
saveDocument(documentId: string, edit: {
|
|
282
|
+
body: string;
|
|
283
|
+
baseVersion: number;
|
|
284
|
+
note?: string;
|
|
285
|
+
}): Promise<{
|
|
286
|
+
version: number;
|
|
287
|
+
}>;
|
|
239
288
|
};
|
|
240
289
|
declare const createPortalData: (config: ApiTransportConfig) => PortalData;
|
|
241
290
|
|
|
@@ -291,4 +340,4 @@ declare const useThread: (client: HolaClient, options?: {
|
|
|
291
340
|
pollMs?: number;
|
|
292
341
|
}) => UseThread;
|
|
293
342
|
|
|
294
|
-
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 };
|
|
343
|
+
export { type ApiTransportConfig, Chat, type ChatMessage, type CreateHolaClientOptions, type HolaArtifact, type HolaAuthor, type HolaClient, type HolaCompletion, type HolaEmployeeInfo, type HolaError, type HolaIdentity, type HolaMessage, HolaProvider, type HolaStreamHandlers, type HolaSubscribeHandlers, type HolaSubscribeOptions, type HolaToolEvent, type HolaTransport, PortalConflict, type PortalData, type PortalDocument, type PortalDocumentSummary, type PortalField, type PortalFile, type PortalRow, type PortalRowPage, type PortalTable, type ThreadMessage, type UseChat, type UseThread, type WidgetTransportConfig, createApiTransport, createHolaClient, createPortalData, createWidgetTransport, mergeMessage, textDeltaOf, thinkingDeltaOf, toolEventOf, useArtifactUrl, useChat, useHolaClient, useThread };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
var O=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},
|
|
3
|
-
`))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&
|
|
4
|
-
`)}:null},
|
|
2
|
+
var O=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="message_update"?t.assistantMessageEvent??null:null},E=e=>{let t=O(e);return t?.type==="text_delta"&&typeof t.delta=="string"?t.delta:null},S=e=>{let t=O(e);return t?.type==="thinking_delta"&&typeof t.delta=="string"?t.delta:null},$=e=>{if(!e||typeof e!="object")return null;let t=e;return t.type==="tool_execution_start"&&t.toolCallId?{phase:"start",id:t.toolCallId,name:t.toolName}:t.type==="tool_execution_end"&&t.toolCallId?{phase:"end",id:t.toolCallId,isError:t.isError}:null};var z=/^ /,x=e=>{if(e&&typeof e=="object"){let t=e,i=typeof t.error=="string"?t.error:"request_failed";return typeof t.retryable=="boolean"?{error:i,retryable:t.retryable}:{error:i}}return{error:"request_failed"}},J=e=>{let t="message",i=[];for(let s of e.split(`
|
|
3
|
+
`))s.startsWith(":")||(s.startsWith("event:")?t=s.slice(6).trim():s.startsWith("data:")&&i.push(s.slice(5).replace(z,"")));return i.length>0?{event:t,data:i.join(`
|
|
4
|
+
`)}:null},q=(e,t,i)=>{let s;try{s=JSON.parse(e.data)}catch{return}if(e.event==="session"){let a=s.sessionId;a&&(i(a),t.onSession?.(a));return}if(e.event==="turn_event"){t.onEvent?.(s);let a=E(s);a&&t.onText?.(a);let d=S(s);d&&t.onThinking?.(d);let r=$(s);r&&t.onTool?.(r);return}if(e.event==="chat_completed"){let a=s;i(a.sessionId),t.onComplete?.(a);return}e.event==="chat_error"&&t.onError?.(x(s))},b=async(e,t)=>{let i=await fetch(e,{headers:t});if(!i.ok)throw new Error(`hola: request failed (${i.status})`);return await i.json()},_=async e=>{let t=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body});if(!t.ok)throw new Error(`hola: message failed (${t.status})`);return await t.json()},F=async(e,t,i,s)=>{let a=await fetch(e.url,{method:"POST",headers:e.headers,body:e.body,signal:s});if(!(a.ok&&a.body)){t.onError?.({error:`stream_failed_${a.status}`});return}let d=a.body.getReader(),r=new TextDecoder,o="",l=()=>{let c=o.indexOf(`
|
|
5
5
|
|
|
6
|
-
`);for(;
|
|
6
|
+
`);for(;c!==-1;){let y=o.slice(0,c);o=o.slice(c+2);let u=J(y);u&&q(u,t,i),c=o.indexOf(`
|
|
7
7
|
|
|
8
|
-
`)}},n=async()=>{let
|
|
8
|
+
`)}},n=async()=>{let c=await d.read();if(!c.done)return o+=r.decode(c.value,{stream:!0}),l(),n()};await n()};var B=/\/+$/,C="/api/v1/end-user",G=e=>{let t=e.baseUrl.replace(B,""),i=()=>typeof e.token=="function"?e.token():e.token,s=r=>e.project?`${r}project=${encodeURIComponent(e.project)}`:"",a=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),d=async r=>{let o=await fetch(`${t}${C}/chat${s("?")}`,{method:"POST",headers:await a(),body:JSON.stringify({message:r})});if(!o.ok)throw new Error(`hola: message failed (${o.status})`);return{reply:(await o.json()).reply??"",sessionId:"",artifacts:[]}};return{sessionId:()=>"",getInfo:async()=>b(`${t}${C}/chat/info${s("?")}`,await a()),sendMessage:d,streamMessage:async(r,o)=>{try{let l=await d(r);o.onText?.(l.reply),o.onComplete?.(l)}catch(l){o.onError?.(x(l))}},subscribe:(r,o)=>{let l=o?.since??"0",n=!1,c,y=o?.intervalMs??3e3,u=async()=>{try{let g=await b(`${t}${C}/chat/thread?since=${encodeURIComponent(l)}${s("&")}`,await a());for(let m of g.messages)r.onMessage(m);l=g.cursor}catch(g){r.onError?.(x(g))}n||(c=setTimeout(u,y))},p=()=>{n=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{await fetch(`${t}${C}/chat/request-human${s("?")}`,{method:"POST",headers:await a(),body:"{}"})}}};import{useEffect as K,useState as V}from"react";var k=(e,t)=>{let[i,s]=V("");return K(()=>{if(!e){s("");return}let a=Uint8Array.from(atob(e),r=>r.charCodeAt(0)),d=URL.createObjectURL(new Blob([a],{type:t}));return s(d),()=>URL.revokeObjectURL(d)},[e,t]),i};import{useEffect as re,useRef as oe}from"react";import{createContext as Y,useContext as Q}from"react";import{jsx as Z}from"react/jsx-runtime";var L=Y(null),X=({client:e,children:t})=>Z(L.Provider,{value:e,children:t}),M=()=>{let e=Q(L);if(!e)throw new Error("useHolaClient must be used within <HolaProvider>");return e};import{useCallback as W,useEffect as ee,useRef as te,useState as P}from"react";var A=e=>{let[t,i]=P([]),[s,a]=P(""),[d,r]=P(!1),[o,l]=P(null),n=te(0),c=W(()=>{let u=n.current;return n.current+=1,u},[]);ee(()=>{let u=!0;return e.getInfo().then(p=>{u&&p.greeting&&i([{id:c(),role:"assistant",text:p.greeting}])}).catch(()=>{}),()=>{u=!1}},[e,c]);let y=W(()=>{let u=s.trim();if(!u||d)return;a(""),l(null),r(!0);let p=c(),g=c();i(f=>[...f,{id:p,role:"user",text:u},{id:g,role:"assistant",text:"",streaming:!0}]);let m=f=>{i(h=>h.map(R=>R.id===g?f(R):R))};e.streamMessage(u,{onText:f=>m(h=>({...h,text:h.text+f})),onComplete:f=>{m(h=>({...h,text:f.reply||h.text,artifacts:f.artifacts,streaming:!1})),r(!1)},onError:f=>{m(h=>({...h,streaming:!1})),l(f.error),r(!1)}}).catch(f=>{m(h=>({...h,streaming:!1})),l(f instanceof Error?f.message:"stream_failed"),r(!1)})},[s,d,e,c]);return{messages:t,input:s,setInput:a,send:y,sending:d,error:o}};import{jsx as H,jsxs as U}from"react/jsx-runtime";var w={root:{display:"flex",flexDirection:"column",height:"100%",fontFamily:"system-ui, -apple-system, sans-serif",fontSize:14,color:"#111827"},log:{flex:1,overflowY:"auto",padding:12,display:"flex",flexDirection:"column",gap:8},user:{alignSelf:"flex-end",background:"#111827",color:"#ffffff",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},assistant:{alignSelf:"flex-start",background:"#f1f1f4",color:"#111827",borderRadius:12,padding:"8px 12px",maxWidth:"80%",whiteSpace:"pre-wrap"},artifacts:{display:"flex",flexDirection:"column",gap:6,marginTop:6},image:{maxHeight:220,borderRadius:8,border:"1px solid #e5e7eb"},link:{color:"#2563eb",textDecoration:"underline",wordBreak:"break-all"},error:{color:"#b91c1c",padding:"4px 12px",fontSize:13},form:{display:"flex",gap:8,borderTop:"1px solid #e5e7eb",padding:8},input:{flex:1,border:"1px solid #d1d5db",borderRadius:8,padding:"8px 10px",fontSize:14,outline:"none"},button:{border:"none",background:"#111827",color:"#ffffff",borderRadius:8,padding:"8px 14px",fontSize:14,cursor:"pointer"}},se=({artifact:e})=>{let t=k(e.dataBase64??"",e.mimeType),i=e.url??t;return i?e.mimeType.startsWith("image/")?H("a",{href:i,rel:"noopener noreferrer",target:"_blank",children:H("img",{alt:e.filename,src:i,style:w.image})}):H("a",{download:e.filename,href:i,style:w.link,children:e.filename}):null},ne=()=>{let e=M(),{messages:t,input:i,setInput:s,send:a,sending:d,error:r}=A(e),o=oe(null);re(()=>{let n=o.current;n&&(n.scrollTop=n.scrollHeight)},[t]);let l=n=>{n.preventDefault(),a()};return U("div",{style:w.root,children:[H("div",{ref:o,style:w.log,children:t.map(n=>U("div",{style:n.role==="user"?w.user:w.assistant,children:[n.text||(n.streaming?"\u2026":""),n.artifacts&&n.artifacts.length>0?H("div",{style:w.artifacts,children:n.artifacts.map(c=>H(se,{artifact:c},c.filename))}):null]},n.id))}),r?H("div",{style:w.error,children:r}):null,U("form",{onSubmit:l,style:w.form,children:[H("input",{"aria-label":"Message",onChange:n=>s(n.target.value),placeholder:"Type a message\u2026",style:w.input,value:i}),H("button",{disabled:d||i.trim().length===0,style:w.button,type:"submit",children:"Send"})]})]})};var ae=/\/+$/,D=e=>{let t=e.sessionId,i=e.baseUrl.replace(ae,""),s=r=>`${i}/endpoint/${encodeURIComponent(e.token)}${r}`,a={"content-type":"application/json"},d=(r,o)=>({url:s(r),headers:a,body:JSON.stringify({message:o,...t?{sessionId:t}:{},...e.identity?{externalUserId:e.identity.externalUserId,userHmac:e.identity.userHmac}:{}})});return{sessionId:()=>t,getInfo:()=>b(s("/config"),{}),sendMessage:async r=>{let o=await _(d("/message",r));return t=o.sessionId,o},streamMessage:(r,o,l)=>F(d("/message/stream",r),o,n=>{t=n},l),subscribe:(r,o)=>{let l=o?.since??"0",n=!1,c,y=o?.intervalMs??3e3,u=async()=>{let g=t;if(g)try{let m=await b(s(`/messages?session=${encodeURIComponent(g)}&since=${encodeURIComponent(l)}`),{});for(let f of m.messages)r.onMessage(f);l=m.cursor}catch(m){r.onError?.(x(m))}n||(c=setTimeout(u,y))},p=()=>{n=!0,c&&clearTimeout(c)};return o?.signal?.addEventListener("abort",p),c=setTimeout(u,0),p},requestHuman:async()=>{t&&await fetch(s("/request-human"),{method:"POST",headers:a,body:JSON.stringify({sessionId:t})})}}};var ie=e=>"transport"in e?e.transport:D(e);var le=/\/+$/,T="/api/v1/end-user",v=class extends Error{constructor(t){super(t),this.name="PortalConflict"}};function ce(e){let t="";for(let s=0;s<e.length;s+=32768)t+=String.fromCharCode(...e.subarray(s,s+32768));return btoa(t)}var de=e=>{let t=e.baseUrl.replace(le,""),i=()=>typeof e.token=="function"?e.token():e.token,s=async()=>({"content-type":"application/json",authorization:`Bearer ${await i()}`}),a=async(r,o)=>{try{let l=await fetch(`${t}${T}${r}`,{method:o.method,headers:await s(),...o.body===void 0?{}:{body:JSON.stringify(o.body)}});if(l.status===409){let n=await l.json().catch(()=>({}));throw new v(n.message??"Someone else saved this first.")}if(!l.ok){let n=await l.text().catch(()=>""),c=`${o.method} ${r} \u2192 ${String(l.status)}`;try{let y=JSON.parse(n);y.message&&(c=y.message)}catch{c=n?`${c}: ${n}`:c}throw x(new Error(c))}return await l.json()}catch(l){throw x(l)}},d=r=>`/tables/${encodeURIComponent(r)}`;return{listTables:async()=>(await b(`${t}${T}/tables`,await s())).tables,listRows:async(r,o)=>await b(`${t}${T}${d(r)}/rows?limit=${String(o??100)}`,await s()),insertRow:async(r,o)=>{let{rowIds:l}=await a(`${d(r)}/rows`,{method:"POST",body:{rows:[o]}}),n=l[0];if(!n)throw x(new Error("The row was not created."));return n},patchRow:async(r,o,l)=>(await a(`${d(r)}/rows/${encodeURIComponent(o)}`,{method:"PATCH",body:l})).updated,deleteRow:async(r,o)=>(await a(`/${encodeURIComponent(r)}/${encodeURIComponent(o)}`,{method:"DELETE"})).deleted,listFiles:async()=>(await b(`${t}${T}/tables/files/rows?limit=200`,await s())).rows.map(o=>({id:o.rowId,filename:String(o.data.filename??""),source:String(o.data.source??"client"),campaign:String(o.data.campaign??""),size:typeof o.data.size=="number"?o.data.size:0,createdAt:o.createdAt})),uploadFile:async(r,o)=>{let l=new Uint8Array(await r.arrayBuffer());return await a("/files",{method:"POST",body:{filename:r.name,mime:r.type||"application/octet-stream",data:ce(l),...o===void 0?{}:{message:o}}})},fileLink:async r=>await b(`${t}${T}/files/${encodeURIComponent(r)}/url`,await s()),deleteFile:async r=>(await a(`/files/${encodeURIComponent(r)}`,{method:"DELETE"})).deleted,listDocuments:async()=>(await b(`${t}${T}/documents`,await s())).documents,readDocument:async r=>(await b(`${t}${T}/documents/${encodeURIComponent(r)}`,await s())).document,saveDocument:async(r,o)=>await a(`/documents/${encodeURIComponent(r)}`,{method:"PATCH",body:o})}};import{useCallback as pe,useEffect as j,useRef as ue,useState as I}from"react";function N(e,t){return e.some(s=>s.id===t.id)?e:[...t.author==="client"?e.filter(s=>!(s.pending&&s.author==="client"&&s.text===t.text)):e,t]}var me=(e,t)=>{let[i,s]=I([]),[a,d]=I(""),[r,o]=I(!1),[l,n]=I(null),c=ue(0),y=t?.pollMs??2e3;j(()=>{let p=!0;return e.getInfo().then(g=>{p&&g.greeting&&s(m=>m.length===0?[{id:"greeting",cursor:"",author:"agent",text:g.greeting}]:m)}).catch(()=>{}),()=>{p=!1}},[e]),j(()=>e.subscribe?.({onMessage:p=>s(g=>N(g,p)),onError:p=>n(p.error)},{intervalMs:y}),[e,y]);let u=pe(()=>{let p=a.trim();if(!p||r)return;d(""),n(null),o(!0),c.current+=1;let g={id:`pending-${c.current}`,cursor:"",author:"client",text:p,pending:!0};s(m=>[...m,g]),e.sendMessage(p).then(()=>o(!1)).catch(m=>{n(m instanceof Error?m.message:"send_failed"),o(!1)})},[a,r,e]);return{messages:i,input:a,setInput:d,send:u,sending:r,error:l}};export{ne as Chat,X as HolaProvider,v as PortalConflict,G as createApiTransport,ie as createHolaClient,de as createPortalData,D as createWidgetTransport,N as mergeMessage,E as textDeltaOf,S as thinkingDeltaOf,$ as toolEventOf,k as useArtifactUrl,A as useChat,M as useHolaClient,me as useThread};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holaboss/client",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.3",
|
|
4
4
|
"description": "Injectable client SDK for embedding a Holaboss AI employee (chat today; dashboard + artifacts next) into your own web app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|