uumerrr684 commited on
Commit
3253c41
·
verified ·
1 Parent(s): 6148335

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +324 -647
app.py CHANGED
@@ -1,652 +1,329 @@
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://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
- .sidebar-transition { transition: transform 0.3s ease-in-out; }
20
- .sidebar-hidden { transform: translateX(-100%); }
21
- .loading-dot-1 { animation-delay: 0s; }
22
- .loading-dot-2 { animation-delay: 0.1s; }
23
- .loading-dot-3 { animation-delay: 0.2s; }
24
- </style>
25
- </head>
26
- <body class="bg-gray-900 text-white h-screen overflow-hidden">
27
- <div class="flex h-screen relative">
28
- <!-- Sidebar -->
29
- <div id="sidebar" class="w-80 bg-gray-800 border-r border-gray-700 flex flex-col sidebar-transition z-10">
30
- <!-- Sidebar Toggle Button -->
31
- <button id="sidebarToggle" class="absolute -right-8 top-4 bg-black text-white p-2 rounded-r-lg hover:bg-gray-800 transition-colors z-20">
32
- <svg id="toggleIcon" class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
33
- <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
34
- </svg>
35
- </button>
36
-
37
- <!-- Header -->
38
- <div class="p-4 border-b border-gray-700">
39
- <div class="flex items-center gap-3 mb-4">
40
- <div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
41
- <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
42
- <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"/>
43
- </svg>
44
- </div>
45
- <h1 class="text-xl font-semibold">Chat Flow</h1>
46
- </div>
47
- <button id="newChatBtn" class="w-full flex items-center gap-2 px-4 py-2 bg-black text-white hover:bg-gray-800 rounded-lg transition-colors">
48
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
49
- <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
50
- </svg>
51
- New Chat
52
- </button>
53
- </div>
54
-
55
- <!-- Chat History -->
56
- <div class="flex-1 overflow-y-auto p-4 chat-scroll">
57
- <h3 class="text-sm font-medium text-gray-400 mb-3">💬 Chat History</h3>
58
- <div id="chatHistoryList" class="space-y-2">
59
- <p class="text-gray-500 text-sm">No saved chats yet</p>
60
- </div>
61
- <button id="saveCurrentChatBtn" class="w-full mt-3 px-3 py-2 bg-black text-white hover:bg-gray-800 rounded-lg transition-colors text-sm">
62
- 💾 Save Current Chat
63
- </button>
64
- </div>
65
-
66
- <!-- Settings -->
67
- <div class="p-4 border-t border-gray-700 space-y-4">
68
- <!-- Location Info -->
69
- <div>
70
- <h3 class="text-sm font-medium text-gray-400 mb-2">📍 Your Location</h3>
71
- <div class="bg-gray-700 rounded-lg p-2 text-xs">
72
- <div id="locationInfo" class="text-green-400">🌍 Getting location...</div>
73
- </div>
74
- </div>
75
-
76
- <!-- Online Users -->
77
- <div>
78
- <h3 class="text-sm font-medium text-gray-400 mb-2">👥 Who's Online</h3>
79
- <div class="flex items-center gap-2 text-sm mb-2">
80
- <div class="w-2 h-2 bg-green-500 rounded-full"></div>
81
- <span id="onlineStatus">Just you online</span>
82
- </div>
83
- <div class="text-xs text-gray-500" id="currentUser">You: Loading...</div>
84
- </div>
85
-
86
- <!-- Model Selection -->
87
- <div>
88
- <label class="block text-sm font-medium text-gray-400 mb-2">AI Model</label>
89
- <select id="modelSelect" 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">
90
- <option value="openai/gpt-3.5-turbo">GPT-3.5 Turbo</option>
91
- <option value="meta-llama/llama-3.1-8b-instruct">LLaMA 3.1 8B</option>
92
- <option value="meta-llama/llama-3.1-70b-instruct">LLaMA 3.1 70B</option>
93
- <option value="deepseek/deepseek-chat-v3-0324:free">DeepSeek Chat v3</option>
94
- <option value="deepseek/deepseek-r1-0528:free">DeepSeek R1</option>
95
- <option value="qwen/qwen3-coder:free">Qwen3 Coder</option>
96
- <option value="microsoft/mai-ds-r1:free">Microsoft MAI DS R1</option>
97
- <option value="google/gemma-3-27b-it:free">Gemma 3 27B</option>
98
- <option value="google/gemma-3-4b-it:free">Gemma 3 4B</option>
99
- <option value="openrouter/auto">Auto (Best Available)</option>
100
- </select>
101
- <p class="text-xs text-green-400 mt-1 font-mono" id="modelId">openai/gpt-3.5-turbo</p>
102
- </div>
103
-
104
- <!-- API Status -->
105
- <div class="flex items-center gap-2 text-sm">
106
- <div id="apiDot" class="w-2 h-2 rounded-full bg-green-500"></div>
107
- <span id="apiStatus">🟢 API Connected</span>
108
- </div>
109
-
110
- <!-- Controls -->
111
- <div class="grid grid-cols-2 gap-2">
112
- <button id="downloadBtn" class="flex items-center justify-center px-3 py-2 bg-black text-white hover:bg-gray-800 rounded-lg transition-colors text-sm" title="Download History">
113
- <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24">
114
- <path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
115
- </svg>
116
- Download
117
- </button>
118
- <button id="clearBtn" class="flex items-center justify-center px-3 py-2 bg-black text-white hover:bg-gray-800 rounded-lg transition-colors text-sm" title="Clear Chat">
119
- <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24">
120
- <path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
121
- </svg>
122
- Clear
123
- </button>
124
- </div>
125
- </div>
126
- </div>
127
-
128
- <!-- Main Chat Area -->
129
- <div id="mainArea" class="flex-1 flex flex-col transition-all duration-300">
130
- <!-- Chat Messages -->
131
- <div id="messagesContainer" class="flex-1 overflow-y-auto chat-scroll">
132
- <!-- Welcome Screen -->
133
- <div id="welcomeScreen" class="h-full flex items-center justify-center">
134
- <div class="text-center max-w-md mx-auto px-4">
135
- <div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-6">
136
- <svg class="w-8 h-8 text-blue-400" fill="currentColor" viewBox="0 0 24 24">
137
- <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"/>
138
- </svg>
139
- </div>
140
- <h2 class="text-2xl font-semibold mb-4 text-gray-100">Which model would you like to talk with today?</h2>
141
- <p class="text-gray-400 leading-relaxed">Choose from 10 powerful AI models and start chatting. Each model has unique strengths for different tasks.</p>
142
- </div>
143
- </div>
144
-
145
- <!-- Messages Area -->
146
- <div id="messagesArea" class="p-6 space-y-6 max-w-4xl mx-auto" style="display: none;">
147
- </div>
148
- </div>
149
-
150
- <!-- Message Input -->
151
- <div class="border-t border-gray-700 p-4">
152
- <div class="max-w-4xl mx-auto">
153
- <div class="flex gap-3">
154
- <input
155
- type="text"
156
- id="messageInput"
157
- placeholder="Chat Smarter. Chat many Brains"
158
- class="flex-1 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"
159
- />
160
- <button
161
- id="sendBtn"
162
- 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"
163
- >
164
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
165
- <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
166
- </svg>
167
- Send
168
- </button>
169
- </div>
170
- <div class="mt-2 text-center">
171
- <span class="text-xs text-gray-500">Currently using: <strong id="currentModelName">GPT-3.5 Turbo</strong></span>
172
- </div>
173
- </div>
174
- </div>
175
- </div>
176
- </div>
177
-
178
- <script>
179
- // 🔑 OpenRouter API Key
180
- const API_KEY = "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";
181
 
