Pepguy commited on
Commit
bb18161
·
verified ·
1 Parent(s): 017e9c7

Update public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +276 -46
public/index.html CHANGED
@@ -1,49 +1,279 @@
1
- <!-- producer.html -->
2
  <!DOCTYPE html>
3
- <html>
4
- <body>
5
- <h3>Broadcaster</h3>
6
- <p>Hello </p>
7
- <video id="localVideo" autoplay muted></video>
8
- <script>
9
- const ws = new WebSocket('ws://pepguy-swarming-1.hf.space');
10
- let transport, producer;
11
-
12
- ws.onmessage = async ({ data }) => {
13
- const { action, data: d } = JSON.parse(data);
14
-
15
- if (action === 'producerTransportCreated') {
16
- transport = device.createSendTransport(d);
17
- transport.on('connect', ({ dtlsParameters }, cb) =>
18
- ws.send(JSON.stringify({ action: 'connectProducerTransport', data: { dtlsParameters } })), cb()
19
- );
20
- transport.on('produce', ({ kind, rtpParameters }, cb) =>
21
- ws.send(JSON.stringify({ action: 'produce', data: { kind, rtpParameters } })), cb({ id: '' })
22
- );
23
- }
24
-
25
- if (action === 'produced') {
26
- console.log('Producer ready:', d.producerId);
27
- }
28
- };
29
-
30
- async function start() {
31
- // 1) getUserMedia
32
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
33
- document.getElementById('localVideo').srcObject = stream;
34
-
35
- // 2) load device RTP capabilities
36
- ws.send(JSON.stringify({ action: 'createProducerTransport' }));
37
- const { rtpCapabilities } = await (await fetch('/rtpCapabilities')).json();
38
- device = new mediasoupClient.Device();
39
- await device.load({ routerRtpCapabilities: rtpCapabilities });
40
-
41
- // 3) wait for transport then produce
42
- stream.getTracks().forEach(track => transport.produce({ track }));
43
- }
44
-
45
- start();
46
- </script>
47
- <script src="https://unpkg.com/mediasoup-client@3/lib/index.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </body>
49
  </html>
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Developer Hub</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ darkMode: 'class',
15
+ theme: { extend: { colors: { gray: { 850: '#1f2937', 900: '#111827', 950: '#030712' } } } }
16
+ }
17
+ </script>
18
+ <style>
19
+ .markdown-body pre { background: #1e1e1e; padding: 1rem; border-radius: 0.5rem; position: relative; }
20
+ .copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: #374151; color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.75rem; }
21
+ .copy-btn:hover { background: #4b5563; }
22
+ .reasoning-block { border-left: 3px solid #6366f1; padding-left: 1rem; margin-bottom: 1rem; color: #9ca3af; font-size: 0.9em; background: rgba(99, 102, 241, 0.05); padding: 0.5rem; border-radius: 0 0.5rem 0.5rem 0; }
23
+ </style>
24
+ </head>
25
+ <body class="bg-gray-950 text-gray-200 h-screen flex font-sans overflow-hidden">
26
+
27
+ <!-- Sidebar -->
28
+ <aside class="w-64 bg-gray-900 border-r border-gray-800 flex flex-col">
29
+ <div class="p-4 border-b border-gray-800 flex justify-between items-center">
30
+ <h1 class="font-bold text-lg text-white">Dev AI Hub</h1>
31
+ <button onclick="createNewChat()" class="text-blue-400 hover:text-blue-300">+</button>
32
+ </div>
33
+ <div id="chat-list" class="flex-1 overflow-y-auto p-2 space-y-1"></div>
34
+ </aside>
35
+
36
+ <!-- Main Chat Area -->
37
+ <main class="flex-1 flex flex-col h-full bg-gray-950">
38
+ <!-- Header -->
39
+ <header class="h-14 border-b border-gray-800 flex items-center px-6 justify-between bg-gray-900/50">
40
+ <h2 id="current-chat-title" class="font-semibold text-gray-100">Select or create a chat</h2>
41
+ <div class="flex items-center space-x-4">
42
+ <span class="text-xs text-gray-500 bg-gray-800 px-2 py-1 rounded">Tokens: <span id="token-counter">0</span></span>
43
+ <select id="model-select" class="bg-gray-800 text-sm rounded border border-gray-700 px-2 py-1 outline-none focus:border-blue-500">
44
+ <option value="claude">Claude Sonnet 3.5</option>
45
+ <option value="haiku">Claude Haiku</option>
46
+ <option value="maverick">Llama 3 Maverick</option>
47
+ </select>
48
+ <button onclick="deleteCurrentChat()" class="text-red-500 text-sm hover:text-red-400">Delete Chat</button>
49
+ </div>
50
+ </header>
51
+
52
+ <!-- Messages -->
53
+ <div id="chat-window" class="flex-1 overflow-y-auto p-6 space-y-6"></div>
54
+
55
+ <!-- Input Area -->
56
+ <div class="p-4 bg-gray-900 border-t border-gray-800">
57
+ <div id="image-preview-container" class="flex gap-2 mb-2 hidden"></div>
58
+ <div class="flex items-end gap-2 bg-gray-800 rounded-xl border border-gray-700 p-2 focus-within:border-blue-500 transition-colors">
59
+ <label class="cursor-pointer p-2 text-gray-400 hover:text-white transition">
60
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
61
+ <input type="file" id="image-input" accept="image/*" multiple class="hidden" onchange="handleImageSelect(event)">
62
+ </label>
63
+ <textarea id="message-input" rows="1" class="flex-1 bg-transparent resize-none outline-none text-sm p-2 max-h-32" placeholder="Ask anything..."></textarea>
64
+ <button onclick="sendMessage()" id="send-btn" class="p-2 text-blue-400 hover:text-blue-300 disabled:opacity-50 transition">
65
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </main>
70
+
71
+ <script>
72
+ // Configure Marked with highlight.js for styling block code
73
+ marked.setOptions({
74
+ highlight: function(code, lang) {
75
+ const language = hljs.getLanguage(lang) ? lang : 'plaintext';
76
+ return hljs.highlight(code, { language }).value;
77
+ }
78
+ });
79
+
80
+ let currentChatId = null;
81
+ let attachedImages =[];
82
+ let pollingInterval = null;
83
+
84
+ async function loadSidebar() {
85
+ const res = await fetch('/api/chats');
86
+ const chats = await res.json();
87
+ const list = document.getElementById('chat-list');
88
+ list.innerHTML = chats.map(c => `
89
+ <div onclick="selectChat('${c.id}')" class="p-3 rounded-lg cursor-pointer hover:bg-gray-800 transition ${c.id === currentChatId ? 'bg-gray-800' : ''}">
90
+ <div class="text-sm font-medium truncate">${c.title}</div>
91
+ <div class="text-xs text-gray-500 mt-1">${c.totalTokens.toLocaleString()} tokens</div>
92
+ </div>
93
+ `).join('');
94
+ }
95
+
96
+ async function createNewChat() {
97
+ const res = await fetch('/api/chats', { method: 'POST' });
98
+ const chat = await res.json();
99
+ selectChat(chat.id);
100
+ loadSidebar();
101
+ }
102
+
103
+ async function deleteCurrentChat() {
104
+ if (!currentChatId) return;
105
+ if (confirm("Permanently delete this chat?")) {
106
+ await fetch(`/api/chats/${currentChatId}`, { method: 'DELETE' });
107
+ currentChatId = null;
108
+ document.getElementById('chat-window').innerHTML = '';
109
+ document.getElementById('current-chat-title').innerText = 'Select or create a chat';
110
+ document.getElementById('token-counter').innerText = '0';
111
+ loadSidebar();
112
+ }
113
+ }
114
+
115
+ async function selectChat(id) {
116
+ currentChatId = id;
117
+ clearInterval(pollingInterval);
118
+ const res = await fetch(`/api/chats/${id}`);
119
+ const chat = await res.json();
120
+
121
+ document.getElementById('current-chat-title').innerText = chat.title;
122
+ document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
123
+
124
+ renderMessages(chat.messages);
125
+ loadSidebar(); // Updates highlighting
126
+
127
+ // If a reload happens during generation, poll until done.
128
+ if (chat.isGenerating) {
129
+ pollGeneratingChat(id);
130
+ }
131
+ }
132
+
133
+ function renderMessages(messages) {
134
+ const container = document.getElementById('chat-window');
135
+ container.innerHTML = messages.map(m => {
136
+ let html = '';
137
+ if (m.reasoning) {
138
+ html += `<div class="reasoning-block"><i>Thinking...</i><br/>${marked.parse(m.reasoning)}</div>`;
139
+ }
140
+ if (m.content) {
141
+ html += DOMPurify.sanitize(marked.parse(m.content));
142
+ }
143
+
144
+ return `
145
+ <div class="flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}">
146
+ <div class="max-w-[80%] rounded-xl p-4 ${m.role === 'user' ? 'bg-blue-600 text-white' : 'bg-gray-850 border border-gray-800 markdown-body'}">
147
+ ${m.role === 'user' ? m.content : html}
148
+ </div>
149
+ </div>
150
+ `;
151
+ }).join('');
152
+
153
+ // Add copy buttons to code blocks
154
+ document.querySelectorAll('.markdown-body pre').forEach(pre => {
155
+ if (!pre.querySelector('.copy-btn')) {
156
+ const btn = document.createElement('button');
157
+ btn.className = 'copy-btn';
158
+ btn.innerText = 'Copy';
159
+ btn.onclick = () => {
160
+ navigator.clipboard.writeText(pre.innerText.replace('Copy', '').trim());
161
+ btn.innerText = 'Copied!';
162
+ setTimeout(() => btn.innerText = 'Copy', 2000);
163
+ };
164
+ pre.appendChild(btn);
165
+ }
166
+ });
167
+ container.scrollTop = container.scrollHeight;
168
+ }
169
+
170
+ function pollGeneratingChat(id) {
171
+ pollingInterval = setInterval(async () => {
172
+ const res = await fetch(`/api/chats/${id}`);
173
+ const chat = await res.json();
174
+ renderMessages(chat.messages);
175
+ document.getElementById('token-counter').innerText = chat.totalTokens.toLocaleString();
176
+ if (!chat.isGenerating) clearInterval(pollingInterval);
177
+ }, 1500);
178
+ }
179
+
180
+ function handleImageSelect(event) {
181
+ const files = event.target.files;
182
+ const container = document.getElementById('image-preview-container');
183
+ container.classList.remove('hidden');
184
+
185
+ Array.from(files).forEach(file => {
186
+ const reader = new FileReader();
187
+ reader.onload = (e) => {
188
+ attachedImages.push(e.target.result);
189
+ container.innerHTML += `<img src="${e.target.result}" class="h-12 w-12 object-cover rounded border border-gray-600">`;
190
+ };
191
+ reader.readAsDataURL(file);
192
+ });
193
+ }
194
+
195
+ async function sendMessage() {
196
+ const input = document.getElementById('message-input');
197
+ const text = input.value.trim();
198
+ if (!text || !currentChatId) return;
199
+
200
+ const payload = {
201
+ model: document.getElementById('model-select').value,
202
+ prompt: text,
203
+ images: attachedImages
204
+ };
205
+
206
+ const container = document.getElementById('chat-window');
207
+ container.innerHTML += `
208
+ <div class="flex justify-end mb-6"><div class="max-w-[80%] rounded-xl p-4 bg-blue-600 text-white">${text}</div></div>
209
+ <div class="flex justify-start mb-6" id="temp-ai-wrapper"><div class="max-w-[80%] rounded-xl p-4 bg-gray-850 border border-gray-800 markdown-body" id="temp-ai-msg"><span class="animate-pulse">Thinking...</span></div></div>
210
+ `;
211
+ container.scrollTop = container.scrollHeight;
212
+
213
+ input.value = '';
214
+ attachedImages =[];
215
+ document.getElementById('image-preview-container').innerHTML = '';
216
+ document.getElementById('image-preview-container').classList.add('hidden');
217
+ document.getElementById('send-btn').disabled = true;
218
+
219
+ try {
220
+ const response = await fetch(`/api/chats/${currentChatId}/stream`, {
221
+ method: 'POST',
222
+ headers: { 'Content-Type': 'application/json' },
223
+ body: JSON.stringify(payload)
224
+ });
225
+
226
+ const reader = response.body.getReader();
227
+ const decoder = new TextDecoder("utf-8");
228
+ let aiContent = "";
229
+ let aiReasoning = "";
230
+
231
+ while (true) {
232
+ const { value, done } = await reader.read();
233
+ if (done) break;
234
+
235
+ const chunkText = decoder.decode(value, { stream: true });
236
+
237
+ const chunks = chunkText.split(/(__THINK__|__USAGE__)/);
238
+ let i = 0;
239
+ while (i < chunks.length) {
240
+ if (chunks[i] === '__THINK__') {
241
+ aiReasoning += chunks[i+1];
242
+ i += 2;
243
+ } else if (chunks[i] === '__USAGE__') {
244
+ const usageData = JSON.parse(chunks[i+1]);
245
+ document.getElementById('token-counter').innerText =
246
+ (parseInt(document.getElementById('token-counter').innerText.replace(/,/g, '')) + usageData.totalTokenCount).toLocaleString();
247
+ i += 2;
248
+ } else {
249
+ aiContent += chunks[i];
250
+ i++;
251
+ }
252
+ }
253
+
254
+ let tempHtml = "";
255
+ if (aiReasoning) tempHtml += `<div class="reasoning-block"><i>Thinking...</i><br/>${marked.parse(aiReasoning)}</div>`;
256
+ if (aiContent) tempHtml += DOMPurify.sanitize(marked.parse(aiContent));
257
+
258
+ document.getElementById('temp-ai-msg').innerHTML = tempHtml || "<span class='animate-pulse'>Thinking...</span>";
259
+ container.scrollTop = container.scrollHeight;
260
+ }
261
+
262
+ selectChat(currentChatId);
263
+
264
+ } catch (error) {
265
+ console.error(error);
266
+ document.getElementById('temp-ai-msg').innerHTML = "<span class='text-red-500'>Error connecting to API.</span>";
267
+ } finally {
268
+ document.getElementById('send-btn').disabled = false;
269
+ }
270
+ }
271
+
272
+ document.getElementById('message-input').addEventListener('keydown', (e) => {
273
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
274
+ });
275
+
276
+ loadSidebar();
277
+ </script>
278
  </body>
279
  </html>