@jazzmind/busibox-app 3.0.37 → 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.
Files changed (41) hide show
  1. package/dist/components/chat/ChatContainer.d.ts.map +1 -1
  2. package/dist/components/chat/ChatContainer.js +122 -602
  3. package/dist/components/chat/ChatContainer.js.map +1 -1
  4. package/dist/components/chat/ChatInterface.d.ts +3 -4
  5. package/dist/components/chat/ChatInterface.d.ts.map +1 -1
  6. package/dist/components/chat/ChatInterface.js +159 -361
  7. package/dist/components/chat/ChatInterface.js.map +1 -1
  8. package/dist/components/chat/MessageList.d.ts +2 -1
  9. package/dist/components/chat/MessageList.d.ts.map +1 -1
  10. package/dist/components/chat/MessageList.js +33 -17
  11. package/dist/components/chat/MessageList.js.map +1 -1
  12. package/dist/components/chat/StepTimeline.d.ts +9 -0
  13. package/dist/components/chat/StepTimeline.d.ts.map +1 -0
  14. package/dist/components/chat/StepTimeline.js +110 -0
  15. package/dist/components/chat/StepTimeline.js.map +1 -0
  16. package/dist/components/chat/ThinkingStream.d.ts +13 -0
  17. package/dist/components/chat/ThinkingStream.d.ts.map +1 -0
  18. package/dist/components/chat/ThinkingStream.js +41 -0
  19. package/dist/components/chat/ThinkingStream.js.map +1 -0
  20. package/dist/components/chat/ThinkingToggle.d.ts.map +1 -1
  21. package/dist/components/chat/ThinkingToggle.js +15 -1
  22. package/dist/components/chat/ThinkingToggle.js.map +1 -1
  23. package/dist/lib/agent/agent-api-base.d.ts.map +1 -1
  24. package/dist/lib/agent/agent-api-base.js +0 -1
  25. package/dist/lib/agent/agent-api-base.js.map +1 -1
  26. package/dist/lib/agent/chat-client.d.ts.map +1 -1
  27. package/dist/lib/agent/chat-client.js +0 -1
  28. package/dist/lib/agent/chat-client.js.map +1 -1
  29. package/dist/lib/agent/index.d.ts +2 -0
  30. package/dist/lib/agent/index.d.ts.map +1 -1
  31. package/dist/lib/agent/index.js +2 -0
  32. package/dist/lib/agent/index.js.map +1 -1
  33. package/dist/lib/agent/stream-event-processor.d.ts +37 -0
  34. package/dist/lib/agent/stream-event-processor.d.ts.map +1 -0
  35. package/dist/lib/agent/stream-event-processor.js +204 -0
  36. package/dist/lib/agent/stream-event-processor.js.map +1 -0
  37. package/dist/lib/hooks/useChatStream.d.ts +34 -0
  38. package/dist/lib/hooks/useChatStream.d.ts.map +1 -0
  39. package/dist/lib/hooks/useChatStream.js +105 -0
  40. package/dist/lib/hooks/useChatStream.js.map +1 -0
  41. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  /**
4
4
  * ChatInterface - Core Chat Component
5
5
  *
@@ -27,36 +27,19 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
27
27
  * />
28
28
  * ```
29
29
  */