182
- // Global variables
183
- let messages = [];
184
- let savedChats = JSON.parse(localStorage.getItem('chatHistory') || '[]');
185
- let selectedModel = 'openai/gpt-3.5-turbo';
186
- let isLoading = false;
187
- let userId = 'User-' + Math.random().toString(36).substr(2, 8);
188
- let userLocation = { city: 'Unknown', country: 'Unknown' };
189
- let sidebarVisible = true;
190
-
191
- const models = {
192
- 'openai/gpt-3.5-turbo': 'GPT-3.5 Turbo',
193
- 'meta-llama/llama-3.1-8b-instruct': 'LLaMA 3.1 8B',
194
- 'meta-llama/llama-3.1-70b-instruct': 'LLaMA 3.1 70B',
195
- 'deepseek/deepseek-chat-v3-0324:free': 'DeepSeek Chat v3',
196
- 'deepseek/deepseek-r1-0528:free': 'DeepSeek R1',
197
- 'qwen/qwen3-coder:free': 'Qwen3 Coder',
198
- 'microsoft/mai-ds-r1:free': 'Microsoft MAI DS R1',
199
- 'google/gemma-3-27b-it:free': 'Gemma 3 27B',
200
- 'google/gemma-3-4b-it:free': 'Gemma 3 4B',
201
- 'openrouter/auto': 'Auto (Best Available)'
202
- };
203
-
204
- // Initialize app
205
- document.addEventListener('DOMContentLoaded', async function() {
206
- console.log('App starting...');
207
- setupEventListeners();
208
- await getUserLocation();
209
- updateUserDisplay();
210
- updateChatHistory();
211
- console.log('App ready!');
212
- });
213
-
214
- function setupEventListeners() {
215
- // Sidebar toggle
216
- document.getElementById('sidebarToggle').onclick = toggleSidebar;
217
-
218
- // Send button
219
- document.getElementById('sendBtn').onclick = function() {
220
- console.log('Send button clicked');
221
- sendMessage();
222
- };
223
-
224
- // Enter key
225
- document.getElementById('messageInput').onkeypress = function(e) {
226
- if (e.key === 'Enter') {
227
- console.log('Enter pressed');
228
- sendMessage();
229
- }
230
- };
231
-
232
- // Model selection
233
- document.getElementById('modelSelect').onchange = function(e) {
234
- selectedModel = e.target.value;
235
- document.getElementById('modelId').textContent = selectedModel;
236
- document.getElementById('currentModelName').textContent = models[selectedModel];
237
- console.log('Model changed to:', selectedModel);
238
- };
239
-
240
- // New chat
241
- document.getElementById('newChatBtn').onclick = function() {
242
- messages = [];
243
- updateDisplay();
244
- console.log('New chat started');
245
- };
246
-
247
- // Save current chat
248
- document.getElementById('saveCurrentChatBtn').onclick = function() {
249
- saveCurrentChat();
250
- };
251
-
252
- // Clear chat
253
- document.getElementById('clearBtn').onclick = function() {
254
- messages = [];
255
- updateDisplay();
256
- console.log('Chat cleared');
257
- };
258
-
259
- // Download chat
260
- document.getElementById('downloadBtn').onclick = function() {
261
- downloadCurrentChat();
262
- };
263
-
264
- console.log('Event listeners setup complete');
265
- }
266
-
267
- function toggleSidebar() {
268
- const sidebar = document.getElementById('sidebar');
269
- const mainArea = document.getElementById('mainArea');
270
- const toggleIcon = document.getElementById('toggleIcon');
271
-
272
- sidebarVisible = !sidebarVisible;
273
-
274
- if (sidebarVisible) {
275
- sidebar.classList.remove('sidebar-hidden');
276
- toggleIcon.innerHTML = '<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>';
277
- } else {
278
- sidebar.classList.add('sidebar-hidden');
279
- toggleIcon.innerHTML = '<path d="M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z"/>';
280
- }
281
- }
282
-
283
- // Get user's real location (like your Streamlit code)
284
- async function getUserLocation() {
285
- try {
286
- if (navigator.geolocation) {
287
- const position = await new Promise((resolve, reject) => {
288
- navigator.geolocation.getCurrentPosition(resolve, reject, {
289
- timeout: 10000,
290
- enableHighAccuracy: false
291
- });
292
- });
293
-
294
- const { latitude, longitude } = position.coords;
295
-
296
- // Use reverse geocoding
297
- try {
298
- const response = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=en`);
299
- const data = await response.json();
300
-
301
- userLocation = {
302
- city: data.city || data.locality || 'Unknown',
303
- country: data.countryName || 'Unknown'
304
- };
305
- } catch (geocodeError) {
306
- await getLocationByIP();
307
- }
308
- } else {
309
- await getLocationByIP();
310
- }
311
- } catch (error) {
312
- await getLocationByIP();
313
- }
314
-
315
- updateLocationDisplay();
316
- }
317
-
318
- async function getLocationByIP() {
319
- try {
320
- const response = await fetch('https://ipapi.co/json/');
321
- const data = await response.json();
322
-
323
- userLocation = {
324
- city: data.city || 'Unknown',
325
- country: data.country_name || 'Unknown'
326
- };
327
- } catch (error) {
328
- userLocation = { city: 'Unknown', country: 'Unknown' };
329
- }
330
- }
331
-
332
- function updateLocationDisplay() {
333
- document.getElementById('locationInfo').textContent = `📍 ${userLocation.city}, ${userLocation.country}`;
334
- }
335
-
336
- async function sendMessage() {
337
- const input = document.getElementById('messageInput');
338
- const message = input.value.trim();
339
-
340
- console.log('Sending message:', message);
341
-
342
- if (!message || isLoading) {
343
- console.log('No message or loading');
344
- return;
345
- }
346
-
347
- // Add user message
348
- messages.push({
349
- role: 'user',
350
- content: message
351
- });
352
-
353
- input.value = '';
354
- updateDisplay();
355
- setLoading(true);
356
-
357
- try {
358
- // Start streaming response - like your Streamlit code
359
- await streamAIResponse(message);
360
- } catch (error) {
361
- console.error('Error:', error);
362
- messages.push({
363
- role: 'assistant',
364
- content: 'Sorry, there was an error. Please try again.'
365
- });
366
- updateDisplay();
367
- }
368
-
369
- setLoading(false);
370
- }
371
-
372
- // Streaming implementation like your Streamlit code
373
- async function streamAIResponse(userMessage) {
374
- console.log('Starting stream...');
375
-
376
- // Add empty assistant message for streaming
377
- messages.push({
378
- role: 'assistant',
379
- content: ''
380
- });
381
 
382
- updateDisplay();
383
-
384
- const apiMessages = [{
385
- role: "system",
386
- content: "You are a helpful AI assistant. Provide clear and helpful responses."
387
- }];
388
-
389
- // Build conversation history (like your Streamlit extend)
390
- messages.slice(0, -1).forEach(msg => {
391
- if (msg.role !== 'assistant' || !msg.content.includes('Response created by:')) {
392
- apiMessages.push({
393
- role: msg.role,
394
- content: msg.content.split('\n\n---\n*Response created by:')[0]
395
- });
396
- }
397
- });
398
-
399
- const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
400
- method: 'POST',
401
- headers: {
402
- "Content-Type": "application/json",
403
- "Authorization": "Bearer " + API_KEY,
404
- "HTTP-Referer": "https://huggingface.co/spaces",
405
- "X-Title": "Chat Flow AI Assistant"
406
- },
407
- body: JSON.stringify({
408
- model: selectedModel,
409
- messages: apiMessages,
410
- stream: true, // Enable streaming like Streamlit
411
- max_tokens: 2000,
412
- temperature: 0.7
413
- })
414
- });
415
-
416
- if (!response.ok) {
417
- throw new Error('API Error: ' + response.status);
418
- }
419
-
420
- const reader = response.body.getReader();
421
- const decoder = new TextDecoder();
422
- let fullResponse = '';
423
-
424
- try {
425
- while (true) {
426
- const { done, value } = await reader.read();
427
- if (done) break;
428
-
429
- const chunk = decoder.decode(value, { stream: true });
430
- const lines = chunk.split('\n');
431
-
432
- for (const line of lines) {
433
- if (line.trim() === '') continue;
434
-
435
- if (line.startsWith('data: ')) {
436
- const data = line.slice(6).trim();
437
-
438
- if (data === '[DONE]') {
439
- // Finalize response with attribution (like Streamlit)
440
- const modelName = models[selectedModel] || "AI";
441
- const finalResponse = fullResponse + "\n\n---\n*Response created by: **" + modelName + "***";
442
- messages[messages.length - 1].content = finalResponse;
443
- updateDisplay();
444
- return;
445
- }
446
-
447
- if (data === '') continue;
448
 
449
- try {
450
- const parsed = JSON.parse(data);
451
- const delta = parsed.choices?.[0]?.delta?.content;
452
-
453
- if (delta) {
454
- fullResponse += delta;
455
- // Update display in real-time (like Streamlit placeholder.markdown)
456
- updateStreamingMessage(fullResponse);
457
- }
458
- } catch (e) {
459
- continue;
460
- }
461
- }
462
- }
463
- }
464
- } finally {
465
- reader.releaseLock();
466
- }
467
- }
468
-
469
- function updateStreamingMessage(partialResponse) {
470
- // Update the last message with streaming content + cursor
471
- if (messages.length > 0) {
472
- const lastMessage = messages[messages.length - 1];
473
- if (lastMessage.role === 'assistant') {
474
- lastMessage.content = partialResponse;
475
-
476
- // Update display with cursor (like Streamlit "▌")
477
- const messagesArea = document.getElementById('messagesArea');
478
- const messageElements = messagesArea.children;
479
-
480
- if (messageElements.length > 0) {
481
- const lastMessageElement = messageElements[messageElements.length - 1];
482
- const contentDiv = lastMessageElement.querySelector('.whitespace-pre-wrap');
483
- if (contentDiv) {
484
- contentDiv.textContent = partialResponse + " ";
485
- }
486
- }
487
-
488
- messagesArea.scrollTop = messagesArea.scrollHeight;
489
- }
490
- }
491
- }
492
-
493
- function updateDisplay() {
494
- const welcome = document.getElementById('welcomeScreen');
495
- const messagesArea = document.getElementById('messagesArea');
496
-
497
- if (messages.length === 0) {
498
- welcome.style.display = 'flex';
499
- messagesArea.style.display = 'none';
500
- return;
501
- }
502
-
503
- welcome.style.display = 'none';
504
- messagesArea.style.display = 'block';
505
-
506
- let html = '';
507
- messages.forEach(msg => {
508
- const isUser = msg.role === 'user';
509
- const avatar = isUser ?
510
- '<div class="w-6 h-6 bg-blue-600 rounded-full"></div>' :
511
- '<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>';
512
-
513
- let content = msg.content;
514
- let attribution = '';
515
-
516
- if (msg.role === 'assistant' && content.includes('---\n*Response created by:')) {
517
- const parts = content.split('\n\n---\n*Response created by:');
518
- content = parts[0];
519
- if (parts[1]) {
520
- const modelName = parts[1].replace(/\*\*/g, '').replace(/\*/g, '');
521
- attribution = '<div class="text-xs text-gray-500 mt-2 italic">Response created by: <strong>' + modelName + '</strong></div>';
522
- }
523
- }
524
-
525
- html += `
526
- <div class="flex gap-4">
527
- <div class="w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0">
528
- ${avatar}
529
- </div>
530
- <div class="flex-1 min-w-0">
531
- <div class="whitespace-pre-wrap text-gray-100">${content}</div>
532
- ${attribution}
533
- </div>
534
- </div>
535
- `;
536
- });
537
-
538
- messagesArea.innerHTML = html;
539
- messagesArea.scrollTop = messagesArea.scrollHeight;
540
- }
541
-
542
- function setLoading(loading) {
543
- isLoading = loading;
544
- const btn = document.getElementById('sendBtn');
545
- const input = document.getElementById('messageInput');
546
-
547
- btn.disabled = loading;
548
- input.disabled = loading;
549
- }
550
-
551
- function updateUserDisplay() {
552
- document.getElementById('currentUser').textContent = 'You: ' + userId;
553
- document.getElementById('onlineStatus').textContent = 'Just you online';
554
- }
555
-
556
- // Chat save functionality (like your Streamlit save_chat_history)
557
- function saveCurrentChat() {
558
- if (messages.length === 0) {
559
- alert('No messages to save!');
560
- return;
561
- }
562
-
563
- const title = generateChatTitle();
564
- const chatData = {
565
- id: Date.now(),
566
- title: title,
567
- messages: JSON.parse(JSON.stringify(messages)),
568
- createdAt: new Date().toISOString(),
569
- location: userLocation
570
- };
571
-
572
- savedChats.unshift(chatData);
573
- localStorage.setItem('chatHistory', JSON.stringify(savedChats));
574
- updateChatHistory();
575
-
576
- alert('Chat saved successfully!');
577
- }
578
-
579
- function generateChatTitle() {
580
- const firstUserMessage = messages.find(m => m.role === 'user');
581
- if (!firstUserMessage) return 'Empty Chat';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
 
583
- const content = firstUserMessage.content;
584
- return content.length > 30 ? content.substring(0, 30) + '...' : content;
585
- }
586
-
587
- function updateChatHistory() {
588
- const historyList = document.getElementById('chatHistoryList');
589
 
590
- if (savedChats.length === 0) {
591
- historyList.innerHTML = '<p class="text-gray-500 text-sm">No saved chats yet</p>';
592
- return;
593
- }
594
-
595
- let html = '';
596
- savedChats.slice(0, 10).forEach(chat => { // Show last 10 chats
597
- const date = new Date(chat.createdAt).toLocaleDateString();
598
- html += `
599
- <div class="group flex items-center gap-2">
600
- <button onclick="loadChat(${chat.id})" class="flex-1 text-left px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg text-gray-300 transition-colors">
601
- <div class="text-sm font-medium truncate">${chat.title}</div>
602
- <div class="text-xs text-gray-400">${date}</div>
603
- </button>
604
- <button onclick="deleteChat(${chat.id})" class="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 transition-all">
605
- <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>
606
- </button>
607
- </div>
608
- `;
609
- });
610
-
611
- historyList.innerHTML = html;
612
- }
613
-
614
- function loadChat(chatId) {
615
- const chat = savedChats.find(c => c.id === chatId);
616
- if (chat) {
617
- messages = JSON.parse(JSON.stringify(chat.messages));
618
- updateDisplay();
619
- console.log('Chat loaded:', chat.title);
620
- }
621
- }
622
-
623
- function deleteChat(chatId) {
624
- if (confirm('Delete this chat?')) {
625
- savedChats = savedChats.filter(c => c.id !== chatId);
626
- localStorage.setItem('chatHistory', JSON.stringify(savedChats));
627
- updateChatHistory();
628
- }
629
- }
630
-
631
- function downloadCurrentChat() {
632
- if (messages.length === 0) {
633
- alert('No messages to download!');
634
- return;
635
- }
636
-
637
- const chatData = {
638
- title: generateChatTitle(),
639
- messages: messages,
640
- exportedAt: new Date().toISOString(),
641
- location: userLocation,
642
- userId: userId
643
- };
644
-
645
- const dataStr = JSON.stringify(chatData, null, 2);
646
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
647
- const url = URL.createObjectURL(dataBlob);
648
- const link = document.createElement('a');
649
- link.href = url;
650
- link.download = `chat_${new Date().toISOString().split('T')[0]}.json`;
651
- link.click();
652
- URL.revokeObjectURL(url);
 
1
+ import requests
2
+ import os
3
+ import json
4
+ import streamlit as st
5
+ from datetime import datetime
6
+ import time
7
+
8
+ # Page configuration
9
+ st.set_page_config(
10
+ page_title="Chat Flow 🕷",
11
+ page_icon="💬",
12
+ initial_sidebar_state="collapsed"
13
+ )
14
+
15
+ # White background
16
+ st.markdown("""
17
+ <style>
18
+ .stApp {
19
+ background: white;
20
+ }
21
+
22
+ .main .block-container {
23
+ max-width: 800px;
24
+ }
25
+
26
+ #MainMenu {visibility: hidden;}
27
+ footer {visibility: hidden;}
28
+ header {visibility: hidden;}
29
+ .stDeployButton {display: none;}
30
+
31
+ .model-id {
32
+ color: #28a745;
33
+ font-family: monospace;
34
+ }
35
+
36
+ .model-attribution {
37
+ color: #28a745;
38
+ font-size: 0.8em;
39
+ font-style: italic;
40
+ }
41
+ </style>
42
+ """, unsafe_allow_html=True)
43
+
44
+ # File to store chat history
45
+ HISTORY_FILE = "chat_history.json"
46
+
47
+ def load_chat_history():
48
+ """Load chat history from file"""
49
+ try:
50
+ if os.path.exists(HISTORY_FILE):
51
+ with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
52
+ return json.load(f)
53
+ except Exception as e:
54
+ st.error(f"Error loading chat history: {e}")
55
+ return []
56
+
57
+ def save_chat_history(messages):
58
+ """Save chat history to file"""
59
+ try:
60
+ with open(HISTORY_FILE, 'w', encoding='utf-8') as f:
61
+ json.dump(messages, f, ensure_ascii=False, indent=2)
62
+ except Exception as e:
63
+ st.error(f"Error saving chat history: {e}")
64
+
65
+ def clear_chat_history():
66
+ """Clear chat history file"""
67
+ try:
68
+ if os.path.exists(HISTORY_FILE):
69
+ os.remove(HISTORY_FILE)
70
+ st.session_state.messages = []
71
+ except Exception as e:
72
+ st.error(f"Error clearing chat history: {e}")
73
+
74
+ # Initialize session state with saved history
75
+ if "messages" not in st.session_state:
76
+ st.session_state.messages = load_chat_history()
77
+
78
+ # Get API key
79
+ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
80
+
81
+ @st.cache_data(ttl=300)
82
+ def check_api_status():
83
+ if not OPENROUTER_API_KEY:
84
+ return "No API Key"
85
+ try:
86
+ url = "https://openrouter.ai/api/v1/models"
87
+ headers = {"Authorization": f"Bearer {OPENROUTER_API_KEY}"}
88
+ response = requests.get(url, headers=headers, timeout=10)
89
+ return "Connected" if response.status_code == 200 else "Error"
90
+ except:
91
+ return "Error"
92
+
93
+
94
+ def get_ai_response(messages, model="openai/gpt-3.5-turbo"):
95
+ if not OPENROUTER_API_KEY:
96
+ return "No API key found. Please add OPENROUTER_API_KEY to environment variables."
97
+
98
+ url = "https://openrouter.ai/api/v1/chat/completions"
99
+ headers = {
100
+ "Content-Type": "application/json",
101
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
102
+ "HTTP-Referer": "http://localhost:8501", # Optional: Your site URL
103
+ "X-Title": "Streamlit AI Assistant" # Optional: Your app name
104
+ }
105
+
106
+ # Create system message and user messages
107
+ api_messages = [{"role": "system", "content": "You are a helpful AI assistant. Provide clear and helpful responses."}]
108
+ api_messages.extend(messages)
109
+
110
+ data = {
111
+ "model": model,
112
+ "messages": api_messages,
113
+ "stream": True,
114
+ "max_tokens": 2000,
115
+ "temperature": 0.7,
116
+ "top_p": 1,
117
+ "frequency_penalty": 0,
118
+ "presence_penalty": 0
119
+ }
120
+
121
+ try:
122
+ response = requests.post(url, headers=headers, json=data, stream=True, timeout=60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ # Better error handling
125
+ if response.status_code != 200:
126
+ error_detail = ""
127
+ try:
128
+ error_data = response.json()
129
+ error_detail = error_data.get('error', {}).get('message', f"HTTP {response.status_code}")
130
+ except:
131
+ error_detail = f"HTTP {response.status_code}: {response.reason}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ yield f"API Error: {error_detail}. Please try a different model or check your API key."
134
+ return
135
+
136
+ full_response = ""
137
+ buffer = ""
138
+
139
+ # Using your working streaming logic
140
+ for line in response.iter_lines():
141
+ if line:
142
+ # The server sends lines starting with "data: ..."
143
+ if line.startswith(b"data: "):
144
+ data_str = line[len(b"data: "):].decode("utf-8")
145
+ if data_str.strip() == "[DONE]":
146
+ break
147
+ try:
148
+ data = json.loads(data_str)
149
+ delta = data["choices"][0]["delta"].get("content", "")
150
+ if delta:
151
+ full_response += delta
152
+ yield full_response
153
+ except json.JSONDecodeError:
154
+ continue
155
+ except (KeyError, IndexError):
156
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ except requests.exceptions.Timeout:
159
+ yield "Request timed out. Please try again with a shorter message or different model."
160
+ except requests.exceptions.ConnectionError:
161
+ yield "Connection error. Please check your internet connection and try again."
162
+ except requests.exceptions.RequestException as e:
163
+ yield f"Request error: {str(e)}. Please try again."
164
+ except Exception as e:
165
+ yield f"Unexpected error: {str(e)}. Please try again or contact support."
166
+
167
+ # Header
168
+ st.title("AI Assistant")
169
+ st.caption("Ask me anything")
170
+
171
+ # Sidebar
172
+ with st.sidebar:
173
+ st.header("Settings")
174
+
175
+ # API Status
176
+ status = check_api_status()
177
+ if status == "Connected":
178
+ st.success("🟢 API Connected")
179
+ elif status == "No API Key":
180
+ st.error("No API Key")
181
+ else:
182
+ st.warning("Connection Issue")
183
+
184
+ st.divider()
185
+
186
+ # All models including new ones
187
+ models = [
188
+ ("GPT-3.5 Turbo", "openai/gpt-3.5-turbo"),
189
+ ("LLaMA 3.1 8B", "meta-llama/llama-3.1-8b-instruct"),
190
+ ("LLaMA 3.1 70B", "meta-llama/llama-3.1-70b-instruct"),
191
+ ("DeepSeek Chat v3", "deepseek/deepseek-chat-v3-0324:free"),
192
+ ("DeepSeek R1", "deepseek/deepseek-r1-0528:free"),
193
+ ("Qwen3 Coder", "qwen/qwen3-coder:free"),
194
+ ("Microsoft MAI DS R1", "microsoft/mai-ds-r1:free"),
195
+ ("Gemma 3 27B", "google/gemma-3-27b-it:free"),
196
+ ("Gemma 3 4B", "google/gemma-3-4b-it:free"),
197
+ ("Auto (Best Available)", "openrouter/auto")
198
+ ]
199
+
200
+ model_names = [name for name, _ in models]
201
+ model_ids = [model_id for _, model_id in models]
202
+
203
+ selected_index = st.selectbox("Model", range(len(model_names)),
204
+ format_func=lambda x: model_names[x],
205
+ index=0)
206
+ selected_model = model_ids[selected_index]
207
+
208
+ # Show selected model ID in green
209
+ st.markdown(f"**Model ID:** <span class='model-id'>{selected_model}</span>", unsafe_allow_html=True)
210
+
211
+ st.divider()
212
+
213
+ # Chat History Controls
214
+ st.header("Chat History")
215
+
216
+ # Show number of messages
217
+ if st.session_state.messages:
218
+ st.info(f"Messages stored: {len(st.session_state.messages)}")
219
+
220
+ # Auto-save toggle
221
+ auto_save = st.checkbox("Auto-save messages", value=True)
222
+
223
+ # Manual save/load buttons
224
+ col1, col2 = st.columns(2)
225
+ with col1:
226
+ if st.button("Save History", use_container_width=True):
227
+ save_chat_history(st.session_state.messages)
228
+ st.success("History saved!")
229
+
230
+ with col2:
231
+ if st.button("Load History", use_container_width=True):
232
+ st.session_state.messages = load_chat_history()
233
+ st.success("History loaded!")
234
+ st.rerun()
235
+
236
+ st.divider()
237
+
238
+ # View History
239
+ if st.button("View History File", use_container_width=True):
240
+ if os.path.exists(HISTORY_FILE):
241
+ with open(HISTORY_FILE, 'r', encoding='utf-8') as f:
242
+ history_content = f.read()
243
+ st.text_area("Chat History (JSON)", history_content, height=200)
244
+ else:
245
+ st.warning("No history file found")
246
+
247
+ # Download History
248
+ if os.path.exists(HISTORY_FILE):
249
+ with open(HISTORY_FILE, 'rb') as f:
250
+ st.download_button(
251
+ label="Download History",
252
+ data=f.read(),
253
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
254
+ mime="application/json",
255
+ use_container_width=True
256
+ )
257
+
258
+ st.divider()
259
+
260
+ # Clear controls
261
+ if st.button("Clear Chat", use_container_width=True, type="secondary"):
262
+ clear_chat_history()
263
+ st.success("Chat cleared!")
264
+ st.rerun()
265
+
266
+ # Show welcome message when no messages
267
+ if not st.session_state.messages:
268
+ st.info("How can I help you today?")
269
+
270
+ # Display chat messages
271
+ for message in st.session_state.messages:
272
+ with st.chat_message(message["role"]):
273
+ # Check if this is an assistant message with attribution
274
+ if message["role"] == "assistant" and "Response created by:" in message["content"]:
275
+ # Split content and attribution
276
+ parts = message["content"].split("\n\n---\n*Response created by:")
277
+ main_content = parts[0]
278
+ if len(parts) > 1:
279
+ model_name = parts[1].replace("***", "").replace("**", "")
280
+ st.markdown(main_content)
281
+ st.markdown(f"<div class='model-attribution'>Response created by: <strong>{model_name}</strong></div>", unsafe_allow_html=True)
282
+ else:
283
+ st.markdown(message["content"])
284
+ else:
285
+ st.markdown(message["content"])
286
+
287
+ # Chat input
288
+ if prompt := st.chat_input("Ask anything..."):
289
+ # Add user message
290
+ user_message = {"role": "user", "content": prompt}
291
+ st.session_state.messages.append(user_message)
292
+
293
+ # Auto-save if enabled
294
+ if auto_save:
295
+ save_chat_history(st.session_state.messages)
296
+
297
+ # Display user message
298
+ with st.chat_message("user"):
299
+ st.markdown(prompt)
300
+
301
+ # Get AI response
302
+ with st.chat_message("assistant"):
303
+ placeholder = st.empty()
304
+
305
+ full_response = ""
306
+ try:
307
+ for response in get_ai_response(st.session_state.messages, selected_model):
308
+ full_response = response
309
+ placeholder.markdown(full_response + "▌")
310
 
311
+ # Remove cursor and show final response
312
+ placeholder.markdown(full_response)
 
 
 
 
313
 
314
+ except Exception as e:
315
+ error_msg = f"An error occurred: {str(e)}"
316
+ placeholder.markdown(error_msg)
317
+ full_response = error_msg
318
+
319
+ # Add AI response to messages with attribution
320
+ full_response_with_attribution = full_response + f"\n\n---\n*Response created by: **{model_names[selected_index]}***"
321
+ assistant_message = {"role": "assistant", "content": full_response_with_attribution}
322
+ st.session_state.messages.append(assistant_message)
323
+
324
+ # Auto-save if enabled
325
+ if auto_save:
326
+ save_chat_history(st.session_state.messages)
327
+
328
+ # Show currently using model
329
+ st.caption(f"Currently using: **{model_names[selected_index]}**")