@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.
- package/dist/components/chat/ChatContainer.d.ts.map +1 -1
- package/dist/components/chat/ChatContainer.js +155 -14
- package/dist/components/chat/ChatContainer.js.map +1 -1
- package/dist/components/chat/ChatInterface.d.ts +37 -14
- package/dist/components/chat/ChatInterface.d.ts.map +1 -1
- package/dist/components/chat/ChatInterface.js +645 -83
- package/dist/components/chat/ChatInterface.js.map +1 -1
- package/dist/components/chat/FullChatInterface.d.ts.map +1 -1
- package/dist/components/chat/FullChatInterface.js +4 -0
- package/dist/components/chat/FullChatInterface.js.map +1 -1
- package/dist/components/chat/LegacyChatInterface.d.ts +16 -0
- package/dist/components/chat/LegacyChatInterface.d.ts.map +1 -0
- package/dist/components/chat/LegacyChatInterface.js +134 -0
- package/dist/components/chat/LegacyChatInterface.js.map +1 -0
- package/dist/components/chat/MessageList.d.ts +4 -1
- package/dist/components/chat/MessageList.d.ts.map +1 -1
- package/dist/components/chat/MessageList.js +64 -2
- package/dist/components/chat/MessageList.js.map +1 -1
- package/dist/components/chat/SimpleChatInterface.d.ts +9 -38
- package/dist/components/chat/SimpleChatInterface.d.ts.map +1 -1
- package/dist/components/chat/SimpleChatInterface.js +1 -596
- package/dist/components/chat/SimpleChatInterface.js.map +1 -1
- package/dist/components/chat/StreamingToolCard.d.ts +11 -0
- package/dist/components/chat/StreamingToolCard.d.ts.map +1 -0
- package/dist/components/chat/StreamingToolCard.js +20 -0
- package/dist/components/chat/StreamingToolCard.js.map +1 -0
- package/dist/components/chat/ThinkingToggle.d.ts +4 -5
- package/dist/components/chat/ThinkingToggle.d.ts.map +1 -1
- package/dist/components/chat/ThinkingToggle.js +62 -12
- package/dist/components/chat/ThinkingToggle.js.map +1 -1
- package/dist/components/chat/chat-utils.d.ts +297 -0
- package/dist/components/chat/chat-utils.d.ts.map +1 -0
- package/dist/components/chat/chat-utils.js +43 -0
- package/dist/components/chat/chat-utils.js.map +1 -0
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -1
- package/dist/components/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/agent/chat-client.d.ts.map +1 -1
- package/dist/lib/agent/chat-client.js +7 -1
- package/dist/lib/agent/chat-client.js.map +1 -1
- package/dist/lib/data/documents.d.ts.map +1 -1
- package/dist/lib/data/documents.js +21 -3
- package/dist/lib/data/documents.js.map +1 -1
- package/dist/types/chat.d.ts +24 -0
- package/dist/types/chat.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,597 +1,2 @@
|
|
|
1
|
-
|
|
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
|