hogiahien commited on
Commit
a65ff1c
Β·
verified Β·
1 Parent(s): 329c8ce

create a simple LLM frontend which lets the user input the API key and custom OpenAI-compatible endpoints and the ability to save chats in local browser storage

Browse files
Files changed (2) hide show
  1. README.md +7 -4
  2. index.html +433 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Chatforge Ai Your Llm Playground
3
- emoji: πŸƒ
4
  colorFrom: blue
5
- colorTo: pink
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: ChatForge AI - Your LLM Playground πŸš€
 
3
  colorFrom: blue
4
+ colorTo: purple
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,434 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>ChatForge AI - Your LLM Playground</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.globe.min.js"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ primary: '#6366f1',
18
+ secondary: '#8b5cf6',
19
+ }
20
+ }
21
+ }
22
+ }
23
+ </script>
24
+ <style>
25
+ .chat-container {
26
+ height: calc(100vh - 200px);
27
+ }
28
+ .message-bubble {
29
+ max-width: 80%;
30
+ word-wrap: break-word;
31
+ }
32
+ .fade-in {
33
+ animation: fadeIn 0.5s ease-in;
34
+ }
35
+ @keyframes fadeIn {
36
+ from { opacity: 0; transform: translateY(10px); }
37
+ to { opacity: 1; transform: translateY(0); }
38
+ }
39
+ .glow {
40
+ box-shadow: 0 0 20px rgba(99, 102, 241, 0.3);
41
+ }
42
+ </style>
43
+ </head>
44
+ <body class="bg-gray-900 text-white">
45
+ <!-- Vanta.js Background -->
46
+ <div id="vanta-bg" class="fixed inset-0 -z-10"></div>
47
+
48
+ <!-- Main Container -->
49
+ <div class="container mx-auto px-4 py-8 max-w-6xl">
50
+ <!-- Header -->
51
+ <header class="text-center mb-8 fade-in">
52
+ <h1 class="text-4xl md:text-6xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent mb-4">
53
+ ChatForge AI
54
+ </h1>
55
+ <p class="text-xl text-gray-300">Your Personal LLM Playground πŸš€</p>
56
+ </header>
57
+
58
+ <!-- Settings Panel -->
59
+ <div class="bg-gray-800/80 backdrop-blur-lg rounded-2xl p-6 mb-6 glow fade-in">
60
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
61
+ <!-- API Configuration -->
62
+ <div>
63
+ <h2 class="text-2xl font-semibold mb-4 text-primary flex items-center">
64
+ <i data-feather="key" class="mr-2"></i>
65
+ API Configuration
66
+ </h2>
67
+ <div class="space-y-4">
68
+ <div>
69
+ <label class="block text-sm font-medium text-gray-300 mb-2">API Key</label>
70
+ <input type="password" id="apiKey"
71
+ class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent transition-all duration-300"
72
+ placeholder="Enter your API key...">
73
+ </div>
74
+ <div>
75
+ <label class="block text-sm font-medium text-gray-300 mb-2">Endpoint URL</label>
76
+ <input type="url" id="endpoint"
77
+ class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent transition-all duration-300"
78
+ placeholder="https://api.openai.com/v1/chat/completions"
79
+ value="https://api.openai.com/v1/chat/completions">
80
+ </div>
81
+ <button onclick="saveSettings()"
82
+ class="w-full bg-primary hover:bg-primary/90 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 flex items-center justify-center">
83
+ <i data-feather="save" class="mr-2"></i>
84
+ Save Settings
85
+ </button>
86
+ </div>
87
+ </div>
88
+
89
+ <!-- Chat Management -->
90
+ <div>
91
+ <h2 class="text-2xl font-semibold mb-4 text-secondary flex items-center">
92
+ <i data-feather="message-square" class="mr-2"></i>
93
+ Chat Management
94
+ </h2>
95
+ <div class="space-y-4">
96
+ <div class="flex space-x-4">
97
+ <input type="text" id="chatName"
98
+ class="flex-1 px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:ring-2 focus:ring-secondary focus:border-transparent transition-all duration-300"
99
+ placeholder="New chat name...">
100
+ <button onclick="createNewChat()"
101
+ class="bg-secondary hover:bg-secondary/90 text-white px-6 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105">
102
+ <i data-feather="plus"></i>
103
+ </button>
104
+ </div>
105
+ <select id="chatSelector" onchange="loadChat(this.value)"
106
+ class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:ring-2 focus:ring-secondary focus:border-transparent transition-all duration-300">
107
+ <option value="">Select a chat...</option>
108
+ </select>
109
+ <button onclick="deleteCurrentChat()"
110
+ class="w-full bg-red-600 hover:bg-red-700 text-white py-3 px-6 rounded-xl font-semibold transition-all duration-300 flex items-center justify-center">
111
+ <i data-feather="trash-2" class="mr-2"></i>
112
+ Delete Current Chat
113
+ </button>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+
119
+ <!-- Chat Interface -->
120
+ <div class="bg-gray-800/80 backdrop-blur-lg rounded-2xl p-6 chat-container flex flex-col glow fade-in">
121
+ <!-- Chat Messages -->
122
+ <div id="chatMessages" class="flex-1 overflow-y-auto mb-6 space-y-4 p-4">
123
+ <div class="text-center text-gray-400 py-8">
124
+ <i data-feather="message-circle" class="w-12 h-12 mx-auto mb-4 opacity-50"></i>
125
+ <p>Start a conversation with your AI assistant!</p>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- Input Area -->
130
+ <div class="border-t border-gray-700 pt-4">
131
+ <div class="flex space-x-4">
132
+ <input type="text" id="messageInput"
133
+ class="flex-1 px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:ring-2 focus:ring-primary focus:border-transparent transition-all duration-300"
134
+ placeholder="Type your message here..."
135
+ onkeypress="handleKeyPress(event)">
136
+ <button onclick="sendMessage()"
137
+ class="bg-primary hover:bg-primary/90 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-300 transform hover:scale-105 flex items-center">
138
+ <i data-feather="send" class="mr-2"></i>
139
+ Send
140
+ </button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Status Bar -->
146
+ <div class="mt-4 text-center text-gray-400 text-sm">
147
+ <div id="status" class="flex items-center justify-center space-x-2">
148
+ <i data-feather="wifi" class="w-4 h-4"></i>
149
+ <span>Ready to chat</span>
150
+ </div>
151
+ </div>
152
+ </div>
153
+
154
+ <script>
155
+ // Initialize Vanta.js background
156
+ VANTA.GLOBE({
157
+ el: "#vanta-bg",
158
+ mouseControls: true,
159
+ touchControls: true,
160
+ gyroControls: false,
161
+ minHeight: 200.00,
162
+ minWidth: 200.00,
163
+ scale: 1.00,
164
+ scaleMobile: 1.00,
165
+ color: 0x6366f1,
166
+ backgroundColor: 0x111827,
167
+ size: 0.8
168
+ });
169
+
170
+ // Global variables
171
+ let currentChatId = null;
172
+ let chats = JSON.parse(localStorage.getItem('chats') || '{}');
173
+ let settings = JSON.parse(localStorage.getItem('settings') || '{}');
174
+
175
+ // Initialize on load
176
+ document.addEventListener('DOMContentLoaded', function() {
177
+ feather.replace();
178
+ loadSettings();
179
+ loadChatList();
180
+
181
+ // Load last active chat if exists
182
+ const lastChat = localStorage.getItem('lastActiveChat');
183
+ if (lastChat && chats[lastChat]) {
184
+ loadChat(lastChat);
185
+ }
186
+ });
187
+
188
+ function saveSettings() {
189
+ const apiKey = document.getElementById('apiKey').value;
190
+ const endpoint = document.getElementById('endpoint').value;
191
+
192
+ settings = { apiKey, endpoint };
193
+ localStorage.setItem('settings', JSON.stringify(settings));
194
+
195
+ showStatus('Settings saved successfully!', 'success');
196
+ }
197
+
198
+ function loadSettings() {
199
+ if (settings.apiKey) {
200
+ document.getElementById('apiKey').value = settings.apiKey;
201
+ }
202
+ if (settings.endpoint) {
203
+ document.getElementById('endpoint').value = settings.endpoint;
204
+ }
205
+ }
206
+
207
+ function createNewChat() {
208
+ const chatName = document.getElementById('chatName').value.trim();
209
+ if (!chatName) {
210
+ showStatus('Please enter a chat name', 'error');
211
+ return;
212
+ }
213
+
214
+ const chatId = 'chat_' + Date.now();
215
+ chats[chatId] = {
216
+ name: chatName,
217
+ messages: [],
218
+ createdAt: new Date().toISOString()
219
+ };
220
+
221
+ localStorage.setItem('chats', JSON.stringify(chats));
222
+ loadChatList();
223
+ loadChat(chatId);
224
+ document.getElementById('chatName').value = '';
225
+
226
+ showStatus('New chat created!', 'success');
227
+ }
228
+
229
+ function loadChatList() {
230
+ const selector = document.getElementById('chatSelector');
231
+ selector.innerHTML = '<option value="">Select a chat...</option>';
232
+
233
+ Object.keys(chats).forEach(chatId => {
234
+ const option = document.createElement('option');
235
+ option.value = chatId;
236
+ option.textContent = chats[chatId].name;
237
+ if (chatId === currentChatId) {
238
+ option.selected = true;
239
+ }
240
+ selector.appendChild(option);
241
+ });
242
+ }
243
+
244
+ function loadChat(chatId) {
245
+ if (!chatId || !chats[chatId]) {
246
+ currentChatId = null;
247
+ document.getElementById('chatMessages').innerHTML = `
248
+ <div class="text-center text-gray-400 py-8">
249
+ <i data-feather="message-circle" class="w-12 h-12 mx-auto mb-4 opacity-50"></i>
250
+ <p>Start a conversation with your AI assistant!</p>
251
+ </div>`;
252
+ feather.replace();
253
+ return;
254
+ }
255
+
256
+ currentChatId = chatId;
257
+ localStorage.setItem('lastActiveChat', chatId);
258
+
259
+ const messagesContainer = document.getElementById('chatMessages');
260
+ messagesContainer.innerHTML = '';
261
+
262
+ chats[chatId].messages.forEach(message => {
263
+ addMessageToChat(message.role, message.content, false);
264
+ });
265
+
266
+ loadChatList();
267
+ showStatus(`Loaded chat: ${chats[chatId].name}`, 'success');
268
+ }
269
+
270
+ function deleteCurrentChat() {
271
+ if (!currentChatId) {
272
+ showStatus('No chat selected to delete', 'error');
273
+ return;
274
+ }
275
+
276
+ if (confirm(`Are you sure you want to delete "${chats[currentChatId].name}"?`)) {
277
+ delete chats[currentChatId];
278
+ localStorage.setItem('chats', JSON.stringify(chats));
279
+ currentChatId = null;
280
+ loadChatList();
281
+ loadChat(null);
282
+ showStatus('Chat deleted successfully', 'success');
283
+ }
284
+ }
285
+
286
+ async function sendMessage() {
287
+ const input = document.getElementById('messageInput');
288
+ const message = input.value.trim();
289
+
290
+ if (!message) return;
291
+ if (!settings.apiKey || !settings.endpoint) {
292
+ showStatus('Please configure API settings first', 'error');
293
+ return;
294
+ }
295
+ if (!currentChatId) {
296
+ createNewChat();
297
+ }
298
+
299
+ // Add user message
300
+ addMessageToChat('user', message, true);
301
+ input.value = '';
302
+
303
+ // Show typing indicator
304
+ const typingIndicator = addTypingIndicator();
305
+
306
+ try {
307
+ const response = await fetch(settings.endpoint, {
308
+ method: 'POST',
309
+ headers: {
310
+ 'Content-Type': 'application/json',
311
+ 'Authorization': `Bearer ${settings.apiKey}`
312
+ },
313
+ body: JSON.stringify({
314
+ model: "gpt-3.5-turbo",
315
+ messages: [
316
+ ...chats[currentChatId].messages.map(msg => ({
317
+ role: msg.role,
318
+ content: msg.content
319
+ })),
320
+ { role: 'user', content: message }
321
+ ],
322
+ stream: false
323
+ })
324
+ });
325
+
326
+ if (!response.ok) {
327
+ throw new Error(`API error: ${response.status}`);
328
+ }
329
+
330
+ const data = await response.json();
331
+ const aiResponse = data.choices[0].message.content;
332
+
333
+ // Remove typing indicator
334
+ typingIndicator.remove();
335
+
336
+ // Add AI response
337
+ addMessageToChat('assistant', aiResponse, true);
338
+
339
+ showStatus('Message sent successfully', 'success');
340
+ } catch (error) {
341
+ typingIndicator.remove();
342
+ showStatus(`Error: ${error.message}`, 'error');
343
+ console.error('API Error:', error);
344
+ }
345
+ }
346
+
347
+ function addMessageToChat(role, content, save = true) {
348
+ const messagesContainer = document.getElementById('chatMessages');
349
+
350
+ // Remove empty state if present
351
+ if (messagesContainer.children.length === 1 && messagesContainer.children[0].querySelector('.text-center')) {
352
+ messagesContainer.innerHTML = '';
353
+ }
354
+
355
+ const messageDiv = document.createElement('div');
356
+ messageDiv.className = `fade-in flex ${role === 'user' ? 'justify-end' : 'justify-start'}`;
357
+
358
+ const bubble = document.createElement('div');
359
+ bubble.className = `message-bubble px-4 py-3 rounded-2xl ${
360
+ role === 'user'
361
+ ? 'bg-primary text-white rounded-br-none'
362
+ : 'bg-gray-700 text-white rounded-bl-none'
363
+ }`;
364
+ bubble.textContent = content;
365
+
366
+ messageDiv.appendChild(bubble);
367
+ messagesContainer.appendChild(messageDiv);
368
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
369
+
370
+ if (save && currentChatId) {
371
+ chats[currentChatId].messages.push({ role, content });
372
+ localStorage.setItem('chats', JSON.stringify(chats));
373
+ }
374
+
375
+ feather.replace();
376
+ }
377
+
378
+ function addTypingIndicator() {
379
+ const messagesContainer = document.getElementById('chatMessages');
380
+ const typingDiv = document.createElement('div');
381
+ typingDiv.className = 'fade-in flex justify-start';
382
+
383
+ const bubble = document.createElement('div');
384
+ bubble.className = 'message-bubble px-4 py-3 rounded-2xl bg-gray-700 text-white rounded-bl-none flex space-x-1';
385
+ bubble.innerHTML = `
386
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
387
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div>
388
+ <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
389
+ `;
390
+
391
+ typingDiv.appendChild(bubble);
392
+ messagesContainer.appendChild(typingDiv);
393
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
394
+
395
+ return typingDiv;
396
+ }
397
+
398
+ function handleKeyPress(event) {
399
+ if (event.key === 'Enter' && !event.shiftKey) {
400
+ event.preventDefault();
401
+ sendMessage();
402
+ }
403
+ }
404
+
405
+ function showStatus(message, type = 'info') {
406
+ const status = document.getElementById('status');
407
+ const icon = status.querySelector('i');
408
+
409
+ status.className = `flex items-center justify-center space-x-2 ${
410
+ type === 'error' ? 'text-red-400' :
411
+ type === 'success' ? 'text-green-400' : 'text-gray-400'
412
+ }`;
413
+
414
+ status.querySelector('span').textContent = message;
415
+
416
+ // Update icon based on status type
417
+ const iconName = type === 'error' ? 'alert-circle' :
418
+ type === 'success' ? 'check-circle' : 'info';
419
+ icon.setAttribute('data-feather', iconName);
420
+ feather.replace();
421
+
422
+ // Auto-hide success messages after 3 seconds
423
+ if (type === 'success') {
424
+ setTimeout(() => {
425
+ status.className = 'flex items-center justify-center space-x-2 text-gray-400';
426
+ status.querySelector('span').textContent = 'Ready to chat';
427
+ icon.setAttribute('data-feather', 'wifi');
428
+ feather.replace();
429
+ }, 3000);
430
+ }
431
+ }
432
+ </script>
433
+ </body>
434
  </html>