Skydata001 commited on
Commit
fa949bc
·
verified ·
1 Parent(s): b31a81e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +391 -68
index.html CHANGED
@@ -2,7 +2,7 @@
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>Qwen 2.5-VL 32B | NVIDIA A100 Workspace</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -12,13 +12,15 @@
12
  --bg-dark: #070a12;
13
  --panel-dark: #0f172a;
14
  --panel-border: #1e293b;
15
- --accent-glow: rgba(59, 130, 246, 0.2);
16
  --accent-blue: #3b82f6;
17
  --accent-hover: #2563eb;
 
18
  --text-main: #f8fafc;
19
  --text-muted: #94a3b8;
20
  --user-bubble: #1e293b;
21
  --bot-bubble: #0f172a;
 
 
22
  --success: #10b981;
23
  --warning: #f59e0b;
24
  --danger: #ef4444;
@@ -29,6 +31,7 @@
29
  margin: 0;
30
  padding: 0;
31
  font-family: 'Tajawal', system-ui, -apple-system, sans-serif;
 
32
  }
33
 
34
  body {
@@ -36,6 +39,7 @@
36
  color: var(--text-main);
37
  display: flex;
38
  height: 100vh;
 
39
  overflow: hidden;
40
  }
41
 
@@ -47,7 +51,8 @@
47
  flex-direction: column;
48
  padding: 1.25rem;
49
  gap: 1.25rem;
50
- z-index: 20;
 
51
  }
52
 
53
  .brand {
@@ -58,22 +63,23 @@
58
  border-bottom: 1px solid var(--panel-border);
59
  }
60
 
