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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +481 -572
app.py CHANGED
@@ -4,601 +4,510 @@
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>
 
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://cdn.tailwindcss.com"></script>
8
  <style>
9
+ body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; }
10
  .chat-scroll { scrollbar-width: thin; scrollbar-color: #4a5568 #2d3748; }
11
  .chat-scroll::-webkit-scrollbar { width: 6px; }
12
  .chat-scroll::-webkit-scrollbar-track { background: #2d3748; }
13
  .chat-scroll::-webkit-scrollbar-thumb { background: #4a5568; border-radius: 3px; }
14
+ .animate-bounce { animation: bounce 1s infinite; }
15
+ @keyframes bounce {
16
+ 0%, 100% { transform: translateY(-25%); animation-timing-function: cubic-bezier(0.8,0,1,1); }
17
+ 50% { transform: none; animation-timing-function: cubic-bezier(0,0,0.2,1); }
18
+ }
19
+ .loading-dot-1 { animation-delay: 0s; }
20
+ .loading-dot-2 { animation-delay: 0.1s; }
21
+ .loading-dot-3 { animation-delay: 0.2s; }
22
  </style>
23
  </head>
24
+ <body class="bg-gray-900 text-white h-screen overflow-hidden">
25
+ <div class="flex h-screen">
26
+ <!-- Sidebar -->
27
+ <div class="w-80 bg-gray-800 border-r border-gray-700 flex flex-col">
28
+ <!-- Header -->
29
+ <div class="p-4 border-b border-gray-700">
30
+ <div class="flex items-center gap-3 mb-4">
31
+ <div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
32
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
33
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
34
+ </svg>
35
+ </div>
36
+ <h1 class="text-xl font-semibold">Chat Flow</h1>
37
+ </div>
38
+ <button onclick="startNewChat()" class="w-full flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
39
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
40
+ <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
41
+ </svg>
42
+ New Chat
43
+ </button>
44
+ </div>
45
+
46
+ <!-- Chat History -->
47
+ <div class="flex-1 overflow-y-auto p-4 chat-scroll">
48
+ <h3 class="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
49
+ <div id="sessions-list" class="space-y-2">
50
+ <p class="text-gray-500 text-sm">No previous chats yet</p>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Settings -->
55
+ <div class="p-4 border-t border-gray-700 space-y-4">
56
+ <!-- Online Users -->
57
+ <div>
58
+ <h3 class="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
59
+ <div class="flex items-center gap-2 text-sm">
60
+ <div class="w-2 h-2 bg-green-500 rounded-full"></div>
61
+ <span id="online-status">Just you online</span>
62
+ </div>
63
+ <p class="text-xs text-gray-500 mt-1" id="user-id">You: User-ABC123</p>
64
+ </div>
65
+
66
+ <!-- Model Selection -->
67
+ <div>
68
+ <label class="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
69
+ <select id="model-select" class="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">
70
+ <option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
71
+ <option value="meta-llama/llama-3.1-8b-instruct">LLaMA 3.1 8B</option>
72
+ <option value="meta-llama/llama-3.1-70b-instruct">LLaMA 3.1 70B</option>
73
+ <option value="deepseek/deepseek-chat-v3-0324:free">DeepSeek Chat v3</option>
74
+ <option value="deepseek/deepseek-r1-0528:free">DeepSeek R1</option>
75
+ <option value="qwen/qwen3-coder:free">Qwen3 Coder</option>
76
+ <option value="microsoft/mai-ds-r1:free">Microsoft MAI DS R1</option>
77
+ <option value="google/gemma-3-27b-it:free">Gemma 3 27B</option>
78
+ <option value="google/gemma-3-4b-it:free">Gemma 3 4B</option>
79
+ <option value="openrouter/auto">Auto (Best Available)</option>
80
+ </select>
81
+ <p class="text-xs text-green-400 mt-1 font-mono" id="model-id">openai/gpt-3.5-turbo</p>
82
+ </div>
83
+
84
+ <!-- API Status -->
85
+ <div class="flex items-center gap-2 text-sm">
86
+ <div id="api-dot" class="w-2 h-2 rounded-full bg-green-500"></div>
87
+ <span id="api-status">🟢 API Connected</span>
88
+ </div>
89
+
90
+ <!-- Controls -->
91
+ <div class="flex gap-2">
92
+ <button onclick="downloadHistory()" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Download History">
93
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
94
+ <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
95
+ </svg>
96
+ </button>
97
+ <button onclick="clearChat()" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Clear Chat">
98
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
99
+ <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
100
+ </svg>
101
+ </button>
102
+ <button onclick="window.location.reload()" class="flex-1 flex items-center justify-center px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors text-sm" title="Refresh">
103
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
104
+ <path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
105
+ </svg>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- Main Chat Area -->
112
+ <div class="flex-1 flex flex-col">
113
+ <!-- Chat Messages -->
114
+ <div id="messages-container" class="flex-1 overflow-y-auto chat-scroll">
115
+ <!-- Welcome Screen -->
116
+ <div id="welcome-screen" class="h-full flex items-center justify-center">
117
+ <div class="text-center max-w-md mx-auto px-4">
118
+ <div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
119
+ <svg class="w-8 h-8 text-blue-400" fill="currentColor" viewBox="0 0 24 24">
120
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
121
+ </svg>
122
+ </div>
123
+ <h2 class="text-2xl font-semibold mb-4 text-gray-100">Your personal assistant</h2>
124
+ <p class="text-gray-400 leading-relaxed">A personal assistant streamlines your life by managing tasks, schedules, and communications efficiently.</p>
125
+ </div>
126
+ </div>
127
+
128
+ <!-- Messages Area -->
129
+ <div id="messages-area" class="p-6 space-y-6 max-w-4xl mx-auto" style="display: none;">
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Message Input -->
134
+ <div class="border-t border-gray-700 p-4">
135
+ <div class="max-w-4xl mx-auto">
136
+ <div class="flex gap-3">
137
+ <div class="flex-1 relative">
138
+ <input
139
+ type="text"
140
+ id="message-input"
141
+ placeholder="Chat Smarter. Chat many Brains"
142
+ class="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"
143
+ />
144
+ </div>
145
+ <button
146
+ onclick="sendMessage()"
147
+ id="send-button"
148
+ class="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"
149
+ >
150
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
151
+ <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
152
+ </svg>
153
+ Send
154
+ </button>
155
+ </div>
156
+ <div class="mt-2 text-center">
157
+ <span class="text-xs text-gray-500">Currently using: <strong id="current-model-name">GPT-3.5 Turbo</strong></span>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ </div>
163
+
164
+ <script>
165
+ // Global variables
166
+ let messages = [];
167
+ let sessions = [];
168
+ let currentSessionId = 'default';
169
+ let isLoading = false;
170
+ let selectedModel = 'openai/gpt-3.5-turbo';
171
+ let userId = 'User-' + Math.random().toString(36).substr(2, 8);
172
+
173
+ const models = {
174
+ 'openai/gpt-3.5-turbo': 'GPT-3.5 Turbo',
175
+ 'meta-llama/llama-3.1-8b-instruct': 'LLaMA 3.1 8B',
176
+ 'meta-llama/llama-3.1-70b-instruct': 'LLaMA 3.1 70B',
177
+ 'deepseek/deepseek-chat-v3-0324:free': 'DeepSeek Chat v3',
178
+ 'deepseek/deepseek-r1-0528:free': 'DeepSeek R1',
179
+ 'qwen/qwen3-coder:free': 'Qwen3 Coder',
180
+ 'microsoft/mai-ds-r1:free': 'Microsoft MAI DS R1',
181
+ 'google/gemma-3-27b-it:free': 'Gemma 3 27B',
182
+ 'google/gemma-3-4b-it:free': 'Gemma 3 4B',
183
+ 'openrouter/auto': 'Auto (Best Available)'
184
+ };
185
+
186
+ // Initialize
187
+ document.addEventListener('DOMContentLoaded', function() {
188
+ setupEventListeners();
189
+ updateOnlineUsers();
190
+ checkAPIStatus();
191
+
192
+ // Update user ID display
193
+ document.getElementById('user-id').textContent = 'You: ' + userId;
194
+ });
195
+
196
+ function setupEventListeners() {
197
+ // Model selection
198
+ document.getElementById('model-select').addEventListener('change', function(e) {
199
+ selectedModel = e.target.value;
200
+ document.getElementById('model-id').textContent = selectedModel;
201
+ document.getElementById('current-model-name').textContent = models[selectedModel];
202
+ });
203
+
204
+ // Enter key to send message
205
+ document.getElementById('message-input').addEventListener('keydown', function(e) {
206
+ if (e.key === 'Enter' && !e.shiftKey) {
207
+ e.preventDefault();
208
+ sendMessage();
209
+ }
210
+ });
211
+ }
212
+
213
+ async function sendMessage() {
214
+ const input = document.getElementById('message-input');
215
+ const message = input.value.trim();
216
+
217
+ if (!message || isLoading) return;
218
+
219
+ // Add user message
220
+ const userMessage = {
221
+ role: 'user',
222
+ content: message,
223
+ timestamp: new Date().toISOString()
224
  };
225
 
226
+ messages.push(userMessage);
227
+ input.value = '';
228
+
229
+ updateMessagesDisplay();
230
+ setLoading(true);
231
+
232
+ try {
233
+ const aiResponse = await getAIResponse(message);
234
+ const assistantMessage = {
235
+ role: 'assistant',
236
+ content: aiResponse,
237
+ timestamp: new Date().toISOString()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  };
239
+ messages.push(assistantMessage);
240
+ } catch (error) {
241
+ const errorMessage = {
242
+ role: 'assistant',
243
+ content: 'Sorry, I encountered an error. Please try again.',
244
+ timestamp: new Date().toISOString()
245
+ };
246
+ messages.push(errorMessage);
247
+ }
248
+
249
+ setLoading(false);
250
+ updateMessagesDisplay();
251
+ }
252
+
253
+ async function getAIResponse(userMessage) {
254
+ // Try to get API key from various sources
255
+ const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY ||
256
+ window.HF_TOKEN ||
257
+ (typeof process !== 'undefined' && process.env && process.env.OPENROUTER_API_KEY);
258
+
259
+ if (!OPENROUTER_API_KEY) {
260
+ throw new Error("No API key found. Please add OPENROUTER_API_KEY to Hugging Face Spaces secrets.");
261
+ }
262
+
263
+ const url = "https://openrouter.ai/api/v1/chat/completions";
264
+ const headers = {
265
+ "Content-Type": "application/json",
266
+ "Authorization": "Bearer " + OPENROUTER_API_KEY,
267
+ "HTTP-Referer": "https://huggingface.co/spaces",
268
+ "X-Title": "Chat Flow AI Assistant"
269
  };
270
 
271
+ const cleanMessages = messages.map(msg => ({
272
+ role: msg.role,
273
+ content: msg.content.split('\n\n---\n*Response created by:')[0]
274
+ }));
 
 
 
 
 
 
 
 
 
 
275
 
276
+ const apiMessages = [
277
+ { role: "system", content: "You are a helpful AI assistant. Provide clear and helpful responses." }
278
+ ].concat(cleanMessages).concat([
279
+ { role: "user", content: userMessage }
280
+ ]);
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
+ const data = {
283
+ model: selectedModel,
284
+ messages: apiMessages,
285
+ stream: false,
286
+ max_tokens: 2000,
287
+ temperature: 0.7
288
  };
289
 
290
+ const response = await fetch(url, {
291
+ method: 'POST',
292
+ headers: headers,
293
+ body: JSON.stringify(data)
294
+ });
295
+
296
+ if (!response.ok) {
297
+ throw new Error("API Error: " + response.status);
298
+ }
299
+
300
+ const result = await response.json();
301
+ const aiResponse = result.choices[0].message.content;
302
+ const modelName = models[selectedModel] || "AI";
303
+
304
+ return aiResponse + "\n\n---\n*Response created by: **" + modelName + "***";
305
+ }
306
+
307
+ function setLoading(loading) {
308
+ isLoading = loading;
309
+ const button = document.getElementById('send-button');
310
+ const input = document.getElementById('message-input');
311
+
312
+ if (loading) {
313
+ button.disabled = true;
314
+ input.disabled = true;
315
+ button.innerHTML = '<div class="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin"></div> Loading...';
316
+ } else {
317
+ button.disabled = false;
318
+ input.disabled = false;
319
+ button.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> Send';
320
+ }
321
+ }
322
+
323
+ function updateMessagesDisplay() {
324
+ const welcomeScreen = document.getElementById('welcome-screen');
325
+ const messagesArea = document.getElementById('messages-area');
326
+
327
+ if (messages.length === 0) {
328
+ welcomeScreen.style.display = 'flex';
329
+ messagesArea.style.display = 'none';
330
+ return;
331
+ }
332
+
333
+ welcomeScreen.style.display = 'none';
334
+ messagesArea.style.display = 'block';
335
+
336
+ let html = '';
337
+ messages.forEach((message, index) => {
338
+ const isUser = message.role === 'user';
339
+ const avatar = isUser
340
+ ? '<div class="w-6 h-6 bg-blue-600 rounded-full"></div>'
341
+ : '<svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
342
 
343
+ let content = message.content;
344
+ let attribution = '';
345
+
346
+ if (message.role === 'assistant' && content.includes('---\n*Response created by:')) {
347
+ const parts = content.split('\n\n---\n*Response created by:');
348
+ content = parts[0];
349
+ if (parts[1]) {
350
+ const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
351
+ attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  }
354
+
355
+ html += `
356
+ <div class="flex gap-4">
357
+ <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
358
+ ${avatar}
359
+ </div>
360
+ <div class="flex-1 min-w-0">
361
+ <div class="whitespace-pre-wrap text-gray-100">${content}</div>
362
+ ${attribution}
363
+ </div>
364
+ </div>
365
+ `;
366
+ });
367
+
368
+ if (isLoading) {
369
+ html += `
370
+ <div class="flex gap-4">
371
+ <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
372
+ <svg class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
373
+ </div>
374
+ <div class="flex-1">
375
+ <div class="flex items-center gap-2 text-gray-400">
376
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-1"></div>
377
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-2"></div>
378
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce loading-dot-3"></div>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ `;
383
+ }
384
+
385
+ messagesArea.innerHTML = html;
386
+ messagesArea.scrollTop = messagesArea.scrollHeight;
387
+ }
388
+
389
+ function startNewChat() {
390
+ if (messages.length > 0) {
391
+ const title = generateChatTitle(messages);
392
+ const newSession = {
393
+ id: 'session-' + Date.now(),
394
+ title: title,
395
+ messages: messages.slice(),
396
+ createdAt: new Date().toISOString(),
397
+ updatedAt: new Date().toISOString()
398
  };
399
+ sessions.unshift(newSession);
400
+ updateSessionsList();
401
+ }
402
+
403
+ messages = [];
404
+ currentSessionId = 'session-' + Date.now();
405
+ updateMessagesDisplay();
406
+ }
407
+
408
+ function generateChatTitle(msgs) {
409
+ if (!msgs || msgs.length === 0) return "New Chat";
410
+ const firstUserMessage = msgs.find(m => m.role === 'user');
411
+ if (!firstUserMessage) return "New Chat";
412
+ const content = firstUserMessage.content;
413
+ return content.length > 30 ? content.substring(0, 30) + "..." : content;
414
+ }
415
+
416
+ function updateSessionsList() {
417
+ const sessionsList = document.getElementById('sessions-list');
418
+
419
+ if (sessions.length === 0) {
420
+ sessionsList.innerHTML = '<p class="text-gray-500 text-sm">No previous chats yet</p>';
421
+ return;
422
+ }
423
+
424
+ let html = '';
425
+ sessions.forEach(session => {
426
+ const isCurrent = session.id === currentSessionId;
427
+ html += `
428
+ <div class="group flex items-center gap-2">
429
+ <button onclick="loadSession('${session.id}')" class="flex-1 text-left px-3 py-2 rounded-lg transition-colors ${isCurrent ? 'bg-blue-600 text-white' : 'bg-gray-700 hover:bg-gray-600 text-gray-300'}">
430
+ <div class="text-sm font-medium truncate">${session.title}</div>
431
+ <div class="text-xs text-gray-400">${new Date(session.updatedAt).toLocaleDateString()}</div>
432
+ </button>
433
+ <button onclick="deleteSession('${session.id}')" class="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all">
434
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
435
+ </button>
436
+ </div>
437
+ `;
438
+ });
439
+
440
+ sessionsList.innerHTML = html;
441
+ }
442
+
443
+ function loadSession(sessionId) {
444
+ const session = sessions.find(s => s.id === sessionId);
445
+ if (!session) return;
446
+
447
+ messages = session.messages.slice();
448
+ currentSessionId = sessionId;
449
+ updateMessagesDisplay();
450
+ updateSessionsList();
451
+ }
452
+
453
+ function deleteSession(sessionId) {
454
+ sessions = sessions.filter(s => s.id !== sessionId);
455
+ if (sessionId === currentSessionId) {
456
+ startNewChat();
457
+ }
458
+ updateSessionsList();
459
+ }
460
+
461
+ function clearChat() {
462
+ messages = [];
463
+ updateMessagesDisplay();
464
+ }
465
+
466
+ function downloadHistory() {
467
+ const dataStr = JSON.stringify(messages, null, 2);
468
+ const dataBlob = new Blob([dataStr], { type: 'application/json' });
469
+ const url = URL.createObjectURL(dataBlob);
470
+ const link = document.createElement('a');
471
+ link.href = url;
472
+ link.download = 'chat_history_' + new Date().toISOString().split('T')[0] + '.json';
473
+ link.click();
474
+ URL.revokeObjectURL(url);
475
+ }
476
+
477
+ function updateOnlineUsers() {
478
+ const count = Math.floor(Math.random() * 5) + 1;
479
+ const status = count === 1 ? 'Just you online' : count + ' people online';
480
+ document.getElementById('online-status').textContent = status;
481
+
482
+ setTimeout(updateOnlineUsers, 10000);
483
+ }
484
+
485
+ async function checkAPIStatus() {
486
+ try {
487
+ const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY || window.HF_TOKEN;
488
+
489
+ if (!OPENROUTER_API_KEY) {
490
+ document.getElementById('api-status').textContent = '🔴 No API Key';
491
+ document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
492
+ return;
493
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
 
495
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
496
+ headers: { "Authorization": "Bearer " + OPENROUTER_API_KEY }
497
+ });
498
+
499
+ if (response.ok) {
500
+ document.getElementById('api-status').textContent = '🟢 API Connected';
501
+ document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-green-500';
502
+ } else {
503
+ document.getElementById('api-status').textContent = '🔴 Connection Issue';
504
+ document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
505
+ }
506
+ } catch (error) {
507
+ document.getElementById('api-status').textContent = '🔴 Connection Issue';
508
+ document.getElementById('api-dot').className = 'w-2 h-2 rounded-full bg-red-500';
509
+ }
510
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  </script>
512
  </body>
513
  </html>