@27works/chat-core 0.1.0
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/README.md +776 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/components/index.js +609 -0
- package/dist/contexts/index.d.ts +11 -0
- package/dist/contexts/index.js +175 -0
- package/dist/hooks/index.d.ts +76 -0
- package/dist/hooks/index.js +406 -0
- package/dist/server/index.d.ts +227 -0
- package/dist/server/index.js +15561 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +0 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +56 -0
- package/package.json +72 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare function ChatSessionProvider({ children, ...props }: {
|
|
2
|
+
[x: string]: any;
|
|
3
|
+
children: any;
|
|
4
|
+
}): any;
|
|
5
|
+
|
|
6
|
+
declare function MessageList({ renderMessage, renderThinking, renderStreamingIndicator }: {
|
|
7
|
+
renderMessage: any;
|
|
8
|
+
renderThinking: any;
|
|
9
|
+
renderStreamingIndicator: any;
|
|
10
|
+
}): any;
|
|
11
|
+
|
|
12
|
+
declare function UserInput({ renderInput }: {
|
|
13
|
+
renderInput: any;
|
|
14
|
+
}): any;
|
|
15
|
+
|
|
16
|
+
declare function useChatSession(): any;
|
|
17
|
+
|
|
18
|
+
export { ChatSessionProvider, MessageList, UserInput, useChatSession };
|
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
// src/components/ChatSessionProvider.js
|
|
2
|
+
import {
|
|
3
|
+
useState as useState5,
|
|
4
|
+
useRef as useRef5,
|
|
5
|
+
useCallback as useCallback3,
|
|
6
|
+
useEffect as useEffect4,
|
|
7
|
+
useMemo,
|
|
8
|
+
Suspense
|
|
9
|
+
} from "react";
|
|
10
|
+
import { useChat } from "@ai-sdk/react";
|
|
11
|
+
import { DefaultChatTransport, isToolUIPart } from "ai";
|
|
12
|
+
import { useSearchParams } from "next/navigation";
|
|
13
|
+
|
|
14
|
+
// src/components/SessionContext.js
|
|
15
|
+
import { createContext, useContext } from "react";
|
|
16
|
+
var SessionContext = createContext(null);
|
|
17
|
+
function useChatSession() {
|
|
18
|
+
const ctx = useContext(SessionContext);
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error("useChatSession must be used within a ChatSessionProvider");
|
|
21
|
+
}
|
|
22
|
+
return ctx;
|
|
23
|
+
}
|
|
24
|
+
var SessionContext_default = SessionContext;
|
|
25
|
+
|
|
26
|
+
// src/contexts/ChatContext.js
|
|
27
|
+
import { createContext as createContext2, useCallback, useContext as useContext2, useRef, useState } from "react";
|
|
28
|
+
import { jsx } from "react/jsx-runtime";
|
|
29
|
+
var ChatContext = createContext2({
|
|
30
|
+
triggerMessage: () => {
|
|
31
|
+
},
|
|
32
|
+
registerTriggerMessage: () => {
|
|
33
|
+
},
|
|
34
|
+
shareConversation: () => {
|
|
35
|
+
},
|
|
36
|
+
shareAnswer: () => {
|
|
37
|
+
},
|
|
38
|
+
canShare: false,
|
|
39
|
+
registerShareHandlers: () => {
|
|
40
|
+
},
|
|
41
|
+
chatControls: null,
|
|
42
|
+
registerChatControls: () => {
|
|
43
|
+
},
|
|
44
|
+
hasTransitioned: false,
|
|
45
|
+
setHasTransitioned: () => {
|
|
46
|
+
},
|
|
47
|
+
shareModalOpen: false,
|
|
48
|
+
openShareModal: () => {
|
|
49
|
+
},
|
|
50
|
+
closeShareModal: () => {
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
var useChatContext = () => {
|
|
54
|
+
const context = useContext2(ChatContext);
|
|
55
|
+
if (!context) {
|
|
56
|
+
return {
|
|
57
|
+
triggerMessage: () => {
|
|
58
|
+
},
|
|
59
|
+
registerTriggerMessage: () => {
|
|
60
|
+
},
|
|
61
|
+
shareConversation: () => {
|
|
62
|
+
},
|
|
63
|
+
shareAnswer: () => {
|
|
64
|
+
},
|
|
65
|
+
canShare: false,
|
|
66
|
+
registerShareHandlers: () => {
|
|
67
|
+
},
|
|
68
|
+
chatControls: null,
|
|
69
|
+
registerChatControls: () => {
|
|
70
|
+
},
|
|
71
|
+
hasTransitioned: false,
|
|
72
|
+
setHasTransitioned: () => {
|
|
73
|
+
},
|
|
74
|
+
shareModalOpen: false,
|
|
75
|
+
openShareModal: () => {
|
|
76
|
+
},
|
|
77
|
+
closeShareModal: () => {
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return context;
|
|
82
|
+
};
|
|
83
|
+
var ChatProvider = ({ children }) => {
|
|
84
|
+
const triggerMessageRef = useRef(() => {
|
|
85
|
+
});
|
|
86
|
+
const shareHandlersRef = useRef({
|
|
87
|
+
shareConversation: () => {
|
|
88
|
+
},
|
|
89
|
+
shareAnswer: () => {
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const [canShare, setCanShare] = useState(false);
|
|
93
|
+
const [messageCount, setMessageCount] = useState(0);
|
|
94
|
+
const [chatControls, setChatControls] = useState(null);
|
|
95
|
+
const [hasTransitioned, setHasTransitioned] = useState(false);
|
|
96
|
+
const [shareModalOpen, setShareModalOpen] = useState(false);
|
|
97
|
+
const registerTriggerMessage = (fn) => {
|
|
98
|
+
triggerMessageRef.current = fn;
|
|
99
|
+
};
|
|
100
|
+
const registerShareHandlers = useCallback((handlers) => {
|
|
101
|
+
shareHandlersRef.current = {
|
|
102
|
+
shareConversation: handlers.shareConversation,
|
|
103
|
+
shareAnswer: handlers.shareAnswer
|
|
104
|
+
};
|
|
105
|
+
setCanShare((prev) => {
|
|
106
|
+
const next = handlers.canShare || false;
|
|
107
|
+
return prev === next ? prev : next;
|
|
108
|
+
});
|
|
109
|
+
setMessageCount((prev) => {
|
|
110
|
+
const next = handlers.messageCount || 0;
|
|
111
|
+
return prev === next ? prev : next;
|
|
112
|
+
});
|
|
113
|
+
}, []);
|
|
114
|
+
const registerChatControls = useCallback((controls) => {
|
|
115
|
+
setChatControls((prev) => {
|
|
116
|
+
if (prev === controls) {
|
|
117
|
+
return prev;
|
|
118
|
+
}
|
|
119
|
+
if (prev === null && controls === null) {
|
|
120
|
+
return prev;
|
|
121
|
+
}
|
|
122
|
+
if (prev && controls && prev.firstQuestionId === controls.firstQuestionId && prev.onMakePublic === controls.onMakePublic) {
|
|
123
|
+
return prev;
|
|
124
|
+
}
|
|
125
|
+
return controls;
|
|
126
|
+
});
|
|
127
|
+
}, []);
|
|
128
|
+
const triggerMessage = (message) => triggerMessageRef.current(message);
|
|
129
|
+
const shareConversation = () => shareHandlersRef.current.shareConversation();
|
|
130
|
+
const shareAnswer = () => shareHandlersRef.current.shareAnswer();
|
|
131
|
+
const openShareModal = useCallback(() => setShareModalOpen(true), []);
|
|
132
|
+
const closeShareModal = useCallback(() => setShareModalOpen(false), []);
|
|
133
|
+
const value = {
|
|
134
|
+
triggerMessage,
|
|
135
|
+
registerTriggerMessage,
|
|
136
|
+
shareConversation,
|
|
137
|
+
shareAnswer,
|
|
138
|
+
canShare,
|
|
139
|
+
messageCount,
|
|
140
|
+
registerShareHandlers,
|
|
141
|
+
chatControls,
|
|
142
|
+
registerChatControls,
|
|
143
|
+
hasTransitioned,
|
|
144
|
+
setHasTransitioned,
|
|
145
|
+
shareModalOpen,
|
|
146
|
+
openShareModal,
|
|
147
|
+
closeShareModal
|
|
148
|
+
};
|
|
149
|
+
return /* @__PURE__ */ jsx(ChatContext.Provider, { value, children });
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/contexts/ToastContext.js
|
|
153
|
+
import {
|
|
154
|
+
createContext as createContext3,
|
|
155
|
+
useContext as useContext3,
|
|
156
|
+
useState as useState2,
|
|
157
|
+
useCallback as useCallback2,
|
|
158
|
+
useRef as useRef2,
|
|
159
|
+
useEffect
|
|
160
|
+
} from "react";
|
|
161
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
162
|
+
var ToastContext = createContext3({
|
|
163
|
+
showToast: () => {
|
|
164
|
+
},
|
|
165
|
+
toast: null,
|
|
166
|
+
hideToast: () => {
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
var useToast = () => useContext3(ToastContext);
|
|
170
|
+
var ToastProvider = ({ children }) => {
|
|
171
|
+
const [toast, setToast] = useState2(null);
|
|
172
|
+
const timeoutRef = useRef2(null);
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
return () => {
|
|
175
|
+
if (timeoutRef.current) {
|
|
176
|
+
clearTimeout(timeoutRef.current);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}, []);
|
|
180
|
+
const showToast = useCallback2((message, type = "success") => {
|
|
181
|
+
if (timeoutRef.current) {
|
|
182
|
+
clearTimeout(timeoutRef.current);
|
|
183
|
+
}
|
|
184
|
+
setToast({ message, type, id: Date.now() });
|
|
185
|
+
timeoutRef.current = setTimeout(() => {
|
|
186
|
+
setToast(null);
|
|
187
|
+
timeoutRef.current = null;
|
|
188
|
+
}, 3e3);
|
|
189
|
+
}, []);
|
|
190
|
+
const hideToast = useCallback2(() => {
|
|
191
|
+
setToast(null);
|
|
192
|
+
}, []);
|
|
193
|
+
return /* @__PURE__ */ jsx2(ToastContext.Provider, { value: { showToast, toast, hideToast }, children });
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/hooks/useConversationLoad.js
|
|
197
|
+
import { useState as useState3, useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
198
|
+
import { useRouter } from "next/navigation";
|
|
199
|
+
function useConversationLoad(chatId, options = {}) {
|
|
200
|
+
const {
|
|
201
|
+
shouldLoad = true,
|
|
202
|
+
skipIfLoaded = false,
|
|
203
|
+
onSuccess,
|
|
204
|
+
onError
|
|
205
|
+
} = options;
|
|
206
|
+
const router = useRouter();
|
|
207
|
+
const [loading, setLoading] = useState3(false);
|
|
208
|
+
const hasLoadedRef = useRef3(false);
|
|
209
|
+
const onSuccessRef = useRef3(onSuccess);
|
|
210
|
+
const onErrorRef = useRef3(onError);
|
|
211
|
+
useEffect2(() => {
|
|
212
|
+
onSuccessRef.current = onSuccess;
|
|
213
|
+
onErrorRef.current = onError;
|
|
214
|
+
}, [onSuccess, onError]);
|
|
215
|
+
useEffect2(() => {
|
|
216
|
+
hasLoadedRef.current = false;
|
|
217
|
+
}, [chatId]);
|
|
218
|
+
useEffect2(() => {
|
|
219
|
+
if (!shouldLoad || !chatId) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (skipIfLoaded && hasLoadedRef.current) {
|
|
223
|
+
setLoading(false);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
const { signal } = controller;
|
|
228
|
+
const loadConversation = async () => {
|
|
229
|
+
setLoading(true);
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetch(`/api/chat/load/${chatId}`, {
|
|
232
|
+
signal: controller.signal
|
|
233
|
+
});
|
|
234
|
+
if (response.status === 403) {
|
|
235
|
+
const reason = "This conversation is private and you do not have access.";
|
|
236
|
+
const err = new Error(reason);
|
|
237
|
+
if (!signal.aborted) {
|
|
238
|
+
onErrorRef.current?.(err);
|
|
239
|
+
}
|
|
240
|
+
router.replace("/?reason=private");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (response.status === 404) {
|
|
244
|
+
const reason = "Conversation not found.";
|
|
245
|
+
const err = new Error(reason);
|
|
246
|
+
if (!signal.aborted) {
|
|
247
|
+
onErrorRef.current?.(err);
|
|
248
|
+
}
|
|
249
|
+
router.replace("/?reason=not_found");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
const reason = `Failed to load conversation (HTTP ${response.status})`;
|
|
254
|
+
const err = new Error(reason);
|
|
255
|
+
if (!signal.aborted) {
|
|
256
|
+
onErrorRef.current?.(err);
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const data = await response.json();
|
|
261
|
+
if (!data || typeof data !== "object") {
|
|
262
|
+
const err = new Error("Invalid response format from server");
|
|
263
|
+
if (!signal.aborted) {
|
|
264
|
+
onErrorRef.current?.(err);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
hasLoadedRef.current = true;
|
|
269
|
+
if (!signal.aborted && Array.isArray(data.messages)) {
|
|
270
|
+
onSuccessRef.current?.(data.messages);
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
if (err.name === "AbortError") {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
277
|
+
console.error(`Error loading conversation ${chatId}:`, errorObj);
|
|
278
|
+
if (!signal.aborted) {
|
|
279
|
+
onErrorRef.current?.(errorObj);
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
if (!signal.aborted) {
|
|
283
|
+
setLoading(false);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
loadConversation();
|
|
288
|
+
return () => {
|
|
289
|
+
controller.abort();
|
|
290
|
+
};
|
|
291
|
+
}, [chatId, shouldLoad, skipIfLoaded, router]);
|
|
292
|
+
return { loading };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/hooks/useCountdown.js
|
|
296
|
+
import { useState as useState4, useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
297
|
+
function useCountdown(initialSeconds = 0, onComplete) {
|
|
298
|
+
const [seconds, setSeconds] = useState4(initialSeconds);
|
|
299
|
+
const onCompleteRef = useRef4(onComplete);
|
|
300
|
+
useEffect3(() => {
|
|
301
|
+
onCompleteRef.current = onComplete;
|
|
302
|
+
}, [onComplete]);
|
|
303
|
+
const isActive = seconds > 0;
|
|
304
|
+
useEffect3(() => {
|
|
305
|
+
if (!isActive) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const timer = setInterval(() => {
|
|
309
|
+
setSeconds((prev) => {
|
|
310
|
+
const next = prev - 1;
|
|
311
|
+
if (next <= 0) {
|
|
312
|
+
onCompleteRef.current?.();
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
return next;
|
|
316
|
+
});
|
|
317
|
+
}, 1e3);
|
|
318
|
+
return () => clearInterval(timer);
|
|
319
|
+
}, [isActive]);
|
|
320
|
+
return { seconds, setSeconds };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/utils/anonymousId.js
|
|
324
|
+
var STORAGE_KEY = "caruuto_anonymous_id";
|
|
325
|
+
function getOrCreateAnonymousId() {
|
|
326
|
+
try {
|
|
327
|
+
const existing = window.localStorage.getItem(STORAGE_KEY);
|
|
328
|
+
if (existing) {
|
|
329
|
+
return existing;
|
|
330
|
+
}
|
|
331
|
+
const id = crypto.randomUUID();
|
|
332
|
+
window.localStorage.setItem(STORAGE_KEY, id);
|
|
333
|
+
return id;
|
|
334
|
+
} catch (err) {
|
|
335
|
+
console.error("Failed to get or create anonymous ID:", err);
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/components/ChatSessionProvider.js
|
|
341
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
342
|
+
var DEFAULT_THINKING_OPTIONS = [
|
|
343
|
+
"Thinking...",
|
|
344
|
+
"Processing your request...",
|
|
345
|
+
"One moment please...",
|
|
346
|
+
"Analysing the information..."
|
|
347
|
+
];
|
|
348
|
+
function ChatSessionProviderInner({
|
|
349
|
+
children,
|
|
350
|
+
chatId,
|
|
351
|
+
apiPath = "/api/chat",
|
|
352
|
+
thinkingOptions = DEFAULT_THINKING_OPTIONS,
|
|
353
|
+
shouldLoadConversation = true,
|
|
354
|
+
onFinish,
|
|
355
|
+
onError: onErrorProp
|
|
356
|
+
}) {
|
|
357
|
+
const { registerTriggerMessage } = useChatContext();
|
|
358
|
+
const { showToast } = useToast();
|
|
359
|
+
const searchParams = useSearchParams();
|
|
360
|
+
const [loadedMessages, setLoadedMessages] = useState5([]);
|
|
361
|
+
const [loadFailed, setLoadFailed] = useState5(false);
|
|
362
|
+
const [lastFailedInput, setLastFailedInput] = useState5(null);
|
|
363
|
+
const [acquisition, setAcquisition] = useState5(null);
|
|
364
|
+
const [anonymousUserId, setAnonymousUserId] = useState5(null);
|
|
365
|
+
const lastInputRef = useRef5("");
|
|
366
|
+
const onErrorRef = useRef5(onErrorProp);
|
|
367
|
+
const acquisitionRef = useRef5(null);
|
|
368
|
+
const anonymousUserIdRef = useRef5(null);
|
|
369
|
+
acquisitionRef.current = acquisition;
|
|
370
|
+
anonymousUserIdRef.current = anonymousUserId;
|
|
371
|
+
useEffect4(() => {
|
|
372
|
+
onErrorRef.current = onErrorProp;
|
|
373
|
+
}, [onErrorProp]);
|
|
374
|
+
const {
|
|
375
|
+
messages,
|
|
376
|
+
sendMessage: rawSendMessage,
|
|
377
|
+
status,
|
|
378
|
+
addToolResult,
|
|
379
|
+
setMessages,
|
|
380
|
+
clearError
|
|
381
|
+
} = useChat({
|
|
382
|
+
id: chatId,
|
|
383
|
+
initialMessages: loadedMessages,
|
|
384
|
+
transport: new DefaultChatTransport({
|
|
385
|
+
api: apiPath,
|
|
386
|
+
body: () => ({
|
|
387
|
+
...acquisitionRef.current && { acquisition: acquisitionRef.current },
|
|
388
|
+
...anonymousUserIdRef.current && {
|
|
389
|
+
clientSessionId: anonymousUserIdRef.current
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
}),
|
|
393
|
+
onFinish,
|
|
394
|
+
onError: (error) => {
|
|
395
|
+
setMessages((prev) => {
|
|
396
|
+
if (prev.length > 0 && prev[prev.length - 1].role === "user") {
|
|
397
|
+
return prev.slice(0, -1);
|
|
398
|
+
}
|
|
399
|
+
return prev;
|
|
400
|
+
});
|
|
401
|
+
setLastFailedInput(lastInputRef.current);
|
|
402
|
+
if (error.message?.includes("Too many requests")) {
|
|
403
|
+
const retryMatch = error.message.match(/(\d+)/i);
|
|
404
|
+
const retrySeconds = retryMatch ? parseInt(retryMatch[1]) : 60;
|
|
405
|
+
setRateLimitSeconds(retrySeconds);
|
|
406
|
+
let waitText;
|
|
407
|
+
if (retrySeconds < 60) {
|
|
408
|
+
waitText = `${retrySeconds} seconds`;
|
|
409
|
+
} else if (retrySeconds < 3600) {
|
|
410
|
+
const mins = Math.ceil(retrySeconds / 60);
|
|
411
|
+
waitText = `${mins} minute${mins > 1 ? "s" : ""}`;
|
|
412
|
+
} else {
|
|
413
|
+
const hours = Math.ceil(retrySeconds / 3600);
|
|
414
|
+
waitText = `${hours} hour${hours > 1 ? "s" : ""}`;
|
|
415
|
+
}
|
|
416
|
+
showToast(`Too many requests. Please wait ${waitText}.`, "error");
|
|
417
|
+
} else if (error.message?.includes("Message too long")) {
|
|
418
|
+
showToast("Your message is too long. Please shorten it.", "error");
|
|
419
|
+
} else {
|
|
420
|
+
showToast("Something went wrong. Please try again.", "error");
|
|
421
|
+
}
|
|
422
|
+
console.error("Chat error:", error);
|
|
423
|
+
onErrorRef.current?.(error);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
const { seconds: rateLimitSeconds, setSeconds: setRateLimitSeconds } = useCountdown(0, clearError);
|
|
427
|
+
const { loading: conversationLoading } = useConversationLoad(chatId, {
|
|
428
|
+
shouldLoad: shouldLoadConversation && !loadFailed,
|
|
429
|
+
onSuccess: (msgs) => {
|
|
430
|
+
setLoadedMessages(msgs);
|
|
431
|
+
setMessages(msgs);
|
|
432
|
+
},
|
|
433
|
+
onError: () => {
|
|
434
|
+
setLoadFailed(true);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const streamingWithNoText = useMemo(() => {
|
|
438
|
+
if (status !== "streaming") {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
const lastMsg = messages[messages.length - 1];
|
|
442
|
+
if (!lastMsg || lastMsg.role !== "assistant") {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
return !lastMsg.parts?.some((p) => p.type === "text" && p.text?.length > 0);
|
|
446
|
+
}, [status, messages]);
|
|
447
|
+
const pendingToolCallConfirmation = useMemo(
|
|
448
|
+
() => messages.some(
|
|
449
|
+
(m) => m.parts?.some(
|
|
450
|
+
(part) => isToolUIPart(part) && part.state === "input-available"
|
|
451
|
+
)
|
|
452
|
+
),
|
|
453
|
+
[messages]
|
|
454
|
+
);
|
|
455
|
+
const [thinkingMessage, setThinkingMessage] = useState5("Thinking...");
|
|
456
|
+
useEffect4(() => {
|
|
457
|
+
setThinkingMessage(
|
|
458
|
+
thinkingOptions[Math.floor(Math.random() * thinkingOptions.length)]
|
|
459
|
+
);
|
|
460
|
+
}, [thinkingOptions]);
|
|
461
|
+
useEffect4(() => {
|
|
462
|
+
if (!searchParams) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const source = searchParams.get("utm_source");
|
|
467
|
+
const medium = searchParams.get("utm_medium");
|
|
468
|
+
const campaign = searchParams.get("utm_campaign");
|
|
469
|
+
const term = searchParams.get("utm_term");
|
|
470
|
+
const content = searchParams.get("utm_content");
|
|
471
|
+
const referrerUrl = document.referrer || null;
|
|
472
|
+
if (source || medium || campaign || term || content || referrerUrl) {
|
|
473
|
+
setAcquisition({
|
|
474
|
+
utm_source: source,
|
|
475
|
+
utm_medium: medium,
|
|
476
|
+
utm_campaign: campaign,
|
|
477
|
+
utm_term: term,
|
|
478
|
+
utm_content: content,
|
|
479
|
+
referrer_url: referrerUrl
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
} catch (err) {
|
|
483
|
+
console.error("Failed to capture acquisition data:", err);
|
|
484
|
+
}
|
|
485
|
+
}, []);
|
|
486
|
+
useEffect4(() => {
|
|
487
|
+
setAnonymousUserId(getOrCreateAnonymousId());
|
|
488
|
+
}, []);
|
|
489
|
+
const sendMessage = useCallback3(
|
|
490
|
+
(payload) => {
|
|
491
|
+
if (payload?.text) {
|
|
492
|
+
lastInputRef.current = payload.text;
|
|
493
|
+
}
|
|
494
|
+
return rawSendMessage(payload);
|
|
495
|
+
},
|
|
496
|
+
[rawSendMessage]
|
|
497
|
+
);
|
|
498
|
+
useEffect4(() => {
|
|
499
|
+
registerTriggerMessage((text) => sendMessage({ text }));
|
|
500
|
+
}, [registerTriggerMessage, sendMessage]);
|
|
501
|
+
const value = {
|
|
502
|
+
messages,
|
|
503
|
+
sendMessage,
|
|
504
|
+
status,
|
|
505
|
+
addToolResult,
|
|
506
|
+
setMessages,
|
|
507
|
+
clearError,
|
|
508
|
+
streamingWithNoText,
|
|
509
|
+
thinkingMessage,
|
|
510
|
+
pendingToolCallConfirmation,
|
|
511
|
+
rateLimitSeconds,
|
|
512
|
+
conversationLoading,
|
|
513
|
+
loadFailed,
|
|
514
|
+
lastFailedInput,
|
|
515
|
+
acquisition,
|
|
516
|
+
anonymousUserId
|
|
517
|
+
};
|
|
518
|
+
return /* @__PURE__ */ jsx3(SessionContext_default.Provider, { value, children });
|
|
519
|
+
}
|
|
520
|
+
function ChatSessionProvider({ children, ...props }) {
|
|
521
|
+
return /* @__PURE__ */ jsx3(ToastProvider, { children: /* @__PURE__ */ jsx3(ChatProvider, { children: /* @__PURE__ */ jsx3(Suspense, { children: /* @__PURE__ */ jsx3(ChatSessionProviderInner, { ...props, children }) }) }) });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/components/MessageList.js
|
|
525
|
+
import { Fragment, jsxs } from "react/jsx-runtime";
|
|
526
|
+
function MessageList({
|
|
527
|
+
renderMessage,
|
|
528
|
+
renderThinking,
|
|
529
|
+
renderStreamingIndicator
|
|
530
|
+
}) {
|
|
531
|
+
const {
|
|
532
|
+
messages,
|
|
533
|
+
status,
|
|
534
|
+
addToolResult,
|
|
535
|
+
streamingWithNoText,
|
|
536
|
+
thinkingMessage
|
|
537
|
+
} = useChatSession();
|
|
538
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
539
|
+
messages.map((message, index) => {
|
|
540
|
+
const isUser = message.role === "user";
|
|
541
|
+
const isStreaming = !isUser && status === "streaming" && index === messages.length - 1;
|
|
542
|
+
return renderMessage({
|
|
543
|
+
key: message.id ?? index,
|
|
544
|
+
message,
|
|
545
|
+
parts: message.parts ?? [],
|
|
546
|
+
isUser,
|
|
547
|
+
isStreaming,
|
|
548
|
+
addToolResult
|
|
549
|
+
});
|
|
550
|
+
}),
|
|
551
|
+
status === "submitted" && renderThinking?.({ message: thinkingMessage }),
|
|
552
|
+
streamingWithNoText && renderStreamingIndicator?.()
|
|
553
|
+
] });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/components/UserInput.js
|
|
557
|
+
import { useState as useState6, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
558
|
+
function UserInput({ renderInput }) {
|
|
559
|
+
const {
|
|
560
|
+
sendMessage,
|
|
561
|
+
status,
|
|
562
|
+
rateLimitSeconds,
|
|
563
|
+
pendingToolCallConfirmation,
|
|
564
|
+
lastFailedInput
|
|
565
|
+
} = useChatSession();
|
|
566
|
+
const [value, setValue] = useState6("");
|
|
567
|
+
useEffect5(() => {
|
|
568
|
+
if (lastFailedInput != null) {
|
|
569
|
+
setValue(lastFailedInput);
|
|
570
|
+
}
|
|
571
|
+
}, [lastFailedInput]);
|
|
572
|
+
const isLoading = status === "submitted" || status === "streaming";
|
|
573
|
+
const disabled = status !== "ready" || !value.trim() || rateLimitSeconds > 0 || pendingToolCallConfirmation;
|
|
574
|
+
const onSubmit = useCallback4(() => {
|
|
575
|
+
if (disabled) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const text = value.trim();
|
|
579
|
+
setValue("");
|
|
580
|
+
sendMessage({ text });
|
|
581
|
+
}, [disabled, value, sendMessage]);
|
|
582
|
+
const onChange = useCallback4((e) => {
|
|
583
|
+
setValue(typeof e === "string" ? e : e.target.value);
|
|
584
|
+
}, []);
|
|
585
|
+
const onKeyDown = useCallback4(
|
|
586
|
+
(e) => {
|
|
587
|
+
if (e.key === "Enter" && !e.shiftKey && !disabled) {
|
|
588
|
+
e.preventDefault();
|
|
589
|
+
onSubmit();
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
[disabled, onSubmit]
|
|
593
|
+
);
|
|
594
|
+
return renderInput({
|
|
595
|
+
value,
|
|
596
|
+
onChange,
|
|
597
|
+
onSubmit,
|
|
598
|
+
onKeyDown,
|
|
599
|
+
disabled,
|
|
600
|
+
isLoading,
|
|
601
|
+
countdownSeconds: rateLimitSeconds
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
export {
|
|
605
|
+
ChatSessionProvider,
|
|
606
|
+
MessageList,
|
|
607
|
+
UserInput,
|
|
608
|
+
useChatSession
|
|
609
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare function useChatContext(): any;
|
|
2
|
+
declare function ChatProvider({ children }: {
|
|
3
|
+
children: any;
|
|
4
|
+
}): any;
|
|
5
|
+
|
|
6
|
+
declare function useToast(): any;
|
|
7
|
+
declare function ToastProvider({ children }: {
|
|
8
|
+
children: any;
|
|
9
|
+
}): any;
|
|
10
|
+
|
|
11
|
+
export { ChatProvider, ToastProvider, useChatContext, useToast };
|