Tafita1206 commited on
Commit
3d7017e
Β·
verified Β·
1 Parent(s): 8c8e560

Create public/index.html

Browse files
Files changed (1) hide show
  1. public/index.html +434 -0
public/index.html ADDED
@@ -0,0 +1,434 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Free Claude Code</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f0f13;
10
+ --surface: #1a1a24;
11
+ --surface2: #24243a;
12
+ --border: #2e2e4a;
13
+ --accent: #7c6ff7;
14
+ --accent2: #5b54d6;
15
+ --text: #e8e8f0;
16
+ --muted: #8888aa;
17
+ --user-bg: #2a2a4a;
18
+ --ai-bg: #1e1e2e;
19
+ --green: #4ade80;
20
+ --red: #f87171;
21
+ --radius: 12px;
22
+ }
23
+ * { box-sizing: border-box; margin: 0; padding: 0; }
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
26
+ background: var(--bg);
27
+ color: var(--text);
28
+ height: 100dvh;
29
+ display: flex;
30
+ flex-direction: column;
31
+ overflow: hidden;
32
+ }
33
+
34
+ /* ── Header ── */
35
+ header {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: space-between;
39
+ padding: 12px 16px;
40
+ background: var(--surface);
41
+ border-bottom: 1px solid var(--border);
42
+ flex-shrink: 0;
43
+ }
44
+ .logo { display: flex; align-items: center; gap: 10px; }
45
+ .logo-icon {
46
+ width: 32px; height: 32px;
47
+ background: linear-gradient(135deg, var(--accent), #a78bfa);
48
+ border-radius: 8px;
49
+ display: flex; align-items: center; justify-content: center;
50
+ font-size: 16px;
51
+ }
52
+ .logo-text h1 { font-size: 15px; font-weight: 600; }
53
+ .logo-text p { font-size: 11px; color: var(--muted); }
54
+
55
+ .status-badge {
56
+ display: flex; align-items: center; gap: 6px;
57
+ padding: 5px 10px;
58
+ background: var(--surface2);
59
+ border: 1px solid var(--border);
60
+ border-radius: 20px;
61
+ font-size: 12px;
62
+ cursor: pointer;
63
+ }
64
+ .dot {
65
+ width: 7px; height: 7px; border-radius: 50%;
66
+ background: var(--muted);
67
+ transition: background 0.3s;
68
+ }
69
+ .dot.ok { background: var(--green); box-shadow: 0 0 6px var(--green); }
70
+ .dot.err { background: var(--red); }
71
+
72
+ /* ── Model selector ── */
73
+ .model-bar {
74
+ display: flex;
75
+ gap: 8px;
76
+ padding: 10px 16px;
77
+ background: var(--surface);
78
+ border-bottom: 1px solid var(--border);
79
+ overflow-x: auto;
80
+ flex-shrink: 0;
81
+ scrollbar-width: none;
82
+ }
83
+ .model-bar::-webkit-scrollbar { display: none; }
84
+ .model-btn {
85
+ padding: 5px 12px;
86
+ border-radius: 20px;
87
+ border: 1px solid var(--border);
88
+ background: transparent;
89
+ color: var(--muted);
90
+ font-size: 12px;
91
+ cursor: pointer;
92
+ white-space: nowrap;
93
+ transition: all 0.2s;
94
+ }
95
+ .model-btn.active {
96
+ background: var(--accent);
97
+ border-color: var(--accent);
98
+ color: #fff;
99
+ }
100
+
101
+ /* ── Messages ── */
102
+ #messages {
103
+ flex: 1;
104
+ overflow-y: auto;
105
+ padding: 16px;
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 12px;
109
+ scroll-behavior: smooth;
110
+ }
111
+ #messages::-webkit-scrollbar { width: 4px; }
112
+ #messages::-webkit-scrollbar-track { background: transparent; }
113
+ #messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
114
+
115
+ .msg {
116
+ display: flex;
117
+ gap: 10px;
118
+ max-width: 100%;
119
+ animation: fadeIn 0.2s ease;
120
+ }
121
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
122
+
123
+ .msg.user { flex-direction: row-reverse; }
124
+ .avatar {
125
+ width: 30px; height: 30px; border-radius: 8px;
126
+ display: flex; align-items: center; justify-content: center;
127
+ font-size: 14px; flex-shrink: 0;
128
+ }
129
+ .msg.user .avatar { background: var(--accent2); }
130
+ .msg.ai .avatar { background: var(--surface2); border: 1px solid var(--border); }
131
+
132
+ .bubble {
133
+ max-width: calc(100% - 48px);
134
+ padding: 10px 14px;
135
+ border-radius: var(--radius);
136
+ font-size: 14px;
137
+ line-height: 1.6;
138
+ word-break: break-word;
139
+ }
140
+ .msg.user .bubble {
141
+ background: var(--user-bg);
142
+ border: 1px solid var(--border);
143
+ border-top-right-radius: 3px;
144
+ }
145
+ .msg.ai .bubble {
146
+ background: var(--ai-bg);
147
+ border: 1px solid var(--border);
148
+ border-top-left-radius: 3px;
149
+ }
150
+
151
+ /* Code blocks */
152
+ .bubble pre {
153
+ background: #0d0d17;
154
+ border: 1px solid var(--border);
155
+ border-radius: 8px;
156
+ padding: 10px;
157
+ overflow-x: auto;
158
+ margin: 8px 0;
159
+ font-size: 12px;
160
+ }
161
+ .bubble code { font-family: 'Fira Code', 'Consolas', monospace; font-size: 12px; }
162
+ .bubble p:not(:last-child) { margin-bottom: 8px; }
163
+
164
+ /* Thinking cursor */
165
+ .cursor {
166
+ display: inline-block;
167
+ width: 8px; height: 14px;
168
+ background: var(--accent);
169
+ border-radius: 2px;
170
+ vertical-align: middle;
171
+ animation: blink 0.8s infinite;
172
+ }
173
+ @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
174
+
175
+ /* ── Input area ── */
176
+ .input-area {
177
+ padding: 12px 16px;
178
+ background: var(--surface);
179
+ border-top: 1px solid var(--border);
180
+ flex-shrink: 0;
181
+ }
182
+ .input-row {
183
+ display: flex;
184
+ gap: 8px;
185
+ align-items: flex-end;
186
+ background: var(--surface2);
187
+ border: 1px solid var(--border);
188
+ border-radius: var(--radius);
189
+ padding: 8px 8px 8px 14px;
190
+ transition: border-color 0.2s;
191
+ }
192
+ .input-row:focus-within { border-color: var(--accent); }
193
+ #input {
194
+ flex: 1;
195
+ background: transparent;
196
+ border: none;
197
+ outline: none;
198
+ color: var(--text);
199
+ font-size: 14px;
200
+ resize: none;
201
+ max-height: 120px;
202
+ min-height: 22px;
203
+ line-height: 1.5;
204
+ font-family: inherit;
205
+ }
206
+ #input::placeholder { color: var(--muted); }
207
+ #send {
208
+ width: 36px; height: 36px;
209
+ background: var(--accent);
210
+ border: none;
211
+ border-radius: 8px;
212
+ cursor: pointer;
213
+ display: flex; align-items: center; justify-content: center;
214
+ flex-shrink: 0;
215
+ transition: background 0.2s, transform 0.1s;
216
+ }
217
+ #send:hover { background: var(--accent2); }
218
+ #send:active { transform: scale(0.93); }
219
+ #send:disabled { background: var(--border); cursor: not-allowed; }
220
+ #send svg { width: 16px; height: 16px; fill: #fff; }
221
+
222
+ .hint { text-align: center; font-size: 11px; color: var(--muted); margin-top: 6px; }
223
+
224
+ /* ── Welcome screen ── */
225
+ .welcome {
226
+ display: flex;
227
+ flex-direction: column;
228
+ align-items: center;
229
+ justify-content: center;
230
+ flex: 1;
231
+ gap: 12px;
232
+ text-align: center;
233
+ padding: 24px;
234
+ color: var(--muted);
235
+ }
236
+ .welcome-icon { font-size: 48px; }
237
+ .welcome h2 { font-size: 18px; color: var(--text); font-weight: 600; }
238
+ .welcome p { font-size: 13px; max-width: 280px; line-height: 1.6; }
239
+ .chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 8px; }
240
+ .chip {
241
+ padding: 7px 13px;
242
+ background: var(--surface2);
243
+ border: 1px solid var(--border);
244
+ border-radius: 20px;
245
+ font-size: 12px;
246
+ color: var(--text);
247
+ cursor: pointer;
248
+ transition: border-color 0.2s;
249
+ }
250
+ .chip:hover { border-color: var(--accent); color: var(--accent); }
251
+ </style>
252
+ </head>
253
+ <body>
254
+
255
+ <!-- Header -->
256
+ <header>
257
+ <div class="logo">
258
+ <div class="logo-icon">✦</div>
259
+ <div class="logo-text">
260
+ <h1>Free Claude Code</h1>
261
+ <p>Powered by NVIDIA NIM</p>
262
+ </div>
263
+ </div>
264
+ <div class="status-badge" onclick="checkStatus()">
265
+ <div class="dot" id="dot"></div>
266
+ <span id="status-text">VΓ©rification...</span>
267
+ </div>
268
+ </header>
269
+
270
+ <!-- Model bar -->
271
+ <div class="model-bar">
272
+ <button class="model-btn active" data-model="claude-sonnet-4-20250514" onclick="selectModel(this)">Kimi K2</button>
273
+ <button class="model-btn" data-model="glm-4" onclick="selectModel(this)">GLM 4.7</button>
274
+ <button class="model-btn" data-model="minimax-m2" onclick="selectModel(this)">MiniMax M2</button>
275
+ <button class="model-btn" data-model="devstral" onclick="selectModel(this)">Devstral ⚑</button>
276
+ </div>
277
+
278
+ <!-- Messages -->
279
+ <div id="messages">
280
+ <div class="welcome" id="welcome">
281
+ <div class="welcome-icon">✦</div>
282
+ <h2>Free Claude Code</h2>
283
+ <p>Utilise des modèles IA puissants gratuitement via NVIDIA. Fonctionne sur téléphone et PC.</p>
284
+ <div class="chips">
285
+ <div class="chip" onclick="send('Explique-moi le machine learning en 5 lignes')">Machine learning</div>
286
+ <div class="chip" onclick="send('Γ‰cris une fonction Python pour trier une liste')">Code Python</div>
287
+ <div class="chip" onclick="send('Corrige ce code JavaScript: function add(a,b){ return a-b }')">DΓ©boguer du code</div>
288
+ <div class="chip" onclick="send('Quels sont les meilleurs modèles IA open source en 2025 ?')">Modèles IA</div>
289
+ </div>
290
+ </div>
291
+ </div>
292
+
293
+ <!-- Input -->
294
+ <div class="input-area">
295
+ <div class="input-row">
296
+ <textarea id="input" placeholder="Pose ta question..." rows="1"
297
+ oninput="autoResize(this)"
298
+ onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}">
299
+ </textarea>
300
+ <button id="send" onclick="sendMsg()">
301
+ <svg viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2z"/></svg>
302
+ </button>
303
+ </div>
304
+ <p class="hint">EntrΓ©e pour envoyer Β· Shift+EntrΓ©e pour saut de ligne</p>
305
+ </div>
306
+
307
+ <script>
308
+ let selectedModel = 'claude-sonnet-4-20250514';
309
+ let history = [];
310
+ let isLoading = false;
311
+
312
+ // ── Auto-resize textarea ──────────────────────────────────────────────────
313
+ function autoResize(el) {
314
+ el.style.height = 'auto';
315
+ el.style.height = Math.min(el.scrollHeight, 120) + 'px';
316
+ }
317
+
318
+ // ── Model selection ───────────────────────────────────────────────────────
319
+ function selectModel(btn) {
320
+ document.querySelectorAll('.model-btn').forEach(b => b.classList.remove('active'));
321
+ btn.classList.add('active');
322
+ selectedModel = btn.dataset.model;
323
+ }
324
+
325
+ // ── Check server status ───────────────────────────────────────────────────
326
+ async function checkStatus() {
327
+ const dot = document.getElementById('dot');
328
+ const txt = document.getElementById('status-text');
329
+ try {
330
+ const r = await fetch('/api/status');
331
+ const d = await r.json();
332
+ if (d.status === 'ok' && d.nvidia_key_set) {
333
+ dot.className = 'dot ok';
334
+ txt.textContent = 'ConnectΓ©';
335
+ } else {
336
+ dot.className = 'dot err';
337
+ txt.textContent = d.nvidia_key_set ? 'Proxy dΓ©marrage...' : 'ClΓ© NVIDIA manquante';
338
+ }
339
+ } catch {
340
+ dot.className = 'dot err';
341
+ txt.textContent = 'Hors ligne';
342
+ }
343
+ }
344
+
345
+ // ── Render markdown simple ────────────────────────────────────────────────
346
+ function renderMarkdown(text) {
347
+ return text
348
+ .replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>
349
+ `<pre><code class="lang-${lang}">${escHtml(code.trim())}</code></pre>`)
350
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
351
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
352
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
353
+ .replace(/\n/g, '<br>');
354
+ }
355
+
356
+ function escHtml(t) {
357
+ return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
358
+ }
359
+
360
+ // ── Add message to UI ─────────────────────────────────────────────────────
361
+ function addMessage(role, content) {
362
+ const wrap = document.getElementById('messages');
363
+ const welcome = document.getElementById('welcome');
364
+ if (welcome) welcome.remove();
365
+
366
+ const div = document.createElement('div');
367
+ div.className = `msg ${role}`;
368
+ div.innerHTML = `
369
+ <div class="avatar">${role === 'user' ? 'πŸ‘€' : '✦'}</div>
370
+ <div class="bubble" id="msg-${Date.now()}">${
371
+ role === 'user' ? escHtml(content) : renderMarkdown(content)
372
+ }</div>`;
373
+ wrap.appendChild(div);
374
+ wrap.scrollTop = wrap.scrollHeight;
375
+ return div.querySelector('.bubble');
376
+ }
377
+
378
+ // ── Chip shortcut ─────────────────────────────────────────────────────────
379
+ function send(text) {
380
+ document.getElementById('input').value = text;
381
+ sendMsg();
382
+ }
383
+
384
+ // ── Send message ──────────────────────────────────────────────────────────
385
+ async function sendMsg() {
386
+ const input = document.getElementById('input');
387
+ const text = input.value.trim();
388
+ if (!text || isLoading) return;
389
+
390
+ isLoading = true;
391
+ document.getElementById('send').disabled = true;
392
+ input.value = '';
393
+ input.style.height = 'auto';
394
+
395
+ addMessage('user', text);
396
+ history.push({ role: 'user', content: text });
397
+
398
+ const aiBubble = addMessage('ai', '');
399
+ aiBubble.innerHTML = '<span class="cursor"></span>';
400
+
401
+ try {
402
+ const resp = await fetch('/api/chat', {
403
+ method: 'POST',
404
+ headers: { 'Content-Type': 'application/json' },
405
+ body: JSON.stringify({
406
+ model: selectedModel,
407
+ max_tokens: 2048,
408
+ messages: history
409
+ })
410
+ });
411
+
412
+ const data = await resp.json();
413
+ const reply = data?.content?.[0]?.text || data?.error || 'Erreur inconnue';
414
+
415
+ aiBubble.innerHTML = renderMarkdown(reply);
416
+ history.push({ role: 'assistant', content: reply });
417
+
418
+ const msgs = document.getElementById('messages');
419
+ msgs.scrollTop = msgs.scrollHeight;
420
+ } catch (err) {
421
+ aiBubble.innerHTML = `<span style="color:var(--red)">Erreur: ${err.message}</span>`;
422
+ }
423
+
424
+ isLoading = false;
425
+ document.getElementById('send').disabled = false;
426
+ input.focus();
427
+ }
428
+
429
+ // Init
430
+ checkStatus();
431
+ setInterval(checkStatus, 30000);
432
+ </script>
433
+ </body>
434
+ </html>