uumerrr684 commited on
Commit
4edfa56
·
verified ·
1 Parent(s): ea943de

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +603 -472
app.py CHANGED
@@ -1,473 +1,604 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Send, Plus, MessageSquare, Settings, Users, Download, Trash2, RefreshCw, Bot } from 'lucide-react';
3
-
4
- const ChatFlowApp = () => {
5
- const [messages, setMessages] = useState([]);
6
- const [currentMessage, setCurrentMessage] = useState('');
7
- const [selectedModel, setSelectedModel] = useState('openai/gpt-3.5-turbo');
8
- const [isLoading, setIsLoading] = useState(false);
9
- const [sessions, setSessions] = useState([]);
10
- const [currentSessionId, setCurrentSessionId] = useState('default');
11
- const [onlineUsers, setOnlineUsers] = useState(1);
12
- const [apiStatus, setApiStatus] = useState('Connected');
13
- const [autoSave, setAutoSave] = useState(true);
14
- const messagesEndRef = useRef(null);
15
- const [userId] = useState('User-' + Math.random().toString(36).substr(2, 8));
16
-
17
- const models = [
18
- { name: "GPT-3.5 Turbo", id: "openai/gpt-3.5-turbo" },
19
- { name: "LLaMA 3.1 8B", id: "meta-llama/llama-3.1-8b-instruct" },
20
- { name: "LLaMA 3.1 70B", id: "meta-llama/llama-3.1-70b-instruct" },
21
- { name: "DeepSeek Chat v3", id: "deepseek/deepseek-chat-v3-0324:free" },
22
- { name: "DeepSeek R1", id: "deepseek/deepseek-r1-0528:free" },
23
- { name: "Qwen3 Coder", id: "qwen/qwen3-coder:free" },
24
- { name: "Microsoft MAI DS R1", id: "microsoft/mai-ds-r1:free" },
25
- { name: "Gemma 3 27B", id: "google/gemma-3-27b-it:free" },
26
- { name: "Gemma 3 4B", id: "google/gemma-3-4b-it:free" },
27
- { name: "Auto (Best Available)", id: "openrouter/auto" }
28
- ];
29
-
30
- const scrollToBottom = () => {
31
- messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
32
- };
33
-
34
- useEffect(() => {
35
- scrollToBottom();
36
- }, [messages]);
37
-
38
- useEffect(() => {
39
- // Simulate online users update
40
- const interval = setInterval(() => {
41
- setOnlineUsers(Math.floor(Math.random() * 5) + 1);
42
- }, 10000);
43
- return () => clearInterval(interval);
44
- }, []);
45
-
46
- useEffect(() => {
47
- // Check API status on component mount
48
- const checkAPIStatus = async () => {
49
- try {
50
- const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
51
- window.OPENROUTER_API_KEY ||
52
- process.env.OPENROUTER_API_KEY;
53
-
54
- if (!OPENROUTER_API_KEY) {
55
- setApiStatus('No API Key');
56
- return;
57
- }
58
-
59
- const response = await fetch("https://openrouter.ai/api/v1/models", {
60
- headers: { "Authorization": `Bearer ${OPENROUTER_API_KEY}` }
61
- });
62
-
63
- setApiStatus(response.ok ? 'Connected' : 'Error');
64
- } catch {
65
- setApiStatus('Error');
66
- }
67
- };
68
-
69
- checkAPIStatus();
70
- }, []);
71
-
72
- const generateChatTitle = (msgs) => {
73
- if (!msgs || msgs.length === 0) return "New Chat";
74
- const firstUserMessage = msgs.find(m => m.role === 'user');
75
- if (!firstUserMessage) return "New Chat";
76
- const content = firstUserMessage.content;
77
- return content.length > 30 ? content.substring(0, 30) + "..." : content;
78
- };
79
-
80
- const startNewChat = () => {
81
- if (messages.length > 0) {
82
- const newSession = {
83
- id: 'session-' + Date.now(),
84
- title: generateChatTitle(messages),
85
- messages: [...messages],
86
- createdAt: new Date().toISOString(),
87
- updatedAt: new Date().toISOString()
88
- };
89
- setSessions(prev => [newSession, ...prev]);
90
- }
91
- setMessages([]);
92
- setCurrentSessionId('session-' + Date.now());
93
- };
94
-
95
- const loadSession = (session) => {
96
- if (messages.length > 0) {
97
- const currentSession = {
98
- id: currentSessionId,
99
- title: generateChatTitle(messages),
100
- messages: [...messages],
101
- createdAt: new Date().toISOString(),
102
- updatedAt: new Date().toISOString()
103
- };
104
- setSessions(prev => {
105
- const filtered = prev.filter(s => s.id !== currentSessionId);
106
- return [currentSession, ...filtered];
107
- });
108
- }
109
- setMessages(session.messages);
110
- setCurrentSessionId(session.id);
111
- };
112
-
113
- const deleteSession = (sessionId) => {
114
- setSessions(prev => prev.filter(s => s.id !== sessionId));
115
- if (sessionId === currentSessionId) {
116
- startNewChat();
117
- }
118
- };
119
-
120
- // OpenRouter API integration
121
- const getAIResponse = async (userMessage) => {
122
- setIsLoading(true);
123
-
124
- try {
125
- // Get API key from environment variables (Hugging Face Spaces secrets)
126
- const OPENROUTER_API_KEY = process.env.REACT_APP_OPENROUTER_API_KEY ||
127
- window.OPENROUTER_API_KEY ||
128
- process.env.OPENROUTER_API_KEY;
129
-
130
- if (!OPENROUTER_API_KEY) {
131
- throw new Error("No API key found. Please add OPENROUTER_API_KEY to environment variables.");
132
- }
133
-
134
- const url = "https://openrouter.ai/api/v1/chat/completions";
135
- const headers = {
136
- "Content-Type": "application/json",
137
- "Authorization": `Bearer ${OPENROUTER_API_KEY}`,
138
- "HTTP-Referer": "https://huggingface.co/spaces",
139
- "X-Title": "Chat Flow AI Assistant"
140
- };
141
-
142
- // Prepare messages for API
143
- const apiMessages = [
144
- { role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." },
145
- ...messages.map(msg => ({
146
- role: msg.role,
147
- content: msg.content.split('\n\n---\n*Response created by:')[0] // Remove attribution from content
148
- })),
149
- { role: "user", content: userMessage }
150
- ];
151
-
152
- const data = {
153
- model: selectedModel,
154
- messages: apiMessages,
155
- stream: false, // Set to false for simpler handling in React
156
- max_tokens: 2000,
157
- temperature: 0.7,
158
- top_p: 1,
159
- frequency_penalty: 0,
160
- presence_penalty: 0
161
- };
162
-
163
- const response = await fetch(url, {
164
- method: 'POST',
165
- headers: headers,
166
- body: JSON.stringify(data)
167
- });
168
-
169
- if (!response.ok) {
170
- let errorDetail = "";
171
- try {
172
- const errorData = await response.json();
173
- errorDetail = errorData.error?.message || `HTTP ${response.status}`;
174
- } catch {
175
- errorDetail = `HTTP ${response.status}: ${response.statusText}`;
176
- }
177
- throw new Error(`API Error: ${errorDetail}. Please try a different model or check your API key.`);
178
- }
179
-
180
- const result = await response.json();
181
- const aiResponse = result.choices[0].message.content;
182
- const selectedModelName = models.find(m => m.id === selectedModel)?.name || "AI";
183
-
184
- setIsLoading(false);
185
- return aiResponse + `\n\n---\n*Response created by: **${selectedModelName}***`;
186
-
187
- } catch (error) {
188
- setIsLoading(false);
189
- console.error('API Error:', error);
190
-
191
- if (error.message.includes('timeout')) {
192
- return "Request timed out. Please try again with a shorter message or different model.";
193
- } else if (error.message.includes('Connection')) {
194
- return "Connection error. Please check your internet connection and try again.";
195
- } else {
196
- return `Error: ${error.message}`;
197
- }
198
- }
199
- };
200
-
201
- const handleSendMessage = async (e) => {
202
- e.preventDefault();
203
- if (!currentMessage.trim() || isLoading) return;
204
-
205
- const userMessage = {
206
- role: 'user',
207
- content: currentMessage.trim(),
208
- timestamp: new Date().toISOString()
209
- };
210
-
211
- setMessages(prev => [...prev, userMessage]);
212
- const messageToSend = currentMessage.trim();
213
- setCurrentMessage('');
214
-
215
- try {
216
- const aiResponse = await getAIResponse(messageToSend);
217
- const assistantMessage = {
218
- role: 'assistant',
219
- content: aiResponse,
220
- timestamp: new Date().toISOString()
221
- };
222
- setMessages(prev => [...prev, assistantMessage]);
223
- } catch (error) {
224
- const errorMessage = {
225
- role: 'assistant',
226
- content: 'Sorry, I encountered an error. Please try again.',
227
- timestamp: new Date().toISOString()
228
- };
229
- setMessages(prev => [...prev, errorMessage]);
230
- }
231
- };
232
-
233
- const clearChat = () => {
234
- setMessages([]);
235
- };
236
-
237
- const downloadHistory = () => {
238
- const dataStr = JSON.stringify(messages, null, 2);
239
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
240
- const url = URL.createObjectURL(dataBlob);
241
- const link = document.createElement('a');
242
- link.href = url;
243
- link.download = `chat_history_${new Date().toISOString().split('T')[0]}.json`;
244
- link.click();
245
- URL.revokeObjectURL(url);
246
- };
247
-
248
- const getSelectedModelName = () => {
249
- return models.find(m => m.id === selectedModel)?.name || "GPT-3.5 Turbo";
250
- };
251
-
252
- return (
253
- <div className="flex h-screen bg-gray-900 text-white">
254
- {/* Sidebar */}
255
- <div className="w-80 bg-gray-800 border-r border-gray-700 flex flex-col">
256
- {/* Header */}
257
- <div className="p-4 border-b border-gray-700">
258
- <div className="flex items-center gap-3 mb-4">
259
- <div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
260
- <Bot className="w-5 h-5" />
261
- </div>
262
- <h1 className="text-xl font-semibold">Chat Flow</h1>
263
- </div>
264
- <button
265
- onClick={startNewChat}
266
- className="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
267
- >
268
- <Plus className="w-4 h-4" />
269
- New Chat
270
- </button>
271
- </div>
272
-
273
- {/* Chat History */}
274
- <div className="flex-1 overflow-y-auto p-4">
275
- <h3 className="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
276
- {sessions.length > 0 ? (
277
- <div className="space-y-2">
278
- {sessions.map((session) => (
279
- <div key={session.id} className="group flex items-center gap-2">
280
- <button
281
- onClick={() => loadSession(session)}
282
- className={`flex-1 text-left px-3 py-2 rounded-lg transition-colors ${
283
- session.id === currentSessionId
284
- ? 'bg-blue-600 text-white'
285
- : 'bg-gray-700 hover:bg-gray-600 text-gray-300'
286
- }`}
287
- >
288
- <div className="text-sm font-medium truncate">{session.title}</div>
289
- <div className="text-xs text-gray-400">
290
- {new Date(session.updatedAt).toLocaleDateString()}
291
- </div>
292
- </button>
293
- <button
294
- onClick={() => deleteSession(session.id)}
295
- className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
296
- >
297
- <Trash2 className="w-4 h-4" />
298
- </button>
299
- </div>
300
- ))}
301
- </div>
302
- ) : (
303
- <p className="text-gray-500 text-sm">No previous chats yet</p>
304
- )}
305
- </div>
306
-
307
- {/* Settings */}
308
- <div className="p-4 border-t border-gray-700 space-y-4">
309
- {/* Online Users */}
310
- <div>
311
- <h3 className="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
312
- <div className="flex items-center gap-2 text-sm">
313
- <div className="w-2 h-2 bg-green-500 rounded-full"></div>
314
- <span>{onlineUsers === 1 ? 'Just you online' : `${onlineUsers} people online`}</span>
315
- </div>
316
- <p className="text-xs text-gray-500 mt-1">You: {userId}</p>
317
- </div>
318
-
319
- {/* Model Selection */}
320
- <div>
321
- <label className="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
322
- <select
323
- value={selectedModel}
324
- onChange={(e) => setSelectedModel(e.target.value)}
325
- className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
326
- >
327
- {models.map((model) => (
328
- <option key={model.id} value={model.id}>
329
- {model.name}
330
- </option>
331
- ))}
332
- </select>
333
- <p className="text-xs text-green-400 mt-1 font-mono">{selectedModel}</p>
334
- </div>
335
-
336
- {/* API Status */}
337
- <div className="flex items-center gap-2 text-sm">
338
- <div className={`w-2 h-2 rounded-full ${apiStatus === 'Connected' ? 'bg-green-500' : 'bg-red-500'}`}></div>
339
- <span>{apiStatus === 'Connected' ? '🟢 API Connected' : '🔴 Connection Issue'}</span>
340
- </div>
341
-
342
- {/* Controls */}
343
- <div className="flex gap-2">
344
- <button
345
- onClick={downloadHistory}
346
- className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
347
- title="Download History"
348
- >
349
- <Download className="w-4 h-4" />
350
- </button>
351
- <button
352
- onClick={clearChat}
353
- className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
354
- title="Clear Chat"
355
- >
356
- <Trash2 className="w-4 h-4" />
357
- </button>
358
- <button
359
- onClick={() => window.location.reload()}
360
- className="flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm"
361
- title="Refresh"
362
- >
363
- <RefreshCw className="w-4 h-4" />
364
- </button>
365
- </div>
366
- </div>
367
- </div>
368
-
369
- {/* Main Chat Area */}
370
- <div className="flex-1 flex flex-col">
371
- {/* Chat Messages */}
372
- <div className="flex-1 overflow-y-auto">
373
- {messages.length === 0 ? (
374
- <div className="h-full flex items-center justify-center">
375
- <div className="text-center max-w-md mx-auto px-4">
376
- <div className="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
377
- <Bot className="w-8 h-8 text-blue-400" />
378
- </div>
379
- <h2 className="text-2xl font-semibold mb-4 text-gray-100">Your personal assistant</h2>
380
- <p className="text-gray-400 leading-relaxed">
381
- A personal assistant streamlines your life by managing tasks, schedules,
382
- and communications efficiently.
383
- </p>
384
- </div>
385
- </div>
386
- ) : (
387
- <div className="p-6 space-y-6 max-w-4xl mx-auto">
388
- {messages.map((message, index) => (
389
- <div key={index} className="flex gap-4">
390
- <div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
391
- {message.role === 'user' ? (
392
- <div className="w-6 h-6 bg-blue-600 rounded-full"></div>
393
- ) : (
394
- <Bot className="w-5 h-5 text-blue-400" />
395
- )}
396
- </div>
397
- <div className="flex-1 min-w-0">
398
- <div className="prose prose-invert max-w-none">
399
- {message.role === 'assistant' && message.content.includes('---\n*Response created by:') ? (
400
- <>
401
- <div className="whitespace-pre-wrap text-gray-100">
402
- {message.content.split('\n\n---\n*Response created by:')[0]}
403
- </div>
404
- <div className="text-xs text-gray-500 mt-2 italic">
405
- Response created by: <strong>{message.content.split('**')[1]}</strong>
406
- </div>
407
- </>
408
- ) : (
409
- <div className="whitespace-pre-wrap text-gray-100">{message.content}</div>
410
- )}
411
- </div>
412
- </div>
413
- </div>
414
- ))}
415
- {isLoading && (
416
- <div className="flex gap-4">
417
- <div className="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
418
- <Bot className="w-5 h-5 text-blue-400" />
419
- </div>
420
- <div className="flex-1">
421
- <div className="flex items-center gap-2 text-gray-400">
422
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
423
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.1s'}}></div>
424
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{animationDelay: '0.2s'}}></div>
425
- </div>
426
- </div>
427
- </div>
428
- )}
429
- <div ref={messagesEndRef} />
430
- </div>
431
- )}
432
- </div>
433
-
434
- {/* Message Input */}
435
- <div className="border-t border-gray-700 p-4">
436
- <div className="max-w-4xl mx-auto">
437
- <div className="flex gap-3">
438
- <div className="flex-1 relative">
439
- <input
440
- type="text"
441
- value={currentMessage}
442
- onChange={(e) => setCurrentMessage(e.target.value)}
443
- onKeyDown={(e) => {
444
- if (e.key === 'Enter' && !e.shiftKey) {
445
- e.preventDefault();
446
- handleSendMessage(e);
447
  }
448
- }}
449
- placeholder="Chat Smarter. Chat many Brains"
450
- className="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
451
- disabled={isLoading}
452
- />
453
- </div>
454
- <button
455
- onClick={handleSendMessage}
456
- disabled={!currentMessage.trim() || isLoading}
457
- className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors flex items-center gap-2"
458
- >
459
- <Send className="w-4 h-4" />
460
- Send
461
- </button>
462
- </div>
463
- <div className="mt-2 text-center">
464
- <span className="text-xs text-gray-500">Currently using: <strong>{getSelectedModelName()}</strong></span>
465
- </div>
466
- </div>
467
- </div>
468
- </div>
469
- </div>
470
- );
471
- };
472
-
473
- export default ChatFlowApp;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Chat Flow 🕷</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.5/babel.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.263.1/umd/lucide.js"></script>
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ <style>
13
+ body { margin: 0; padding: 0; overflow: hidden; }
14
+ .chat-scroll { scrollbar-width: thin; scrollbar-color: #4a5568 #2d3748; }
15
+ .chat-scroll::-webkit-scrollbar { width: 6px; }
16
+ .chat-scroll::-webkit-scrollbar-track { background: #2d3748; }
17
+ .chat-scroll::-webkit-scrollbar-thumb { background: #4a5568; border-radius: 3px; }
18
+ .chat-scroll::-webkit-scrollbar-thumb:hover { background: #718096; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div id="root"></div>
23
+
24
+ <script type="text/babel">
25
+ const { useState, useEffect, useRef } = React;
26
+ const { Send, Plus, Bot, Download, Trash2, RefreshCw } = lucide;
27
+
28
+ const ChatFlowApp = () => {
29
+ const [messages, setMessages] = useState([]);
30
+ const [currentMessage, setCurrentMessage] = useState('');
31
+ const [selectedModel, setSelectedModel] = useState('openai/gpt-3.5-turbo');
32
+ const [isLoading, setIsLoading] = useState(false);
33
+ const [sessions, setSessions] = useState([]);
34
+ const [currentSessionId, setCurrentSessionId] = useState('default');
35
+ const [onlineUsers, setOnlineUsers] = useState(1);
36
+ const [apiStatus, setApiStatus] = useState('Connected');
37
+ const messagesEndRef = useRef(null);
38
+ const [userId] = useState('User-' + Math.random().toString(36).substr(2, 8));
39
+
40
+ const models = [
41
+ { name: "GPT-3.5 Turbo", id: "openai/gpt-3.5-turbo" },
42
+ { name: "LLaMA 3.1 8B", id: "meta-llama/llama-3.1-8b-instruct" },
43
+ { name: "LLaMA 3.1 70B", id: "meta-llama/llama-3.1-70b-instruct" },
44
+ { name: "DeepSeek Chat v3", id: "deepseek/deepseek-chat-v3-0324:free" },
45
+ { name: "DeepSeek R1", id: "deepseek/deepseek-r1-0528:free" },
46
+ { name: "Qwen3 Coder", id: "qwen/qwen3-coder:free" },
47
+ { name: "Microsoft MAI DS R1", id: "microsoft/mai-ds-r1:free" },
48
+ { name: "Gemma 3 27B", id: "google/gemma-3-27b-it:free" },
49
+ { name: "Gemma 3 4B", id: "google/gemma-3-4b-it:free" },
50
+ { name: "Auto (Best Available)", id: "openrouter/auto" }
51
+ ];
52
+
53
+ const scrollToBottom = () => {
54
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
55
+ };
56
+
57
+ useEffect(() => {
58
+ scrollToBottom();
59
+ }, [messages]);
60
+
61
+ useEffect(() => {
62
+ const interval = setInterval(() => {
63
+ setOnlineUsers(Math.floor(Math.random() * 5) + 1);
64
+ }, 10000);
65
+ return () => clearInterval(interval);
66
+ }, []);
67
+
68
+ useEffect(() => {
69
+ const checkAPIStatus = async () => {
70
+ try {
71
+ // Try to get API key from various sources (Hugging Face Spaces)
72
+ const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY ||
73
+ window.HF_TOKEN ||
74
+ (typeof process !== 'undefined' && process.env && process.env.OPENROUTER_API_KEY);
75
+
76
+ if (!OPENROUTER_API_KEY) {
77
+ setApiStatus('No API Key');
78
+ return;
79
+ }
80
+
81
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
82
+ headers: { "Authorization": "Bearer " + OPENROUTER_API_KEY }
83
+ });
84
+
85
+ setApiStatus(response.ok ? 'Connected' : 'Error');
86
+ } catch (e) {
87
+ setApiStatus('Error');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
+ };
90
+
91
+ checkAPIStatus();
92
+ }, []);
93
+
94
+ const generateChatTitle = (msgs) => {
95
+ if (!msgs || msgs.length === 0) return "New Chat";
96
+ const firstUserMessage = msgs.find(m => m.role === 'user');
97
+ if (!firstUserMessage) return "New Chat";
98
+ const content = firstUserMessage.content;
99
+ return content.length > 30 ? content.substring(0, 30) + "..." : content;
100
+ };
101
+
102
+ const startNewChat = () => {
103
+ if (messages.length > 0) {
104
+ const newSession = {
105
+ id: 'session-' + Date.now(),
106
+ title: generateChatTitle(messages),
107
+ messages: messages.slice(),
108
+ createdAt: new Date().toISOString(),
109
+ updatedAt: new Date().toISOString()
110
+ };
111
+ setSessions(prev => [newSession].concat(prev));
112
+ }
113
+ setMessages([]);
114
+ setCurrentSessionId('session-' + Date.now());
115
+ };
116
+
117
+ const loadSession = (session) => {
118
+ if (messages.length > 0) {
119
+ const currentSession = {
120
+ id: currentSessionId,
121
+ title: generateChatTitle(messages),
122
+ messages: messages.slice(),
123
+ createdAt: new Date().toISOString(),
124
+ updatedAt: new Date().toISOString()
125
+ };
126
+ setSessions(prev => {
127
+ const filtered = prev.filter(s => s.id !== currentSessionId);
128
+ return [currentSession].concat(filtered);
129
+ });
130
+ }
131
+ setMessages(session.messages);
132
+ setCurrentSessionId(session.id);
133
+ };
134
+
135
+ const deleteSession = (sessionId) => {
136
+ setSessions(prev => prev.filter(s => s.id !== sessionId));
137
+ if (sessionId === currentSessionId) {
138
+ startNewChat();
139
+ }
140
+ };
141
+
142
+ const getAIResponse = async (userMessage) => {
143
+ setIsLoading(true);
144
+
145
+ try {
146
+ // Get API key from Hugging Face Spaces environment
147
+ const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY ||
148
+ window.HF_TOKEN ||
149
+ (typeof process !== 'undefined' && process.env && process.env.OPENROUTER_API_KEY);
150
+
151
+ if (!OPENROUTER_API_KEY) {
152
+ throw new Error("No API key found. Please add OPENROUTER_API_KEY to Hugging Face Spaces secrets.");
153
+ }
154
+
155
+ const url = "https://openrouter.ai/api/v1/chat/completions";
156
+ const headers = {
157
+ "Content-Type": "application/json",
158
+ "Authorization": "Bearer " + OPENROUTER_API_KEY,
159
+ "HTTP-Referer": "https://huggingface.co/spaces",
160
+ "X-Title": "Chat Flow AI Assistant"
161
+ };
162
+
163
+ const cleanMessages = messages.map(msg => ({
164
+ role: msg.role,
165
+ content: msg.content.split('\n\n---\n*Response created by:')[0]
166
+ }));
167
+
168
+ const apiMessages = [
169
+ { role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." }
170
+ ].concat(cleanMessages).concat([
171
+ { role: "user", content: userMessage }
172
+ ]);
173
+
174
+ const data = {
175
+ model: selectedModel,
176
+ messages: apiMessages,
177
+ stream: false,
178
+ max_tokens: 2000,
179
+ temperature: 0.7,
180
+ top_p: 1,
181
+ frequency_penalty: 0,
182
+ presence_penalty: 0
183
+ };
184
+
185
+ const response = await fetch(url, {
186
+ method: 'POST',
187
+ headers: headers,
188
+ body: JSON.stringify(data)
189
+ });
190
+
191
+ if (!response.ok) {
192
+ let errorDetail = "";
193
+ try {
194
+ const errorData = await response.json();
195
+ errorDetail = errorData.error.message || ("HTTP " + response.status);
196
+ } catch (e) {
197
+ errorDetail = "HTTP " + response.status + ": " + response.statusText;
198
+ }
199
+ throw new Error("API Error: " + errorDetail + ". Please try a different model.");
200
+ }
201
+
202
+ const result = await response.json();
203
+ const aiResponse = result.choices[0].message.content;
204
+ const selectedModelName = models.find(m => m.id === selectedModel);
205
+ const modelName = selectedModelName ? selectedModelName.name : "AI";
206
+
207
+ setIsLoading(false);
208
+ return aiResponse + "\n\n---\n*Response created by: **" + modelName + "***";
209
+
210
+ } catch (error) {
211
+ setIsLoading(false);
212
+ console.error('API Error:', error);
213
+ return "Error: " + error.message;
214
+ }
215
+ };
216
+
217
+ const handleSendMessage = async (e) => {
218
+ if (e && e.preventDefault) {
219
+ e.preventDefault();
220
+ }
221
+ if (!currentMessage.trim() || isLoading) return;
222
+
223
+ const userMessage = {
224
+ role: 'user',
225
+ content: currentMessage.trim(),
226
+ timestamp: new Date().toISOString()
227
+ };
228
+
229
+ setMessages(prev => prev.concat([userMessage]));
230
+ const messageToSend = currentMessage.trim();
231
+ setCurrentMessage('');
232
+
233
+ try {
234
+ const aiResponse = await getAIResponse(messageToSend);
235
+ const assistantMessage = {
236
+ role: 'assistant',
237
+ content: aiResponse,
238
+ timestamp: new Date().toISOString()
239
+ };
240
+ setMessages(prev => prev.concat([assistantMessage]));
241
+ } catch (error) {
242
+ const errorMessage = {
243
+ role: 'assistant',
244
+ content: 'Sorry, I encountered an error. Please try again.',
245
+ timestamp: new Date().toISOString()
246
+ };
247
+ setMessages(prev => prev.concat([errorMessage]));
248
+ }
249
+ };
250
+
251
+ const clearChat = () => {
252
+ setMessages([]);
253
+ };
254
+
255
+ const downloadHistory = () => {
256
+ const dataStr = JSON.stringify(messages, null, 2);
257
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
258
+ const url = URL.createObjectURL(dataBlob);
259
+ const link = document.createElement('a');
260
+ link.href = url;
261
+ link.download = `chat_history_${new Date().toISOString().split('T')[0]}.json`;
262
+ link.click();
263
+ URL.revokeObjectURL(url);
264
+ };
265
+
266
+ const getSelectedModelName = () => {
267
+ return models.find(m => m.id === selectedModel)?.name || "GPT-3.5 Turbo";
268
+ };
269
+
270
+ return React.createElement('div', {
271
+ className: "flex h-screen bg-gray-900 text-white"
272
+ }, [
273
+ // Sidebar
274
+ React.createElement('div', {
275
+ key: 'sidebar',
276
+ className: "w-80 bg-gray-800 border-r border-gray-700 flex flex-col"
277
+ }, [
278
+ // Header
279
+ React.createElement('div', {
280
+ key: 'header',
281
+ className: "p-4 border-b border-gray-700"
282
+ }, [
283
+ React.createElement('div', {
284
+ key: 'title-section',
285
+ className: "flex items-center gap-3 mb-4"
286
+ }, [
287
+ React.createElement('div', {
288
+ key: 'icon',
289
+ className: "w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center"
290
+ }, React.createElement(Bot, { className: "w-5 h-5" })),
291
+ React.createElement('h1', {
292
+ key: 'title',
293
+ className: "text-xl font-semibold"
294
+ }, "Chat Flow")
295
+ ]),
296
+ React.createElement('button', {
297
+ key: 'new-chat-btn',
298
+ onClick: startNewChat,
299
+ className: "w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
300
+ }, [
301
+ React.createElement(Plus, { key: 'plus-icon', className: "w-4 h-4" }),
302
+ "New Chat"
303
+ ])
304
+ ]),
305
+
306
+ // Chat History
307
+ React.createElement('div', {
308
+ key: 'chat-history',
309
+ className: "flex-1 overflow-y-auto p-4 chat-scroll"
310
+ }, [
311
+ React.createElement('h3', {
312
+ key: 'history-title',
313
+ className: "text-sm font-medium text-gray-400 mb-3"
314
+ }, "💬 Chat History"),
315
+ sessions.length > 0
316
+ ? React.createElement('div', {
317
+ key: 'sessions-list',
318
+ className: "space-y-2"
319
+ }, sessions.map((session) =>
320
+ React.createElement('div', {
321
+ key: session.id,
322
+ className: "group flex items-center gap-2"
323
+ }, [
324
+ React.createElement('button', {
325
+ key: 'load-btn',
326
+ onClick: () => loadSession(session),
327
+ className: `flex-1 text-left px-3 py-2 rounded-lg transition-colors ${
328
+ session.id === currentSessionId
329
+ ? 'bg-blue-600 text-white'
330
+ : 'bg-gray-700 hover:bg-gray-600 text-gray-300'
331
+ }`
332
+ }, [
333
+ React.createElement('div', {
334
+ key: 'session-title',
335
+ className: "text-sm font-medium truncate"
336
+ }, session.title),
337
+ React.createElement('div', {
338
+ key: 'session-date',
339
+ className: "text-xs text-gray-400"
340
+ }, new Date(session.updatedAt).toLocaleDateString())
341
+ ]),
342
+ React.createElement('button', {
343
+ key: 'delete-btn',
344
+ onClick: () => deleteSession(session.id),
345
+ className: "opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all"
346
+ }, React.createElement(Trash2, { className: "w-4 h-4" }))
347
+ ])
348
+ ))
349
+ : React.createElement('p', {
350
+ key: 'no-chats',
351
+ className: "text-gray-500 text-sm"
352
+ }, "No previous chats yet")
353
+ ]),
354
+
355
+ // Settings
356
+ React.createElement('div', {
357
+ key: 'settings',
358
+ className: "p-4 border-t border-gray-700 space-y-4"
359
+ }, [
360
+ // Online Users
361
+ React.createElement('div', { key: 'online-users' }, [
362
+ React.createElement('h3', {
363
+ key: 'users-title',
364
+ className: "text-sm font-medium text-gray-400 mb-2"
365
+ }, "👥 Who's Online"),
366
+ React.createElement('div', {
367
+ key: 'users-status',
368
+ className: "flex items-center gap-2 text-sm"
369
+ }, [
370
+ React.createElement('div', {
371
+ key: 'status-dot',
372
+ className: "w-2 h-2 bg-green-500 rounded-full"
373
+ }),
374
+ React.createElement('span', { key: 'status-text' },
375
+ onlineUsers === 1 ? 'Just you online' : `${onlineUsers} people online`)
376
+ ]),
377
+ React.createElement('p', {
378
+ key: 'user-id',
379
+ className: "text-xs text-gray-500 mt-1"
380
+ }, `You: ${userId}`)
381
+ ]),
382
+
383
+ // Model Selection
384
+ React.createElement('div', { key: 'model-selection' }, [
385
+ React.createElement('label', {
386
+ key: 'model-label',
387
+ className: "block text-sm font-medium text-gray-400 mb-2"
388
+ }, "AI Model"),
389
+ React.createElement('select', {
390
+ key: 'model-select',
391
+ value: selectedModel,
392
+ onChange: (e) => setSelectedModel(e.target.value),
393
+ className: "w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
394
+ }, models.map((model) =>
395
+ React.createElement('option', {
396
+ key: model.id,
397
+ value: model.id
398
+ }, model.name)
399
+ )),
400
+ React.createElement('p', {
401
+ key: 'model-id',
402
+ className: "text-xs text-green-400 mt-1 font-mono"
403
+ }, selectedModel)
404
+ ]),
405
+
406
+ // API Status
407
+ React.createElement('div', {
408
+ key: 'api-status',
409
+ className: "flex items-center gap-2 text-sm"
410
+ }, [
411
+ React.createElement('div', {
412
+ key: 'api-dot',
413
+ className: `w-2 h-2 rounded-full ${apiStatus === 'Connected' ? 'bg-green-500' : 'bg-red-500'}`
414
+ }),
415
+ React.createElement('span', { key: 'api-text' },
416
+ apiStatus === 'Connected' ? '🟢 API Connected' :
417
+ apiStatus === 'No API Key' ? '🔴 No API Key' : '🔴 Connection Issue')
418
+ ]),
419
+
420
+ // Controls
421
+ React.createElement('div', {
422
+ key: 'controls',
423
+ className: "flex gap-2"
424
+ }, [
425
+ React.createElement('button', {
426
+ key: 'download-btn',
427
+ onClick: downloadHistory,
428
+ className: "flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm",
429
+ title: "Download History"
430
+ }, React.createElement(Download, { className: "w-4 h-4" })),
431
+ React.createElement('button', {
432
+ key: 'clear-btn',
433
+ onClick: clearChat,
434
+ className: "flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm",
435
+ title: "Clear Chat"
436
+ }, React.createElement(Trash2, { className: "w-4 h-4" })),
437
+ React.createElement('button', {
438
+ key: 'refresh-btn',
439
+ onClick: () => window.location.reload(),
440
+ className: "flex-1 flex items-center justify-center gap-1 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm",
441
+ title: "Refresh"
442
+ }, React.createElement(RefreshCw, { className: "w-4 h-4" }))
443
+ ])
444
+ ])
445
+ ]),
446
+
447
+ // Main Chat Area
448
+ React.createElement('div', {
449
+ key: 'main-area',
450
+ className: "flex-1 flex flex-col"
451
+ }, [
452
+ // Chat Messages
453
+ React.createElement('div', {
454
+ key: 'messages-container',
455
+ className: "flex-1 overflow-y-auto chat-scroll"
456
+ },
457
+ messages.length === 0
458
+ ? React.createElement('div', {
459
+ key: 'welcome',
460
+ className: "h-full flex items-center justify-center"
461
+ }, React.createElement('div', {
462
+ className: "text-center max-w-md mx-auto px-4"
463
+ }, [
464
+ React.createElement('div', {
465
+ key: 'welcome-icon',
466
+ className: "w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6"
467
+ }, React.createElement(Bot, { className: "w-8 h-8 text-blue-400" })),
468
+ React.createElement('h2', {
469
+ key: 'welcome-title',
470
+ className: "text-2xl font-semibold mb-4 text-gray-100"
471
+ }, "Your personal assistant"),
472
+ React.createElement('p', {
473
+ key: 'welcome-desc',
474
+ className: "text-gray-400 leading-relaxed"
475
+ }, "A personal assistant streamlines your life by managing tasks, schedules, and communications efficiently.")
476
+ ]))
477
+ : React.createElement('div', {
478
+ key: 'messages',
479
+ className: "p-6 space-y-6 max-w-4xl mx-auto"
480
+ }, [
481
+ ...messages.map((message, index) =>
482
+ React.createElement('div', {
483
+ key: index,
484
+ className: "flex gap-4"
485
+ }, [
486
+ React.createElement('div', {
487
+ key: 'avatar',
488
+ className: "w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0"
489
+ }, message.role === 'user'
490
+ ? React.createElement('div', { className: "w-6 h-6 bg-blue-600 rounded-full" })
491
+ : React.createElement(Bot, { className: "w-5 h-5 text-blue-400" })
492
+ ),
493
+ React.createElement('div', {
494
+ key: 'content',
495
+ className: "flex-1 min-w-0"
496
+ }, React.createElement('div', {
497
+ className: "prose prose-invert max-w-none"
498
+ }, message.role === 'assistant' && message.content.includes('---\n*Response created by:')
499
+ ? [
500
+ React.createElement('div', {
501
+ key: 'message-text',
502
+ className: "whitespace-pre-wrap text-gray-100"
503
+ }, message.content.split('\n\n---\n*Response created by:')[0]),
504
+ React.createElement('div', {
505
+ key: 'attribution',
506
+ className: "text-xs text-gray-500 mt-2 italic"
507
+ }, `Response created by: ${message.content.split('**')[1]}`)
508
+ ]
509
+ : React.createElement('div', {
510
+ className: "whitespace-pre-wrap text-gray-100"
511
+ }, message.content)
512
+ ))
513
+ ])
514
+ ),
515
+ isLoading && React.createElement('div', {
516
+ key: 'loading',
517
+ className: "flex gap-4"
518
+ }, [
519
+ React.createElement('div', {
520
+ key: 'loading-avatar',
521
+ className: "w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0"
522
+ }, React.createElement(Bot, { className: "w-5 h-5 text-blue-400" })),
523
+ React.createElement('div', {
524
+ key: 'loading-content',
525
+ className: "flex-1"
526
+ }, React.createElement('div', {
527
+ className: "flex items-center gap-2 text-gray-400"
528
+ }, [
529
+ React.createElement('div', {
530
+ key: 'dot1',
531
+ className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce"
532
+ }),
533
+ React.createElement('div', {
534
+ key: 'dot2',
535
+ className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
536
+ style: {animationDelay: '0.1s'}
537
+ }),
538
+ React.createElement('div', {
539
+ key: 'dot3',
540
+ className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
541
+ style: {animationDelay: '0.2s'}
542
+ })
543
+ ]))
544
+ ]),
545
+ React.createElement('div', {
546
+ key: 'scroll-anchor',
547
+ ref: messagesEndRef
548
+ })
549
+ ])
550
+ ),
551
+
552
+ // Message Input
553
+ React.createElement('div', {
554
+ key: 'input-area',
555
+ className: "border-t border-gray-700 p-4"
556
+ }, React.createElement('div', {
557
+ className: "max-w-4xl mx-auto"
558
+ }, [
559
+ React.createElement('div', {
560
+ key: 'input-container',
561
+ className: "flex gap-3"
562
+ }, [
563
+ React.createElement('div', {
564
+ key: 'input-wrapper',
565
+ className: "flex-1 relative"
566
+ }, React.createElement('input', {
567
+ type: "text",
568
+ value: currentMessage,
569
+ onChange: (e) => setCurrentMessage(e.target.value),
570
+ onKeyDown: (e) => {
571
+ if (e.key === 'Enter' && !e.shiftKey) {
572
+ e.preventDefault();
573
+ handleSendMessage(e);
574
+ }
575
+ },
576
+ placeholder: "Chat Smarter. Chat many Brains",
577
+ className: "w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
578
+ disabled: isLoading
579
+ })),
580
+ React.createElement('button', {
581
+ key: 'send-btn',
582
+ onClick: handleSendMessage,
583
+ disabled: !currentMessage.trim() || isLoading,
584
+ className: "px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 disabled:cursor-not-allowed rounded-lg transition-colors flex items-center gap-2"
585
+ }, [
586
+ React.createElement(Send, { key: 'send-icon', className: "w-4 h-4" }),
587
+ "Send"
588
+ ])
589
+ ]),
590
+ React.createElement('div', {
591
+ key: 'model-status',
592
+ className: "mt-2 text-center"
593
+ }, React.createElement('span', {
594
+ className: "text-xs text-gray-500"
595
+ }, `Currently using: ${getSelectedModelName()}`))
596
+ ]))
597
+ ])
598
+ ]);
599
+ };
600
+
601
+ ReactDOM.render(React.createElement(ChatFlowApp), document.getElementById('root'));
602
+ </script>
603
+ </body>
604
+ </html>