@inkeep/agents-ui 0.15.27 → 0.15.28
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/primitives/components/embedded-chat/use-inkeep-chat.cjs +3 -3
- package/dist/primitives/components/embedded-chat/use-inkeep-chat.d.ts +1 -1
- package/dist/primitives/components/embedded-chat/use-inkeep-chat.js +185 -175
- package/dist/primitives/hooks/use-initial-conversation.cjs +1 -0
- package/dist/primitives/hooks/use-initial-conversation.d.ts +16 -0
- package/dist/primitives/hooks/use-initial-conversation.js +31 -0
- package/dist/primitives/providers/base-events-provider.cjs +1 -1
- package/dist/primitives/providers/base-events-provider.js +1 -1
- package/dist/types/config/ai.d.ts +7 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Ze=require("@ai-sdk/react"),Ae=require("./file-upload-input.cjs"),Xe=require("ai"),t=require("react"),Ye=require("../modal/modal-provider.cjs"),et=require("../../providers/chat-bubble-provider.cjs"),tt=require("../../providers/sidebar-chat-provider.cjs"),st=require("../../providers/config-provider.cjs"),rt=require("./use-captcha.cjs"),nt=require("../../hooks/use-media-query.cjs"),at=require("../../hooks/use-anonymous-session.cjs"),ot=require("../../hooks/use-auth-token.cjs"),it=require("../../hooks/use-conversation-loader.cjs"),ut=require("../../hooks/use-initial-conversation.cjs"),O=require("../../utils/generate-uid.cjs"),ct=require("../../providers/base-events-provider.cjs"),lt=require("../../providers/chat-form-provider.cjs"),dt=require("../../providers/widget-provider.cjs"),ft=require("@radix-ui/react-use-controllable-state"),pt=require("../../hooks/use-streaming-events.cjs"),ht=require("../../hooks/use-inkeep-api-client.cjs"),gt=require("../../hooks/use-input-notification.cjs");function mt(m){const h=m.message??"";let r=Number(m.code)||Number(m.statusCode);if(Number.isNaN(r))try{r=Number(JSON.parse(h).status)}catch{}const d=ht.parseAuthError(r,{detail:h});return d!==null?d:r===401?"session":null}const _=`Hmm..
|
|
2
2
|
|
|
3
|
-
It seems I might be having some issues right now. Please clear the chat and try again.`,
|
|
4
|
-
`)??"";t.useEffect(()=>{
|
|
3
|
+
It seems I might be having some issues right now. Please clear the chat and try again.`,vt=()=>{const{baseSettings:m,aiChatSettings:h}=st.useInkeepConfig(),[r="",d]=ft.useControllableState({prop:h.conversationIdOverride,defaultProp:h.conversationIdOverride??""}),ke=Ye.useModal(),we=et.useOptionalChatBubble(),Te=tt.useOptionalSidebarChat(),{logEvent:v}=ct.useBaseEvents(),{setConversationId:Ee,emitToParent:E}=pt.useStreamingEvents(),Y=t.useRef(r);t.useEffect(()=>{const e=Y.current;Y.current=r,e!==r&&v({eventName:"chat_conversation_changed",properties:{conversationId:r,previousConversationId:e}})},[r,v]);const[C,M]=t.useState(""),Me=e=>M(e.target.value),{shouldBypassCaptcha:ee,filters:te,privacyPreferences:qe,userProperties:U}=m,{authToken:q,isLoading:Fe,refreshToken:se}=ot.useAuthToken(),re=!!m.getAuthToken,f=!!q,{onInputMessageChange:xe,filters:ne,baseUrl:F,agentUrl:Pe,context:ae,headers:oe,appId:R,apiKey:y,files:S}=h,Ne=ke?.isOpen??we?.isOpen??Te?.isOpen??!0,{getCaptchaHeader:I,invalidate:c}=rt.useCaptcha({baseUrl:F,shouldBypassCaptcha:ee||!!y,shouldMakeInitialRequest:Ne}),ie=t.useRef(I);ie.current=I;const ue=Pe||`${F}/run/api/chat`,{sessionToken:D,refreshSession:L}=at.useAnonymousSession({baseUrl:F,appId:R,getCaptchaHeader:I,invalidateCaptcha:c,optOutAllAnalytics:qe?.optOutAllAnalytics,enabled:!f&&!Fe}),ce=y??(f?q:D),{loadConversation:le}=it.useConversationLoader({baseUrl:F,appId:R,authToken:ce,getCaptchaHeader:I,invalidateCaptcha:c,refreshSession:y||f?void 0:L}),[Oe,de]=t.useState(!1),$=t.useRef(null);$.current=D;const B=t.useRef(null);B.current=q;const z=t.useRef(void 0);z.current=U&&Object.keys(U).length>0?JSON.stringify(U):void 0;const b=t.useRef(0),fe=t.useRef(null),H=t.useRef(null),A=t.useRef(void 0),_e=S?.map(e=>`${e.filename??""}:${e.mediaType}:${e.url.length}:${e.url.slice(0,64)}:${e.url.slice(-32)}`).join(`
|
|
4
|
+
`)??"";t.useEffect(()=>{A.current=S?.length?S:void 0},[_e]);const pe=t.useRef(oe);pe.current=oe;const K=t.useRef(void 0);K.current=te||ne?JSON.stringify({...te,...ne}):void 0;const Ue=e=>{switch(e.code){case 400:return e.message;case 401:return re?"Authentication failed. Please try again.":_;case 403:return`There seems to be a configuration error. Please contact ${m.organizationDisplayName??"Administrator"}`;default:return _}},[x,j]=t.useState([]),De=t.useMemo(()=>new Xe.DefaultChatTransport({api:ue,headers:()=>{const e=y??B.current??$.current;return{"x-inkeep-client-timezone":Intl.DateTimeFormat().resolvedOptions().timeZone,"x-inkeep-client-timestamp":new Date().toISOString(),...R?{"x-inkeep-app-id":R}:{},...e?{Authorization:`Bearer ${e}`}:{},...K.current?{"inkeep-filters":K.current}:{},...z.current?{"x-inkeep-user-properties":z.current}:{},...pe.current}},prepareSendMessagesRequest:async e=>{const u=await ie.current(),s=e.messages[e.messages.length-1];return s||console.warn("[useInkeepChat] prepareSendMessagesRequest called with empty messages array"),{body:{...e.body,id:e.id,messages:s?[s]:[],trigger:e.trigger,messageId:e.messageId},headers:{...e.headers,...u}}},body:{requestContext:ae}}),[ue,ae,R,y]),{messages:P,sendMessage:J,addToolApprovalResponse:G,status:he,setMessages:g,stop:N,error:Q}=Ze.useChat({transport:De,onData(e){E(e.type,e.data)},async onFinish(){E("completion",{conversationId:r}),await v({eventName:"assistant_message_received",properties:{conversationId:r}}),v({eventName:"assistant_answer_displayed",properties:{conversationId:r}})},onError(e){console.error("onError",{code:e.code,message:e.message});const u=ee||y?null:mt(e);if(u!==null&&b.current<1){b.current++;const s=H.current,a=fe.current;(async()=>{if(u==="session"&&re){const n=await se();if(!n)throw new Error("Auth token refresh failed");B.current=n}else if(u==="session"){const n=await L();n&&($.current=n)}else c();if(s){G(s);return}a&&(g(n=>{let o=[...n];return o.at(-1)?.role==="assistant"&&(o=o.slice(0,-1)),o.at(-1)?.role==="user"&&(o=o.slice(0,-1)),o}),J(a.files?.length?{parts:[{type:"text",text:a.content},...a.files]}:{text:a.content},{body:a.body}))})().catch(()=>{b.current=0,c(),g(n=>{const o=[...n],p=o[o.length-1];if(!p)return o;const l=_;return p.role==="user"?o.push({id:O.generateUid(16),role:"assistant",parts:[{type:"text",text:l}]}):p.parts=[{type:"text",text:l}],o})});return}b.current=0,u!==null&&c(),v({eventName:"chat_error",properties:{conversationId:r,error:e.message}}),g(s=>{const a=[...s],i=a[a.length-1];if(i){const n=Ue(e);i.role==="user"?a.push({id:O.generateUid(16),role:"assistant",parts:[{type:"text",text:n}]}):i.parts=[{type:"text",text:n}]}return a})}}),ge=t.useRef(f);t.useEffect(()=>{const e=ge.current;ge.current=f,e!==f&&(N(),k(null),g([]),d(""),M(""),j([]),c())},[f,N,g,d,c]);const me=he==="submitted",W=he==="streaming",Le=t.useMemo(()=>{const e=i=>{if(!i||typeof i!="object")return!1;const n=i;return typeof n.type=="string"&&n.type.startsWith("tool-")},s=[...P??[]].reverse().find(i=>i.role==="assistant");if(!s)return!1;const a=s.parts?.at(-1);return!(!e(a)||a.state!=="output-available"||!a.approval?.id||W)},[P,W]),ve=W||Le,ye=me||ve,$e=P.length===0,V=!C.trim()&&x.length===0||ye,Be=nt.useMediaQuery("(max-width: 768px)"),[ze,k]=t.useState(null);t.useEffect(()=>{Q&&k(Q)},[Q]);const He=()=>k(null),{inputNotification:Ke,showInputNotification:be,clearInputNotification:je}=gt.useInputNotification(),Ce=t.useRef(null);t.useEffect(()=>{xe?.(C)},[C]);const Je=e=>{e.key==="Enter"&&!e.shiftKey&&!V&&!e.nativeEvent.isComposing&&(e.preventDefault(),Z())},Z=async(e=C)=>{if(V&&(!e||e.trim().length===0)&&x.length===0)return;const u=x;j([]),M(""),b.current=0,H.current=null,await v({eventName:"user_message_submitted",properties:{conversationId:r}});let s=r;s||(s=`conv_${O.generateUid(16)}`,d(s)),Ee(s);const a=A.current;A.current=void 0;let i,n;if(a?.length){let o;try{o=await Promise.all(u.map(p=>{const l=Ae.normalizeFileType(p);return new Promise((Ve,Ie)=>{const T=new FileReader;T.onload=()=>{if(typeof T.result!="string"){Ie(new Error(`Failed to read file "${l.name}"`));return}Ve({type:"file",url:T.result,mediaType:l.type,filename:l.name})},T.onerror=()=>Ie(new Error(`Failed to read file "${l.name}"`)),T.readAsDataURL(l)})}))}catch{be({title:"Failed to attach files",message:"Could not read one or more files. Please try again."});return}n=[...o,...a],i=e.trim()?{parts:[{type:"text",text:e},...n]}:{parts:n}}else if(u.length>0){const o=new DataTransfer;for(const l of u)o.items.add(Ae.normalizeFileType(l));const p=o.files;i=e.trim()?{text:e,files:p}:{files:p}}else i={text:e};fe.current={content:e,body:{conversationId:s},files:n},J(i,{body:{conversationId:s}})},Ge=t.useCallback(e=>{b.current=0,H.current=e,G(e)},[G]),X=t.useCallback(()=>{N().then(()=>{E("aborted",{conversationId:r})})},[N,r,E]),Re=()=>{He(),g([]),d(""),c(),A.current=S?.length?S:void 0,v({eventName:"chat_clear_button_clicked",properties:{conversationId:r}})},w=t.useCallback((e,u)=>{k(null),g(u),d(e),c(),A.current=void 0},[g,d,c]),Se=t.useCallback(async(e,u)=>{X(),w(e,[]),de(!0);try{const s=await le(e,u);if(s===null)return!1;const i=s[s.length-1]?.role==="user"?[...s,{id:O.generateUid(16),role:"assistant",parts:[{type:"text",text:"This session was interrupted. Please check back in a few minutes or start a new conversation."}]}]:s;return w(e,i),!0}finally{u?.aborted||de(!1)}},[w,le,X]);ut.useInitialConversation({conversationId:h.conversationId,effectiveAuthToken:ce,loadAndRestoreSession:Se,onLoadFailed:()=>w("",[])});const{openForm:Qe}=lt.useChatForm(),We=dt.useWidget();return t.useImperativeHandle(h.chatFunctionsRef,()=>({submitMessage:Z,updateInputMessage(e){M(e)},clearChat:Re,openForm:e=>{We?.setView("chat"),Qe(e,void 0)},focusInput:()=>{Ce.current?.focus()}})),{messages:P,sendMessage:J,addToolApprovalResponse:Ge,isLoading:me,isStreaming:ve,isBusy:ye,error:ze,setError:k,isSubmitDisabled:V,input:C,handleInputChange:Me,handleInputKeyDown:Je,handleSubmit:Z,stop:X,clear:Re,inputRef:Ce,isMobile:Be,files:x,setFiles:j,isNewChat:$e,conversationId:r,restoreSession:w,loadAndRestoreSession:Se,isSessionLoading:Oe,authToken:f?q:D,refreshSession:f?se:L,getCaptchaHeader:I,invalidateCaptcha:c,inputNotification:Ke,showInputNotification:be,clearInputNotification:je}};exports.DEFAULT_ERROR_MESSAGE=_;exports.useInkeepChat=vt;
|
|
@@ -47,7 +47,7 @@ export declare const useInkeepChat: () => {
|
|
|
47
47
|
isNewChat: boolean;
|
|
48
48
|
conversationId: string;
|
|
49
49
|
restoreSession: (sessionId: string, loadedMessages: Message[]) => void;
|
|
50
|
-
loadAndRestoreSession: (sessionId: string, signal?: AbortSignal) => Promise<
|
|
50
|
+
loadAndRestoreSession: (sessionId: string, signal?: AbortSignal) => Promise<boolean>;
|
|
51
51
|
isSessionLoading: boolean;
|
|
52
52
|
authToken: string | null;
|
|
53
53
|
refreshSession: () => Promise<string | null>;
|
|
@@ -1,122 +1,123 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useChat as
|
|
3
|
-
import { normalizeFileType as
|
|
4
|
-
import { DefaultChatTransport as
|
|
5
|
-
import { useRef as l, useEffect as
|
|
6
|
-
import { useModal as
|
|
7
|
-
import { useOptionalChatBubble as
|
|
8
|
-
import { useOptionalSidebarChat as
|
|
9
|
-
import { useInkeepConfig as
|
|
10
|
-
import { useCaptcha as
|
|
11
|
-
import { useMediaQuery as
|
|
12
|
-
import { useAnonymousSession as
|
|
13
|
-
import { useAuthToken as
|
|
14
|
-
import { useConversationLoader as
|
|
2
|
+
import { useChat as tt } from "@ai-sdk/react";
|
|
3
|
+
import { normalizeFileType as ke } from "./file-upload-input.js";
|
|
4
|
+
import { DefaultChatTransport as st } from "ai";
|
|
5
|
+
import { useRef as l, useEffect as x, useState as D, useMemo as xe, useCallback as $, useImperativeHandle as rt } from "react";
|
|
6
|
+
import { useModal as nt } from "../modal/modal-provider.js";
|
|
7
|
+
import { useOptionalChatBubble as ot } from "../../providers/chat-bubble-provider.js";
|
|
8
|
+
import { useOptionalSidebarChat as at } from "../../providers/sidebar-chat-provider.js";
|
|
9
|
+
import { useInkeepConfig as it } from "../../providers/config-provider.js";
|
|
10
|
+
import { useCaptcha as lt } from "./use-captcha.js";
|
|
11
|
+
import { useMediaQuery as ct } from "../../hooks/use-media-query.js";
|
|
12
|
+
import { useAnonymousSession as ut } from "../../hooks/use-anonymous-session.js";
|
|
13
|
+
import { useAuthToken as pt } from "../../hooks/use-auth-token.js";
|
|
14
|
+
import { useConversationLoader as dt } from "../../hooks/use-conversation-loader.js";
|
|
15
|
+
import { useInitialConversation as ft } from "../../hooks/use-initial-conversation.js";
|
|
15
16
|
import { generateUid as L } from "../../utils/generate-uid.js";
|
|
16
|
-
import { useBaseEvents as
|
|
17
|
-
import { useChatForm as
|
|
18
|
-
import { useWidget as
|
|
19
|
-
import { useControllableState as
|
|
20
|
-
import { useStreamingEvents as
|
|
21
|
-
import { parseAuthError as
|
|
22
|
-
import { useInputNotification as
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
let s = Number(
|
|
17
|
+
import { useBaseEvents as mt } from "../../providers/base-events-provider.js";
|
|
18
|
+
import { useChatForm as ht } from "../../providers/chat-form-provider.js";
|
|
19
|
+
import { useWidget as gt } from "../../providers/widget-provider.js";
|
|
20
|
+
import { useControllableState as vt } from "@radix-ui/react-use-controllable-state";
|
|
21
|
+
import { useStreamingEvents as yt } from "../../hooks/use-streaming-events.js";
|
|
22
|
+
import { parseAuthError as bt } from "../../hooks/use-inkeep-api-client.js";
|
|
23
|
+
import { useInputNotification as Ct } from "../../hooks/use-input-notification.js";
|
|
24
|
+
function It(g) {
|
|
25
|
+
const m = g.message ?? "";
|
|
26
|
+
let s = Number(g.code) || Number(g.statusCode);
|
|
26
27
|
if (Number.isNaN(s))
|
|
27
28
|
try {
|
|
28
|
-
s = Number(JSON.parse(
|
|
29
|
+
s = Number(JSON.parse(m).status);
|
|
29
30
|
} catch {
|
|
30
31
|
}
|
|
31
|
-
const p =
|
|
32
|
+
const p = bt(s, { detail: m });
|
|
32
33
|
return p !== null ? p : s === 401 ? "session" : null;
|
|
33
34
|
}
|
|
34
35
|
const te = `Hmm..
|
|
35
36
|
|
|
36
|
-
It seems I might be having some issues right now. Please clear the chat and try again.`,
|
|
37
|
-
const { baseSettings:
|
|
38
|
-
prop:
|
|
39
|
-
defaultProp:
|
|
40
|
-
}),
|
|
41
|
-
|
|
37
|
+
It seems I might be having some issues right now. Please clear the chat and try again.`, Kt = () => {
|
|
38
|
+
const { baseSettings: g, aiChatSettings: m } = it(), [s = "", p] = vt({
|
|
39
|
+
prop: m.conversationIdOverride,
|
|
40
|
+
defaultProp: m.conversationIdOverride ?? ""
|
|
41
|
+
}), Me = nt(), Fe = ot(), Ee = at(), { logEvent: v } = mt(), { setConversationId: Ne, emitToParent: M } = yt(), se = l(s);
|
|
42
|
+
x(() => {
|
|
42
43
|
const e = se.current;
|
|
43
|
-
se.current = s, e !== s &&
|
|
44
|
+
se.current = s, e !== s && v({
|
|
44
45
|
eventName: "chat_conversation_changed",
|
|
45
46
|
properties: {
|
|
46
47
|
conversationId: s,
|
|
47
48
|
previousConversationId: e
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
|
-
}, [s,
|
|
51
|
-
const [C,
|
|
52
|
-
onInputMessageChange:
|
|
51
|
+
}, [s, v]);
|
|
52
|
+
const [C, F] = D(""), Oe = (e) => F(e.target.value), { shouldBypassCaptcha: re, filters: ne, privacyPreferences: Pe, userProperties: B } = g, { authToken: E, isLoading: _e, refreshToken: oe } = pt(), ae = !!g.getAuthToken, d = !!E, {
|
|
53
|
+
onInputMessageChange: De,
|
|
53
54
|
filters: ie,
|
|
54
|
-
baseUrl:
|
|
55
|
-
agentUrl:
|
|
55
|
+
baseUrl: N,
|
|
56
|
+
agentUrl: $e,
|
|
56
57
|
context: le,
|
|
57
58
|
headers: ce,
|
|
58
|
-
appId:
|
|
59
|
-
apiKey:
|
|
60
|
-
files:
|
|
61
|
-
} =
|
|
62
|
-
baseUrl:
|
|
63
|
-
shouldBypassCaptcha: re || !!
|
|
64
|
-
shouldMakeInitialRequest:
|
|
65
|
-
}), ue = l(
|
|
66
|
-
ue.current =
|
|
67
|
-
const pe =
|
|
68
|
-
baseUrl:
|
|
69
|
-
appId:
|
|
70
|
-
getCaptchaHeader:
|
|
59
|
+
appId: I,
|
|
60
|
+
apiKey: y,
|
|
61
|
+
files: w
|
|
62
|
+
} = m, Le = Me?.isOpen ?? Fe?.isOpen ?? Ee?.isOpen ?? !0, { getCaptchaHeader: S, invalidate: c } = lt({
|
|
63
|
+
baseUrl: N,
|
|
64
|
+
shouldBypassCaptcha: re || !!y,
|
|
65
|
+
shouldMakeInitialRequest: Le
|
|
66
|
+
}), ue = l(S);
|
|
67
|
+
ue.current = S;
|
|
68
|
+
const pe = $e || `${N}/run/api/chat`, { sessionToken: U, refreshSession: z } = ut({
|
|
69
|
+
baseUrl: N,
|
|
70
|
+
appId: I,
|
|
71
|
+
getCaptchaHeader: S,
|
|
71
72
|
invalidateCaptcha: c,
|
|
72
|
-
optOutAllAnalytics:
|
|
73
|
-
enabled: !d && !
|
|
74
|
-
}), { loadConversation:
|
|
75
|
-
baseUrl:
|
|
76
|
-
appId:
|
|
77
|
-
authToken:
|
|
78
|
-
getCaptchaHeader:
|
|
73
|
+
optOutAllAnalytics: Pe?.optOutAllAnalytics,
|
|
74
|
+
enabled: !d && !_e
|
|
75
|
+
}), de = y ?? (d ? E : U), { loadConversation: fe } = dt({
|
|
76
|
+
baseUrl: N,
|
|
77
|
+
appId: I,
|
|
78
|
+
authToken: de,
|
|
79
|
+
getCaptchaHeader: S,
|
|
79
80
|
invalidateCaptcha: c,
|
|
80
|
-
refreshSession:
|
|
81
|
-
}), [
|
|
81
|
+
refreshSession: y || d ? void 0 : z
|
|
82
|
+
}), [Be, me] = D(!1), H = l(null);
|
|
82
83
|
H.current = U;
|
|
83
84
|
const q = l(null);
|
|
84
85
|
q.current = E;
|
|
85
86
|
const K = l(void 0);
|
|
86
87
|
K.current = B && Object.keys(B).length > 0 ? JSON.stringify(B) : void 0;
|
|
87
|
-
const b = l(0),
|
|
88
|
+
const b = l(0), he = l(null), J = l(null), A = l(void 0), Ue = w?.map((e) => `${e.filename ?? ""}:${e.mediaType}:${e.url.length}:${e.url.slice(0, 64)}:${e.url.slice(-32)}`).join(`
|
|
88
89
|
`) ?? "";
|
|
89
|
-
|
|
90
|
-
A.current =
|
|
91
|
-
}, [
|
|
92
|
-
const
|
|
93
|
-
|
|
90
|
+
x(() => {
|
|
91
|
+
A.current = w?.length ? w : void 0;
|
|
92
|
+
}, [Ue]);
|
|
93
|
+
const ge = l(ce);
|
|
94
|
+
ge.current = ce;
|
|
94
95
|
const j = l(void 0);
|
|
95
96
|
j.current = ne || ie ? JSON.stringify({ ...ne, ...ie }) : void 0;
|
|
96
|
-
const
|
|
97
|
+
const ze = (e) => {
|
|
97
98
|
switch (e.code) {
|
|
98
99
|
case 400:
|
|
99
100
|
return e.message;
|
|
100
101
|
case 401:
|
|
101
102
|
return ae ? "Authentication failed. Please try again." : te;
|
|
102
103
|
case 403:
|
|
103
|
-
return `There seems to be a configuration error. Please contact ${
|
|
104
|
+
return `There seems to be a configuration error. Please contact ${g.organizationDisplayName ?? "Administrator"}`;
|
|
104
105
|
default:
|
|
105
106
|
return te;
|
|
106
107
|
}
|
|
107
|
-
}, [
|
|
108
|
-
() => new
|
|
108
|
+
}, [O, W] = D([]), He = xe(
|
|
109
|
+
() => new st({
|
|
109
110
|
api: pe,
|
|
110
111
|
headers: () => {
|
|
111
|
-
const e =
|
|
112
|
+
const e = y ?? q.current ?? H.current;
|
|
112
113
|
return {
|
|
113
114
|
"x-inkeep-client-timezone": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
114
115
|
"x-inkeep-client-timestamp": (/* @__PURE__ */ new Date()).toISOString(),
|
|
115
|
-
...
|
|
116
|
+
...I ? { "x-inkeep-app-id": I } : {},
|
|
116
117
|
...e ? { Authorization: `Bearer ${e}` } : {},
|
|
117
118
|
...j.current ? { "inkeep-filters": j.current } : {},
|
|
118
119
|
...K.current ? { "x-inkeep-user-properties": K.current } : {},
|
|
119
|
-
...
|
|
120
|
+
...ge.current
|
|
120
121
|
};
|
|
121
122
|
},
|
|
122
123
|
prepareSendMessagesRequest: async (e) => {
|
|
@@ -139,27 +140,27 @@ It seems I might be having some issues right now. Please clear the chat and try
|
|
|
139
140
|
requestContext: le
|
|
140
141
|
}
|
|
141
142
|
}),
|
|
142
|
-
[pe, le,
|
|
143
|
+
[pe, le, I, y]
|
|
143
144
|
), {
|
|
144
145
|
messages: P,
|
|
145
146
|
sendMessage: G,
|
|
146
147
|
addToolApprovalResponse: Q,
|
|
147
|
-
status:
|
|
148
|
-
setMessages:
|
|
149
|
-
stop:
|
|
148
|
+
status: ve,
|
|
149
|
+
setMessages: h,
|
|
150
|
+
stop: _,
|
|
150
151
|
error: V
|
|
151
|
-
} =
|
|
152
|
-
transport:
|
|
152
|
+
} = tt({
|
|
153
|
+
transport: He,
|
|
153
154
|
onData(e) {
|
|
154
|
-
|
|
155
|
+
M(e.type, e.data);
|
|
155
156
|
},
|
|
156
157
|
async onFinish() {
|
|
157
|
-
|
|
158
|
+
M("completion", { conversationId: s }), await v({
|
|
158
159
|
eventName: "assistant_message_received",
|
|
159
160
|
properties: {
|
|
160
161
|
conversationId: s
|
|
161
162
|
}
|
|
162
|
-
}),
|
|
163
|
+
}), v({
|
|
163
164
|
eventName: "assistant_answer_displayed",
|
|
164
165
|
properties: {
|
|
165
166
|
conversationId: s
|
|
@@ -168,34 +169,34 @@ It seems I might be having some issues right now. Please clear the chat and try
|
|
|
168
169
|
},
|
|
169
170
|
onError(e) {
|
|
170
171
|
console.error("onError", { code: e.code, message: e.message });
|
|
171
|
-
const i = re ||
|
|
172
|
+
const i = re || y ? null : It(e);
|
|
172
173
|
if (i !== null && b.current < 1) {
|
|
173
174
|
b.current++;
|
|
174
|
-
const t = J.current,
|
|
175
|
+
const t = J.current, n = he.current;
|
|
175
176
|
(async () => {
|
|
176
177
|
if (i === "session" && ae) {
|
|
177
|
-
const
|
|
178
|
-
if (!
|
|
179
|
-
q.current =
|
|
178
|
+
const r = await oe();
|
|
179
|
+
if (!r) throw new Error("Auth token refresh failed");
|
|
180
|
+
q.current = r;
|
|
180
181
|
} else if (i === "session") {
|
|
181
|
-
const
|
|
182
|
-
|
|
182
|
+
const r = await z();
|
|
183
|
+
r && (H.current = r);
|
|
183
184
|
} else
|
|
184
185
|
c();
|
|
185
186
|
if (t) {
|
|
186
187
|
Q(t);
|
|
187
188
|
return;
|
|
188
189
|
}
|
|
189
|
-
|
|
190
|
-
let o = [...
|
|
190
|
+
n && (h((r) => {
|
|
191
|
+
let o = [...r];
|
|
191
192
|
return o.at(-1)?.role === "assistant" && (o = o.slice(0, -1)), o.at(-1)?.role === "user" && (o = o.slice(0, -1)), o;
|
|
192
193
|
}), G(
|
|
193
|
-
|
|
194
|
-
{ body:
|
|
194
|
+
n.files?.length ? { parts: [{ type: "text", text: n.content }, ...n.files] } : { text: n.content },
|
|
195
|
+
{ body: n.body }
|
|
195
196
|
));
|
|
196
197
|
})().catch(() => {
|
|
197
|
-
b.current = 0, c(),
|
|
198
|
-
const o = [...
|
|
198
|
+
b.current = 0, c(), h((r) => {
|
|
199
|
+
const o = [...r], f = o[o.length - 1];
|
|
199
200
|
if (!f) return o;
|
|
200
201
|
const u = te;
|
|
201
202
|
return f.role === "user" ? o.push({
|
|
@@ -207,83 +208,83 @@ It seems I might be having some issues right now. Please clear the chat and try
|
|
|
207
208
|
});
|
|
208
209
|
return;
|
|
209
210
|
}
|
|
210
|
-
b.current = 0, i !== null && c(),
|
|
211
|
+
b.current = 0, i !== null && c(), v({
|
|
211
212
|
eventName: "chat_error",
|
|
212
213
|
properties: {
|
|
213
214
|
conversationId: s,
|
|
214
215
|
error: e.message
|
|
215
216
|
}
|
|
216
|
-
}),
|
|
217
|
-
const
|
|
217
|
+
}), h((t) => {
|
|
218
|
+
const n = [...t], a = n[n.length - 1];
|
|
218
219
|
if (a) {
|
|
219
|
-
const
|
|
220
|
-
a.role === "user" ?
|
|
220
|
+
const r = ze(e);
|
|
221
|
+
a.role === "user" ? n.push({
|
|
221
222
|
id: L(16),
|
|
222
223
|
role: "assistant",
|
|
223
|
-
parts: [{ type: "text", text:
|
|
224
|
-
}) : a.parts = [{ type: "text", text:
|
|
224
|
+
parts: [{ type: "text", text: r }]
|
|
225
|
+
}) : a.parts = [{ type: "text", text: r }];
|
|
225
226
|
}
|
|
226
|
-
return
|
|
227
|
+
return n;
|
|
227
228
|
});
|
|
228
229
|
}
|
|
229
230
|
}), ye = l(d);
|
|
230
|
-
|
|
231
|
+
x(() => {
|
|
231
232
|
const e = ye.current;
|
|
232
|
-
ye.current = d, e !== d && (
|
|
233
|
-
}, [d,
|
|
234
|
-
const
|
|
233
|
+
ye.current = d, e !== d && (_(), T(null), h([]), p(""), F(""), W([]), c());
|
|
234
|
+
}, [d, _, h, p, c]);
|
|
235
|
+
const be = ve === "submitted", Z = ve === "streaming", qe = xe(() => {
|
|
235
236
|
const e = (a) => {
|
|
236
237
|
if (!a || typeof a != "object") return !1;
|
|
237
|
-
const
|
|
238
|
-
return typeof
|
|
238
|
+
const r = a;
|
|
239
|
+
return typeof r.type == "string" && r.type.startsWith("tool-");
|
|
239
240
|
}, t = [...P ?? []].reverse().find((a) => a.role === "assistant");
|
|
240
241
|
if (!t) return !1;
|
|
241
|
-
const
|
|
242
|
-
return !(!e(
|
|
243
|
-
}, [P, Z]),
|
|
244
|
-
|
|
245
|
-
V &&
|
|
242
|
+
const n = t.parts?.at(-1);
|
|
243
|
+
return !(!e(n) || n.state !== "output-available" || !n.approval?.id || Z);
|
|
244
|
+
}, [P, Z]), Ce = Z || qe, Ie = be || Ce, Ke = P.length === 0, X = !C.trim() && O.length === 0 || Ie, Je = ct("(max-width: 768px)"), [je, T] = D(null);
|
|
245
|
+
x(() => {
|
|
246
|
+
V && T(V);
|
|
246
247
|
}, [V]);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
const We = () => T(null), { inputNotification: Ge, showInputNotification: we, clearInputNotification: Qe } = Ct(), Se = l(null);
|
|
249
|
+
x(() => {
|
|
250
|
+
De?.(C);
|
|
250
251
|
}, [C]);
|
|
251
|
-
const
|
|
252
|
+
const Ve = (e) => {
|
|
252
253
|
e.key === "Enter" && !e.shiftKey && !X && !e.nativeEvent.isComposing && (e.preventDefault(), Y());
|
|
253
254
|
}, Y = async (e = C) => {
|
|
254
|
-
if (X && (!e || e.trim().length === 0) &&
|
|
255
|
-
const i =
|
|
256
|
-
W([]),
|
|
255
|
+
if (X && (!e || e.trim().length === 0) && O.length === 0) return;
|
|
256
|
+
const i = O;
|
|
257
|
+
W([]), F(""), b.current = 0, J.current = null, await v({
|
|
257
258
|
eventName: "user_message_submitted",
|
|
258
259
|
properties: {
|
|
259
260
|
conversationId: s
|
|
260
261
|
}
|
|
261
262
|
});
|
|
262
263
|
let t = s;
|
|
263
|
-
t || (t = `conv_${L(16)}`, p(t)),
|
|
264
|
-
const
|
|
264
|
+
t || (t = `conv_${L(16)}`, p(t)), Ne(t);
|
|
265
|
+
const n = A.current;
|
|
265
266
|
A.current = void 0;
|
|
266
|
-
let a,
|
|
267
|
-
if (
|
|
267
|
+
let a, r;
|
|
268
|
+
if (n?.length) {
|
|
268
269
|
let o;
|
|
269
270
|
try {
|
|
270
271
|
o = await Promise.all(
|
|
271
272
|
i.map((f) => {
|
|
272
|
-
const u =
|
|
273
|
-
return new Promise((
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
if (typeof
|
|
277
|
-
|
|
273
|
+
const u = ke(f);
|
|
274
|
+
return new Promise((et, Re) => {
|
|
275
|
+
const k = new FileReader();
|
|
276
|
+
k.onload = () => {
|
|
277
|
+
if (typeof k.result != "string") {
|
|
278
|
+
Re(new Error(`Failed to read file "${u.name}"`));
|
|
278
279
|
return;
|
|
279
280
|
}
|
|
280
|
-
|
|
281
|
+
et({
|
|
281
282
|
type: "file",
|
|
282
|
-
url:
|
|
283
|
+
url: k.result,
|
|
283
284
|
mediaType: u.type,
|
|
284
285
|
filename: u.name
|
|
285
286
|
});
|
|
286
|
-
},
|
|
287
|
+
}, k.onerror = () => Re(new Error(`Failed to read file "${u.name}"`)), k.readAsDataURL(u);
|
|
287
288
|
});
|
|
288
289
|
})
|
|
289
290
|
);
|
|
@@ -294,66 +295,75 @@ It seems I might be having some issues right now. Please clear the chat and try
|
|
|
294
295
|
});
|
|
295
296
|
return;
|
|
296
297
|
}
|
|
297
|
-
|
|
298
|
+
r = [...o, ...n], a = e.trim() ? { parts: [{ type: "text", text: e }, ...r] } : { parts: r };
|
|
298
299
|
} else if (i.length > 0) {
|
|
299
300
|
const o = new DataTransfer();
|
|
300
|
-
for (const u of i) o.items.add(
|
|
301
|
+
for (const u of i) o.items.add(ke(u));
|
|
301
302
|
const f = o.files;
|
|
302
303
|
a = e.trim() ? { text: e, files: f } : { files: f };
|
|
303
304
|
} else
|
|
304
305
|
a = { text: e };
|
|
305
|
-
|
|
306
|
+
he.current = {
|
|
306
307
|
content: e,
|
|
307
308
|
body: { conversationId: t },
|
|
308
|
-
files:
|
|
309
|
+
files: r
|
|
309
310
|
}, G(a, {
|
|
310
311
|
body: { conversationId: t }
|
|
311
312
|
});
|
|
312
|
-
},
|
|
313
|
+
}, Ze = $(
|
|
313
314
|
(e) => {
|
|
314
315
|
b.current = 0, J.current = e, Q(e);
|
|
315
316
|
},
|
|
316
317
|
[Q]
|
|
317
318
|
), ee = $(() => {
|
|
318
|
-
|
|
319
|
-
|
|
319
|
+
_().then(() => {
|
|
320
|
+
M("aborted", { conversationId: s });
|
|
320
321
|
});
|
|
321
|
-
}, [
|
|
322
|
-
|
|
322
|
+
}, [_, s, M]), Ae = () => {
|
|
323
|
+
We(), h([]), p(""), c(), A.current = w?.length ? w : void 0, v({
|
|
323
324
|
eventName: "chat_clear_button_clicked",
|
|
324
325
|
properties: {
|
|
325
326
|
conversationId: s
|
|
326
327
|
}
|
|
327
328
|
});
|
|
328
|
-
},
|
|
329
|
+
}, R = $(
|
|
329
330
|
(e, i) => {
|
|
330
|
-
|
|
331
|
+
T(null), h(i), p(e), c(), A.current = void 0;
|
|
331
332
|
},
|
|
332
|
-
[
|
|
333
|
-
),
|
|
333
|
+
[h, p, c]
|
|
334
|
+
), Te = $(
|
|
334
335
|
async (e, i) => {
|
|
335
|
-
ee(),
|
|
336
|
+
ee(), R(e, []), me(!0);
|
|
336
337
|
try {
|
|
337
|
-
const t = await
|
|
338
|
+
const t = await fe(e, i);
|
|
339
|
+
if (t === null) return !1;
|
|
340
|
+
const a = t[t.length - 1]?.role === "user" ? [...t, {
|
|
338
341
|
id: L(16),
|
|
339
342
|
role: "assistant",
|
|
340
343
|
parts: [{ type: "text", text: "This session was interrupted. Please check back in a few minutes or start a new conversation." }]
|
|
341
344
|
}] : t;
|
|
342
|
-
|
|
345
|
+
return R(e, a), !0;
|
|
343
346
|
} finally {
|
|
344
|
-
i?.aborted ||
|
|
347
|
+
i?.aborted || me(!1);
|
|
345
348
|
}
|
|
346
349
|
},
|
|
347
|
-
[
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
+
[R, fe, ee]
|
|
351
|
+
);
|
|
352
|
+
ft({
|
|
353
|
+
conversationId: m.conversationId,
|
|
354
|
+
effectiveAuthToken: de,
|
|
355
|
+
loadAndRestoreSession: Te,
|
|
356
|
+
onLoadFailed: () => R("", [])
|
|
357
|
+
});
|
|
358
|
+
const { openForm: Xe } = ht(), Ye = gt();
|
|
359
|
+
return rt(m.chatFunctionsRef, () => ({
|
|
350
360
|
submitMessage: Y,
|
|
351
361
|
updateInputMessage(e) {
|
|
352
|
-
|
|
362
|
+
F(e);
|
|
353
363
|
},
|
|
354
|
-
clearChat:
|
|
364
|
+
clearChat: Ae,
|
|
355
365
|
openForm: (e) => {
|
|
356
|
-
|
|
366
|
+
Ye?.setView("chat"), Xe(e, void 0);
|
|
357
367
|
},
|
|
358
368
|
focusInput: () => {
|
|
359
369
|
Se.current?.focus();
|
|
@@ -361,38 +371,38 @@ It seems I might be having some issues right now. Please clear the chat and try
|
|
|
361
371
|
})), {
|
|
362
372
|
messages: P,
|
|
363
373
|
sendMessage: G,
|
|
364
|
-
addToolApprovalResponse:
|
|
365
|
-
isLoading:
|
|
366
|
-
isStreaming:
|
|
367
|
-
isBusy:
|
|
368
|
-
error:
|
|
369
|
-
setError:
|
|
374
|
+
addToolApprovalResponse: Ze,
|
|
375
|
+
isLoading: be,
|
|
376
|
+
isStreaming: Ce,
|
|
377
|
+
isBusy: Ie,
|
|
378
|
+
error: je,
|
|
379
|
+
setError: T,
|
|
370
380
|
isSubmitDisabled: X,
|
|
371
381
|
input: C,
|
|
372
|
-
handleInputChange:
|
|
373
|
-
handleInputKeyDown:
|
|
382
|
+
handleInputChange: Oe,
|
|
383
|
+
handleInputKeyDown: Ve,
|
|
374
384
|
handleSubmit: Y,
|
|
375
385
|
stop: ee,
|
|
376
|
-
clear:
|
|
386
|
+
clear: Ae,
|
|
377
387
|
inputRef: Se,
|
|
378
|
-
isMobile:
|
|
379
|
-
files:
|
|
388
|
+
isMobile: Je,
|
|
389
|
+
files: O,
|
|
380
390
|
setFiles: W,
|
|
381
|
-
isNewChat:
|
|
391
|
+
isNewChat: Ke,
|
|
382
392
|
conversationId: s,
|
|
383
|
-
restoreSession:
|
|
384
|
-
loadAndRestoreSession:
|
|
385
|
-
isSessionLoading:
|
|
393
|
+
restoreSession: R,
|
|
394
|
+
loadAndRestoreSession: Te,
|
|
395
|
+
isSessionLoading: Be,
|
|
386
396
|
authToken: d ? E : U,
|
|
387
397
|
refreshSession: d ? oe : z,
|
|
388
|
-
getCaptchaHeader:
|
|
398
|
+
getCaptchaHeader: S,
|
|
389
399
|
invalidateCaptcha: c,
|
|
390
|
-
inputNotification:
|
|
400
|
+
inputNotification: Ge,
|
|
391
401
|
showInputNotification: we,
|
|
392
|
-
clearInputNotification:
|
|
402
|
+
clearInputNotification: Qe
|
|
393
403
|
};
|
|
394
404
|
};
|
|
395
405
|
export {
|
|
396
406
|
te as DEFAULT_ERROR_MESSAGE,
|
|
397
|
-
|
|
407
|
+
Kt as useInkeepChat
|
|
398
408
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react"),f=({conversationId:e,effectiveAuthToken:s,loadAndRestoreSession:u,onLoadFailed:l})=>{const r=o.useRef(null),c=o.useRef(u);c.current=u;const a=o.useRef(l);a.current=l,o.useEffect(()=>{if(!e||!s||r.current===e)return;r.current=e;const t=new AbortController,i=n=>{r.current=null,console.error("[useInitialConversation] failed to load conversation:",e,n),a.current?.()};return(async()=>{try{!await c.current(e,t.signal)&&!t.signal.aborted&&i()}catch(n){t.signal.aborted||i(n)}})(),()=>{r.current=null,t.abort()}},[e,s])};exports.useInitialConversation=f;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface UseInitialConversationOptions {
|
|
2
|
+
/** The conversationId provided via props to load and restore. */
|
|
3
|
+
conversationId: string | undefined;
|
|
4
|
+
/** Resolved auth token — fetch is skipped until one is available. */
|
|
5
|
+
effectiveAuthToken: string | null;
|
|
6
|
+
/** Returns true if the conversation was loaded successfully, false otherwise. */
|
|
7
|
+
loadAndRestoreSession: (sessionId: string, signal?: AbortSignal) => Promise<boolean>;
|
|
8
|
+
/** Called when the load fails (404 or other error) — use to reset conversation state. */
|
|
9
|
+
onLoadFailed?: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads and restores a conversation by ID once an auth token is available.
|
|
13
|
+
* Calls `onLoadFailed` if the conversation is not found or the fetch fails.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useInitialConversation: ({ conversationId, effectiveAuthToken, loadAndRestoreSession, onLoadFailed, }: UseInitialConversationOptions) => void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRef as o, useEffect as f } from "react";
|
|
3
|
+
const C = ({
|
|
4
|
+
conversationId: r,
|
|
5
|
+
effectiveAuthToken: l,
|
|
6
|
+
loadAndRestoreSession: s,
|
|
7
|
+
onLoadFailed: c
|
|
8
|
+
}) => {
|
|
9
|
+
const t = o(null), u = o(s);
|
|
10
|
+
u.current = s;
|
|
11
|
+
const a = o(c);
|
|
12
|
+
a.current = c, f(() => {
|
|
13
|
+
if (!r || !l || t.current === r) return;
|
|
14
|
+
t.current = r;
|
|
15
|
+
const e = new AbortController(), i = (n) => {
|
|
16
|
+
t.current = null, console.error("[useInitialConversation] failed to load conversation:", r, n), a.current?.();
|
|
17
|
+
};
|
|
18
|
+
return (async () => {
|
|
19
|
+
try {
|
|
20
|
+
!await u.current(r, e.signal) && !e.signal.aborted && i();
|
|
21
|
+
} catch (n) {
|
|
22
|
+
e.signal.aborted || i(n);
|
|
23
|
+
}
|
|
24
|
+
})(), () => {
|
|
25
|
+
t.current = null, e.abort();
|
|
26
|
+
};
|
|
27
|
+
}, [r, l]);
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
C as useInitialConversation
|
|
31
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),t=require("react"),l=require("./config-provider.cjs"),a=t.createContext(void 0),p=({children:e})=>{const{baseSettings:s,componentType:n}=l.useInkeepConfig(),{tags:o,analyticsProperties:r}=s,i=t.useMemo(()=>({widgetLibraryVersion:"0.15.
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),t=require("react"),l=require("./config-provider.cjs"),a=t.createContext(void 0),p=({children:e})=>{const{baseSettings:s,componentType:n}=l.useInkeepConfig(),{tags:o,analyticsProperties:r}=s,i=t.useMemo(()=>({widgetLibraryVersion:"0.15.28",componentType:n,tags:o}),[n,o]),u={logEvent:t.useCallback(async c=>{const v={...i,...c.properties,...r},d={eventName:c.eventName,properties:v};return s.onEvent?.(d)},[s,i,r])};return E.jsx(a.Provider,{value:u,children:e})},g=()=>{const e=t.useContext(a);if(!e)throw new Error("useBaseEvents must be used within a BaseEventsProvider");return e};exports.BaseEventsProvider=p;exports.useBaseEvents=g;
|
|
@@ -5,7 +5,7 @@ import { useInkeepConfig as g } from "./config-provider.js";
|
|
|
5
5
|
const a = d(void 0), P = ({ children: e }) => {
|
|
6
6
|
const { baseSettings: t, componentType: o } = g(), { tags: s, analyticsProperties: n } = t, r = u(
|
|
7
7
|
() => ({
|
|
8
|
-
widgetLibraryVersion: "0.15.
|
|
8
|
+
widgetLibraryVersion: "0.15.28",
|
|
9
9
|
componentType: o,
|
|
10
10
|
tags: s
|
|
11
11
|
}),
|
|
@@ -130,8 +130,15 @@ export interface InkeepAIChatSettings {
|
|
|
130
130
|
/**
|
|
131
131
|
* Unique identifier for loading a specific conversation.
|
|
132
132
|
* Use this to restore a previous conversation state.
|
|
133
|
+
* If the conversation is not found, a new conversation is started.
|
|
133
134
|
*/
|
|
134
135
|
conversationId?: string;
|
|
136
|
+
/**
|
|
137
|
+
* @deprecated Internal use only. Bypasses conversation loading and sets the
|
|
138
|
+
* conversation ID directly without fetching. Use `conversationId` instead to
|
|
139
|
+
* restore a previous conversation state.
|
|
140
|
+
*/
|
|
141
|
+
conversationIdOverride?: string;
|
|
135
142
|
/**
|
|
136
143
|
* When enabled, prevents users from sending new messages.
|
|
137
144
|
* Useful for displaying archived or shared chat sessions.
|