@jazzmind/busibox-app 3.0.29 → 3.0.31

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 (49) hide show
  1. package/dist/components/chat/ChatContainer.d.ts.map +1 -1
  2. package/dist/components/chat/ChatContainer.js +155 -14
  3. package/dist/components/chat/ChatContainer.js.map +1 -1
  4. package/dist/components/chat/ChatInterface.d.ts +37 -14
  5. package/dist/components/chat/ChatInterface.d.ts.map +1 -1
  6. package/dist/components/chat/ChatInterface.js +645 -83
  7. package/dist/components/chat/ChatInterface.js.map +1 -1
  8. package/dist/components/chat/FullChatInterface.d.ts.map +1 -1
  9. package/dist/components/chat/FullChatInterface.js +4 -0
  10. package/dist/components/chat/FullChatInterface.js.map +1 -1
  11. package/dist/components/chat/LegacyChatInterface.d.ts +16 -0
  12. package/dist/components/chat/LegacyChatInterface.d.ts.map +1 -0
  13. package/dist/components/chat/LegacyChatInterface.js +134 -0
  14. package/dist/components/chat/LegacyChatInterface.js.map +1 -0
  15. package/dist/components/chat/MessageList.d.ts +4 -1
  16. package/dist/components/chat/MessageList.d.ts.map +1 -1
  17. package/dist/components/chat/MessageList.js +64 -2
  18. package/dist/components/chat/MessageList.js.map +1 -1
  19. package/dist/components/chat/SimpleChatInterface.d.ts +9 -38
  20. package/dist/components/chat/SimpleChatInterface.d.ts.map +1 -1
  21. package/dist/components/chat/SimpleChatInterface.js +1 -596
  22. package/dist/components/chat/SimpleChatInterface.js.map +1 -1
  23. package/dist/components/chat/StreamingToolCard.d.ts +11 -0
  24. package/dist/components/chat/StreamingToolCard.d.ts.map +1 -0
  25. package/dist/components/chat/StreamingToolCard.js +20 -0
  26. package/dist/components/chat/StreamingToolCard.js.map +1 -0
  27. package/dist/components/chat/ThinkingToggle.d.ts +4 -5
  28. package/dist/components/chat/ThinkingToggle.d.ts.map +1 -1
  29. package/dist/components/chat/ThinkingToggle.js +62 -12
  30. package/dist/components/chat/ThinkingToggle.js.map +1 -1
  31. package/dist/components/chat/chat-utils.d.ts +297 -0
  32. package/dist/components/chat/chat-utils.d.ts.map +1 -0
  33. package/dist/components/chat/chat-utils.js +43 -0
  34. package/dist/components/chat/chat-utils.js.map +1 -0
  35. package/dist/components/index.d.ts +5 -0
  36. package/dist/components/index.d.ts.map +1 -1
  37. package/dist/components/index.js +6 -1
  38. package/dist/components/index.js.map +1 -1
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/lib/agent/chat-client.d.ts.map +1 -1
  42. package/dist/lib/agent/chat-client.js +7 -1
  43. package/dist/lib/agent/chat-client.js.map +1 -1
  44. package/dist/lib/data/documents.d.ts.map +1 -1
  45. package/dist/lib/data/documents.js +21 -3
  46. package/dist/lib/data/documents.js.map +1 -1
  47. package/dist/types/chat.d.ts +24 -0
  48. package/dist/types/chat.d.ts.map +1 -1
  49. package/package.json +1 -1