30
- import { useState, useRef, useEffect } from 'react';
31
- import { Send, Bot, Loader2, Paperclip, Brain, CheckCircle, AlertCircle, Plus, Trash2, Volume2, X } from 'lucide-react';
30
+ import { useState, useRef, useEffect, useCallback } from 'react';
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 { ThinkingToggle } from './ThinkingToggle';
40
- import { StreamingToolCard } from './StreamingToolCard';
41
- import { stripThinkTags, extractThinkContent, preprocessLatex, streamingMarkdownComponents } from './chat-utils';
42
- import { sendChatMessage, streamChatMessage, streamChatMessageAgentic, getConversationHistory } from '../../lib/agent/chat-client';
43
- // Real-time execution status display (legacy, for non-agentic streaming)
44
- function ExecutionStatus({ events, isActive }) {
45
- if (events.length === 0)
46
- return null;
47
- // Only show the last few relevant events
48
- const relevantEvents = events.filter(e => ['planning', 'tool_start', 'tool_result', 'agent_start', 'agent_result', 'synthesis_start'].includes(e.type)).slice(-4);
49
- if (relevantEvents.length === 0)
50
- return null;
51
- return (_jsxs("div", { className: "flex gap-3 justify-start mb-4", children: [_jsx("div", { className: "bg-blue-600 dark:bg-blue-500 rounded-full p-2 h-8 w-8 flex items-center justify-center flex-shrink-0", children: _jsx(Bot, { className: "w-4 h-4 text-white" }) }), _jsx("div", { className: "max-w-[80%] rounded-lg p-3 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100", children: _jsxs("div", { className: "space-y-1.5", children: [relevantEvents.map((event, idx) => (_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [event.type === 'planning' && (_jsxs(_Fragment, { children: [_jsx(Brain, { className: "w-3.5 h-3.5 text-purple-500 flex-shrink-0" }), _jsx("span", { className: "text-purple-700 dark:text-purple-400", children: event.message || 'Analyzing...' })] })), event.type === 'tool_start' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin flex-shrink-0" }), _jsx("span", { className: "text-blue-700 dark:text-blue-400", children: event.message || `Running ${event.data?.display_name || event.data?.tool}...` })] })), event.type === 'tool_result' && (_jsxs(_Fragment, { children: [event.data?.success !== false ? (_jsx(CheckCircle, { className: "w-3.5 h-3.5 text-green-500 flex-shrink-0" })) : (_jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-500 flex-shrink-0" })), _jsx("span", { className: event.data?.success !== false ? 'text-green-700 dark:text-green-400' : 'text-red-700 dark:text-red-400', children: event.message || `${event.data?.display_name || event.data?.tool_name} done` })] })), event.type === 'agent_start' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-indigo-500 animate-spin flex-shrink-0" }), _jsx("span", { className: "text-indigo-700 dark:text-indigo-400", children: event.message || `Consulting ${event.data?.display_name || event.data?.agent}...` })] })), event.type === 'agent_result' && (_jsxs(_Fragment, { children: [event.data?.success !== false ? (_jsx(CheckCircle, { className: "w-3.5 h-3.5 text-green-500 flex-shrink-0" })) : (_jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-500 flex-shrink-0" })), _jsx("span", { className: event.data?.success !== false ? 'text-green-700 dark:text-green-400' : 'text-red-700 dark:text-red-400', children: event.message || 'Agent responded' })] })), event.type === 'synthesis_start' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-purple-500 animate-spin flex-shrink-0" }), _jsx("span", { className: "text-purple-700 dark:text-purple-400", children: event.message || 'Combining results...' })] }))] }, idx))), isActive && (_jsxs("div", { className: "flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 mt-1", children: [_jsx(Loader2, { className: "w-3 h-3 animate-spin" }), _jsx("span", { children: "Processing..." })] }))] }) })] }));
52
- }
53
- export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = false, enableDocSearch = false, allowAttachments = false, placeholder = 'Type your message...', welcomeMessage, model = 'auto', useStreaming = true, useAgenticStreaming = false, className = '', onMessageSent, onResponseReceived, initialConversationId, metadata, onConversationDeleted, }) {
34
+ import { stripThinkTags } from './chat-utils';
35
+ import { sendChatMessage, streamChatMessageAgentic, getConversationHistory } from '../../lib/agent/chat-client';
36
+ import { createAccumulator, processStreamEvent } from '../../lib/agent/stream-event-processor';
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, }) {
54
38
  const [messages, setMessages] = useState([]);
55
39
  const [input, setInput] = useState('');
56
40
  const [isLoading, setIsLoading] = useState(false);
57
41
  const [streamingContent, setStreamingContent] = useState('');
58
- const [executionEvents, setExecutionEvents] = useState([]);
59
- const [thoughts, setThoughts] = useState([]); // For agentic streaming
42
+ const [thoughts, setThoughts] = useState([]);
60
43
  const [interimMessages, setInterimMessages] = useState([]);
61
44
  const [streamingAgentName, setStreamingAgentName] = useState(undefined);
62
45
  const [conversationId, setConversationId] = useState(initialConversationId);
@@ -70,29 +53,31 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
70
53
  const messagesEndRef = useRef(null);
71
54
  const fileInputRef = useRef(null);
72
55
  const textareaRef = useRef(null);
56
+ const tokenRef = useRef(token);
57
+ useEffect(() => {
58
+ tokenRef.current = token;
59
+ }, [token]);
73
60
  const scrollToBottom = () => {
74
61
  messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
75
62
  };
76
63
  useEffect(() => {
77
64
  scrollToBottom();
78
65
  }, [messages, streamingContent]);
79
- // Load conversation history when initialConversationId is provided
80
- useEffect(() => {
81
- if (initialConversationId && token) {
82
- loadConversationHistory(initialConversationId);
83
- }
84
- }, [initialConversationId, token]);
85
- const loadConversationHistory = async (convId) => {
66
+ const loadConversationHistory = useCallback(async (convId) => {
86
67
  setLoadingHistory(true);
87
68
  try {
88
- const history = await getConversationHistory(convId, { token, agentUrl });
89
- // Convert to DisplayMessage format
90
- const displayMessages = history.map(msg => ({
91
- id: msg.id,
92
- role: msg.role,
93
- content: msg.content,
94
- createdAt: new Date(msg.created_at || msg.createdAt || Date.now()),
95
- }));
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
+ });
96
81
  setMessages(displayMessages);
97
82
  setConversationId(convId);
98
83
  }
@@ -103,7 +88,13 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
103
88
  finally {
104
89
  setLoadingHistory(false);
105
90
  }
106
- };
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]);
107
98
  const handleFileSelect = async (e) => {
108
99
  const files = e.target.files;
109
100
  if (!files || files.length === 0)
@@ -129,11 +120,10 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
129
120
  const handleSubmit = async (e, overrideMessage) => {
130
121
  e?.preventDefault();
131
122
  const messageText = overrideMessage ?? input.trim();
132
- // Allow submission when promptActive even if isLoading is still true
133
- if (!messageText || (isLoading && !promptActive))
123
+ if (!messageText)
134
124
  return;
135
- // If we're submitting during a prompt-active state, abort the lingering stream
136
- if (isLoading && promptActive && abortController) {
125
+ // Abort any in-flight stream so the new message can proceed
126
+ if (isLoading && abortController) {
137
127
  abortController.abort();
138
128
  }
139
129
  const userMessage = messageText;
@@ -174,337 +164,149 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
174
164
  setInterimMessages([]);
175
165
  setStreamingAgentName(undefined);
176
166
  setStreamingParts([]);
177
- let fullContent = '';
178
- let collectedThoughts = [];
179
- let collectedParts = [];
167
+ const accumulated = createAccumulator();
180
168
  let hasAddedMessage = false;
181
- // Track in-flight tool calls by source so we can update their status
182
- const pendingTools = new Map(); // source -> index in collectedParts
183
- for await (const event of streamChatMessageAgentic(request, { token, agentUrl, signal: controller.signal })) {
184
- const newEvent = {
185
- type: event.type,
186
- source: event.data?.source,
187
- message: event.data?.message,
188
- data: event.data,
189
- timestamp: new Date(),
190
- };
191
- if (event.data?.source && !event.data.source.includes('dispatcher')) {
192
- setStreamingAgentName(event.data.source);
193
- }
194
- switch (event.type) {
195
- case 'conversation_created':
196
- setConversationId(event.data.conversation_id);
197
- break;
198
- case 'thought':
199
- case 'plan':
200
- case 'progress':
201
- collectedThoughts = [...collectedThoughts, newEvent];
202
- setThoughts(collectedThoughts);
203
- break;
204
- case 'tool_start':
205
- {
206
- collectedThoughts = [...collectedThoughts, newEvent];
207
- setThoughts(collectedThoughts);
208
- const toolSource = event.data?.source || 'tool';
209
- const toolName = String(event.data?.data?.tool_name || event.data?.data?.display_name || toolSource);
210
- const toolPart = {
211
- type: 'tool_call',
212
- id: `tool-${Date.now()}-${toolName}`,
213
- name: toolName,
214
- displayName: String(event.data?.data?.display_name || event.data?.message || toolName),
215
- status: 'running',
216
- input: (event.data?.data || undefined),
217
- 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,
218
189
  };
219
- pendingTools.set(toolSource, collectedParts.length);
220
- collectedParts = [...collectedParts, toolPart];
221
- setStreamingParts(collectedParts);
222
- }
223
- break;
224
- case 'tool_result':
225
- {
226
- collectedThoughts = [...collectedThoughts, newEvent];
227
- setThoughts(collectedThoughts);
228
- const resultSource = event.data?.source || 'tool';
229
- const idx = pendingTools.get(resultSource);
230
- if (idx !== undefined && collectedParts[idx]?.type === 'tool_call') {
231
- const existing = collectedParts[idx];
232
- const updated = {
233
- ...existing,
234
- status: event.data?.data?.success === false ? 'error' : 'completed',
235
- output: event.data?.message || undefined,
236
- error: event.data?.data?.success === false ? String(event.data?.message || 'Failed') : undefined,
237
- completedAt: new Date(),
238
- };
239
- collectedParts = [...collectedParts];
240
- collectedParts[idx] = updated;
241
- pendingTools.delete(resultSource);
242
- }
243
- else {
244
- // No matching tool_start; append as a standalone completed tool
245
- const toolName = String(event.data?.data?.tool_name || event.data?.data?.display_name || resultSource);
246
- collectedParts = [...collectedParts, {
247
- type: 'tool_call',
248
- id: `tool-${Date.now()}-${toolName}`,
249
- name: toolName,
250
- displayName: String(event.data?.data?.display_name || toolName),
251
- status: event.data?.data?.success === false ? 'error' : 'completed',
252
- output: event.data?.message || undefined,
253
- completedAt: new Date(),
254
- }];
255
- }
256
- setStreamingParts(collectedParts);
190
+ setMessages((prev) => [...prev, assistantMessage]);
191
+ hasAddedMessage = true;
257
192
  }
258
- break;
259
- case 'interim':
260
- {
261
- const payload = event.data || {};
262
- const nested = payload.data || {};
263
- const interimMessage = String(payload.message || '').trim();
264
- const audioUrl = typeof nested.audio_url === 'string' ? nested.audio_url : '';
265
- const rendered = audioUrl
266
- ? `${interimMessage || 'Voice output ready'} (${audioUrl})`
267
- : interimMessage;
268
- if (rendered) {
269
- setInterimMessages(prev => [...prev, rendered]);
270
- }
271
- }
272
- break;
273
- case 'content':
274
- {
275
- const contentData = event.data?.data || {};
276
- const msgText = event.data?.message || '';
277
- if (contentData.streaming && contentData.partial) {
278
- fullContent += msgText;
279
- }
280
- else if (contentData.complete) {
281
- // Final marker
282
- }
283
- else if (msgText) {
284
- fullContent = msgText;
285
- }
286
- const thinkTexts = extractThinkContent(fullContent);
287
- if (thinkTexts.length > 0) {
288
- const thinkThought = {
289
- type: 'thought',
290
- source: 'model',
291
- message: thinkTexts.join('\n\n'),
292
- data: event.data,
293
- timestamp: new Date(),
294
- };
295
- const hasModelThought = collectedThoughts.some(t => t.source === 'model' && t.type === 'thought');
296
- if (hasModelThought) {
297
- collectedThoughts = collectedThoughts.map(t => t.source === 'model' && t.type === 'thought' ? thinkThought : t);
298
- }
299
- else {
300
- collectedThoughts = [...collectedThoughts, thinkThought];
301
- }
302
- setThoughts(collectedThoughts);
303
- }
304
- setStreamingContent(stripThinkTags(fullContent));
193
+ setStreamingContent('');
194
+ setThoughts([]);
195
+ setInterimMessages([]);
196
+ setStreamingAgentName(undefined);
197
+ setStreamingParts([]);
198
+ setIsLoading(false);
199
+ if (cleanedContent) {
200
+ onResponseReceived?.(cleanedContent);
305
201
  }
306
- break;
307
- case 'prompt':
308
- {
309
- const promptOptions = event.data?.data?.options || event.data?.options;
310
- if (promptOptions && Array.isArray(promptOptions)) {
311
- setQuickReplies(promptOptions);
312
- setPromptActive(true);
313
- // Add a prompt part for rendering
314
- const promptType = (event.data?.data?.prompt_type || 'choice');
315
- collectedParts = [...collectedParts, { type: 'prompt', options: promptOptions, promptType }];
316
- setStreamingParts(collectedParts);
317
- // Finalize accumulated content as a completed message
318
- const cleanedSoFar = stripThinkTags(fullContent);
319
- if (cleanedSoFar && !hasAddedMessage) {
320
- const assistantMessage = {
321
- role: 'assistant',
322
- content: cleanedSoFar,
323
- timestamp: new Date(),
324
- thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
325
- agentName: streamingAgentName,
326
- parts: collectedParts,
327
- };
328
- setMessages((prev) => [...prev, assistantMessage]);
329
- hasAddedMessage = true;
330
- }
331
- setStreamingContent('');
332
- setThoughts([]);
333
- setInterimMessages([]);
334
- setStreamingParts([]);
335
- }
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);
336
225
  }
337
- break;
338
- case 'complete':
339
- break;
340
- case 'message_complete':
226
+ }
227
+ else {
228
+ toast.error(errorMessage);
341
229
  if (!hasAddedMessage) {
342
- setConversationId(event.data.conversation_id);
343
- const cleanedContent = stripThinkTags(fullContent);
344
- if (cleanedContent) {
345
- // Build final text part if there's content not yet in parts
346
- const finalParts = [
347
- ...collectedParts,
348
- { type: 'text', content: cleanedContent },
349
- ];
350
- const assistantMessage = {
351
- role: 'assistant',
352
- content: cleanedContent,
353
- timestamp: new Date(),
354
- thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
355
- agentName: streamingAgentName,
356
- parts: finalParts,
357
- };
358
- setMessages((prev) => [...prev, assistantMessage]);
359
- hasAddedMessage = true;
360
- }
361
- setStreamingContent('');
362
- setThoughts([]);
363
- setInterimMessages([]);
364
- setStreamingAgentName(undefined);
365
- setStreamingParts([]);
366
- setIsLoading(false);
367
- if (cleanedContent) {
368
- onResponseReceived?.(cleanedContent);
369
- }
370
- }
371
- break;
372
- case 'error':
373
- {
374
- const errorMessage = event.data?.message || event.data?.error || 'An error occurred';
375
- const errorSource = event.data?.source || event.data?.data?.source || '';
376
- const isToolError = errorSource && !errorSource.includes('agent') && !errorSource.includes('dispatcher');
377
- if (isToolError) {
378
- collectedThoughts = [...collectedThoughts, {
379
- type: 'error',
380
- source: errorSource,
381
- message: `Tool error (${errorSource}): ${errorMessage}`,
382
- data: event.data,
383
- timestamp: new Date(),
384
- }];
385
- setThoughts(collectedThoughts);
386
- // Update matching pending tool part to error status
387
- const errIdx = pendingTools.get(errorSource);
388
- if (errIdx !== undefined && collectedParts[errIdx]?.type === 'tool_call') {
389
- const existing = collectedParts[errIdx];
390
- collectedParts = [...collectedParts];
391
- collectedParts[errIdx] = { ...existing, status: 'error', error: errorMessage, completedAt: new Date() };
392
- pendingTools.delete(errorSource);
393
- setStreamingParts(collectedParts);
394
- }
395
- }
396
- else {
397
- toast.error(errorMessage);
398
- if (!hasAddedMessage) {
399
- const errorContent = fullContent.trim()
400
- ? `${fullContent}\n\n**Error:** ${errorMessage}`
401
- : `**Error:** ${errorMessage}`;
402
- const errorAssistantMessage = {
403
- role: 'assistant',
404
- content: errorContent,
405
- timestamp: new Date(),
406
- thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
407
- agentName: streamingAgentName,
408
- parts: collectedParts,
409
- };
410
- setMessages((prev) => [...prev, errorAssistantMessage]);
411
- hasAddedMessage = true;
412
- }
413
- setStreamingContent('');
414
- setThoughts([]);
415
- setInterimMessages([]);
416
- setStreamingAgentName(undefined);
417
- setStreamingParts([]);
418
- }
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;
419
243
  }
420
- break;
244
+ setStreamingContent('');
245
+ setThoughts([]);
246
+ setInterimMessages([]);
247
+ setStreamingAgentName(undefined);
248
+ setStreamingParts([]);
249
+ }
250
+ continue;
421
251
  }
422
- }
423
- }
424
- else if (useStreaming) {
425
- // Standard streaming response
426
- setStreamingContent('');
427
- setExecutionEvents([]);
428
- let fullContent = '';
429
- let collectedEvents = [];
430
- for await (const event of streamChatMessage(request, { token, agentUrl, signal: controller.signal })) {
431
- const newEvent = {
432
- type: event.type,
433
- message: event.data?.message,
434
- data: event.data,
435
- timestamp: new Date(),
436
- };
437
- switch (event.type) {
438
- case 'conversation_created':
439
- setConversationId(event.data.conversation_id);
440
- break;
441
- case 'planning':
442
- case 'tool_start':
443
- case 'agent_start':
444
- case 'agent_response_start':
445
- case 'synthesis_start':
446
- collectedEvents = [...collectedEvents, newEvent];
447
- setExecutionEvents(collectedEvents);
448
- break;
449
- case 'tool_result':
450
- case 'agent_result':
451
- case 'routing_decision':
452
- case 'model_selected':
453
- collectedEvents = [...collectedEvents, newEvent];
454
- setExecutionEvents(collectedEvents);
455
- break;
456
- case 'content_chunk':
457
- // Only append if chunk is defined and not empty
458
- if (event.data.chunk !== undefined && event.data.chunk !== null) {
459
- fullContent += event.data.chunk;
460
- setStreamingContent(fullContent);
461
- }
462
- break;
463
- case 'execution_complete':
464
- // Clear execution events when content starts flowing
465
- break;
466
- case 'message_complete':
467
- setConversationId(event.data.conversation_id);
468
- // Add assistant message to display
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) {
469
282
  const assistantMessage = {
470
283
  role: 'assistant',
471
- content: fullContent,
284
+ content: cleanedSoFar,
472
285
  timestamp: new Date(),
286
+ thoughts: accumulated.thoughts.length > 0 ? accumulated.thoughts : undefined,
287
+ agentName: accumulated.agentName,
288
+ parts: accumulated.parts,
473
289
  };
474
290
  setMessages((prev) => [...prev, assistantMessage]);
475
- setStreamingContent('');
476
- setExecutionEvents([]);
477
- // Callback
478
- onResponseReceived?.(fullContent);
479
- break;
480
- case 'error':
481
- const streamErrorMessage = event.data?.error || 'An error occurred';
482
- toast.error(streamErrorMessage);
483
- // Add error message to chat
484
- const streamErrorAssistantMessage = {
485
- role: 'assistant',
486
- content: `⚠️ **Error:** ${streamErrorMessage}`,
487
- timestamp: new Date(),
488
- };
489
- setMessages((prev) => [...prev, streamErrorAssistantMessage]);
490
- setStreamingContent('');
491
- setExecutionEvents([]);
492
- break;
291
+ hasAddedMessage = true;
292
+ }
293
+ setStreamingContent('');
294
+ setThoughts([]);
295
+ setInterimMessages([]);
296
+ setStreamingParts([]);
493
297
  }
494
298
  }
495
299
  }