61
- .brand-icon {
62
- width: 40px;
63
- height: 40px;
64
  background: linear-gradient(135deg, #3b82f6, #10b981);
65
- border-radius: 12px;
66
  display: flex;
67
  align-items: center;
68
  justify-content: center;
69
- font-weight: bold;
70
- font-size: 1.3rem;
71
- box-shadow: 0 0 15px var(--accent-glow);
72
  }
73
 
74
- .brand-title {
75
- font-size: 1.15rem;
76
- font-weight: 700;
 
 
 
77
  }
78
 
79
  .status-card {
@@ -109,22 +115,40 @@
109
  display: flex;
110
  flex-direction: column;
111
  height: 100vh;
 
 
112
  }
113
 
114
  header {
115
- background-color: rgba(15, 23, 42, 0.8);
116
  backdrop-filter: blur(12px);
117
  border-bottom: 1px solid var(--panel-border);
118
- padding: 1rem 1.5rem;
119
  display: flex;
120
  justify-content: space-between;
121
  align-items: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  }
123
 
124
  .chat-container {
125
  flex: 1;
126
  overflow-y: auto;
127
- padding: 2rem 1.5rem;
128
  display: flex;
129
  flex-direction: column;
130
  gap: 1.25rem;
@@ -133,7 +157,7 @@
133
  .message-row {
134
  display: flex;
135
  gap: 0.85rem;
136
- max-width: 85%;
137
  animation: fadeIn 0.25s ease-in-out;
138
  }
139
 
@@ -151,19 +175,74 @@
151
  align-self: flex-start;
152
  }
153
 
154
- .avatar {
155
- width: 38px;
156
- height: 38px;
157
  border-radius: 10px;
158
  display: flex;
159
  align-items: center;
160
  justify-content: center;
161
- font-size: 1.1rem;
162
  flex-shrink: 0;
163
  }
164
 
165
- .user .avatar { background: var(--accent-blue); color: white; }
166
- .bot .avatar { background: var(--panel-border); color: var(--accent-blue); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
  .message-bubble {
169
  background-color: var(--bot-bubble);
@@ -179,18 +258,19 @@
179
  .user .message-bubble {
180
  background-color: var(--user-bubble);
181
  border-color: rgba(59, 130, 246, 0.3);
 
182
  }
183
 
184
  .preview-bar {
185
  display: flex;
186
  gap: 0.75rem;
187
- padding: 0.5rem 1rem;
188
  background-color: var(--panel-dark);
189
  border-top: 1px solid var(--panel-border);
 
190
  }
191
 
192
  .preview-item {
193
- position: relative;
194
  display: flex;
195
  align-items: center;
196
  gap: 0.5rem;
@@ -199,29 +279,37 @@
199
  border-radius: 8px;
200
  font-size: 0.8rem;
201
  border: 1px solid var(--panel-border);
 
202
  }
203
 
204
  .preview-item img {
205
- width: 32px;
206
- height: 32px;
207
  object-fit: cover;
208
  border-radius: 4px;
209
  }
210
 
211
- .remove-btn {
212
  cursor: pointer;
213
- color: var(--danger);
214
- font-weight: bold;
 
 
 
 
 
 
 
215
  }
216
 
217
  .input-dock {
218
- padding: 1rem 1.5rem;
219
- background-color: rgba(15, 23, 42, 0.9);
220
  backdrop-filter: blur(12px);
221
  border-top: 1px solid var(--panel-border);
222
  display: flex;
223
  flex-direction: column;
224
- gap: 0.75rem;
225
  }
226
 
227
  .input-box-container {
@@ -231,7 +319,12 @@
231
  background-color: var(--bg-dark);
232
  border: 1px solid var(--panel-border);
233
  border-radius: 12px;
234
- padding: 0.6rem 0.85rem;
 
 
 
 
 
235
  }
236
 
237
  textarea {
@@ -244,11 +337,11 @@
244
  resize: none;
245
  max-height: 120px;
246
  min-height: 24px;
 
247
  }
248
 
249
- .btn-icon {
250
  background: var(--panel-border);
251
- color: var(--text-main);
252
  border: none;
253
  width: 38px;
254
  height: 38px;
@@ -258,35 +351,96 @@
258
  justify-content: center;
259
  cursor: pointer;
260
  transition: 0.2s;
 
261
  }
262
 
263
- .btn-icon:hover { background: var(--accent-blue); }
 
 
 
 
 
 
264
 
265
- .btn-send {
 
 
 
 
266
  background: var(--accent-blue);
267
- color: white;
268
  border: none;
269
- width: 42px;
270
- height: 42px;
271
  border-radius: 10px;
 
 
 
272
  cursor: pointer;
273
- font-size: 1.1rem;
 
 
 
 
 
 
 
 
 
274
  }
275
 
276
- .btn-send:disabled { opacity: 0.4; cursor: not-allowed; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  </style>
278
  </head>
279
  <body>
280
 
281
- <aside>
282
  <div class="brand">
283
- <div class="brand-icon">⚡</div>
284
- <div class="brand-title">Qwen 2.5-VL 32B</div>
 
 
 
 
 
285
  </div>
286
 
287
  <div style="display: flex; flex-direction: column; gap: 0.5rem;">
288
  <span style="font-size: 0.8rem; color: var(--text-muted);">توجيه النظام (System Prompt)</span>
289
- <textarea id="systemPrompt" style="width: 100%; height: 80px; background: var(--bg-dark); border: 1px solid var(--panel-border); border-radius: 8px; color: white; padding: 0.5rem; font-size: 0.85rem; resize: none;">أنت مساعد ذكاء اصطناعي خارق متعدد الوسائط، قادر على فهم النصوص والصور والصوتيات بدقة فائقة.</textarea>
290
  </div>
291
 
292
  <div class="status-card">
@@ -303,19 +457,32 @@
303
  <span id="dbBadge" class="badge badge-loading">جاري الفحص...</span>
304
  </div>
305
  </div>
 
 
 
 
306
  </aside>
307
 
308
  <main>
309
  <header>
310
- <h3>Qwen2.5-VL Multimodal Workspace</h3>
311
- <div id="statusIndicator" style="font-size: 0.85rem; color: var(--text-muted);">جاهز للمعالجة المباشرة</div>
 
 
 
 
 
312
  </header>
313
 
314
  <div class="chat-container" id="chatBox">
315
  <div class="message-row bot">
316
- <div class="avatar">🤖</div>
317
- <div class="message-bubble">
318
- مرحباً بك! أنا نموذج **Qwen 2.5-VL 32B** الخارق على **NVIDIA A100**. يمكنني فهم النصوص، تحليلات الصور المستندات والصوتيات الفورية!
 
 
 
 
319
  </div>
320
  </div>
321
  </div>
@@ -324,21 +491,32 @@
324
 
325
  <div class="input-dock">
326
  <div style="display: flex; align-items: center; justify-content: space-between;">
327
- <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; cursor: pointer; color: var(--text-muted);">
328
- <input type="checkbox" id="webSearchToggle"> 🌐 تفعيل وكيل التصفح المباشر (Web Agent)
 
329
  </label>
330
  </div>
331
 
332
  <div class="input-box-container">
333
- <textarea id="userInput" placeholder="اكتب سؤالك، أو ارفق صورة/ملف صوتي..." rows="1" onkeydown="handleKeyPress(event)"></textarea>
334
 
335
  <input type="file" id="imageInput" accept="image/*" style="display: none;" onchange="handleFileSelect(event, 'image')">
336
- <button class="btn-icon" onclick="document.getElementById('imageInput').click()" title="رفع صورة">📷</button>
 
 
337
 
338
  <input type="file" id="audioInput" accept="audio/*" style="display: none;" onchange="handleFileSelect(event, 'audio')">
339
- <button class="btn-icon" onclick="document.getElementById('audioInput').click()" title="رفع صوت">🎵</button>
 
 
340
 
341
- <button id="sendBtn" class="btn-send" onclick="sendMessage()">➔</button>
 
 
 
 
 
 
342
  </div>
343
  </div>
344
  </main>
@@ -347,6 +525,42 @@
347
  let selectedImage = null;
348
  let selectedAudio = null;
349
  let isProcessing = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
  async function checkStatus() {
352
  try {
@@ -378,6 +592,33 @@
378
  checkStatus();
379
  setInterval(checkStatus, 10000);
380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  function handleFileSelect(e, type) {
382
  const file = e.target.files[0];
383
  if (!file) return;
@@ -393,7 +634,9 @@
393
  <div class="preview-item" id="imgPreview">
394
  <img src="${ev.target.result}">
395
  <span>صورة مرفقة</span>
396
- <span class="remove-btn" onclick="removeFile('image')">✕</span>
 
 
397
  </div>`;
398
  };
399
  reader.readAsDataURL(file);
@@ -401,8 +644,10 @@
401
  selectedAudio = file;
402
  previewBar.innerHTML += `
403
  <div class="preview-item" id="audioPreview">
404
- <span>🎵 ${file.name}</span>
405
- <span class="remove-btn" onclick="removeFile('audio')">✕</span>
 
 
406
  </div>`;
407
  }
408
  }
@@ -441,21 +686,24 @@
441
  isProcessing = true;
442
  sendBtn.disabled = true;
443
 
 
 
444
  const formData = new FormData();
445
  formData.append('message', text);
446
  formData.append('use_web_search', document.getElementById('webSearchToggle').checked);
447
  formData.append('system_prompt', document.getElementById('systemPrompt').value);
 
448
 
449
  if (selectedImage) formData.append('image_file', selectedImage);
450
  if (selectedAudio) formData.append('audio_file', selectedAudio);
451
 
452
- appendMessage(text || "تحليل وسائط مرفقة...", 'user');
453
 
454
  inputEl.value = '';
455
  removeFile('image');
456
  removeFile('audio');
457
 
458
- const botBubble = appendMessage('', 'bot');
459
 
460
  try {
461
  const response = await fetch('/api/chat', {
@@ -467,15 +715,37 @@
467
 
468
  const reader = response.body.getReader();
469
  const decoder = new TextDecoder();
 
470
 
471
  while (true) {
472
  const { done, value } = await reader.read();
473
  if (done) break;
474
- botBubble.innerText += decoder.decode(value, { stream: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
  document.getElementById('chatBox').scrollTop = document.getElementById('chatBox').scrollHeight;
476
  }
477
  } catch (err) {
478
- botBubble.innerText = "❌ تعذر إكمال الطلب: " + err.message;
479
  } finally {
480
  isProcessing = false;
481
  sendBtn.disabled = false;
@@ -488,20 +758,73 @@
488
  row.className = `message-row ${role}`;
489
 
490
  const avatar = document.createElement('div');
491
- avatar.className = 'avatar';
492
- avatar.innerText = role === 'user' ? '👤' : '🤖';
 
 
 
 
 
493
 
494
  const bubble = document.createElement('div');
495
  bubble.className = 'message-bubble';
496
  bubble.innerText = text;
497
 
 
498
  row.appendChild(avatar);
499
- row.appendChild(bubble);
500
 
501
  chatBox.appendChild(row);
502
  chatBox.scrollTop = chatBox.scrollHeight;
503
  return bubble;
504
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  </script>
506
  </body>
507
  </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, maximum-scale=1.0, user-scalable=no">
6
  <title>Qwen 2.5-VL 32B | NVIDIA A100 Workspace</title>
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
12
  --bg-dark: #070a12;
13
  --panel-dark: #0f172a;
14
  --panel-border: #1e293b;
 
15
  --accent-blue: #3b82f6;
16
  --accent-hover: #2563eb;
17
+ --accent-glow: rgba(59, 130, 246, 0.15);
18
  --text-main: #f8fafc;
19
  --text-muted: #94a3b8;
20
  --user-bubble: #1e293b;
21
  --bot-bubble: #0f172a;
22
+ --think-bg: #090e1a;
23
+ --think-border: #1e293b;
24
  --success: #10b981;
25
  --warning: #f59e0b;
26
  --danger: #ef4444;
 
31
  margin: 0;
32
  padding: 0;
33
  font-family: 'Tajawal', system-ui, -apple-system, sans-serif;
34
+ -webkit-tap-highlight-color: transparent;
35
  }
36
 
37
  body {
 
39
  color: var(--text-main);
40
  display: flex;
41
  height: 100vh;
42
+ height: 100dvh;
43
  overflow: hidden;
44
  }
45
 
 
51
  flex-direction: column;
52
  padding: 1.25rem;
53
  gap: 1.25rem;
54
+ z-index: 30;
55
+ transition: transform 0.3s ease;
56
  }
57
 
58
  .brand {
 
63
  border-bottom: 1px solid var(--panel-border);
64
  }
65
 
66
+ .brand-icon-svg {
67
+ width: 38px;
68
+ height: 38px;
69
  background: linear-gradient(135deg, #3b82f6, #10b981);
70
+ border-radius: 10px;
71
  display: flex;
72
  align-items: center;
73
  justify-content: center;
74
+ box-shadow: 0 0 12px var(--accent-glow);
 
 
75
  }
76
 
77
+ .brand-icon-svg svg {
78
+ width: 22px;
79
+ height: 22px;
80
+ fill: none;
81
+ stroke: #ffffff;
82
+ stroke-width: 2;
83
  }
84
 
85
  .status-card {
 
115
  display: flex;
116
  flex-direction: column;
117
  height: 100vh;
118
+ height: 100dvh;
119
+ position: relative;
120
  }
121
 
122
  header {
123
+ background-color: rgba(15, 23, 42, 0.9);
124
  backdrop-filter: blur(12px);
125
  border-bottom: 1px solid var(--panel-border);
126
+ padding: 0.85rem 1.25rem;
127
  display: flex;
128
  justify-content: space-between;
129
  align-items: center;
130
+ z-index: 20;
131
+ }
132
+
133
+ .menu-toggle {
134
+ display: none;
135
+ background: transparent;
136
+ border: none;
137
+ cursor: pointer;
138
+ padding: 0.25rem;
139
+ }
140
+
141
+ .menu-toggle svg {
142
+ width: 24px;
143
+ height: 24px;
144
+ stroke: var(--text-main);
145
+ stroke-width: 2;
146
  }
147
 
148
  .chat-container {
149
  flex: 1;
150
  overflow-y: auto;
151
+ padding: 1.5rem;
152
  display: flex;
153
  flex-direction: column;
154
  gap: 1.25rem;
 
157
  .message-row {
158
  display: flex;
159
  gap: 0.85rem;
160
+ max-width: 90%;
161
  animation: fadeIn 0.25s ease-in-out;
162
  }
163
 
 
175
  align-self: flex-start;
176
  }
177
 
178
+ .avatar-svg {
179
+ width: 36px;
180
+ height: 36px;
181
  border-radius: 10px;
182
  display: flex;
183
  align-items: center;
184
  justify-content: center;
 
185
  flex-shrink: 0;
186
  }
187
 
188
+ .user .avatar-svg { background: var(--accent-blue); }
189
+ .bot .avatar-svg { background: var(--panel-border); }
190
+
191
+ .avatar-svg svg {
192
+ width: 20px;
193
+ height: 20px;
194
+ fill: none;
195
+ stroke: #ffffff;
196
+ stroke-width: 2;
197
+ }
198
+
199
+ .message-body {
200
+ display: flex;
201
+ flex-direction: column;
202
+ gap: 0.5rem;
203
+ max-width: 100%;
204
+ }
205
+
206
+ .think-box {
207
+ background-color: var(--think-bg);
208
+ border: 1px solid var(--think-border);
209
+ border-radius: 10px;
210
+ padding: 0.85rem 1rem;
211
+ font-size: 0.85rem;
212
+ color: var(--text-muted);
213
+ line-height: 1.6;
214
+ }
215
+
216
+ .think-header {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 0.5rem;
220
+ font-weight: 600;
221
+ color: var(--accent-blue);
222
+ cursor: pointer;
223
+ user-select: none;
224
+ margin-bottom: 0.5rem;
225
+ }
226
+
227
+ .think-spinner {
228
+ width: 14px;
229
+ height: 14px;
230
+ border: 2px solid var(--accent-blue);
231
+ border-top-color: transparent;
232
+ border-radius: 50%;
233
+ animation: spin 0.8s linear infinite;
234
+ }
235
+
236
+ @keyframes spin {
237
+ to { transform: rotate(360deg); }
238
+ }
239
+
240
+ .think-content {
241
+ white-space: pre-wrap;
242
+ word-break: break-word;
243
+ border-top: 1px dashed var(--think-border);
244
+ padding-top: 0.5rem;
245
+ }
246
 
247
  .message-bubble {
248
  background-color: var(--bot-bubble);
 
258
  .user .message-bubble {
259
  background-color: var(--user-bubble);
260
  border-color: rgba(59, 130, 246, 0.3);
261
+ color: #ffffff;
262
  }
263
 
264
  .preview-bar {
265
  display: flex;
266
  gap: 0.75rem;
267
+ padding: 0.5rem 1.25rem;
268
  background-color: var(--panel-dark);
269
  border-top: 1px solid var(--panel-border);
270
+ overflow-x: auto;
271
  }
272
 
273
  .preview-item {
 
274
  display: flex;
275
  align-items: center;
276
  gap: 0.5rem;
 
279
  border-radius: 8px;
280
  font-size: 0.8rem;
281
  border: 1px solid var(--panel-border);
282
+ white-space: nowrap;
283
  }
284
 
285
  .preview-item img {
286
+ width: 28px;
287
+ height: 28px;
288
  object-fit: cover;
289
  border-radius: 4px;
290
  }
291
 
292
+ .remove-btn-svg {
293
  cursor: pointer;
294
+ display: flex;
295
+ align-items: center;
296
+ }
297
+
298
+ .remove-btn-svg svg {
299
+ width: 14px;
300
+ height: 14px;
301
+ stroke: var(--danger);
302
+ stroke-width: 2;
303
  }
304
 
305
  .input-dock {
306
+ padding: 1rem 1.25rem;
307
+ background-color: rgba(15, 23, 42, 0.95);
308
  backdrop-filter: blur(12px);
309
  border-top: 1px solid var(--panel-border);
310
  display: flex;
311
  flex-direction: column;
312
+ gap: 0.6rem;
313
  }
314
 
315
  .input-box-container {
 
319
  background-color: var(--bg-dark);
320
  border: 1px solid var(--panel-border);
321
  border-radius: 12px;
322
+ padding: 0.5rem 0.75rem;
323
+ }
324
+
325
+ .input-box-container:focus-within {
326
+ border-color: var(--accent-blue);
327
+ box-shadow: 0 0 10px var(--accent-glow);
328
  }
329
 
330
  textarea {
 
337
  resize: none;
338
  max-height: 120px;
339
  min-height: 24px;
340
+ line-height: 1.5;
341
  }
342
 
343
+ .btn-icon-svg {
344
  background: var(--panel-border);
 
345
  border: none;
346
  width: 38px;
347
  height: 38px;
 
351
  justify-content: center;
352
  cursor: pointer;
353
  transition: 0.2s;
354
+ flex-shrink: 0;
355
  }
356
 
357
+ .btn-icon-svg svg {
358
+ width: 18px;
359
+ height: 18px;
360
+ stroke: var(--text-main);
361
+ stroke-width: 2;
362
+ fill: none;
363
+ }
364
 
365
+ .btn-icon-svg:hover {
366
+ background: var(--accent-blue);
367
+ }
368
+
369
+ .btn-send-svg {
370
  background: var(--accent-blue);
 
371
  border: none;
372
+ width: 40px;
373
+ height: 40px;
374
  border-radius: 10px;
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: center;
378
  cursor: pointer;
379
+ transition: 0.2s;
380
+ flex-shrink: 0;
381
+ }
382
+
383
+ .btn-send-svg svg {
384
+ width: 20px;
385
+ height: 20px;
386
+ stroke: white;
387
+ stroke-width: 2;
388
+ fill: none;
389
  }
390
 
391
+ .btn-send-svg:disabled {
392
+ opacity: 0.4;
393
+ cursor: not-allowed;
394
+ }
395
+
396
+ .recording-pulse {
397
+ animation: pulse-red 1.5s infinite;
398
+ background-color: var(--danger) !important;
399
+ }
400
+
401
+ @keyframes pulse-red {
402
+ 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
403
+ 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
404
+ 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
405
+ }
406
+
407
+ @media (max-width: 768px) {
408
+ aside {
409
+ position: fixed;
410
+ right: 0;
411
+ top: 0;
412
+ height: 100%;
413
+ transform: translateX(100%);
414
+ box-shadow: -5px 0 25px rgba(0,0,0,0.5);
415
+ }
416
+ aside.open {
417
+ transform: translateX(0);
418
+ }
419
+ .menu-toggle {
420
+ display: block;
421
+ }
422
+ .message-row {
423
+ max-width: 95%;
424
+ }
425
+ }
426
  </style>
427
  </head>
428
  <body>
429
 
430
+ <aside id="sidebar">
431
  <div class="brand">
432
+ <div class="brand-icon-svg">
433
+ <svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
434
+ </div>
435
+ <div>
436
+ <div style="font-weight: 700; font-size: 1.1rem;">Qwen 2.5-VL 32B</div>
437
+ <div style="font-size: 0.75rem; color: var(--text-muted);">NVIDIA A100 Workspace</div>
438
+ </div>
439
  </div>
440
 
441
  <div style="display: flex; flex-direction: column; gap: 0.5rem;">
442
  <span style="font-size: 0.8rem; color: var(--text-muted);">توجيه النظام (System Prompt)</span>
443
+ <textarea id="systemPrompt" style="width: 100%; height: 90px; background: var(--bg-dark); border: 1px solid var(--panel-border); border-radius: 8px; color: white; padding: 0.6rem; font-size: 0.8rem; resize: none; outline: none;"></textarea>
444
  </div>
445
 
446
  <div class="status-card">
 
457
  <span id="dbBadge" class="badge badge-loading">جاري الفحص...</span>
458
  </div>
459
  </div>
460
+
461
+ <button onclick="clearChatUI()" style="margin-top: auto; padding: 0.75rem; background: var(--panel-border); color: var(--text-main); border: none; border-radius: 8px; cursor: pointer; font-weight: bold; font-size: 0.85rem;">
462
+ مسح محادثات الشاشة
463
+ </button>
464
  </aside>
465
 
466
  <main>
467
  <header>
468
+ <div style="display: flex; align-items: center; gap: 0.75rem;">
469
+ <button class="menu-toggle" onclick="toggleSidebar()">
470
+ <svg viewBox="0 0 24 24"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
471
+ </button>
472
+ <h3 style="font-size: 1rem;">Qwen2.5-VL Multimodal Workspace</h3>
473
+ </div>
474
+ <div id="statusIndicator" style="font-size: 0.8rem; color: var(--text-muted);">جاهز للمعالجة المباشرة</div>
475
  </header>
476
 
477
  <div class="chat-container" id="chatBox">
478
  <div class="message-row bot">
479
+ <div class="avatar-svg">
480
+ <svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4M8 16h.01M16 16h.01"/></svg>
481
+ </div>
482
+ <div class="message-body">
483
+ <div class="message-bubble">
484
+ مرحباً بك! أنا نموذج **Qwen 2.5-VL 32B** على **NVIDIA A100**. أستطيع التفكير المنطقي العميق، فهم النصوص، تحليل الصور والرسوم البيانية، والمقاطع الصوتية المباشرة!
485
+ </div>
486
  </div>
487
  </div>
488
  </div>
 
491
 
492
  <div class="input-dock">
493
  <div style="display: flex; align-items: center; justify-content: space-between;">
494
+ <label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.8rem; cursor: pointer; color: var(--text-muted);">
495
+ <input type="checkbox" id="webSearchToggle">
496
+ <span>تفعيل وكيل التصفح المباشر (Web Agent)</span>
497
  </label>
498
  </div>
499
 
500
  <div class="input-box-container">
501
+ <textarea id="userInput" placeholder="اكتب سؤالك، أو ارفق صورة/صوت..." rows="1" onkeydown="handleKeyPress(event)"></textarea>
502
 
503
  <input type="file" id="imageInput" accept="image/*" style="display: none;" onchange="handleFileSelect(event, 'image')">
504
+ <button class="btn-icon-svg" onclick="document.getElementById('imageInput').click()" title="رفع صورة">
505
+ <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
506
+ </button>
507
 
508
  <input type="file" id="audioInput" accept="audio/*" style="display: none;" onchange="handleFileSelect(event, 'audio')">
509
+ <button class="btn-icon-svg" onclick="document.getElementById('audioInput').click()" title="رفع ملف صوتي">
510
+ <svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
511
+ </button>
512
 
513
+ <button id="voiceBtn" class="btn-icon-svg" onclick="toggleVoiceInput()" title="تسجيل صوتي">
514
+ <svg viewBox="0 0 24 24"><path d="M12 2a3 3 0 00-3 3v7a3 3 0 006 0V5a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2M12 19v3"/></svg>
515
+ </button>
516
+
517
+ <button id="sendBtn" class="btn-send-svg" onclick="sendMessage()">
518
+ <svg viewBox="0 0 24 24"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>
519
+ </button>
520
  </div>
521
  </div>
522
  </main>
 
525
  let selectedImage = null;
526
  let selectedAudio = null;
527
  let isProcessing = false;
528
+ let recognition = null;
529
+ let clientSensorsData = {};
530
+
531
+ function toggleSidebar() {
532
+ document.getElementById('sidebar').classList.toggle('open');
533
+ }
534
+
535
+ async function captureClientSensors() {
536
+ const now = new Date();
537
+ clientSensorsData = {
538
+ time: now.toLocaleTimeString('ar-SA'),
539
+ date: now.toLocaleDateString('ar-SA'),
540
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
541
+ };
542
+
543
+ if ('getBattery' in navigator) {
544
+ try {
545
+ const battery = await navigator.getBattery();
546
+ clientSensorsData.battery = `${Math.round(battery.level * 100)}% (${battery.charging ? 'جاري الشحن' : 'على البطارية'})`;
547
+ } catch(e) {}
548
+ }
549
+
550
+ if ('geolocation' in navigator) {
551
+ navigator.geolocation.getCurrentPosition(
552
+ (pos) => {
553
+ clientSensorsData.location = `خط عرض ${pos.coords.latitude.toFixed(2)}, خط طول ${pos.coords.longitude.toFixed(2)}`;
554
+ },
555
+ (err) => {
556
+ clientSensorsData.location = 'غير مصرح للوصول للموقع';
557
+ },
558
+ { timeout: 5000 }
559
+ );
560
+ }
561
+ }
562
+
563
+ captureClientSensors();
564
 
565
  async function checkStatus() {
566
  try {
 
592
  checkStatus();
593
  setInterval(checkStatus, 10000);
594
 
595
+ if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
596
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
597
+ recognition = new SpeechRecognition();
598
+ recognition.lang = 'ar-SA';
599
+ recognition.continuous = false;
600
+
601
+ recognition.onstart = () => {
602
+ document.getElementById('voiceBtn').classList.add('recording-pulse');
603
+ };
604
+
605
+ recognition.onresult = (event) => {
606
+ document.getElementById('userInput').value = event.results[0][0].transcript;
607
+ document.getElementById('voiceBtn').classList.remove('recording-pulse');
608
+ };
609
+
610
+ recognition.onerror = () => document.getElementById('voiceBtn').classList.remove('recording-pulse');
611
+ recognition.onend = () => document.getElementById('voiceBtn').classList.remove('recording-pulse');
612
+ }
613
+
614
+ function toggleVoiceInput() {
615
+ if (!recognition) {
616
+ alert("المتصفح لا يدعم التسجيل الصوتي المباشر");
617
+ return;
618
+ }
619
+ recognition.start();
620
+ }
621
+
622
  function handleFileSelect(e, type) {
623
  const file = e.target.files[0];
624
  if (!file) return;
 
634
  <div class="preview-item" id="imgPreview">
635
  <img src="${ev.target.result}">
636
  <span>صورة مرفقة</span>
637
+ <span class="remove-btn-svg" onclick="removeFile('image')">
638
+ <svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
639
+ </span>
640
  </div>`;
641
  };
642
  reader.readAsDataURL(file);
 
644
  selectedAudio = file;
645
  previewBar.innerHTML += `
646
  <div class="preview-item" id="audioPreview">
647
+ <span>صوت: ${file.name}</span>
648
+ <span class="remove-btn-svg" onclick="removeFile('audio')">
649
+ <svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"/></svg>
650
+ </span>
651
  </div>`;
652
  }
653
  }
 
686
  isProcessing = true;
687
  sendBtn.disabled = true;
688
 
689
+ await captureClientSensors();
690
+
691
  const formData = new FormData();
692
  formData.append('message', text);
693
  formData.append('use_web_search', document.getElementById('webSearchToggle').checked);
694
  formData.append('system_prompt', document.getElementById('systemPrompt').value);
695
+ formData.append('client_sensors', JSON.stringify(clientSensorsData));
696
 
697
  if (selectedImage) formData.append('image_file', selectedImage);
698
  if (selectedAudio) formData.append('audio_file', selectedAudio);
699
 
700
+ appendMessage(text || "معالجة الوسائط...", 'user');
701
 
702
  inputEl.value = '';
703
  removeFile('image');
704
  removeFile('audio');
705
 
706
+ const { thinkContentEl, bubbleEl } = createBotMessageElements();
707
 
708
  try {
709
  const response = await fetch('/api/chat', {
 
715
 
716
  const reader = response.body.getReader();
717
  const decoder = new TextDecoder();
718
+ let fullStreamText = "";
719
 
720
  while (true) {
721
  const { done, value } = await reader.read();
722
  if (done) break;
723
+
724
+ const chunk = decoder.decode(value, { stream: true });
725
+ fullStreamText += chunk;
726
+
727
+ if (fullStreamText.includes('<think>')) {
728
+ const parts = fullStreamText.split('<think>');
729
+ const afterThink = parts[1] || "";
730
+
731
+ if (afterThink.includes('</think>')) {
732
+ const thinkAndAnswer = afterThink.split('</think>');
733
+ thinkContentEl.innerText = thinkAndAnswer[0].trim();
734
+ bubbleEl.innerText = thinkAndAnswer[1].trim();
735
+
736
+ const spinner = thinkContentEl.parentElement.querySelector('.think-spinner');
737
+ if (spinner) spinner.remove();
738
+ } else {
739
+ thinkContentEl.innerText = afterThink.trim();
740
+ }
741
+ } else {
742
+ bubbleEl.innerText = fullStreamText.trim();
743
+ }
744
+
745
  document.getElementById('chatBox').scrollTop = document.getElementById('chatBox').scrollHeight;
746
  }
747
  } catch (err) {
748
+ bubbleEl.innerText = "تعذر إكمال الطلب: " + err.message;
749
  } finally {
750
  isProcessing = false;
751
  sendBtn.disabled = false;
 
758
  row.className = `message-row ${role}`;
759
 
760
  const avatar = document.createElement('div');
761
+ avatar.className = 'avatar-svg';
762
+ avatar.innerHTML = role === 'user'
763
+ ? `<svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`
764
+ : `<svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4M8 16h.01M16 16h.01"/></svg>`;
765
+
766
+ const body = document.createElement('div');
767
+ body.className = 'message-body';
768
 
769
  const bubble = document.createElement('div');
770
  bubble.className = 'message-bubble';
771
  bubble.innerText = text;
772
 
773
+ body.appendChild(bubble);
774
  row.appendChild(avatar);
775
+ row.appendChild(body);
776
 
777
  chatBox.appendChild(row);
778
  chatBox.scrollTop = chatBox.scrollHeight;
779
  return bubble;
780
  }
781
+
782
+ function createBotMessageElements() {
783
+ const chatBox = document.getElementById('chatBox');
784
+ const row = document.createElement('div');
785
+ row.className = 'message-row bot';
786
+
787
+ const avatar = document.createElement('div');
788
+ avatar.className = 'avatar-svg';
789
+ avatar.innerHTML = `<svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4M8 16h.01M16 16h.01"/></svg>`;
790
+
791
+ const body = document.createElement('div');
792
+ body.className = 'message-body';
793
+
794
+ const thinkBox = document.createElement('div');
795
+ thinkBox.className = 'think-box';
796
+
797
+ const thinkHeader = document.createElement('div');
798
+ thinkHeader.className = 'think-header';
799
+ thinkHeader.innerHTML = `
800
+ <div class="think-spinner"></div>
801
+ <span>التفكير المنطقي (Reasoning Process)</span>
802
+ `;
803
+
804
+ const thinkContent = document.createElement('div');
805
+ thinkContent.className = 'think-content';
806
+
807
+ thinkBox.appendChild(thinkHeader);
808
+ thinkBox.appendChild(thinkContent);
809
+
810
+ const bubble = document.createElement('div');
811
+ bubble.className = 'message-bubble';
812
+
813
+ body.appendChild(thinkBox);
814
+ body.appendChild(bubble);
815
+
816
+ row.appendChild(avatar);
817
+ row.appendChild(body);
818
+
819
+ chatBox.appendChild(row);
820
+ chatBox.scrollTop = chatBox.scrollHeight;
821
+
822
+ return { thinkContentEl: thinkContent, bubbleEl: bubble };
823
+ }
824
+
825
+ function clearChatUI() {
826
+ document.getElementById('chatBox').innerHTML = '';
827
+ }
828
  </script>
829
  </body>
830
  </html>