@jazzmind/busibox-app 3.0.38 → 3.0.39
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/components/chat/ChatContainer.d.ts.map +1 -1
- package/dist/components/chat/ChatContainer.js +122 -602
- package/dist/components/chat/ChatContainer.js.map +1 -1
- package/dist/components/chat/ChatInterface.d.ts +0 -1
- package/dist/components/chat/ChatInterface.d.ts.map +1 -1
- package/dist/components/chat/ChatInterface.js +159 -302
- package/dist/components/chat/ChatInterface.js.map +1 -1
- package/dist/components/chat/MessageList.d.ts +2 -1
- package/dist/components/chat/MessageList.d.ts.map +1 -1
- package/dist/components/chat/MessageList.js +33 -17
- package/dist/components/chat/MessageList.js.map +1 -1
- package/dist/components/chat/ThinkingToggle.d.ts.map +1 -1
- package/dist/components/chat/ThinkingToggle.js +15 -1
- package/dist/components/chat/ThinkingToggle.js.map +1 -1
- package/dist/lib/agent/agent-api-base.d.ts.map +1 -1
- package/dist/lib/agent/agent-api-base.js +0 -1
- package/dist/lib/agent/agent-api-base.js.map +1 -1
- package/dist/lib/agent/chat-client.d.ts.map +1 -1
- package/dist/lib/agent/chat-client.js +0 -1
- package/dist/lib/agent/chat-client.js.map +1 -1
- package/dist/lib/agent/index.d.ts +2 -0
- package/dist/lib/agent/index.d.ts.map +1 -1
- package/dist/lib/agent/index.js +2 -0
- package/dist/lib/agent/index.js.map +1 -1
- package/dist/lib/agent/stream-event-processor.d.ts +37 -0
- package/dist/lib/agent/stream-event-processor.d.ts.map +1 -0
- package/dist/lib/agent/stream-event-processor.js +204 -0
- package/dist/lib/agent/stream-event-processor.js.map +1 -0
- package/dist/lib/hooks/useChatStream.d.ts.map +1 -1
- package/dist/lib/hooks/useChatStream.js +27 -173
- package/dist/lib/hooks/useChatStream.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,21 +27,13 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
27
27
|
* />
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
-
import { useState, useRef, useEffect } from 'react';
|
|
30
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
31
31
|
import { Send, Bot, Loader2, Paperclip, Plus, Trash2, Volume2, X } from 'lucide-react';
|
|
32
32
|
import toast from 'react-hot-toast';
|
|
33
|
-
import ReactMarkdown from 'react-markdown';
|
|
34
|
-
import remarkGfm from 'remark-gfm';
|
|
35
|
-
import remarkMath from 'remark-math';
|
|
36
|
-
import rehypeKatex from 'rehype-katex';
|
|
37
|
-
import 'katex/dist/katex.min.css';
|
|
38
33
|
import { MessageList } from './MessageList';
|
|
39
|
-
import {
|
|
40
|
-
import { ThinkingStream } from './ThinkingStream';
|
|
41
|
-
import { StepTimeline } from './StepTimeline';
|
|
42
|
-
import { StreamingToolCard } from './StreamingToolCard';
|
|
43
|
-
import { stripThinkTags, extractThinkContent, preprocessLatex, streamingMarkdownComponents } from './chat-utils';
|
|
34
|
+
import { stripThinkTags } from './chat-utils';
|
|
44
35
|
import { sendChatMessage, streamChatMessageAgentic, getConversationHistory } from '../../lib/agent/chat-client';
|
|
36
|
+
import { createAccumulator, processStreamEvent } from '../../lib/agent/stream-event-processor';
|
|
45
37
|
export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = false, enableDocSearch = false, allowAttachments = false, placeholder = 'Type your message...', welcomeMessage, model = 'auto', useStreaming: _useStreaming = true, useAgenticStreaming = true, className = '', onMessageSent, onResponseReceived, initialConversationId, metadata, onConversationDeleted, }) {
|
|
46
38
|
const [messages, setMessages] = useState([]);
|
|
47
39
|
const [input, setInput] = useState('');
|
|
@@ -61,29 +53,31 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
61
53
|
const messagesEndRef = useRef(null);
|
|
62
54
|
const fileInputRef = useRef(null);
|
|
63
55
|
const textareaRef = useRef(null);
|
|
56
|
+
const tokenRef = useRef(token);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
tokenRef.current = token;
|
|
59
|
+
}, [token]);
|
|
64
60
|
const scrollToBottom = () => {
|
|
65
61
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
66
62
|
};
|
|
67
63
|
useEffect(() => {
|
|
68
64
|
scrollToBottom();
|
|
69
65
|
}, [messages, streamingContent]);
|
|
70
|
-
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
if (initialConversationId && token) {
|
|
73
|
-
loadConversationHistory(initialConversationId);
|
|
74
|
-
}
|
|
75
|
-
}, [initialConversationId, token]);
|
|
76
|
-
const loadConversationHistory = async (convId) => {
|
|
66
|
+
const loadConversationHistory = useCallback(async (convId) => {
|
|
77
67
|
setLoadingHistory(true);
|
|
78
68
|
try {
|
|
79
|
-
const history = await getConversationHistory(convId, { token, agentUrl });
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
69
|
+
const history = await getConversationHistory(convId, { token: tokenRef.current, agentUrl });
|
|
70
|
+
const displayMessages = history.map(msg => {
|
|
71
|
+
const raw = msg;
|
|
72
|
+
const thoughts = raw.routing_decision?.thoughts || raw.thoughts;
|
|
73
|
+
return {
|
|
74
|
+
id: msg.id,
|
|
75
|
+
role: msg.role,
|
|
76
|
+
content: msg.content,
|
|
77
|
+
createdAt: new Date(raw.created_at || raw.createdAt || Date.now()),
|
|
78
|
+
thoughts: thoughts?.length > 0 ? thoughts : undefined,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
87
81
|
setMessages(displayMessages);
|
|
88
82
|
setConversationId(convId);
|
|
89
83
|
}
|
|
@@ -94,7 +88,13 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
94
88
|
finally {
|
|
95
89
|
setLoadingHistory(false);
|
|
96
90
|
}
|
|
97
|
-
};
|
|
91
|
+
}, [agentUrl]);
|
|
92
|
+
// Load conversation history when initialConversationId changes (not on token refresh)
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (initialConversationId && tokenRef.current) {
|
|
95
|
+
loadConversationHistory(initialConversationId);
|
|
96
|
+
}
|
|
97
|
+
}, [initialConversationId, loadConversationHistory]);
|
|
98
98
|
const handleFileSelect = async (e) => {
|
|
99
99
|
const files = e.target.files;
|
|
100
100
|
if (!files || files.length === 0)
|
|
@@ -120,11 +120,10 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
120
120
|
const handleSubmit = async (e, overrideMessage) => {
|
|
121
121
|
e?.preventDefault();
|
|
122
122
|
const messageText = overrideMessage ?? input.trim();
|
|
123
|
-
|
|
124
|
-
if (!messageText || (isLoading && !promptActive))
|
|
123
|
+
if (!messageText)
|
|
125
124
|
return;
|
|
126
|
-
//
|
|
127
|
-
if (isLoading &&
|
|
125
|
+
// Abort any in-flight stream so the new message can proceed
|
|
126
|
+
if (isLoading && abortController) {
|
|
128
127
|
abortController.abort();
|
|
129
128
|
}
|
|
130
129
|
const userMessage = messageText;
|
|
@@ -165,282 +164,142 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
165
164
|
setInterimMessages([]);
|
|
166
165
|
setStreamingAgentName(undefined);
|
|
167
166
|
setStreamingParts([]);
|
|
168
|
-
|
|
169
|
-
let collectedThoughts = [];
|
|
170
|
-
let collectedParts = [];
|
|
167
|
+
const accumulated = createAccumulator();
|
|
171
168
|
let hasAddedMessage = false;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
collectedThoughts = [...collectedThoughts, newEvent];
|
|
193
|
-
setThoughts(collectedThoughts);
|
|
194
|
-
break;
|
|
195
|
-
case 'tool_start':
|
|
196
|
-
{
|
|
197
|
-
collectedThoughts = [...collectedThoughts, newEvent];
|
|
198
|
-
setThoughts(collectedThoughts);
|
|
199
|
-
const toolSource = event.data?.source || 'tool';
|
|
200
|
-
const toolName = String(event.data?.data?.tool_name || event.data?.data?.display_name || toolSource);
|
|
201
|
-
const toolPart = {
|
|
202
|
-
type: 'tool_call',
|
|
203
|
-
id: `tool-${Date.now()}-${toolName}`,
|
|
204
|
-
name: toolName,
|
|
205
|
-
displayName: String(event.data?.data?.display_name || event.data?.message || toolName),
|
|
206
|
-
status: 'running',
|
|
207
|
-
input: (event.data?.data || undefined),
|
|
208
|
-
startedAt: new Date(),
|
|
169
|
+
for await (const event of streamChatMessageAgentic(request, { token: tokenRef.current, agentUrl, signal: controller.signal })) {
|
|
170
|
+
const parsed = event.data;
|
|
171
|
+
// message_complete and error have ChatInterface-specific side effects
|
|
172
|
+
// (creating DisplayMessages, toasts) so they're handled inline.
|
|
173
|
+
if (event.type === 'message_complete') {
|
|
174
|
+
if (!hasAddedMessage) {
|
|
175
|
+
setConversationId(parsed.conversation_id);
|
|
176
|
+
const cleanedContent = stripThinkTags(accumulated.fullContent);
|
|
177
|
+
if (cleanedContent) {
|
|
178
|
+
const finalParts = [
|
|
179
|
+
...accumulated.parts,
|
|
180
|
+
{ type: 'text', content: cleanedContent },
|
|
181
|
+
];
|
|
182
|
+
const assistantMessage = {
|
|
183
|
+
role: 'assistant',
|
|
184
|
+
content: cleanedContent,
|
|
185
|
+
timestamp: new Date(),
|
|
186
|
+
thoughts: accumulated.thoughts.length > 0 ? accumulated.thoughts : undefined,
|
|
187
|
+
agentName: accumulated.agentName,
|
|
188
|
+
parts: finalParts,
|
|
209
189
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
setStreamingParts(collectedParts);
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
case 'tool_result':
|
|
216
|
-
{
|
|
217
|
-
collectedThoughts = [...collectedThoughts, newEvent];
|
|
218
|
-
setThoughts(collectedThoughts);
|
|
219
|
-
const resultSource = event.data?.source || 'tool';
|
|
220
|
-
const idx = pendingTools.get(resultSource);
|
|
221
|
-
if (idx !== undefined && collectedParts[idx]?.type === 'tool_call') {
|
|
222
|
-
const existing = collectedParts[idx];
|
|
223
|
-
const updated = {
|
|
224
|
-
...existing,
|
|
225
|
-
status: event.data?.data?.success === false ? 'error' : 'completed',
|
|
226
|
-
output: event.data?.message || undefined,
|
|
227
|
-
error: event.data?.data?.success === false ? String(event.data?.message || 'Failed') : undefined,
|
|
228
|
-
completedAt: new Date(),
|
|
229
|
-
};
|
|
230
|
-
collectedParts = [...collectedParts];
|
|
231
|
-
collectedParts[idx] = updated;
|
|
232
|
-
pendingTools.delete(resultSource);
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
// No matching tool_start; append as a standalone completed tool
|
|
236
|
-
const toolName = String(event.data?.data?.tool_name || event.data?.data?.display_name || resultSource);
|
|
237
|
-
collectedParts = [...collectedParts, {
|
|
238
|
-
type: 'tool_call',
|
|
239
|
-
id: `tool-${Date.now()}-${toolName}`,
|
|
240
|
-
name: toolName,
|
|
241
|
-
displayName: String(event.data?.data?.display_name || toolName),
|
|
242
|
-
status: event.data?.data?.success === false ? 'error' : 'completed',
|
|
243
|
-
output: event.data?.message || undefined,
|
|
244
|
-
completedAt: new Date(),
|
|
245
|
-
}];
|
|
246
|
-
}
|
|
247
|
-
setStreamingParts(collectedParts);
|
|
248
|
-
}
|
|
249
|
-
break;
|
|
250
|
-
case 'interim':
|
|
251
|
-
{
|
|
252
|
-
const payload = event.data || {};
|
|
253
|
-
const nested = payload.data || {};
|
|
254
|
-
const interimMessage = String(payload.message || '').trim();
|
|
255
|
-
const audioUrl = typeof nested.audio_url === 'string' ? nested.audio_url : '';
|
|
256
|
-
const rendered = audioUrl
|
|
257
|
-
? `${interimMessage || 'Voice output ready'} (${audioUrl})`
|
|
258
|
-
: interimMessage;
|
|
259
|
-
if (rendered) {
|
|
260
|
-
setInterimMessages(prev => [...prev, rendered]);
|
|
261
|
-
}
|
|
190
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
191
|
+
hasAddedMessage = true;
|
|
262
192
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
else if (contentData.complete) {
|
|
272
|
-
// Final marker
|
|
273
|
-
}
|
|
274
|
-
else if (msgText) {
|
|
275
|
-
fullContent = msgText;
|
|
276
|
-
}
|
|
277
|
-
const thinkTexts = extractThinkContent(fullContent);
|
|
278
|
-
if (thinkTexts.length > 0) {
|
|
279
|
-
const thinkThought = {
|
|
280
|
-
type: 'thought',
|
|
281
|
-
source: 'model',
|
|
282
|
-
message: thinkTexts.join('\n\n'),
|
|
283
|
-
data: event.data,
|
|
284
|
-
timestamp: new Date(),
|
|
285
|
-
};
|
|
286
|
-
const hasModelThought = collectedThoughts.some(t => t.source === 'model' && t.type === 'thought');
|
|
287
|
-
if (hasModelThought) {
|
|
288
|
-
collectedThoughts = collectedThoughts.map(t => t.source === 'model' && t.type === 'thought' ? thinkThought : t);
|
|
289
|
-
}
|
|
290
|
-
else {
|
|
291
|
-
collectedThoughts = [...collectedThoughts, thinkThought];
|
|
292
|
-
}
|
|
293
|
-
setThoughts(collectedThoughts);
|
|
294
|
-
}
|
|
295
|
-
setStreamingContent(stripThinkTags(fullContent));
|
|
193
|
+
setStreamingContent('');
|
|
194
|
+
setThoughts([]);
|
|
195
|
+
setInterimMessages([]);
|
|
196
|
+
setStreamingAgentName(undefined);
|
|
197
|
+
setStreamingParts([]);
|
|
198
|
+
setIsLoading(false);
|
|
199
|
+
if (cleanedContent) {
|
|
200
|
+
onResponseReceived?.(cleanedContent);
|
|
296
201
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
setPromptActive(true);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
break;
|
|
324
|
-
case 'prompt':
|
|
325
|
-
{
|
|
326
|
-
const promptOptions = event.data?.data?.options || event.data?.options;
|
|
327
|
-
if (promptOptions && Array.isArray(promptOptions)) {
|
|
328
|
-
setQuickReplies(promptOptions);
|
|
329
|
-
setPromptActive(true);
|
|
330
|
-
// Add a prompt part for rendering
|
|
331
|
-
const promptType = (event.data?.data?.prompt_type || 'choice');
|
|
332
|
-
collectedParts = [...collectedParts, { type: 'prompt', options: promptOptions, promptType }];
|
|
333
|
-
setStreamingParts(collectedParts);
|
|
334
|
-
// Finalize accumulated content as a completed message
|
|
335
|
-
const cleanedSoFar = stripThinkTags(fullContent);
|
|
336
|
-
if (cleanedSoFar && !hasAddedMessage) {
|
|
337
|
-
const assistantMessage = {
|
|
338
|
-
role: 'assistant',
|
|
339
|
-
content: cleanedSoFar,
|
|
340
|
-
timestamp: new Date(),
|
|
341
|
-
thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
|
|
342
|
-
agentName: streamingAgentName,
|
|
343
|
-
parts: collectedParts,
|
|
344
|
-
};
|
|
345
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
346
|
-
hasAddedMessage = true;
|
|
347
|
-
}
|
|
348
|
-
setStreamingContent('');
|
|
349
|
-
setThoughts([]);
|
|
350
|
-
setInterimMessages([]);
|
|
351
|
-
setStreamingParts([]);
|
|
352
|
-
}
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (event.type === 'error') {
|
|
206
|
+
const errorMessage = parsed?.message || parsed?.error || 'An error occurred';
|
|
207
|
+
const errorSource = parsed?.source || parsed?.data?.source || '';
|
|
208
|
+
const isToolError = errorSource && !errorSource.includes('agent') && !errorSource.includes('dispatcher');
|
|
209
|
+
if (isToolError) {
|
|
210
|
+
accumulated.thoughts = [...accumulated.thoughts, {
|
|
211
|
+
type: 'error',
|
|
212
|
+
source: errorSource,
|
|
213
|
+
message: `Tool error (${errorSource}): ${errorMessage}`,
|
|
214
|
+
data: parsed,
|
|
215
|
+
timestamp: new Date(),
|
|
216
|
+
}];
|
|
217
|
+
setThoughts(accumulated.thoughts);
|
|
218
|
+
const errIdx = accumulated.pendingTools.get(errorSource);
|
|
219
|
+
if (errIdx !== undefined && accumulated.parts[errIdx]?.type === 'tool_call') {
|
|
220
|
+
const existing = accumulated.parts[errIdx];
|
|
221
|
+
accumulated.parts = [...accumulated.parts];
|
|
222
|
+
accumulated.parts[errIdx] = { ...existing, status: 'error', error: errorMessage, completedAt: new Date() };
|
|
223
|
+
accumulated.pendingTools.delete(errorSource);
|
|
224
|
+
setStreamingParts(accumulated.parts);
|
|
353
225
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
case 'message_complete':
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
toast.error(errorMessage);
|
|
358
229
|
if (!hasAddedMessage) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
agentName: streamingAgentName,
|
|
373
|
-
parts: finalParts,
|
|
374
|
-
};
|
|
375
|
-
setMessages((prev) => [...prev, assistantMessage]);
|
|
376
|
-
hasAddedMessage = true;
|
|
377
|
-
}
|
|
378
|
-
setStreamingContent('');
|
|
379
|
-
setThoughts([]);
|
|
380
|
-
setInterimMessages([]);
|
|
381
|
-
setStreamingAgentName(undefined);
|
|
382
|
-
setStreamingParts([]);
|
|
383
|
-
setIsLoading(false);
|
|
384
|
-
if (cleanedContent) {
|
|
385
|
-
onResponseReceived?.(cleanedContent);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
break;
|
|
389
|
-
case 'error':
|
|
390
|
-
{
|
|
391
|
-
const errorMessage = event.data?.message || event.data?.error || 'An error occurred';
|
|
392
|
-
const errorSource = event.data?.source || event.data?.data?.source || '';
|
|
393
|
-
const isToolError = errorSource && !errorSource.includes('agent') && !errorSource.includes('dispatcher');
|
|
394
|
-
if (isToolError) {
|
|
395
|
-
collectedThoughts = [...collectedThoughts, {
|
|
396
|
-
type: 'error',
|
|
397
|
-
source: errorSource,
|
|
398
|
-
message: `Tool error (${errorSource}): ${errorMessage}`,
|
|
399
|
-
data: event.data,
|
|
400
|
-
timestamp: new Date(),
|
|
401
|
-
}];
|
|
402
|
-
setThoughts(collectedThoughts);
|
|
403
|
-
// Update matching pending tool part to error status
|
|
404
|
-
const errIdx = pendingTools.get(errorSource);
|
|
405
|
-
if (errIdx !== undefined && collectedParts[errIdx]?.type === 'tool_call') {
|
|
406
|
-
const existing = collectedParts[errIdx];
|
|
407
|
-
collectedParts = [...collectedParts];
|
|
408
|
-
collectedParts[errIdx] = { ...existing, status: 'error', error: errorMessage, completedAt: new Date() };
|
|
409
|
-
pendingTools.delete(errorSource);
|
|
410
|
-
setStreamingParts(collectedParts);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
toast.error(errorMessage);
|
|
415
|
-
if (!hasAddedMessage) {
|
|
416
|
-
const errorContent = fullContent.trim()
|
|
417
|
-
? `${fullContent}\n\n**Error:** ${errorMessage}`
|
|
418
|
-
: `**Error:** ${errorMessage}`;
|
|
419
|
-
const errorAssistantMessage = {
|
|
420
|
-
role: 'assistant',
|
|
421
|
-
content: errorContent,
|
|
422
|
-
timestamp: new Date(),
|
|
423
|
-
thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
|
|
424
|
-
agentName: streamingAgentName,
|
|
425
|
-
parts: collectedParts,
|
|
426
|
-
};
|
|
427
|
-
setMessages((prev) => [...prev, errorAssistantMessage]);
|
|
428
|
-
hasAddedMessage = true;
|
|
429
|
-
}
|
|
430
|
-
setStreamingContent('');
|
|
431
|
-
setThoughts([]);
|
|
432
|
-
setInterimMessages([]);
|
|
433
|
-
setStreamingAgentName(undefined);
|
|
434
|
-
setStreamingParts([]);
|
|
435
|
-
}
|
|
230
|
+
const errorContent = accumulated.fullContent.trim()
|
|
231
|
+
? `${accumulated.fullContent}\n\n**Error:** ${errorMessage}`
|
|
232
|
+
: `**Error:** ${errorMessage}`;
|
|
233
|
+
const errorAssistantMessage = {
|
|
234
|
+
role: 'assistant',
|
|
235
|
+
content: errorContent,
|
|
236
|
+
timestamp: new Date(),
|
|
237
|
+
thoughts: accumulated.thoughts.length > 0 ? accumulated.thoughts : undefined,
|
|
238
|
+
agentName: accumulated.agentName,
|
|
239
|
+
parts: accumulated.parts,
|
|
240
|
+
};
|
|
241
|
+
setMessages((prev) => [...prev, errorAssistantMessage]);
|
|
242
|
+
hasAddedMessage = true;
|
|
436
243
|
}
|
|
437
|
-
|
|
244
|
+
setStreamingContent('');
|
|
245
|
+
setThoughts([]);
|
|
246
|
+
setInterimMessages([]);
|
|
247
|
+
setStreamingAgentName(undefined);
|
|
248
|
+
setStreamingParts([]);
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// All other events go through the shared processor
|
|
253
|
+
const result = processStreamEvent(event.type, parsed, accumulated);
|
|
254
|
+
if (result.conversationId && !result.titleUpdate) {
|
|
255
|
+
setConversationId(result.conversationId);
|
|
256
|
+
}
|
|
257
|
+
if (accumulated.agentName) {
|
|
258
|
+
setStreamingAgentName(accumulated.agentName);
|
|
259
|
+
}
|
|
260
|
+
if (result.content !== undefined) {
|
|
261
|
+
setStreamingContent(result.content);
|
|
262
|
+
}
|
|
263
|
+
if (result.thoughts) {
|
|
264
|
+
setThoughts(result.thoughts);
|
|
265
|
+
}
|
|
266
|
+
if (result.parts) {
|
|
267
|
+
setStreamingParts(result.parts);
|
|
268
|
+
}
|
|
269
|
+
if (result.interimMessages) {
|
|
270
|
+
setInterimMessages(result.interimMessages);
|
|
271
|
+
}
|
|
272
|
+
if (result.quickReplies) {
|
|
273
|
+
setQuickReplies(result.quickReplies);
|
|
274
|
+
}
|
|
275
|
+
if (result.promptActive !== undefined) {
|
|
276
|
+
setPromptActive(result.promptActive);
|
|
277
|
+
}
|
|
278
|
+
// prompt event: finalize accumulated content as a completed message
|
|
279
|
+
if (event.type === 'prompt' && result.promptActive) {
|
|
280
|
+
const cleanedSoFar = stripThinkTags(accumulated.fullContent);
|
|
281
|
+
if (cleanedSoFar && !hasAddedMessage) {
|
|
282
|
+
const assistantMessage = {
|
|
283
|
+
role: 'assistant',
|
|
284
|
+
content: cleanedSoFar,
|
|
285
|
+
timestamp: new Date(),
|
|
286
|
+
thoughts: accumulated.thoughts.length > 0 ? accumulated.thoughts : undefined,
|
|
287
|
+
agentName: accumulated.agentName,
|
|
288
|
+
parts: accumulated.parts,
|
|
289
|
+
};
|
|
290
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
291
|
+
hasAddedMessage = true;
|
|
292
|
+
}
|
|
293
|
+
setStreamingContent('');
|
|
294
|
+
setThoughts([]);
|
|
295
|
+
setInterimMessages([]);
|
|
296
|
+
setStreamingParts([]);
|
|
438
297
|
}
|
|
439
298
|
}
|
|
440
299
|
}
|
|
441
300
|
else {
|
|
442
301
|
// Non-streaming fallback
|
|
443
|
-
const response = await sendChatMessage(request, { token, agentUrl });
|
|
302
|
+
const response = await sendChatMessage(request, { token: tokenRef.current, agentUrl });
|
|
444
303
|
setConversationId(response.conversation_id);
|
|
445
304
|
const assistantMessage = {
|
|
446
305
|
role: 'assistant',
|
|
@@ -533,7 +392,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
533
392
|
const response = await fetch(`${agentUrl || ''}/chat/${conversationId}/messages/${messageId}`, {
|
|
534
393
|
method: 'DELETE',
|
|
535
394
|
headers: {
|
|
536
|
-
'Authorization': `Bearer ${
|
|
395
|
+
'Authorization': `Bearer ${tokenRef.current}`,
|
|
537
396
|
'Content-Type': 'application/json',
|
|
538
397
|
},
|
|
539
398
|
});
|
|
@@ -561,7 +420,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
561
420
|
const response = await fetch(`${agentUrl || ''}/conversations/${conversationId}`, {
|
|
562
421
|
method: 'DELETE',
|
|
563
422
|
headers: {
|
|
564
|
-
'Authorization': `Bearer ${
|
|
423
|
+
'Authorization': `Bearer ${tokenRef.current}`,
|
|
565
424
|
'Content-Type': 'application/json',
|
|
566
425
|
},
|
|
567
426
|
});
|
|
@@ -630,9 +489,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
630
489
|
});
|
|
631
490
|
}
|
|
632
491
|
return displayMessages;
|
|
633
|
-
})(), streamingContent: undefined, streamingAgentName: streamingAgentName, isLoading:
|
|
634
|
-
.filter((p) => p.type === 'tool_call')
|
|
635
|
-
.map((part) => (_jsx(StreamingToolCard, { part: part }, part.id))) })), interimMessages.length > 0 && (_jsx("div", { className: "mb-3 space-y-1", children: interimMessages.map((msg, idx) => (_jsx("div", { className: "text-xs rounded border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-900/20 px-2 py-1 text-amber-800 dark:text-amber-200", children: msg }, `interim-${idx}`))) })), streamingContent ? (_jsxs("div", { className: "prose prose-sm dark:prose-invert max-w-none prose-headings:font-semibold prose-h1:text-xl prose-h1:mt-4 prose-h1:mb-3 prose-h2:text-lg prose-h2:mt-4 prose-h2:mb-2 prose-h3:text-base prose-h3:mt-3 prose-h3:mb-2 prose-p:my-2 prose-p:leading-relaxed prose-ul:my-3 prose-ol:my-3 prose-li:my-0.5 prose-hr:my-6 prose-strong:font-semibold prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-pre:border prose-blockquote:border-l-4 prose-blockquote:pl-4 prose-blockquote:italic prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline", children: [_jsx(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkMath], rehypePlugins: [rehypeKatex], components: streamingMarkdownComponents, children: preprocessLatex(streamingContent) }), _jsx("span", { className: "inline-block w-2 h-4 bg-gray-400 dark:bg-gray-500 animate-pulse ml-1" })] })) : (_jsxs("div", { className: "flex items-center gap-2 text-gray-500 dark:text-gray-400", children: [_jsx(Loader2, { className: "w-4 h-4 animate-spin" }), _jsx("span", { children: "Thinking..." })] }))] }) })] })), _jsx("div", { ref: messagesEndRef })] }), attachments.length > 0 && (_jsx("div", { className: "flex-shrink-0 px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: _jsx("div", { className: "flex flex-wrap gap-2", children: attachments.map((attachment, index) => (_jsxs("div", { className: "flex items-center gap-2 bg-white dark:bg-gray-700 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-600", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-200", children: attachment.name }), _jsx("button", { onClick: () => handleRemoveAttachment(index), className: "text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400", children: "\u00D7" })] }, index))) }) })), quickReplies.length > 0 && (_jsx("div", { className: "flex-shrink-0 px-3 pt-2 pb-1 flex flex-wrap gap-2 justify-center border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900", children: quickReplies.map((reply) => (_jsx("button", { type: "button", onClick: () => handleQuickReply(reply), className: "px-4 py-1.5 text-sm font-medium rounded-full border border-blue-500 dark:border-blue-400 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors", children: reply }, reply))) })), _jsx("form", { onSubmit: handleSubmit, className: "flex-shrink-0 p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900", children: _jsxs("div", { className: "flex gap-2 items-end", children: [allowAttachments && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, onChange: handleFileSelect, className: "hidden" }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isLoading, className: "p-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0", title: "Attach files", children: _jsx(Paperclip, { className: "w-5 h-5" }) })] })), _jsx("textarea", { ref: textareaRef, value: input, onChange: (e) => {
|
|
492
|
+
})(), streamingContent: streamingContent || undefined, streamingAgentName: streamingAgentName, streamingThoughts: thoughts, streamingParts: streamingParts, isLoading: isLoading, onDeleteMessage: handleDeleteMessage, onRetryMessage: handleRetryMessage, onSuggestedAction: (action) => handleSubmit(null, action) })), _jsx("div", { ref: messagesEndRef })] }), attachments.length > 0 && (_jsx("div", { className: "flex-shrink-0 px-4 py-2 border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: _jsx("div", { className: "flex flex-wrap gap-2", children: attachments.map((attachment, index) => (_jsxs("div", { className: "flex items-center gap-2 bg-white dark:bg-gray-700 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-600", children: [_jsx("span", { className: "text-sm text-gray-700 dark:text-gray-200", children: attachment.name }), _jsx("button", { onClick: () => handleRemoveAttachment(index), className: "text-gray-400 dark:text-gray-500 hover:text-red-600 dark:hover:text-red-400", children: "\u00D7" })] }, index))) }) })), quickReplies.length > 0 && (_jsx("div", { className: "flex-shrink-0 px-3 pt-2 pb-1 flex flex-wrap gap-2 justify-center border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900", children: quickReplies.map((reply) => (_jsx("button", { type: "button", onClick: () => handleQuickReply(reply), className: "px-4 py-1.5 text-sm font-medium rounded-full border border-blue-500 dark:border-blue-400 text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/30 transition-colors", children: reply }, reply))) })), _jsx("form", { onSubmit: handleSubmit, className: "flex-shrink-0 p-3 border-t border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900", children: _jsxs("div", { className: "flex gap-2 items-end", children: [allowAttachments && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, onChange: handleFileSelect, className: "hidden" }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), disabled: isLoading, className: "p-2 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 disabled:opacity-50 disabled:cursor-not-allowed flex-shrink-0", title: "Attach files", children: _jsx(Paperclip, { className: "w-5 h-5" }) })] })), _jsx("textarea", { ref: textareaRef, value: input, onChange: (e) => {
|
|
636
493
|
setInput(e.target.value);
|
|
637
494
|
if (textareaRef.current) {
|
|
638
495
|
textareaRef.current.style.height = 'auto';
|
|
@@ -641,10 +498,10 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
|
|
|
641
498
|
}, onKeyDown: (e) => {
|
|
642
499
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
643
500
|
e.preventDefault();
|
|
644
|
-
if (input.trim()
|
|
501
|
+
if (input.trim()) {
|
|
645
502
|
handleSubmit(e);
|
|
646
503
|
}
|
|
647
504
|
}
|
|
648
|
-
}, placeholder: promptActive ? 'Type your reply...' : placeholder, rows: 1, className: "flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent resize-none", style: { minHeight: '40px', maxHeight: '200px' }, disabled:
|
|
505
|
+
}, placeholder: promptActive ? 'Type your reply...' : placeholder, rows: 1, className: "flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent resize-none", style: { minHeight: '40px', maxHeight: '200px' }, disabled: false }), isLoading && !promptActive && (_jsx("button", { type: "button", onClick: handleCancel, className: "bg-red-600 dark:bg-red-500 hover:bg-red-700 dark:hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2 font-medium flex-shrink-0", title: "Cancel", children: _jsx("span", { className: "w-5 h-5 flex items-center justify-center", children: "\u23F9" }) })), _jsx("button", { type: "submit", disabled: !input.trim(), className: "bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-600 disabled:bg-gray-400 dark:disabled:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2 font-medium disabled:cursor-not-allowed flex-shrink-0", children: _jsx(Send, { className: "w-5 h-5" }) })] }) }), showVoiceComingSoon && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4", children: _jsxs("div", { className: "w-full max-w-sm rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-xl p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Voice Mode" }), _jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-300", children: "Voice mode is coming soon. Chat remains text-only for now." })] }), _jsx("button", { type: "button", onClick: () => setShowVoiceComingSoon(false), className: "p-1 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700", "aria-label": "Close voice mode modal", children: _jsx(X, { className: "w-4 h-4" }) })] }), _jsx("div", { className: "mt-4 flex justify-end", children: _jsx("button", { type: "button", onClick: () => setShowVoiceComingSoon(false), className: "px-3 py-1.5 text-sm font-medium rounded-lg bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600", children: "Got it" }) })] }) }))] }));
|
|
649
506
|
}
|
|
650
507
|
//# sourceMappingURL=ChatInterface.js.map
|