496
300
  else {
497
- // Non-streaming response
498
- const response = await sendChatMessage(request, { token, agentUrl });
301
+ // Non-streaming fallback
302
+ const response = await sendChatMessage(request, { token: tokenRef.current, agentUrl });
499
303
  setConversationId(response.conversation_id);
500
- // Add assistant message to display
501
304
  const assistantMessage = {
502
305
  role: 'assistant',
503
306
  content: response.content,
504
307
  timestamp: new Date(),
505
308
  };
506
309
  setMessages((prev) => [...prev, assistantMessage]);
507
- // Callback
508
310
  onResponseReceived?.(response.content);
509
311
  }
510
312
  // Clear attachments after successful send
@@ -539,7 +341,6 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
539
341
  finally {
540
342
  setIsLoading(false);
541
343
  setStreamingContent('');
542
- setExecutionEvents([]);
543
344
  setThoughts([]);
544
345
  setInterimMessages([]);
545
346
  setAbortController(null);
@@ -571,7 +372,6 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
571
372
  setMessages([]);
572
373
  setConversationId(undefined);
573
374
  setStreamingContent('');
574
- setExecutionEvents([]);
575
375
  setThoughts([]);
576
376
  setInterimMessages([]);
577
377
  setIsLoading(false);
@@ -592,7 +392,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
592
392
  const response = await fetch(`${agentUrl || ''}/chat/${conversationId}/messages/${messageId}`, {
593
393
  method: 'DELETE',
594
394
  headers: {
595
- 'Authorization': `Bearer ${token}`,
395
+ 'Authorization': `Bearer ${tokenRef.current}`,
596
396
  'Content-Type': 'application/json',
597
397
  },
598
398
  });
@@ -620,7 +420,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
620
420
  const response = await fetch(`${agentUrl || ''}/conversations/${conversationId}`, {
621
421
  method: 'DELETE',
622
422
  headers: {
623
- 'Authorization': `Bearer ${token}`,
423
+ 'Authorization': `Bearer ${tokenRef.current}`,
624
424
  'Content-Type': 'application/json',
625
425
  },
626
426
  });
@@ -689,9 +489,7 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
689
489
  });