@@ -1,597 +1,2 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- /**
4
- * Simple Chat Interface Component
5
- *
6
- * A minimal chat interface with:
7
- * - Auto model selection
8
- * - Optional pre-configured tool enablement
9
- * - Optional attachment support
10
- * - Single conversation UI (no history sidebar)
11
- * - No model selector or tool toggles in UI
12
- * - Real-time thinking section for agentic streaming
13
- *
14
- * Perfect for embedded chat widgets or simple Q&A interfaces.
15
- *
16
- * Usage:
17
- * ```typescript
18
- * <SimpleChatInterface
19
- * token="bearer-token"
20
- * enableWebSearch={true}
21
- * enableDocSearch={false}
22
- * allowAttachments={false}
23
- * placeholder="Ask me anything..."
24
- * useAgenticStreaming={true}
25
- * />
26
- * ```
27
- */
28
- import { useState, useRef, useEffect } from 'react';
29
- import { Send, Bot, Loader2, Paperclip, Brain, CheckCircle, AlertCircle, Plus, Trash2, Volume2, X } from 'lucide-react';
30
- import toast from 'react-hot-toast';
31
- import ReactMarkdown from 'react-markdown';
32
- import remarkGfm from 'remark-gfm';
33
- import remarkMath from 'remark-math';
34
- import rehypeKatex from 'rehype-katex';
35
- import 'katex/dist/katex.min.css';
36
- import { MessageList } from './MessageList';
37
- import { ThinkingToggle } from './ThinkingToggle';
38
- const DOC_LINK_RE = /^doc:(.+)$/;
39
- function CitationLink({ href, children, ...props }) {
40
- if (href) {
41
- const match = DOC_LINK_RE.exec(href);
42
- if (match) {
43
- const fileId = match[1];
44
- return (_jsxs("a", { ...props, href: `/documents/${fileId}`, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-xs font-medium text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 px-1.5 py-0.5 rounded hover:bg-blue-100 dark:hover:bg-blue-900/50 no-underline transition-colors border border-blue-200 dark:border-blue-700", title: "Open source document", children: [_jsx("svg", { className: "w-3 h-3 flex-shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" }) }), children] }));
45
- }
46
- }
47
- return _jsx("a", { ...props, href: href, target: "_blank", rel: "noopener noreferrer", children: children });
48
- }
49
- const streamingMarkdownComponents = { a: CitationLink };
50
- import { sendChatMessage, streamChatMessage, streamChatMessageAgentic, getConversationHistory } from '../../lib/agent/chat-client';
51
- const THINK_TAG_RE = /<think>[\s\S]*?<\/think>/g;
52
- function stripThinkTags(text) {
53
- return text.replace(THINK_TAG_RE, '').trim();
54
- }
55
- // Preprocess LaTeX to ensure proper rendering
56
- function preprocessLatex(content) {
57
- return content
58
- .replace(/\\\[/g, '$$')
59
- .replace(/\\\]/g, '$$')
60
- .replace(/\\\(/g, '$')
61
- .replace(/\\\)/g, '$');
62
- }
63
- // Real-time execution status display (legacy, for non-agentic streaming)
64
- function ExecutionStatus({ events, isActive }) {
65
- if (events.length === 0)
66
- return null;
67
- // Only show the last few relevant events
68
- const relevantEvents = events.filter(e => ['planning', 'tool_start', 'tool_result', 'agent_start', 'agent_result', 'synthesis_start'].includes(e.type)).slice(-4);
69
- if (relevantEvents.length === 0)
70
- return null;
71
- 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..." })] }))] }) })] }));
72
- }
73
- export function SimpleChatInterface({ 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, }) {
74
- const [messages, setMessages] = useState([]);
75
- const [input, setInput] = useState('');
76
- const [isLoading, setIsLoading] = useState(false);
77
- const [streamingContent, setStreamingContent] = useState('');
78
- const [executionEvents, setExecutionEvents] = useState([]);
79
- const [thoughts, setThoughts] = useState([]); // For agentic streaming
80
- const [interimMessages, setInterimMessages] = useState([]);
81
- const [streamingAgentName, setStreamingAgentName] = useState(undefined);
82
- const [conversationId, setConversationId] = useState(initialConversationId);
83
- const [attachments, setAttachments] = useState([]);
84
- const [abortController, setAbortController] = useState(null);
85
- const [loadingHistory, setLoadingHistory] = useState(false);
86
- const [showVoiceComingSoon, setShowVoiceComingSoon] = useState(false);
87
- const [quickReplies, setQuickReplies] = useState([]);
88
- const messagesEndRef = useRef(null);
89
- const fileInputRef = useRef(null);
90
- const textareaRef = useRef(null);
91
- const scrollToBottom = () => {
92
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
93
- };
94
- useEffect(() => {
95
- scrollToBottom();
96
- }, [messages, streamingContent]);
97
- // Load conversation history when initialConversationId is provided
98
- useEffect(() => {
99
- if (initialConversationId && token) {
100
- loadConversationHistory(initialConversationId);
101
- }
102
- }, [initialConversationId, token]);
103
- const loadConversationHistory = async (convId) => {
104
- setLoadingHistory(true);
105
- try {
106
- const history = await getConversationHistory(convId, { token, agentUrl });
107
- // Convert to DisplayMessage format
108
- const displayMessages = history.map(msg => ({
109
- id: msg.id,
110
- role: msg.role,
111
- content: msg.content,
112
- createdAt: new Date(msg.created_at || msg.createdAt || Date.now()),
113
- }));
114
- setMessages(displayMessages);
115
- setConversationId(convId);
116
- }
117
- catch (error) {
118
- console.error('Failed to load conversation history:', error);
119
- toast.error('Failed to load conversation history');
120
- }
121
- finally {
122
- setLoadingHistory(false);
123
- }
124
- };
125
- const handleFileSelect = async (e) => {
126
- const files = e.target.files;
127
- if (!files || files.length === 0)
128
- return;
129
- // For simplicity, just store file info
130
- // In a real implementation, you'd upload to data API first
131
- const newAttachments = Array.from(files).map((file) => ({
132
- name: file.name,
133
- type: file.type,
134
- url: URL.createObjectURL(file),
135
- size: file.size,
136
- }));
137
- setAttachments((prev) => [...prev, ...newAttachments]);
138
- toast.success(`${files.length} file(s) attached`);
139
- // Reset input
140
- if (fileInputRef.current) {
141
- fileInputRef.current.value = '';
142
- }
143
- };
144
- const handleRemoveAttachment = (index) => {
145
- setAttachments((prev) => prev.filter((_, i) => i !== index));
146
- };
147
- const handleSubmit = async (e, overrideMessage) => {
148
- e?.preventDefault();
149
- const messageText = overrideMessage ?? input.trim();
150
- if (!messageText || isLoading)
151
- return;
152
- const userMessage = messageText;
153
- setInput('');
154
- if (textareaRef.current) {
155
- textareaRef.current.style.height = 'auto';
156
- }
157
- // Add user message to display
158
- const userDisplayMessage = {
159
- role: 'user',
160
- content: userMessage,
161
- timestamp: new Date(),
162
- };
163
- setMessages((prev) => [...prev, userDisplayMessage]);
164
- // Callback
165
- onMessageSent?.(userMessage);
166
- setIsLoading(true);
167
- setQuickReplies([]);
168
- // Create abort controller for cancellation
169
- const controller = new AbortController();
170
- setAbortController(controller);
171
- try {
172
- const request = {
173
- message: userMessage,
174
- conversation_id: conversationId,
175
- model,
176
- enable_web_search: enableWebSearch,
177
- enable_doc_search: enableDocSearch,
178
- attachments: attachments.length > 0 ? attachments : undefined,
179
- selected_agents: agentId ? [agentId] : undefined,
180
- metadata: metadata || undefined,
181
- };
182
- if (useAgenticStreaming) {
183
- // Agentic streaming with real-time thoughts
184
- setStreamingContent('');
185
- setThoughts([]);
186
- setInterimMessages([]);
187
- setStreamingAgentName(undefined);
188
- let fullContent = '';
189
- let collectedThoughts = [];
190
- let hasAddedMessage = false; // Track if we've already added a message (error or complete)
191
- for await (const event of streamChatMessageAgentic(request, { token, agentUrl, signal: controller.signal })) {
192
- const newEvent = {
193
- type: event.type,
194
- source: event.data?.source,
195
- message: event.data?.message,
196
- data: event.data,
197
- timestamp: new Date(),
198
- };
199
- // Extract agent name from source if available
200
- if (event.data?.source && !event.data.source.includes('dispatcher')) {
201
- setStreamingAgentName(event.data.source);
202
- }
203
- switch (event.type) {
204
- case 'conversation_created':
205
- setConversationId(event.data.conversation_id);
206
- break;
207
- case 'thought':
208
- case 'plan':
209
- case 'progress':
210
- case 'tool_start':
211
- case 'tool_result':
212
- // Add to thoughts section
213
- collectedThoughts = [...collectedThoughts, newEvent];
214
- setThoughts(collectedThoughts);
215
- break;
216
- case 'interim':
217
- {
218
- const payload = event.data || {};
219
- const nested = payload.data || {};
220
- const interimMessage = String(payload.message || '').trim();
221
- const audioUrl = typeof nested.audio_url === 'string' ? nested.audio_url : '';
222
- const rendered = audioUrl
223
- ? `${interimMessage || 'Voice output ready'} (${audioUrl})`
224
- : interimMessage;
225
- if (rendered) {
226
- setInterimMessages(prev => [...prev, rendered]);
227
- }
228
- }
229
- break;
230
- case 'content':
231
- // Stream content to message area
232
- // The nested data object contains streaming flags
233
- const contentData = event.data?.data || {};
234
- const messageText = event.data?.message || '';
235
- if (contentData.streaming && contentData.partial) {
236
- fullContent += messageText;
237
- }
238
- else if (contentData.complete) {
239
- // Final marker - content already accumulated
240
- }
241
- else if (messageText) {
242
- fullContent = messageText;
243
- }
244
- setStreamingContent(stripThinkTags(fullContent));
245
- break;
246
- case 'prompt':
247
- {
248
- const promptOptions = event.data?.data?.options || event.data?.options;
249
- if (promptOptions && Array.isArray(promptOptions)) {
250
- setQuickReplies(promptOptions);
251
- }
252
- }
253
- break;
254
- case 'complete':
255
- // Done - add final message
256
- break;
257
- case 'message_complete':
258
- // Only add message if we haven't already (e.g., from an error)
259
- if (!hasAddedMessage) {
260
- setConversationId(event.data.conversation_id);
261
- const cleanedContent = stripThinkTags(fullContent);
262
- if (cleanedContent) {
263
- const assistantMessage = {
264
- role: 'assistant',
265
- content: cleanedContent,
266
- timestamp: new Date(),
267
- thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
268
- agentName: streamingAgentName,
269
- };
270
- setMessages((prev) => [...prev, assistantMessage]);
271
- hasAddedMessage = true;
272
- }
273
- setStreamingContent('');
274
- setThoughts([]);
275
- setInterimMessages([]);
276
- setStreamingAgentName(undefined);
277
- setIsLoading(false);
278
- // Callback
279
- if (cleanedContent) {
280
- onResponseReceived?.(cleanedContent);
281
- }
282
- }
283
- break;
284
- case 'error':
285
- const errorMessage = event.data?.message || event.data?.error || 'An error occurred';
286
- const errorSource = event.data?.source || event.data?.data?.source || '';
287
- const isToolError = errorSource && !errorSource.includes('agent') && !errorSource.includes('dispatcher');
288
- if (isToolError) {
289
- // Tool-level error: log it as a thought, don't wipe content.
290
- // The agent will continue and produce a final response.
291
- collectedThoughts = [...collectedThoughts, {
292
- type: 'error',
293
- source: errorSource,
294
- message: `Tool error (${errorSource}): ${errorMessage}`,
295
- data: event.data,
296
- timestamp: new Date(),
297
- }];
298
- setThoughts(collectedThoughts);
299
- }
300
- else {
301
- // Fatal error: show toast and add error message
302
- toast.error(errorMessage);
303
- if (!hasAddedMessage) {
304
- const errorAssistantMessage = {
305
- role: 'assistant',
306
- content: fullContent.trim()
307
- ? `${fullContent}\n\n⚠️ **Error:** ${errorMessage}`
308
- : `⚠️ **Error:** ${errorMessage}`,
309
- timestamp: new Date(),
310
- thoughts: collectedThoughts.length > 0 ? collectedThoughts : undefined,
311
- agentName: streamingAgentName,
312
- };
313
- setMessages((prev) => [...prev, errorAssistantMessage]);
314
- hasAddedMessage = true;
315
- }
316
- setStreamingContent('');
317
- setThoughts([]);
318
- setInterimMessages([]);
319
- setStreamingAgentName(undefined);
320
- }
321
- break;
322
- }
323
- }
324
- }
325
- else if (useStreaming) {
326
- // Standard streaming response
327
- setStreamingContent('');
328
- setExecutionEvents([]);
329
- let fullContent = '';
330
- let collectedEvents = [];
331
- for await (const event of streamChatMessage(request, { token, agentUrl, signal: controller.signal })) {
332
- const newEvent = {
333
- type: event.type,
334
- message: event.data?.message,
335
- data: event.data,
336
- timestamp: new Date(),
337
- };
338
- switch (event.type) {
339
- case 'conversation_created':
340
- setConversationId(event.data.conversation_id);
341
- break;
342
- case 'planning':
343
- case 'tool_start':
344
- case 'agent_start':
345
- case 'agent_response_start':
346
- case 'synthesis_start':
347
- collectedEvents = [...collectedEvents, newEvent];
348
- setExecutionEvents(collectedEvents);
349
- break;
350
- case 'tool_result':
351
- case 'agent_result':
352
- case 'routing_decision':
353
- case 'model_selected':
354
- collectedEvents = [...collectedEvents, newEvent];
355
- setExecutionEvents(collectedEvents);
356
- break;
357
- case 'content_chunk':
358
- // Only append if chunk is defined and not empty
359
- if (event.data.chunk !== undefined && event.data.chunk !== null) {
360
- fullContent += event.data.chunk;
361
- setStreamingContent(fullContent);
362
- }
363
- break;
364
- case 'execution_complete':
365
- // Clear execution events when content starts flowing
366
- break;
367
- case 'message_complete':
368
- setConversationId(event.data.conversation_id);
369
- // Add assistant message to display
370
- const assistantMessage = {
371
- role: 'assistant',
372
- content: fullContent,
373
- timestamp: new Date(),
374
- };
375
- setMessages((prev) => [...prev, assistantMessage]);
376
- setStreamingContent('');
377
- setExecutionEvents([]);
378
- // Callback
379
- onResponseReceived?.(fullContent);
380
- break;
381
- case 'error':
382
- const streamErrorMessage = event.data?.error || 'An error occurred';
383
- toast.error(streamErrorMessage);
384
- // Add error message to chat
385
- const streamErrorAssistantMessage = {
386
- role: 'assistant',
387
- content: `⚠️ **Error:** ${streamErrorMessage}`,
388
- timestamp: new Date(),
389
- };
390
- setMessages((prev) => [...prev, streamErrorAssistantMessage]);
391
- setStreamingContent('');
392
- setExecutionEvents([]);
393
- break;
394
- }
395
- }
396
- }
397
- else {
398
- // Non-streaming response
399
- const response = await sendChatMessage(request, { token, agentUrl });
400
- setConversationId(response.conversation_id);
401
- // Add assistant message to display
402
- const assistantMessage = {
403
- role: 'assistant',
404
- content: response.content,
405
- timestamp: new Date(),
406
- };
407
- setMessages((prev) => [...prev, assistantMessage]);
408
- // Callback
409
- onResponseReceived?.(response.content);
410
- }
411
- // Clear attachments after successful send
412
- setAttachments([]);
413
- }
414
- catch (error) {
415
- if (error.name === 'AbortError') {
416
- // Request was cancelled - add partial response if any
417
- if (streamingContent) {
418
- const partialMessage = {
419
- role: 'assistant',
420
- content: streamingContent + '\n\n*[Response interrupted]*',
421
- timestamp: new Date(),
422
- thoughts: thoughts.length > 0 ? thoughts : undefined,
423
- };
424
- setMessages((prev) => [...prev, partialMessage]);
425
- }
426
- toast('Response cancelled', { icon: '⏹️' });
427
- }
428
- else {
429
- console.error('Chat error:', error);
430
- toast.error(error.message || 'Failed to send message');
431
- // Add error message
432
- const errorMessage = {
433
- role: 'assistant',
434
- content: `❌ Error: ${error.message || 'Failed to send message'}`,
435
- timestamp: new Date(),
436
- };
437
- setMessages((prev) => [...prev, errorMessage]);
438
- }
439
- }
440
- finally {
441
- setIsLoading(false);
442
- setStreamingContent('');
443
- setExecutionEvents([]);
444
- setThoughts([]);
445
- setInterimMessages([]);
446
- setAbortController(null);
447
- }
448
- };
449
- const handleCancel = () => {
450
- if (abortController) {
451
- abortController.abort();
452
- }
453
- };
454
- const handleQuickReply = (reply) => {
455
- setQuickReplies([]);
456
- handleSubmit(null, reply);
457
- };
458
- const handleNewChat = () => {
459
- // Cancel any ongoing request
460
- if (abortController) {
461
- abortController.abort();
462
- }
463
- // Reset all state
464
- setMessages([]);
465
- setConversationId(undefined);
466
- setStreamingContent('');
467
- setExecutionEvents([]);
468
- setThoughts([]);
469
- setInterimMessages([]);
470
- setIsLoading(false);
471
- setAttachments([]);
472
- setStreamingAgentName(undefined);
473
- setQuickReplies([]);
474
- setInput('');
475
- toast.success('Started new chat');
476
- };
477
- const handleDeleteMessage = async (messageId) => {
478
- if (!conversationId) {
479
- // No conversation - just remove from local state
480
- setMessages(prev => prev.filter(m => m.id !== messageId));
481
- return;
482
- }
483
- try {
484
- const response = await fetch(`${agentUrl || ''}/chat/${conversationId}/messages/${messageId}`, {
485
- method: 'DELETE',
486
- headers: {
487
- 'Authorization': `Bearer ${token}`,
488
- 'Content-Type': 'application/json',
489
- },
490
- });
491
- if (!response.ok) {
492
- throw new Error('Failed to delete message');
493
- }
494
- setMessages(prev => prev.filter(m => m.id !== messageId));
495
- toast.success('Message deleted');
496
- }
497
- catch (error) {
498
- console.error('Failed to delete message:', error);
499
- toast.error('Failed to delete message');
500
- }
501
- };
502
- const handleDeleteConversation = async () => {
503
- if (!conversationId) {
504
- // No conversation to delete, just clear local messages
505
- handleNewChat();
506
- return;
507
- }
508
- if (!confirm('Are you sure you want to delete this conversation?'))
509
- return;
510
- const deletedId = conversationId;
511
- try {
512
- const response = await fetch(`${agentUrl || ''}/conversations/${conversationId}`, {
513
- method: 'DELETE',
514
- headers: {
515
- 'Authorization': `Bearer ${token}`,
516
- 'Content-Type': 'application/json',
517
- },
518
- });
519
- if (!response.ok) {
520
- throw new Error('Failed to delete conversation');
521
- }
522
- // Clear local state
523
- setMessages([]);
524
- setConversationId(undefined);
525
- setStreamingContent('');
526
- setThoughts([]);
527
- setInterimMessages([]);
528
- setIsLoading(false);
529
- setAttachments([]);
530
- setStreamingAgentName(undefined);
531
- setInput('');
532
- // Notify parent about the deletion
533
- onConversationDeleted?.(deletedId);
534
- toast.success('Conversation deleted');
535
- }
536
- catch (error) {
537
- console.error('Failed to delete conversation:', error);
538
- toast.error('Failed to delete conversation');
539
- }
540
- };
541
- const handleRetryMessage = async (messageContent, attachmentIds) => {
542
- // Delete the last assistant message if it exists
543
- if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
544
- setMessages(prev => prev.slice(0, -1));
545
- }
546
- // Delete the user message that we're retrying
547
- if (messages.length > 0 && messages[messages.length - 1].role === 'user') {
548
- setMessages(prev => prev.slice(0, -1));
549
- }
550
- // Re-send the message by setting input and triggering submit
551
- setInput(messageContent);
552
- // Small delay to ensure state is updated, then trigger submit
553
- setTimeout(() => {
554
- const form = document.querySelector('form');
555
- if (form) {
556
- form.requestSubmit();
557
- }
558
- }, 50);
559
- };
560
- return (_jsxs("div", { className: `flex flex-col h-full min-h-0 bg-white dark:bg-gray-900 ${className}`, children: [_jsxs("div", { className: "flex-shrink-0 flex items-center gap-2 px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800", children: [_jsx(Bot, { className: "w-5 h-5 text-blue-600 dark:text-blue-400" }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx("h3", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 truncate", children: "AI Assistant" }) }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsxs("button", { onClick: () => setShowVoiceComingSoon(true), className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white bg-white dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 border border-gray-200 dark:border-gray-600 rounded-lg transition-colors", title: "Voice mode (coming soon)", children: [_jsx(Volume2, { className: "w-3.5 h-3.5" }), _jsx("span", { children: "Voice" })] }), _jsxs("button", { onClick: handleNewChat, className: "flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white bg-white dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 border border-gray-200 dark:border-gray-600 rounded-lg transition-colors", title: "Start new chat", children: [_jsx(Plus, { className: "w-3.5 h-3.5" }), _jsx("span", { children: "New Chat" })] }), conversationId && (_jsx("button", { onClick: handleDeleteConversation, className: "flex items-center gap-1.5 p-1.5 text-xs font-medium text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors", title: "Delete this conversation", children: _jsx(Trash2, { className: "w-4 h-4" }) }))] })] }), _jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto p-4 space-y-4 bg-white dark:bg-gray-900", children: [loadingHistory && (_jsxs("div", { className: "flex items-center justify-center py-8", children: [_jsx(Loader2, { className: "w-8 h-8 animate-spin text-blue-600 dark:text-blue-400" }), _jsx("span", { className: "ml-3 text-gray-600 dark:text-gray-400", children: "Loading conversation..." })] })), !loadingHistory && (_jsx(MessageList, { messages: (() => {
561
- // Build messages array, optionally including welcome message
562
- const displayMessages = messages.map(m => ({
563
- id: m.id || `msg-${Math.random()}`,
564
- role: m.role,
565
- content: m.content,
566
- createdAt: m.createdAt || new Date(),
567
- agentName: m.agentName,
568
- thoughts: m.thoughts,
569
- }));
570
- // Add welcome message as first assistant message if no messages yet
571
- if (welcomeMessage && displayMessages.length === 0) {
572
- displayMessages.unshift({
573
- id: 'welcome',
574
- role: 'assistant',
575
- content: welcomeMessage,
576
- createdAt: new Date(),
577
- agentName: undefined,
578
- thoughts: undefined,
579
- });
580
- }
581
- return displayMessages;
582
- })(), streamingContent: !useAgenticStreaming ? streamingContent : undefined, streamingAgentName: streamingAgentName, isLoading: !useAgenticStreaming && isLoading && messages.length === 0, onDeleteMessage: handleDeleteMessage, onRetryMessage: handleRetryMessage })), useAgenticStreaming && (isLoading || streamingContent) && (_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 }) })), 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 && !isLoading && (_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) => {
583
- setInput(e.target.value);
584
- if (textareaRef.current) {
585
- textareaRef.current.style.height = 'auto';
586
- textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
587
- }
588
- }, onKeyDown: (e) => {
589
- if (e.key === 'Enter' && !e.shiftKey) {
590
- e.preventDefault();
591
- if (input.trim() && !isLoading) {
592
- handleSubmit(e);
593
- }
594
- }
595
- }, placeholder: 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 }), isLoading ? (_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" }) })] }) }))] }));
596
- }
1
+ export { ChatInterface as SimpleChatInterface } from './ChatInterface';
597
2
  //# sourceMappingURL=SimpleChatInterface.js.map