gijl commited on
Commit
8e37ef9
·
verified ·
1 Parent(s): f1ce4a1

Delete chat.html

Browse files
Files changed (1) hide show
  1. chat.html +0 -610
chat.html DELETED
@@ -1,610 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="ar" dir="rtl">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Gemma Code Runner</title>
7
- <style>
8
- * { box-sizing: border-box; margin: 0; padding: 0; }
9
-
10
- :root {
11
- --bg: #0f0f0f;
12
- --surface: #1a1a1a;
13
- --surface2: #242424;
14
- --border: #2e2e2e;
15
- --accent: #7c6df0;
16
- --accent2: #5b4fd4;
17
- --text: #e8e8e8;
18
- --text2: #888;
19
- --green: #4ade80;
20
- --red: #f87171;
21
- --code-bg: #111;
22
- }
23
-
24
- body {
25
- font-family: 'Segoe UI', system-ui, sans-serif;
26
- background: var(--bg);
27
- color: var(--text);
28
- height: 100vh;
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 20px;
40
- border-bottom: 1px solid var(--border);
41
- background: var(--surface);
42
- flex-shrink: 0;
43
- }
44
- .logo { display: flex; align-items: center; gap: 10px; }
45
- .logo-dot {
46
- width: 10px; height: 10px; border-radius: 50%;
47
- background: var(--accent); box-shadow: 0 0 8px var(--accent);
48
- }
49
- .logo-text { font-size: 15px; font-weight: 600; letter-spacing: -0.3px; }
50
- .status {
51
- font-size: 12px; color: var(--text2);
52
- display: flex; align-items: center; gap: 6px;
53
- }
54
- .status-dot {
55
- width: 7px; height: 7px; border-radius: 50%; background: var(--green);
56
- }
57
-
58
- /* ── Main layout: chat | preview ── */
59
- .main {
60
- display: flex;
61
- flex: 1;
62
- overflow: hidden;
63
- }
64
-
65
- /* ── Chat panel ── */
66
- .chat-panel {
67
- display: flex;
68
- flex-direction: column;
69
- width: 45%;
70
- min-width: 340px;
71
- border-left: 1px solid var(--border);
72
- background: var(--bg);
73
- }
74
-
75
- .messages {
76
- flex: 1;
77
- overflow-y: auto;
78
- padding: 20px 16px;
79
- display: flex;
80
- flex-direction: column;
81
- gap: 16px;
82
- scroll-behavior: smooth;
83
- }
84
- .messages::-webkit-scrollbar { width: 4px; }
85
- .messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
86
-
87
- .msg {
88
- display: flex;
89
- flex-direction: column;
90
- gap: 6px;
91
- max-width: 92%;
92
- }
93
- .msg.user { align-self: flex-start; }
94
- .msg.assistant { align-self: flex-end; }
95
-
96
- .msg-label {
97
- font-size: 11px; color: var(--text2);
98
- padding: 0 4px;
99
- }
100
- .msg.user .msg-label { text-align: right; }
101
-
102
- .bubble {
103
- padding: 10px 14px;
104
- border-radius: 12px;
105
- font-size: 14px;
106
- line-height: 1.6;
107
- white-space: pre-wrap;
108
- word-break: break-word;
109
- }
110
- .msg.user .bubble {
111
- background: var(--surface2);
112
- border: 1px solid var(--border);
113
- border-radius: 12px 12px 4px 12px;
114
- }
115
- .msg.assistant .bubble {
116
- background: var(--surface);
117
- border: 1px solid var(--border);
118
- border-radius: 12px 12px 12px 4px;
119
- }
120
-
121
- /* code blocks inside messages */
122
- .bubble pre {
123
- background: var(--code-bg);
124
- border: 1px solid var(--border);
125
- border-radius: 8px;
126
- padding: 12px;
127
- overflow-x: auto;
128
- margin: 8px 0;
129
- font-size: 12px;
130
- font-family: 'Cascadia Code', 'Fira Code', monospace;
131
- position: relative;
132
- }
133
- .run-btn {
134
- position: absolute;
135
- top: 8px;
136
- left: 8px;
137
- background: var(--accent);
138
- color: #fff;
139
- border: none;
140
- border-radius: 6px;
141
- padding: 3px 10px;
142
- font-size: 11px;
143
- font-weight: 600;
144
- cursor: pointer;
145
- transition: background 0.15s;
146
- }
147
- .run-btn:hover { background: var(--accent2); }
148
-
149
- /* typing indicator */
150
- .typing {
151
- display: flex; align-items: center; gap: 5px;
152
- padding: 10px 14px;
153
- background: var(--surface);
154
- border: 1px solid var(--border);
155
- border-radius: 12px;
156
- width: fit-content;
157
- }
158
- .typing span {
159
- width: 6px; height: 6px; border-radius: 50%;
160
- background: var(--text2);
161
- animation: bounce 1.2s infinite;
162
- }
163
- .typing span:nth-child(2) { animation-delay: 0.2s; }
164
- .typing span:nth-child(3) { animation-delay: 0.4s; }
165
- @keyframes bounce {
166
- 0%, 60%, 100% { transform: translateY(0); }
167
- 30% { transform: translateY(-5px); }
168
- }
169
-
170
- /* ── Input area ── */
171
- .input-area {
172
- padding: 14px 16px;
173
- border-top: 1px solid var(--border);
174
- background: var(--surface);
175
- display: flex;
176
- gap: 10px;
177
- align-items: flex-end;
178
- }
179
- textarea {
180
- flex: 1;
181
- background: var(--surface2);
182
- border: 1px solid var(--border);
183
- border-radius: 10px;
184
- color: var(--text);
185
- font-size: 14px;
186
- font-family: inherit;
187
- padding: 10px 14px;
188
- resize: none;
189
- min-height: 42px;
190
- max-height: 140px;
191
- outline: none;
192
- transition: border-color 0.15s;
193
- direction: rtl;
194
- }
195
- textarea:focus { border-color: var(--accent); }
196
- textarea::placeholder { color: var(--text2); }
197
-
198
- .send-btn {
199
- background: var(--accent);
200
- border: none;
201
- border-radius: 10px;
202
- color: #fff;
203
- width: 42px; height: 42px;
204
- cursor: pointer;
205
- display: flex; align-items: center; justify-content: center;
206
- flex-shrink: 0;
207
- transition: background 0.15s, transform 0.1s;
208
- }
209
- .send-btn:hover { background: var(--accent2); }
210
- .send-btn:active { transform: scale(0.95); }
211
- .send-btn svg { width: 18px; height: 18px; }
212
-
213
- /* ── Preview panel ── */
214
- .preview-panel {
215
- flex: 1;
216
- display: flex;
217
- flex-direction: column;
218
- background: var(--surface);
219
- }
220
-
221
- .preview-header {
222
- padding: 10px 16px;
223
- border-bottom: 1px solid var(--border);
224
- display: flex;
225
- align-items: center;
226
- justify-content: space-between;
227
- flex-shrink: 0;
228
- }
229
- .preview-title {
230
- font-size: 13px; font-weight: 600; color: var(--text2);
231
- display: flex; align-items: center; gap: 8px;
232
- }
233
- .preview-badge {
234
- font-size: 10px;
235
- background: var(--surface2);
236
- border: 1px solid var(--border);
237
- border-radius: 4px;
238
- padding: 2px 7px;
239
- color: var(--text2);
240
- }
241
- .preview-actions { display: flex; gap: 8px; }
242
- .icon-btn {
243
- background: var(--surface2);
244
- border: 1px solid var(--border);
245
- border-radius: 7px;
246
- color: var(--text2);
247
- padding: 5px 10px;
248
- font-size: 12px;
249
- cursor: pointer;
250
- transition: color 0.15s, border-color 0.15s;
251
- }
252
- .icon-btn:hover { color: var(--text); border-color: var(--accent); }
253
-
254
- #preview-frame {
255
- flex: 1;
256
- border: none;
257
- background: #fff;
258
- }
259
-
260
- .empty-preview {
261
- flex: 1;
262
- display: flex;
263
- flex-direction: column;
264
- align-items: center;
265
- justify-content: center;
266
- gap: 12px;
267
- color: var(--text2);
268
- }
269
- .empty-preview svg {
270
- width: 48px; height: 48px; opacity: 0.3;
271
- }
272
- .empty-preview p { font-size: 14px; }
273
-
274
- /* ── Toast ── */
275
- #toast {
276
- position: fixed;
277
- bottom: 24px;
278
- left: 50%;
279
- transform: translateX(-50%) translateY(60px);
280
- background: var(--surface2);
281
- border: 1px solid var(--border);
282
- border-radius: 10px;
283
- padding: 10px 18px;
284
- font-size: 13px;
285
- transition: transform 0.3s;
286
- z-index: 100;
287
- }
288
- #toast.show { transform: translateX(-50%) translateY(0); }
289
- </style>
290
- </head>
291
- <body>
292
-
293
- <header>
294
- <div class="logo">
295
- <div class="logo-dot"></div>
296
- <span class="logo-text">Gemma Code Runner</span>
297
- </div>
298
- <div class="status">
299
- <div class="status-dot" id="status-dot"></div>
300
- <span id="status-text">جاري الاتصال...</span>
301
- </div>
302
- </header>
303
-
304
- <div class="main">
305
-
306
- <!-- Preview panel (right/left depending on RTL) -->
307
- <div class="preview-panel">
308
- <div class="preview-header">
309
- <div class="preview-title">
310
- معاينة الكود
311
- <span class="preview-badge" id="preview-badge">فارغ</span>
312
- </div>
313
- <div class="preview-actions">
314
- <button class="icon-btn" onclick="clearPreview()">مسح</button>
315
- <button class="icon-btn" onclick="saveProject()">حفظ</button>
316
- <button class="icon-btn" onclick="openFullscreen()">تكبير ↗</button>
317
- </div>
318
- </div>
319
-
320
- <div id="empty-state" class="empty-preview">
321
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2">
322
- <polyline points="16 18 22 12 16 6"></polyline>
323
- <polyline points="8 6 2 12 8 18"></polyline>
324
- </svg>
325
- <p>اطلب من النموذج كتابة كود React أو HTML</p>
326
- <p style="font-size:12px">سيظهر هنا تلقائياً</p>
327
- </div>
328
- <iframe id="preview-frame" style="display:none" sandbox="allow-scripts allow-same-origin"></iframe>
329
- </div>
330
-
331
- <!-- Chat panel -->
332
- <div class="chat-panel">
333
- <div class="messages" id="messages">
334
- <div class="msg assistant">
335
- <div class="msg-label">Gemma</div>
336
- <div class="bubble">مرحباً! اطلب مني كتابة أي مكوّن React أو HTML وسيظهر في المعاينة مباشرةً.
337
-
338
- مثال: «اكتب مكوّن عداد بأزرار + و −»</div>
339
- </div>
340
- </div>
341
-
342
- <div class="input-area">
343
- <button class="send-btn" onclick="sendMessage()" title="إرسال">
344
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
345
- <line x1="22" y1="2" x2="11" y2="13"></line>
346
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
347
- </svg>
348
- </button>
349
- <textarea id="input" placeholder="اكتب طلبك هنا..." rows="1"
350
- onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage()}"
351
- oninput="autoResize(this)"></textarea>
352
- </div>
353
- </div>
354
-
355
- </div>
356
-
357
- <div id="toast"></div>
358
-
359
- <script>
360
- const LLAMA_URL = 'http://localhost:7860';
361
- const SANDBOX_URL = 'http://localhost:5173';
362
- let conversationHistory = [];
363
- let currentCode = '';
364
-
365
- // ── Check server status ──────────────────────────────────────
366
- async function checkStatus() {
367
- try {
368
- const r = await fetch(`${LLAMA_URL}/health`, { signal: AbortSignal.timeout(3000) });
369
- if (r.ok) {
370
- document.getElementById('status-dot').style.background = '#4ade80';
371
- document.getElementById('status-text').textContent = 'متصل';
372
- }
373
- } catch {
374
- document.getElementById('status-dot').style.background = '#f87171';
375
- document.getElementById('status-text').textContent = 'غير متصل';
376
- setTimeout(checkStatus, 5000);
377
- }
378
- }
379
- checkStatus();
380
-
381
- // ── Auto-resize textarea ─────────────────────────────────────
382
- function autoResize(el) {
383
- el.style.height = 'auto';
384
- el.style.height = Math.min(el.scrollHeight, 140) + 'px';
385
- }
386
-
387
- // ── Extract code blocks from markdown ────────────────────────
388
- function extractCodeBlocks(text) {
389
- const blocks = [];
390
- const re = /```(jsx?|tsx?|html|javascript|typescript)?\n?([\s\S]*?)```/gi;
391
- let m;
392
- while ((m = re.exec(text)) !== null) {
393
- blocks.push({ lang: (m[1] || 'js').toLowerCase(), code: m[2].trim() });
394
- }
395
- return blocks;
396
- }
397
-
398
- // ── Render code in preview iframe ────────────────────────────
399
- function runCodeInPreview(code, lang) {
400
- currentCode = code;
401
- const frame = document.getElementById('preview-frame');
402
- const empty = document.getElementById('empty-state');
403
- const badge = document.getElementById('preview-badge');
404
-
405
- let html = '';
406
-
407
- if (lang === 'html') {
408
- html = code;
409
- } else {
410
- // React/JSX → load via CDN Babel + React
411
- html = `<!DOCTYPE html>
412
- <html>
413
- <head>
414
- <meta charset="UTF-8">
415
- <meta name="viewport" content="width=device-width,initial-scale=1">
416
- <script src="https://unpkg.com/react@18/umd/react.development.js"><\/script>
417
- <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"><\/script>
418
- <script src="https://unpkg.com/@babel/standalone/babel.min.js"><\/script>
419
- <style>
420
- body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; }
421
- * { box-sizing: border-box; }
422
- <\/style>
423
- </head>
424
- <body>
425
- <div id="root"></div>
426
- <script type="text/babel">
427
- ${code}
428
-
429
- // Try to render — works if file exports default or defines App
430
- try {
431
- const root = ReactDOM.createRoot(document.getElementById('root'));
432
- const Comp = typeof App !== 'undefined' ? App
433
- : typeof exports !== 'undefined' && exports.default ? exports.default
434
- : () => <div style={{color:'red'}}>لم أجد مكوّناً باسم App</div>;
435
- root.render(<Comp />);
436
- } catch(e) {
437
- document.getElementById('root').innerHTML =
438
- '<pre style="color:red;font-size:12px">' + e.message + '<\/pre>';
439
- }
440
- <\/script>
441
- </body>
442
- </html>`;
443
- }
444
-
445
- frame.style.display = 'block';
446
- empty.style.display = 'none';
447
- badge.textContent = lang.toUpperCase();
448
- frame.srcdoc = html;
449
- showToast('✅ الكود يعمل في المعاينة');
450
- }
451
-
452
- // ── Render message with code blocks ─────────────────────────
453
- function renderMessage(text) {
454
- const div = document.createElement('div');
455
-
456
- // Split by code fences
457
- const parts = text.split(/(```(?:jsx?|tsx?|html|javascript|typescript)?\n?[\s\S]*?```)/gi);
458
- parts.forEach(part => {
459
- const m = part.match(/```(jsx?|tsx?|html|javascript|typescript)?\n?([\s\S]*?)```/i);
460
- if (m) {
461
- const lang = (m[1] || 'js').toLowerCase();
462
- const code = m[2].trim();
463
- const pre = document.createElement('pre');
464
- pre.textContent = code;
465
-
466
- const btn = document.createElement('button');
467
- btn.className = 'run-btn';
468
- btn.textContent = '▶ تشغيل';
469
- btn.onclick = () => runCodeInPreview(code, lang);
470
- pre.prepend(btn);
471
- div.appendChild(pre);
472
-
473
- // Auto-run the first code block
474
- runCodeInPreview(code, lang);
475
- } else if (part.trim()) {
476
- const p = document.createElement('span');
477
- p.textContent = part;
478
- div.appendChild(p);
479
- }
480
- });
481
-
482
- return div;
483
- }
484
-
485
- // ── Add message to chat ──────────────────────────────────────
486
- function addMessage(role, content, isHtml = false) {
487
- const container = document.getElementById('messages');
488
- const wrap = document.createElement('div');
489
- wrap.className = `msg ${role}`;
490
-
491
- const label = document.createElement('div');
492
- label.className = 'msg-label';
493
- label.textContent = role === 'user' ? 'أنت' : 'Gemma';
494
-
495
- const bubble = document.createElement('div');
496
- bubble.className = 'bubble';
497
-
498
- if (role === 'assistant' && !isHtml) {
499
- bubble.appendChild(renderMessage(content));
500
- } else {
501
- bubble.textContent = content;
502
- }
503
-
504
- wrap.appendChild(label);
505
- wrap.appendChild(bubble);
506
- container.appendChild(wrap);
507
- container.scrollTop = container.scrollHeight;
508
- return bubble;
509
- }
510
-
511
- // ── Show typing indicator ────────────────────────────────────
512
- function showTyping() {
513
- const container = document.getElementById('messages');
514
- const wrap = document.createElement('div');
515
- wrap.className = 'msg assistant';
516
- wrap.id = 'typing-indicator';
517
- wrap.innerHTML = `
518
- <div class="msg-label">Gemma</div>
519
- <div class="typing"><span></span><span></span><span></span></div>`;
520
- container.appendChild(wrap);
521
- container.scrollTop = container.scrollHeight;
522
- }
523
- function removeTyping() {
524
- document.getElementById('typing-indicator')?.remove();
525
- }
526
-
527
- // ── Send message ─────────────────────────────────────────────
528
- async function sendMessage() {
529
- const input = document.getElementById('input');
530
- const text = input.value.trim();
531
- if (!text) return;
532
-
533
- input.value = '';
534
- input.style.height = 'auto';
535
-
536
- addMessage('user', text);
537
- conversationHistory.push({ role: 'user', content: text });
538
-
539
- showTyping();
540
-
541
- try {
542
- const response = await fetch(`${LLAMA_URL}/v1/chat/completions`, {
543
- method: 'POST',
544
- headers: { 'Content-Type': 'application/json' },
545
- body: JSON.stringify({
546
- model: 'gemma',
547
- messages: [
548
- {
549
- role: 'system',
550
- content: `أنت مساعد برمجي متخصص في React و JavaScript.
551
- عندما يطلب المستخدم كوداً، اكتبه دائماً داخل \`\`\`jsx أو \`\`\`html.
552
- اكتب مكوّنات React كاملة مع export default App.
553
- لا تشرح كثيراً، ركّز على الكود.`
554
- },
555
- ...conversationHistory
556
- ],
557
- stream: false,
558
- temperature: 0.7,
559
- max_tokens: 2048
560
- })
561
- });
562
-
563
- const data = await response.json();
564
- removeTyping();
565
-
566
- const reply = data.choices?.[0]?.message?.content || 'لم أستلم رداً من النموذج.';
567
- conversationHistory.push({ role: 'assistant', content: reply });
568
- addMessage('assistant', reply);
569
-
570
- } catch (err) {
571
- removeTyping();
572
- addMessage('assistant', `❌ خطأ في الاتصال: ${err.message}`);
573
- }
574
- }
575
-
576
- // ── Preview controls ─────────────────────────────────────────
577
- function clearPreview() {
578
- document.getElementById('preview-frame').style.display = 'none';
579
- document.getElementById('empty-state').style.display = 'flex';
580
- document.getElementById('preview-badge').textContent = 'فارغ';
581
- }
582
-
583
- function saveProject() {
584
- if (!currentCode) { showToast('لا يوجد كود للحفظ'); return; }
585
- fetch('/save-project', {
586
- method: 'POST',
587
- headers: { 'Content-Type': 'application/json' },
588
- body: JSON.stringify({ code: currentCode })
589
- }).then(() => showToast('✅ محفوظ في /data/projects'))
590
- .catch(() => showToast('❌ فشل الحفظ — تحقق من السيرفر'));
591
- }
592
-
593
- function openFullscreen() {
594
- const frame = document.getElementById('preview-frame');
595
- if (frame.srcdoc) {
596
- const w = window.open('', '_blank');
597
- w.document.write(frame.srcdoc);
598
- }
599
- }
600
-
601
- // ── Toast ────────────────────────────────────────────────────
602
- function showToast(msg) {
603
- const t = document.getElementById('toast');
604
- t.textContent = msg;
605
- t.classList.add('show');
606
- setTimeout(() => t.classList.remove('show'), 2500);
607
- }
608
- </script>
609
- </body>
610
- </html>