690
490
  }
691
491
  return displayMessages;
692
- })(), streamingContent: !useAgenticStreaming ? streamingContent : undefined, streamingAgentName: streamingAgentName, isLoading: !useAgenticStreaming && isLoading && messages.length === 0, onDeleteMessage: handleDeleteMessage, onRetryMessage: handleRetryMessage, onSuggestedAction: (action) => handleSubmit(null, action) })), useAgenticStreaming && (isLoading || streamingContent) && !promptActive && (_jsxs("div", { className: "flex gap-4 justify-start", children: [_jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("div", { className: "w-8 h-8 rounded-full bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center flex-shrink-0 text-white font-semibold text-sm", children: streamingAgentName?.charAt(0).toUpperCase() || 'A' }), streamingAgentName && (_jsx("span", { className: "text-[10px] text-gray-500 dark:text-gray-400 font-medium capitalize", children: streamingAgentName }))] }), _jsx("div", { className: "max-w-3xl flex-1", children: _jsxs("div", { className: "rounded-lg px-4 py-3 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100", children: [thoughts.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-2 mb-2 text-xs", children: _jsx(ThinkingToggle, { thoughts: thoughts, isActive: isLoading && !streamingContent }) })), streamingParts.filter(p => p.type === 'tool_call').length > 0 && (_jsx("div", { className: "mb-2", children: streamingParts
693
- .filter((p) => p.type === 'tool_call')
694
- .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..." })] }))] }) })] })), !useAgenticStreaming && isLoading && executionEvents.length > 0 && !streamingContent && (_jsx(ExecutionStatus, { events: executionEvents, isActive: isLoading })), !useAgenticStreaming && isLoading && !streamingContent && executionEvents.length === 0 && (_jsxs("div", { className: "flex gap-3 justify-start", children: [_jsx("div", { className: "bg-blue-600 dark:bg-blue-500 rounded-full p-2 h-8 w-8 flex items-center justify-center flex-shrink-0", children: _jsx(Bot, { className: "w-4 h-4 text-white" }) }), _jsx("div", { className: "max-w-[80%] rounded-lg p-4 bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Loader2, { className: "w-4 h-4 animate-spin" }), _jsx("span", { className: "text-sm", 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) => {
695
493
  setInput(e.target.value);
696
494
  if (textareaRef.current) {
697
495
  textareaRef.current.style.height = 'auto';
@@ -700,10 +498,10 @@ export function ChatInterface({ token, agentUrl, agentId, enableWebSearch = fals
700
498
  }, onKeyDown: (e) => {
701
499
  if (e.key === 'Enter' && !e.shiftKey) {
702
500
  e.preventDefault();
703
- if (input.trim() && (!isLoading || promptActive)) {
501
+ if (input.trim()) {
704
502
  handleSubmit(e);
705
503
  }
706
504
  }
707
- }, 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: isLoading && !promptActive }), 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" }) })] }) }))] }));
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" }) })] }) }))] }));
708
506
  }
709
507
  //# sourceMappingURL=ChatInterface.js.map