protae5544 commited on
Commit
f1574d6
·
verified ·
1 Parent(s): 4d41e72

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +346 -605
index.html CHANGED
@@ -3,28 +3,30 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6
- <title>AI Chatbot - Cyber Material</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
8
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
9
- <link rel="preconnect" href="https://fonts.googleapis.com">
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&family=Roboto+Mono:wght@300;400;500&display=swap" rel="stylesheet">
12
 
13
  <style>
14
  :root {
15
- --bg-primary: #08080a; /* เกือบดำ */
16
- --bg-secondary: #18181b; /* เทาเข้ม */
17
- --bg-tertiary: #27272a; /* เทาเข้มกลาง */
18
- --bg-cream: #f4f1ea; /* ครีม */
19
- --text-primary: #e0e0e0; /* ขาวนวล */
20
- --text-secondary: #a0a0a0; /* เทา */
21
- --text-dark: #1f1f1f; /* ดำสำหรับพื้นครีม */
22
- --accent-green: #00ffab; /* เขียวไซเบอร์ */
23
- --accent-orange: #ff8c00; /* ส้มไซเบอร์ */
24
- --accent-brown: #a0522d; /* น้ำตาล */
25
- --border-color: rgba(0, 255, 171, 0.2);
26
- --shadow-color-green: rgba(0, 255, 171, 0.3);
27
- --shadow-color-orange: rgba(255, 140, 0, 0.3);
 
 
 
 
28
  --font-main: 'Roboto Mono', 'Courier Prime', 'Courier New', monospace;
29
  --font-code: 'Courier Prime', 'Courier New', monospace;
30
  }
@@ -33,447 +35,310 @@
33
  box-sizing: border-box;
34
  margin: 0;
35
  padding: 0;
36
- scrollbar-width: thin;
37
- scrollbar-color: var(--accent-green) transparent;
38
  }
39
 
40
- *::-webkit-scrollbar { width: 5px; }
41
- *::-webkit-scrollbar-track { background: transparent; }
42
- *::-webkit-scrollbar-thumb { background: var(--accent-green); border-radius: 3px; }
43
-
44
  body {
45
  font-family: var(--font-main);
46
  background: var(--bg-primary);
 
 
 
47
  color: var(--text-primary);
48
  display: flex;
49
  justify-content: center;
50
  align-items: center;
51
  min-height: 100vh;
52
  overflow: hidden;
53
- -webkit-tap-highlight-color: transparent; /* สำคัญสำหรับทัช */
54
  font-weight: 300;
55
  }
56
 
57
  .app-container {
58
- width: 100%;
59
- height: 100%;
60
- max-width: 1600px; /* ขยายสำหรับแท็บเล็ต */
61
- max-height: 100vh; /* เต็มจอ */
62
- background: var(--bg-secondary);
63
- border: 1px solid var(--border-color);
64
- border-radius: 0; /* เต็มจอ */
65
- box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
66
  display: flex;
67
  overflow: hidden;
 
68
  position: relative;
 
69
  }
70
 
71
- /* --- Panel ทั่วไป --- */
72
- .panel {
73
- background: var(--bg-secondary);
74
- transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
75
- position: absolute;
76
- top: 0;
77
- bottom: 0;
78
- z-index: 200;
79
- display: flex;
80
- flex-direction: column;
81
- padding: 25px;
82
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
83
- border-color: var(--border-color);
84
  }
85
 
86
- /* --- Settings Panel (ซ้าย) --- */
87
  .settings-panel {
88
- left: 0;
89
  width: 320px;
90
- border-right: 1px solid var(--border-color);
91
- transform: translateX(0);
92
- }
93
- .settings-panel.collapsed {
94
- transform: translateX(-100%);
95
- }
96
-
97
- /* --- Prompt Panel (ขวา) --- */
98
- .prompt-panel {
99
- right: 0;
100
- width: 400px;
101
- border-left: 1px solid var(--border-color);
102
- transform: translateX(0);
103
- }
104
- .prompt-panel.collapsed {
105
- transform: translateX(100%);
106
  }
 
 
 
 
107
 
108
- /* --- Main Content (กลาง) --- */
109
  .main-content {
110
  flex: 1;
111
  display: flex;
112
  flex-direction: column;
113
  position: relative;
114
- background: var(--bg-primary);
115
- transition: margin 0.5s cubic-bezier(0.4, 0, 0.2, 1);
116
- margin-left: 320px; /* Default margin */
117
- margin-right: 400px; /* Default margin */
118
- height: 100%;
119
  }
120
 
121
- /* --- การปรับ Margin เมื่อ Panel ยุบ --- */
122
- .main-content.settings-collapsed { margin-left: 0; }
123
- .main-content.prompts-collapsed { margin-right: 0; }
124
-
125
- /* --- UI Elements --- */
126
  h2 {
127
  font-size: 1.3rem;
128
  margin-bottom: 25px;
129
- color: var(--accent-green);
130
- font-weight: 500;
131
- border-bottom: 1px solid var(--border-color);
132
- padding-bottom: 10px;
 
 
 
 
133
  }
134
 
135
  .form-field { margin-bottom: 20px; }
136
  .form-field label {
137
- display: block;
138
- margin-bottom: 8px;
139
- font-size: 0.85rem;
140
- font-weight: 400;
141
- color: var(--text-secondary);
142
- text-transform: uppercase;
143
- letter-spacing: 1px;
144
  }
145
-
146
  .form-field select, .form-field textarea {
147
- width: 100%;
148
- padding: 12px 15px;
149
- border: 1px solid var(--border-color);
150
- border-radius: 4px; /* Material Flat */
151
- background: var(--bg-tertiary);
152
- color: var(--text-primary);
153
- font-size: 0.9rem;
154
- font-family: var(--font-main);
155
- transition: all 0.2s ease-in-out;
156
  }
157
-
158
  .form-field select:focus, .form-field textarea:focus {
159
- outline: none;
160
- border-color: var(--accent-green);
161
- box-shadow: 0 0 10px var(--shadow-color-green);
162
  }
163
-
164
- .form-field textarea { resize: vertical; min-height: 90px; }
165
 
166
  button {
167
- padding: 12px 25px;
168
- border: 1px solid var(--accent-green);
169
- border-radius: 4px;
170
- background: transparent;
171
- color: var(--accent-green);
172
- cursor: pointer;
173
- font-size: 0.9rem;
174
- font-weight: 500;
175
- transition: all 0.2s ease-in-out;
176
- touch-action: manipulation; /* สำคัญสำหรับทัช */
177
- text-transform: uppercase;
178
  font-family: var(--font-main);
179
  }
180
-
181
- button:hover, button.active {
182
- background: var(--accent-green);
183
- color: var(--bg-primary);
184
- box-shadow: 0 0 12px var(--shadow-color-green);
185
  }
 
186
 
187
- button:active { transform: scale(0.97); }
188
-
189
- .material-icons {
190
- font-size: 22px;
191
- vertical-align: middle;
192
- line-height: 1; /* จัดแนว */
193
- }
194
 
195
- /* --- Chat Area --- */
196
  .chat-container {
197
- flex: 1;
198
- padding: 25px;
199
- overflow-y: auto;
200
- display: flex;
201
- flex-direction: column;
202
- gap: 18px;
203
- background: var(--bg-cream);
204
- color: var(--text-dark);
205
  }
 
 
 
206
 
207
  .message {
208
- max-width: 80%;
209
- padding: 15px 20px;
210
- border-radius: 6px; /* Flat edges */
211
- font-size: 0.95rem;
212
- line-height: 1.6;
213
- position: relative;
214
- animation: messageSlide 0.4s ease-out forwards;
215
- opacity: 0;
216
- transform: translateY(15px);
217
- font-family: 'Roboto', 'Segoe UI', sans-serif; /* Font อ่านง่ายสำหรับแชท */
218
- font-weight: 400;
219
  }
220
-
221
- @keyframes messageSlide { to { opacity: 1; transform: translateY(0); } }
222
-
223
  .message.user {
224
- background: var(--accent-orange);
225
- align-self: flex-end;
226
  color: #fff;
227
- box-shadow: 0 4px 8px var(--shadow-color-orange);
228
  }
229
-
230
  .message.ai {
231
- background: #ffffff;
232
- align-self: flex-start;
233
- border: 1px solid #e0e0e0;
234
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
235
  }
236
-
237
  .message.ai pre {
238
- background: #1e1e1e; /* Darker code bg */
239
- color: #d4d4d4;
240
- padding: 18px;
241
- border-radius: 4px;
242
- overflow-x: auto;
243
- border: 1px solid #333;
244
- margin-top: 12px;
245
- position: relative;
246
- }
247
- .message.ai pre code {
248
- font-family: var(--font-code);
249
- font-size: 0.85rem;
250
- font-weight: 400; /* Thin */
251
- background: none !important;
252
- color: inherit !important;
253
  }
254
-
255
  .code-tools {
256
- position: absolute;
257
- top: 8px; right: 8px;
258
- display: flex; gap: 6px;
259
  opacity: 0; transition: opacity 0.3s ease;
260
  }
261
  pre:hover .code-tools { opacity: 1; }
262
  .code-tools button {
263
- padding: 5px 10px; font-size: 0.75rem;
264
- background: #333; color: var(--accent-green);
265
- border: 1px solid var(--accent-green);
266
  }
267
  .code-tools button:hover { background: var(--accent-green); color: #1e1e1e; }
268
 
269
- /* --- Input Area --- */
270
  .input-container {
271
- display: flex;
272
- gap: 10px;
273
- padding: 15px;
274
- background: var(--bg-secondary);
275
- border-top: 1px solid var(--border-color);
276
- align-items: center;
277
  }
278
-
279
  #userInput {
280
- flex: 1;
281
- padding: 15px 18px;
282
- border: 1px solid var(--border-color);
283
- border-radius: 4px;
284
- background: var(--bg-tertiary);
285
- color: var(--text-primary);
286
- resize: none;
287
- min-height: 54px;
288
- max-height: 150px;
289
- font-size: 0.95rem;
290
- font-family: var(--font-main);
291
- transition: all 0.2s ease-in-out;
292
  }
293
  #userInput:focus {
294
- outline: none;
295
- border-color: var(--accent-orange);
296
- box-shadow: 0 0 10px var(--shadow-color-orange);
297
  }
298
 
299
  .input-btn {
300
- width: 54px; height: 54px;
301
- border-radius: 4px;
302
- display: flex;
303
- align-items: center;
304
- justify-content: center;
305
- padding: 0;
306
- border-color: var(--accent-orange);
307
- color: var(--accent-orange);
308
  }
309
  .input-btn:hover {
310
- background: var(--accent-orange);
311
- color: var(--bg-primary);
312
- box-shadow: 0 0 12px var(--shadow-color-orange);
313
  }
314
  .input-btn:disabled {
315
- background: var(--bg-tertiary); color: #555;
316
- border-color: #444; cursor: not-allowed;
317
- box-shadow: none; opacity: 0.5;
318
  }
319
  .spinner {
320
- width: 24px; height: 24px;
321
- border: 3px solid rgba(255, 140, 0, 0.3);
322
- border-top: 3px solid var(--accent-orange);
323
- border-radius: 50%;
324
  animation: spin 1s linear infinite;
325
  }
326
  @keyframes spin { to { transform: rotate(360deg); } }
327
 
328
- /* --- Carousel --- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  .carousel-container {
330
- perspective: 1000px;
331
- height: 100%;
332
- display: flex;
333
- flex-direction: column;
334
- align-items: center;
335
- justify-content: center;
336
- position: relative;
337
- user-select: none;
338
- padding: 20px;
339
- flex: 1; /* ให้เต็มพื้นที่ */
340
- overflow: hidden; /* ป้องกันการล้น */
341
  }
342
  .carousel {
343
- position: relative;
344
- width: 95%;
345
- max-width: 350px; /* ลดขนาดให้พอดี Panel */
346
- height: 280px; /* ลดขนาด */
347
  transform-style: preserve-3d;
348
- transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
349
  cursor: grab;
350
  }
351
  .carousel.dragging { cursor: grabbing; }
352
  .carousel-card {
353
- position: absolute;
354
- width: 100%; height: 100%;
355
- background: var(--bg-tertiary);
356
- border: 1px solid var(--border-color);
357
- border-radius: 6px;
358
- padding: 20px;
359
- box-shadow: 0 5px 15px rgba(0,0,0,0.3);
360
- display: flex;
361
- flex-direction: column;
362
- gap: 10px;
363
- transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
364
  backface-visibility: hidden;
365
  }
366
- .carousel-card label { font-size: 0.8rem; }
367
- .carousel-card textarea { font-size: 0.85rem; min-height: 150px; }
368
- .carousel-controls, .carousel-indicator {
369
- display: flex; gap: 15px; margin-top: 20px;
370
- }
371
  .carousel-controls button {
372
- padding: 8px; width: 40px; height: 40px;
373
- border-radius: 50%; display: flex;
374
- align-items: center; justify-content: center;
 
375
  }
 
376
  .carousel-dot {
377
- width: 12px; height: 12px;
378
- background: var(--bg-tertiary);
379
- border-radius: 50%;
380
- cursor: pointer;
381
- transition: all 0.3s ease;
382
- border: 1px solid var(--accent-green);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  }
384
- .carousel-dot.active { background: var(--accent-green); transform: scale(1.3); }
385
 
386
- /* --- Floating Buttons --- */
387
  .floating-buttons {
388
- position: absolute;
389
- top: 15px;
390
- left: 15px; /* เปลี่ยนมาซ้าย */
391
- display: flex;
392
- flex-direction: column; /* เรียงแนวตั้ง */
393
- gap: 10px;
394
- z-index: 300;
395
  }
396
  .floating-btn {
397
- width: 44px; height: 44px;
398
- border-radius: 50%;
399
- background: var(--bg-tertiary);
400
- border: 1px solid var(--border-color);
401
- display: flex;
402
- align-items: center;
403
- justify-content: center;
404
- transition: all 0.2s ease;
405
- padding: 0;
406
- color: var(--text-primary);
407
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
408
  }
409
  .floating-btn:hover {
410
- background: var(--accent-green);
411
- color: var(--bg-primary);
412
- transform: scale(1.1);
413
- box-shadow: 0 4px 12px var(--shadow-color-green);
414
- }
415
- #promptToggle { border-color: var(--accent-orange); color: var(--accent-orange); }
416
- #promptToggle:hover { background: var(--accent-orange); color: var(--bg-primary); box-shadow: 0 4px 12px var(--shadow-color-orange); }
417
-
418
- /* --- อื่นๆ --- */
419
- .dropzone {
420
- position: absolute; top: 0; left: 0; right: 0; bottom: 0;
421
- background: rgba(0, 255, 171, 0.1);
422
- border: 2px dashed var(--accent-green);
423
- display: none; align-items: center; justify-content: center;
424
- color: var(--accent-green); font-size: 1.3rem; z-index: 150;
425
- }
426
- .dropzone.active { display: flex; }
427
- .file-name {
428
- font-size: 0.8rem; color: var(--accent-green);
429
- margin-left: 10px; align-self: center;
430
- max-width: 150px; white-space: nowrap;
431
- overflow: hidden; text-overflow: ellipsis;
432
- }
433
- .notification {
434
- position: fixed; bottom: 20px; left: 50%;
435
- transform: translateX(-50%);
436
- padding: 12px 20px; border-radius: 4px;
437
- color: #fff; font-size: 0.9rem; z-index: 1000;
438
- display: none; background: #333;
439
- border: 1px solid var(--border-color);
440
- animation: notificationFade 3.5s ease forwards;
441
- }
442
- .notification.error-message { background: #d32f2f; border-color: #d32f2f; }
443
- .notification.success-message { background: #388e3c; border-color: #388e3c; }
444
- @keyframes notificationFade {
445
- 0% { opacity: 0; transform: translate(-50%, 20px); }
446
- 15% { opacity: 1; transform: translate(-50%, 0); }
447
- 85% { opacity: 1; transform: translate(-50%, 0); }
448
- 100% { opacity: 0; transform: translate(-50%, -20px); }
449
- }
450
-
451
- /* --- Responsive for Tablet --- */
452
- @media (max-width: 1024px) {
453
- .settings-panel, .prompt-panel {
454
- width: 280px;
455
- transform: translateX(-100%); /* Start collapsed */
456
- }
457
- .prompt-panel { transform: translateX(100%); }
458
- .settings-panel.open { transform: translateX(0); }
459
- .prompt-panel.open { transform: translateX(0); }
460
- .main-content { margin-left: 0; margin-right: 0; }
461
  }
 
 
 
 
462
  @media (max-width: 768px) {
463
- .settings-panel, .prompt-panel { width: 100%; border: none; }
464
- .floating-buttons { top: 10px; left: 10px; }
465
- .chat-container { padding: 15px; }
466
- .input-container { padding: 10px; }
467
- #userInput { min-height: 50px; }
468
- .input-btn { width: 50px; height: 50px; }
469
  }
470
 
471
  </style>
472
  </head>
473
  <body>
474
  <div class="app-container" id="appContainer">
475
-
476
- <div class="settings-panel panel" id="settingsPanel">
477
  <h2>⚙️ ตั้งค่า</h2>
478
  <div class="form-field">
479
  <label>🤖 โมเดลหลัก:</label>
@@ -482,6 +347,9 @@
482
  <option value="Qwen/Qwen2-VL-72B-Instruct">Qwen2-VL 72B (Image)</option>
483
  <option value="microsoft/Florence-2-large">Florence-2 Large (Image)</option>
484
  </select>
 
 
 
485
  </div>
486
  <div class="form-field">
487
  <label>👁️ โมเดล OCR:</label>
@@ -491,49 +359,54 @@
491
  </select>
492
  </div>
493
  <button id="saveSettingsBtn">💾 บันทึก</button>
494
- <p style="font-size: 0.7rem; color: var(--text-secondary); margin-top: auto; padding-top: 20px;">
495
- UI v2.1 - Cyber Material Touch.
496
- </p>
497
  </div>
498
 
499
- <div class="main-content" id="mainContent">
500
- <div class="chat-container" id="chatContainer">
501
- <div class="message ai">สวัสดี! ฉันคือ AI Chatbot ที่พร้อมช่วยคุณ. เลือกโมเดลและเริ่มแชทได้เลย!</div>
 
 
502
  </div>
503
- <div class="input-container">
504
- <button id="attachButton" class="input-btn"><span class="material-icons">attach_file</span></button>
505
- <input type="file" id="fileInput" multiple accept="image/*" style="display: none;">
506
- <span class="file-name" id="fileName"></span>
507
- <textarea id="userInput" placeholder="พิมพ์ข้อความ..."></textarea>
508
- <button id="sendButton" class="input-btn"><span class="material-icons">send</span></button>
509
  </div>
510
- <div class="dropzone" id="dropzone">📁 วางไฟล์ที่นี่</div>
511
- </div>
512
 
513
- <div class="prompt-panel panel" id="promptPanel">
514
- <h2>🎛️ แก้ไข Prompt</h2>
515
- <div class="carousel-container">
516
- <div class="carousel" id="promptCarousel">
 
 
 
517
  </div>
518
- <div class="carousel-controls">
519
- <button id="prevCard"><span class="material-icons">chevron_left</span></button>
520
- <button id="nextCard"><span class="material-icons">chevron_right</span></button>
521
  </div>
522
- <div class="carousel-indicator" id="carouselIndicator">
523
- </div>
524
  </div>
525
- <button id="applyPromptBtn" style="margin-top: auto;">✅ ใช้ Prompt</button>
526
- </div>
527
-
528
- <div class="floating-buttons">
529
- <button id="settingsToggle" class="floating-btn"><span class="material-icons">settings</span></button>
530
- <button id="promptToggle" class="floating-btn"><span class="material-icons">edit_note</span></button>
531
- <button id="refreshBtn" class="floating-btn"><span class="material-icons">refresh</span></button>
532
- <button id="fullscreenToggle" class="floating-btn"><span class="material-icons">fullscreen</span></button>
533
- </div>
534
-
535
- <div class="notification" id="notificationBar"></div>
536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  </div>
538
 
539
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
@@ -544,30 +417,24 @@
544
  let attachedFiles = [];
545
  let currentCarouselIndex = 0;
546
  let isLoading = false;
547
- let isDragging = false;
548
- let startX = 0;
549
- let currentAngle = 0;
550
- let startAngle = 0;
551
- let carouselElement; // Define globally
552
 
553
  const prompts = [
554
- { id: 'primarySystemPrompt', label: '🎯 Primary System Prompt', value: 'You are a powerful AI assistant focusing on code, images, and tech. Be clear, accurate, helpful. Focus on solutions & explanations.' },
555
- { id: 'ocrSystemPrompt', label: '👁️ OCR System Prompt', value: 'Extract all text accurately. Preserve format. Indicate unclear parts.' },
556
- { id: 'codeTemplate', label: '💻 Code Template', value: '```javascript\n// Code goes here\nfunction main() {\n return "Hello";\n}\n```' },
557
- { id: 'additionalInstructions', label: '📋 Additional Instructions', value: 'Provide tested examples. Include error handling. Explain step-by-step. Use modern practices.' },
558
- { id: 'promptPrefix', label: '🚀 Prompt Prefix', value: 'Analyze carefully and provide a comprehensive solution:' },
559
- { id: 'promptSuffix', label: '✨ Prompt Suffix', value: 'Ensure response is complete, accurate, with examples.' }
560
  ];
561
 
562
  document.addEventListener('DOMContentLoaded', () => {
563
- carouselElement = document.getElementById('promptCarousel'); // Assign here
564
- loadSettings();
565
  populateCarousel();
 
566
  setupEventListeners();
567
- setupCarouselInteraction();
568
- setupDropzone();
569
- updateCarouselPosition(true); // Initial setup without transition
570
- updatePanelLayout(); // Set initial layout based on screen size
571
  });
572
 
573
  function populateCarousel() {
@@ -575,10 +442,16 @@
575
  const indicator = document.getElementById('carouselIndicator');
576
  carousel.innerHTML = '';
577
  indicator.innerHTML = '';
 
 
 
 
578
 
579
  prompts.forEach((prompt, index) => {
580
  const card = document.createElement('div');
581
  card.className = 'carousel-card';
 
 
582
  card.innerHTML = `
583
  <label>${prompt.label}</label>
584
  <textarea id="${prompt.id}" placeholder="Enter prompt...">${localStorage.getItem(prompt.id) || prompt.value}</textarea>
@@ -590,148 +463,88 @@
590
  dot.dataset.index = index;
591
  indicator.appendChild(dot);
592
  });
 
593
  }
594
 
595
  function loadSettings() {
596
  document.getElementById('modelSelect').value = localStorage.getItem('selectedModel') || 'Qwen/Qwen2.5-Coder-32B-Instruct';
597
  document.getElementById('ocrModelSelect').value = localStorage.getItem('selectedOcrModel') || 'none';
598
- prompts.forEach(p => {
599
- const el = document.getElementById(p.id);
600
- if (el) el.value = localStorage.getItem(p.id) || p.value;
601
- });
602
  }
603
 
604
  function saveSettings() {
605
  localStorage.setItem('selectedModel', document.getElementById('modelSelect').value);
606
  localStorage.setItem('selectedOcrModel', document.getElementById('ocrModelSelect').value);
607
  prompts.forEach(p => localStorage.setItem(p.id, document.getElementById(p.id).value));
608
- showNotification('บันทึกการตั้งค่าแล้ว!', 'success');
609
  }
610
 
611
  function setupEventListeners() {
 
 
612
  document.getElementById('saveSettingsBtn').addEventListener('click', saveSettings);
613
- document.getElementById('settingsToggle').addEventListener('click', toggleSettingsPanel);
614
- document.getElementById('promptToggle').addEventListener('click', togglePromptPanel);
615
  document.getElementById('prevCard').addEventListener('click', () => rotateCarousel(-1));
616
  document.getElementById('nextCard').addEventListener('click', () => rotateCarousel(1));
617
- document.getElementById('applyPromptBtn').addEventListener('click', applyPrompts);
618
  document.getElementById('sendButton').addEventListener('click', sendMessage);
619
  document.getElementById('attachButton').addEventListener('click', () => document.getElementById('fileInput').click());
620
  document.getElementById('fileInput').addEventListener('change', handleFileSelect);
621
  document.getElementById('userInput').addEventListener('keydown', handleKeyDown);
622
- document.getElementById('refreshBtn').addEventListener('click', () => location.reload());
623
  document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
 
624
 
 
 
 
 
 
 
625
  document.querySelectorAll('.carousel-dot').forEach(dot => {
626
  dot.addEventListener('click', (e) => goToCarouselIndex(parseInt(e.target.dataset.index)));
627
  });
628
 
629
- window.addEventListener('resize', updatePanelLayout); // Update layout on resize
630
- }
631
-
632
- function updatePanelLayout() {
633
- const settingsPanel = document.getElementById('settingsPanel');
634
- const promptPanel = document.getElementById('promptPanel');
635
- const mainContent = document.getElementById('mainContent');
636
-
637
- if (window.innerWidth <= 1024) {
638
- settingsPanel.classList.add('collapsed');
639
- promptPanel.classList.add('collapsed');
640
- settingsPanel.classList.remove('open');
641
- promptPanel.classList.remove('open');
642
- mainContent.classList.add('settings-collapsed', 'prompts-collapsed');
643
- } else {
644
- settingsPanel.classList.remove('collapsed', 'open');
645
- promptPanel.classList.remove('collapsed', 'open');
646
- mainContent.classList.remove('settings-collapsed', 'prompts-collapsed');
647
  }
648
  }
649
 
650
- function togglePanel(panelId, mainContentClass) {
651
- const panel = document.getElementById(panelId);
652
- const mainContent = document.getElementById('mainContent');
653
- const isOpen = panel.classList.contains('open');
654
-
655
- if (window.innerWidth <= 1024) {
656
- if (isOpen) {
657
- panel.classList.remove('open');
658
- panel.classList.add('collapsed');
659
- } else {
660
- // Close other panel if open
661
- const otherPanelId = panelId === 'settingsPanel' ? 'promptPanel' : 'settingsPanel';
662
- document.getElementById(otherPanelId).classList.remove('open');
663
- document.getElementById(otherPanelId).classList.add('collapsed');
664
- // Open current panel
665
- panel.classList.add('open');
666
- panel.classList.remove('collapsed');
667
- }
668
  } else {
669
- panel.classList.toggle('collapsed');
670
- mainContent.classList.toggle(mainContentClass);
 
 
 
 
671
  }
672
  }
673
 
674
-
675
- function toggleSettingsPanel() {
676
- const panel = document.getElementById('settingsPanel');
677
- const main = document.getElementById('mainContent');
678
- panel.classList.toggle('collapsed');
679
- main.classList.toggle('settings-collapsed');
680
- }
681
-
682
- function togglePromptPanel() {
683
- const panel = document.getElementById('promptPanel');
684
- const main = document.getElementById('mainContent');
685
- panel.classList.toggle('collapsed');
686
- main.classList.toggle('prompts-collapsed');
687
- }
688
-
689
-
690
- function setupCarouselInteraction() {
691
- const carousel = carouselElement;
692
- carousel.addEventListener('touchstart', dragStart, { passive: false });
693
- carousel.addEventListener('touchmove', dragging, { passive: false });
694
- carousel.addEventListener('touchend', dragEnd);
695
- carousel.addEventListener('mousedown', dragStart);
696
- carousel.addEventListener('mousemove', dragging);
697
- carousel.addEventListener('mouseup', dragEnd);
698
- carousel.addEventListener('mouseleave', dragEnd);
699
- }
700
-
701
- function dragStart(e) {
702
- e.preventDefault();
703
- isDragging = true;
704
- startX = (e.type === 'touchstart' ? e.touches[0].clientX : e.clientX);
705
- startAngle = currentAngle;
706
- carouselElement.style.transition = 'none'; // Disable transition during drag
707
- carouselElement.classList.add('dragging');
708
- }
709
-
710
- function dragging(e) {
711
- if (!isDragging) return;
712
- e.preventDefault();
713
- const x = (e.type === 'touchmove' ? e.touches[0].clientX : e.clientX);
714
- const dx = x - startX;
715
- const sensitivity = 0.5; // Adjust drag speed
716
- const angleChange = dx * sensitivity;
717
- currentAngle = startAngle - angleChange; // Invert for natural feel
718
- carouselElement.style.transform = `translateZ(-288px) rotateY(${currentAngle}deg)`;
719
- }
720
-
721
- function dragEnd(e) {
722
- if (!isDragging) return;
723
- isDragging = false;
724
- carouselElement.style.transition = 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)';
725
- carouselElement.classList.remove('dragging');
726
-
727
- const totalCards = prompts.length;
728
- const anglePerCard = 360 / totalCards;
729
- // Snap to the nearest card
730
- currentCarouselIndex = Math.round(currentAngle / anglePerCard);
731
- currentCarouselIndex = (currentCarouselIndex % totalCards + totalCards) % totalCards; // Ensure positive index
732
- updateCarouselPosition();
733
- }
734
-
735
  function rotateCarousel(direction) {
736
  const totalCards = prompts.length;
737
  currentCarouselIndex = (currentCarouselIndex + direction + totalCards) % totalCards;
@@ -743,77 +556,32 @@
743
  updateCarouselPosition();
744
  }
745
 
746
- function updateCarouselPosition(noTransition = false) {
747
- const carousel = carouselElement;
748
- const cards = carousel.querySelectorAll('.carousel-card');
749
- const dots = document.querySelectorAll('.carousel-dot');
750
- const totalCards = cards.length;
751
  const anglePerCard = 360 / totalCards;
752
- const cardWidth = cards.length > 0 ? cards[0].offsetWidth : 350;
753
- // Recalculate or use a fixed good value for translateZ
754
- const translateZ = (cardWidth / 2) / Math.tan((anglePerCard / 2) * (Math.PI / 180)) + 20; // Added some padding
755
-
756
- currentAngle = currentCarouselIndex * anglePerCard;
757
 
758
- carousel.style.transition = noTransition ? 'none' : 'transform 0.6s cubic-bezier(0.4, 0, 0.2, 1)';
759
- carousel.style.transform = `translateZ(-${translateZ}px) rotateY(${-currentAngle}deg)`;
760
 
761
- cards.forEach((card, index) => {
762
- const angle = index * anglePerCard;
763
- card.style.transform = `rotateY(${angle}deg) translateZ(${translateZ}px)`;
764
- card.style.opacity = index === currentCarouselIndex ? '1' : '0.5';
765
  });
766
 
767
- dots.forEach((dot, index) => {
768
  dot.classList.toggle('active', index === currentCarouselIndex);
769
  });
770
  }
771
 
772
-
773
- function applyPrompts() {
774
  saveSettings();
 
775
  showNotification('ใช้ Prompt แล้ว!', 'success');
776
- if (window.innerWidth <= 1024) {
777
- togglePromptPanel(); // Close panel on mobile after apply
778
- }
779
- }
780
-
781
- function setupDropzone() {
782
- const dropzone = document.getElementById('dropzone');
783
- const chatContainer = document.getElementById('chatContainer');
784
-
785
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
786
- chatContainer.addEventListener(eventName, preventDefaults, false);
787
- document.body.addEventListener(eventName, preventDefaults, false); // Global prevent
788
- });
789
-
790
- function preventDefaults(e) {
791
- e.preventDefault();
792
- e.stopPropagation();
793
- }
794
-
795
- ['dragenter', 'dragover'].forEach(eventName => {
796
- chatContainer.addEventListener(eventName, () => dropzone.classList.add('active'), false);
797
- });
798
- ['dragleave', 'drop'].forEach(eventName => {
799
- chatContainer.addEventListener(eventName, () => dropzone.classList.remove('active'), false);
800
- });
801
- chatContainer.addEventListener('drop', handleDrop, false);
802
- }
803
-
804
- function handleDrop(e) {
805
- const dt = e.dataTransfer;
806
- handleFiles(dt.files);
807
  }
808
 
809
  function handleFileSelect(e) {
810
- handleFiles(e.target.files);
811
- }
812
-
813
- function handleFiles(files) {
814
- attachedFiles.push(...Array.from(files).filter(f => f.type.startsWith('image/')));
815
  updateFileDisplay();
816
- showNotification(`${files.length} รูปถูกแนบแล้ว`, 'success');
817
  }
818
 
819
  function updateFileDisplay() {
@@ -830,7 +598,7 @@
830
  }
831
  }
832
 
833
- async function sendMessage() {
834
  const userInput = document.getElementById('userInput');
835
  const message = userInput.value.trim();
836
  if (!message && attachedFiles.length === 0 || isLoading) return;
@@ -864,49 +632,26 @@
864
  async function callHuggingFaceAPI(message, files) {
865
  const model = document.getElementById('modelSelect').value;
866
  const ocrModel = document.getElementById('ocrModelSelect').value;
867
- let fullPrompt = `${localStorage.getItem('primarySystemPrompt') || prompts[0].value}\n\n`;
868
- fullPrompt += `${localStorage.getItem('promptPrefix') || prompts[4].value}\n\nUser: ${message}`;
869
-
870
- let imageBase64 = null;
871
- if (files.length > 0) {
872
- const file = files[0];
873
- imageBase64 = await new Promise((resolve) => {
874
- const reader = new FileReader();
875
- reader.onloadend = () => resolve(reader.result.split(',')[1]);
876
- reader.readAsDataURL(file);
877
- });
878
-
879
- if (ocrModel !== 'none') {
880
- try {
881
- const ocrText = await performOCR(file, ocrModel);
882
- fullPrompt += `\n\n[Image Content: ${ocrText}]`;
883
- } catch (error) {
884
- console.warn('OCR failed:', error);
885
- fullPrompt += `\n\n[Image attached, OCR failed]`;
886
- }
887
- } else {
888
- fullPrompt += `\n\n[Image attached]`;
889
  }
 
 
890
  }
891
 
892
- fullPrompt += `\n\n${localStorage.getItem('promptSuffix') || prompts[5].value}\n\nAI:`;
893
 
894
  const payload = {
895
  inputs: fullPrompt,
896
- parameters: { max_new_tokens: 1500, temperature: 0.5, top_p: 0.9, do_sample: true, repetition_penalty: 1.1 }
897
  };
898
 
899
- // Note: HuggingFace Inference API doesn't have a standard way to send images + text
900
- // for all models via JSON. Qwen-VL and Florence-2 might need specific formats or
901
- // dedicated libraries/endpoints. This basic call mostly suits text models or
902
- // text+OCR text. A real implementation might need adjustments.
903
- if (imageBase64 && (model.includes('VL') || model.includes('Florence'))) {
904
- // This part is tricky and model-dependent. HF API often prefers
905
- // sending image data separately or using dedicated libs. We'll proceed
906
- // with text + OCR for now as a fallback.
907
- console.warn("Image sending via simple JSON is not standard. Sending text+OCR.");
908
- }
909
-
910
  const response = await fetch(`https://api-inference.huggingface.co/models/${model}`, {
911
  method: 'POST',
912
  headers: { 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}`, 'Content-Type': 'application/json' },
@@ -917,14 +662,13 @@
917
  const errorBody = await response.text();
918
  throw new Error(`HTTP ${response.status}: ${errorBody}`);
919
  }
920
-
921
  const data = await response.json();
922
  if (data.error) throw new Error(data.error);
923
  let text = data[0]?.generated_text || 'No response.';
924
- return text.replace(fullPrompt, '').trim(); // Remove prompt echo
925
  }
926
 
927
- async function performOCR(file, ocrModel) {
928
  const response = await fetch(`https://api-inference.huggingface.co/models/${ocrModel}`, {
929
  method: 'POST',
930
  headers: { 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}` },
@@ -940,7 +684,7 @@
940
  const chatContainer = document.getElementById('chatContainer');
941
  const messageDiv = document.createElement('div');
942
  messageDiv.className = `message ${sender}`;
943
- if (isError) messageDiv.style.borderColor = '#d32f2f';
944
 
945
  if (sender === 'ai') {
946
  messageDiv.innerHTML = marked.parse(content);
@@ -952,32 +696,31 @@
952
  messageDiv.textContent = content;
953
  }
954
  chatContainer.appendChild(messageDiv);
955
- scrollToBottom(chatContainer);
956
  }
957
 
958
  function addImageMessage(imageUrl, sender) {
959
  const chatContainer = document.getElementById('chatContainer');
960
  const messageDiv = document.createElement('div');
961
  messageDiv.className = `message ${sender}`;
962
- messageDiv.innerHTML = `<img src="${imageUrl}" alt="Uploaded image" style="max-width: 100%; max-height: 300px; border-radius: 4px;">`;
963
  chatContainer.appendChild(messageDiv);
964
  }
965
 
966
- function addCodeTools(preElement) {
967
  const toolsDiv = document.createElement('div');
968
  toolsDiv.className = 'code-tools';
969
  const copyBtn = document.createElement('button');
970
- copyBtn.innerHTML = '<span class="material-icons">content_copy</span>';
971
  copyBtn.onclick = () => {
972
  navigator.clipboard.writeText(preElement.querySelector('code').innerText)
973
  .then(() => showNotification('คัดลอกโค้ดแล้ว!', 'success'))
974
  .catch(() => showNotification('คัดลอกไม่สำเร็จ!', 'error'));
975
  };
976
  toolsDiv.appendChild(copyBtn);
977
- preElement.style.position = 'relative';
978
  preElement.appendChild(toolsDiv);
979
  }
980
 
 
981
  function setLoading(loading) {
982
  isLoading = loading;
983
  const sendButton = document.getElementById('sendButton');
@@ -997,11 +740,9 @@
997
  }
998
 
999
  function showNotification(message, type = 'success') {
1000
- const bar = document.getElementById('notificationBar');
1001
- bar.textContent = message;
1002
- bar.className = `notification ${type === 'error' ? 'error-message' : 'success-message'}`;
1003
- bar.style.display = 'block';
1004
- setTimeout(() => { bar.style.display = 'none'; }, 3400);
1005
  }
1006
 
1007
  function scrollToBottom(element) {
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6
+ <title>AI Chatbot - Classic Enhanced</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
8
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&family=Roboto+Mono:wght@300;400&display=swap" rel="stylesheet">
 
 
10
 
11
  <style>
12
  :root {
13
+ --bg-primary: #1a1a1a; /* น้ำตาลเข้ม/ดำ */
14
+ --bg-secondary: #2c2c2c; /* น้ำตาลเข้ม/ดำ (สว่างขึ้น) */
15
+ --bg-tertiary: #3d3d3d; /* น้ำตาลเข้ม/ดำ (สว่างสุด) */
16
+ --text-primary: #f0e6d2; /* ครีม/สว่าง */
17
+ --text-secondary: #a0937d; /* ครีม/น้ำตาล (หม่น) */
18
+ --accent-primary: #33ff99; /* เขียว */
19
+ --accent-secondary: #ff9933; /* ส้ม */
20
+ --accent-tertiary: #8b4513; /* น้ำตาล */
21
+ --chat-bg: #f5f5dc; /* ครีมสำหรับแชท */
22
+ --chat-text: #333333; /* ข้อความเข้มสำหรับแชท */
23
+ --error: #ff4757;
24
+ --success: #2ed573;
25
+ --glass-bg: rgba(44, 44, 44, 0.2); /* ปรับให้โปร่งใสขึ้น */
26
+ --glass-border: rgba(51, 255, 153, 0.2); /* เขียวโปร่ง */
27
+ --shadow-lg: 0 15px 35px rgba(0, 0, 0, 0.5);
28
+ --shadow-xl: 0 20px 45px rgba(0, 0, 0, 0.6);
29
+ --gradient-primary: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
30
  --font-main: 'Roboto Mono', 'Courier Prime', 'Courier New', monospace;
31
  --font-code: 'Courier Prime', 'Courier New', monospace;
32
  }
 
35
  box-sizing: border-box;
36
  margin: 0;
37
  padding: 0;
38
+ -webkit-tap-highlight-color: transparent;
 
39
  }
40
 
 
 
 
 
41
  body {
42
  font-family: var(--font-main);
43
  background: var(--bg-primary);
44
+ background-image:
45
+ radial-gradient(ellipse at top left, rgba(51, 255, 153, 0.06) 0%, transparent 55%),
46
+ radial-gradient(ellipse at bottom right, rgba(255, 153, 51, 0.06) 0%, transparent 55%);
47
  color: var(--text-primary);
48
  display: flex;
49
  justify-content: center;
50
  align-items: center;
51
  min-height: 100vh;
52
  overflow: hidden;
 
53
  font-weight: 300;
54
  }
55
 
56
  .app-container {
57
+ width: 95%;
58
+ max-width: 1400px;
59
+ background: var(--glass-bg);
60
+ backdrop-filter: blur(15px);
61
+ -webkit-backdrop-filter: blur(15px);
62
+ border: 1px solid var(--glass-border);
63
+ border-radius: 20px;
64
+ box-shadow: var(--shadow-xl);
65
  display: flex;
66
  overflow: hidden;
67
+ height: 90vh;
68
  position: relative;
69
+ animation: slideIn 0.8s cubic-bezier(0.23, 1, 0.32, 1);
70
  }
71
 
72
+ @keyframes slideIn {
73
+ from { opacity: 0; transform: translateY(40px) scale(0.97); }
74
+ to { opacity: 1; transform: translateY(0) scale(1); }
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
 
77
  .settings-panel {
 
78
  width: 320px;
79
+ padding: 25px;
80
+ background: rgba(30, 30, 30, 0.5);
81
+ backdrop-filter: blur(10px);
82
+ border-right: 1px solid var(--glass-border);
83
+ transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1);
84
+ overflow-y: auto;
85
+ scrollbar-width: thin;
86
+ scrollbar-color: var(--accent-primary) transparent;
 
 
 
 
 
 
 
 
87
  }
88
+ .settings-panel.collapsed { width: 0; padding: 0; overflow: hidden; border-right: none; }
89
+ .settings-panel::-webkit-scrollbar { width: 6px; }
90
+ .settings-panel::-webkit-scrollbar-track { background: transparent; }
91
+ .settings-panel::-webkit-scrollbar-thumb { background: var(--accent-primary); border-radius: 3px; }
92
 
 
93
  .main-content {
94
  flex: 1;
95
  display: flex;
96
  flex-direction: column;
97
  position: relative;
98
+ background: transparent;
 
 
 
 
99
  }
100
 
 
 
 
 
 
101
  h2 {
102
  font-size: 1.3rem;
103
  margin-bottom: 25px;
104
+ font-weight: 400;
105
+ background: var(--gradient-primary);
106
+ -webkit-background-clip: text;
107
+ -webkit-text-fill-color: transparent;
108
+ background-clip: text;
109
+ text-fill-color: transparent;
110
+ padding-bottom: 5px;
111
+ border-bottom: 1px solid var(--glass-border);
112
  }
113
 
114
  .form-field { margin-bottom: 20px; }
115
  .form-field label {
116
+ display: block; margin-bottom: 8px; font-size: 0.9rem;
117
+ font-weight: 400; color: var(--text-secondary);
118
+ text-transform: uppercase; letter-spacing: 0.5px;
 
 
 
 
119
  }
 
120
  .form-field select, .form-field textarea {
121
+ width: 100%; padding: 12px 16px; border: 1px solid var(--glass-border);
122
+ border-radius: 8px; background: var(--bg-tertiary);
123
+ color: var(--text-primary); font-size: 0.95rem;
124
+ transition: all 0.3s ease; font-family: var(--font-main);
 
 
 
 
 
125
  }
 
126
  .form-field select:focus, .form-field textarea:focus {
127
+ outline: none; border-color: var(--accent-primary);
128
+ box-shadow: 0 0 15px var(--accent-primary);
 
129
  }
130
+ .form-field textarea { resize: vertical; min-height: 100px; }
 
131
 
132
  button {
133
+ padding: 12px 24px; border: 1px solid var(--accent-primary);
134
+ border-radius: 8px; background: var(--accent-primary);
135
+ color: var(--bg-primary); cursor: pointer;
136
+ font-size: 1rem; font-weight: 700;
137
+ transition: all 0.3s ease; touch-action: manipulation;
 
 
 
 
 
 
138
  font-family: var(--font-main);
139
  }
140
+ button:hover {
141
+ background: transparent; color: var(--accent-primary);
142
+ box-shadow: 0 0 10px var(--accent-primary);
143
+ transform: translateY(-2px);
 
144
  }
145
+ button:active { transform: translateY(0px) scale(0.98); }
146
 
147
+ .material-icons { font-size: 24px; vertical-align: middle; }
 
 
 
 
 
 
148
 
 
149
  .chat-container {
150
+ flex: 1; padding: 25px; overflow-y: auto; display: flex;
151
+ flex-direction: column; gap: 16px;
152
+ background: var(--chat-bg); /* Cream Background */
153
+ color: var(--chat-text);
154
+ scrollbar-width: thin; scrollbar-color: var(--accent-secondary) transparent;
 
 
 
155
  }
156
+ .chat-container::-webkit-scrollbar { width: 8px; }
157
+ .chat-container::-webkit-scrollbar-track { background: transparent; }
158
+ .chat-container::-webkit-scrollbar-thumb { background: var(--accent-secondary); border-radius: 4px; }
159
 
160
  .message {
161
+ max-width: 80%; padding: 16px 20px; border-radius: 15px; /* Softer */
162
+ font-size: 1rem; line-height: 1.6; position: relative;
163
+ animation: messageSlide 0.5s cubic-bezier(0.23, 1, 0.32, 1);
164
+ font-family: 'Segoe UI', Roboto, sans-serif; /* Readable font */
165
+ }
166
+ @keyframes messageSlide {
167
+ from { opacity: 0; transform: translateY(20px); }
168
+ to { opacity: 1; transform: translateY(0); }
 
 
 
169
  }
 
 
 
170
  .message.user {
171
+ background: linear-gradient(135deg, var(--accent-secondary), #ff6b35);
172
+ align-self: flex-end; box-shadow: 0 6px 15px rgba(255, 153, 51, 0.3);
173
  color: #fff;
 
174
  }
 
175
  .message.ai {
176
+ background: #ffffff; border: 1px solid #e0e0e0;
177
+ align-self: flex-start; box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
178
+ color: var(--chat-text);
 
179
  }
 
180
  .message.ai pre {
181
+ background: #1e1e1e; color: #d4d4d4; padding: 20px;
182
+ border-radius: 8px; overflow-x: auto;
183
+ border: 1px solid #333; margin-top: 15px; position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
+ .message.ai pre code { font-family: var(--font-code); font-size: 0.9rem; }
186
  .code-tools {
187
+ position: absolute; top: 10px; right: 10px; display: flex; gap: 8px;
 
 
188
  opacity: 0; transition: opacity 0.3s ease;
189
  }
190
  pre:hover .code-tools { opacity: 1; }
191
  .code-tools button {
192
+ padding: 6px 10px; font-size: 0.8rem; background: #333;
193
+ color: var(--accent-green); border: 1px solid var(--accent-green);
 
194
  }
195
  .code-tools button:hover { background: var(--accent-green); color: #1e1e1e; }
196
 
 
197
  .input-container {
198
+ display: flex; gap: 12px; padding: 20px;
199
+ background: rgba(30, 30, 30, 0.6); backdrop-filter: blur(10px);
200
+ border-top: 1px solid var(--glass-border); align-items: center;
 
 
 
201
  }
 
202
  #userInput {
203
+ flex: 1; padding: 16px 20px; border: 1px solid var(--glass-border);
204
+ border-radius: 10px; background: var(--bg-secondary);
205
+ color: var(--text-primary); resize: none; min-height: 58px;
206
+ max-height: 150px; font-size: 1rem; transition: all 0.3s ease;
 
 
 
 
 
 
 
 
207
  }
208
  #userInput:focus {
209
+ outline: none; border-color: var(--accent-primary);
210
+ box-shadow: 0 0 20px var(--accent-primary);
 
211
  }
212
 
213
  .input-btn {
214
+ display: flex; align-items: center; justify-content: center;
215
+ width: 58px; height: 58px; border-radius: 50%; /* Round buttons */
216
+ background: var(--bg-tertiary); color: var(--accent-primary);
217
+ transition: all 0.3s ease; padding: 0;
218
+ border: 1px solid var(--accent-primary);
 
 
 
219
  }
220
  .input-btn:hover {
221
+ background: var(--accent-primary); color: var(--bg-primary);
222
+ transform: scale(1.05); box-shadow: 0 0 15px var(--accent-primary);
 
223
  }
224
  .input-btn:disabled {
225
+ background: var(--bg-tertiary); cursor: not-allowed;
226
+ transform: none; box-shadow: none; opacity: 0.5;
227
+ border-color: #555; color: #555;
228
  }
229
  .spinner {
230
+ width: 26px; height: 26px; border: 3px solid rgba(51, 255, 153, 0.3);
231
+ border-top: 3px solid var(--accent-primary); border-radius: 50%;
 
 
232
  animation: spin 1s linear infinite;
233
  }
234
  @keyframes spin { to { transform: rotate(360deg); } }
235
 
236
+ .mode-toggle {
237
+ display: flex; background: var(--bg-secondary);
238
+ border-radius: 15px; padding: 4px; position: relative;
239
+ margin: 15px 20px; border: 1px solid var(--glass-border);
240
+ }
241
+ .mode-toggle button {
242
+ flex: 1; padding: 10px 18px; background: none; border: none;
243
+ color: var(--text-secondary); cursor: pointer; z-index: 1;
244
+ font-size: 0.9rem; font-weight: 600; border-radius: 12px;
245
+ transition: color 0.4s ease; box-shadow: none;
246
+ }
247
+ .mode-toggle button.active { color: var(--text-primary); }
248
+ .mode-toggle-slider {
249
+ position: absolute; top: 4px; left: 4px; height: calc(100% - 8px);
250
+ background: var(--gradient-primary); border-radius: 12px;
251
+ transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1);
252
+ box-shadow: 0 4px 15px rgba(51, 255, 153, 0.3);
253
+ width: calc(50% - 4px); /* Corrected width */
254
+ }
255
+
256
+ /* --- Carousel (Original Layout) --- */
257
  .carousel-container {
258
+ perspective: 1200px; height: 100%; display: flex; flex-direction: column;
259
+ align-items: center; justify-content: center; position: relative;
260
+ user-select: none; padding: 24px; flex: 1; /* Ensure it fills space */
 
 
 
 
 
 
 
 
261
  }
262
  .carousel {
263
+ position: relative; width: 90%; max-width: 500px; height: 300px;
 
 
 
264
  transform-style: preserve-3d;
265
+ transition: transform 0.6s cubic-bezier(0.23, 1, 0.32, 1);
266
  cursor: grab;
267
  }
268
  .carousel.dragging { cursor: grabbing; }
269
  .carousel-card {
270
+ position: absolute; width: 100%; height: 100%;
271
+ background: var(--bg-secondary); backdrop-filter: blur(10px);
272
+ border: 1px solid var(--glass-border); border-radius: 16px;
273
+ padding: 24px; box-shadow: 0 10px 25px rgba(0,0,0,0.4);
274
+ display: flex; flex-direction: column; gap: 12px;
275
+ transition: all 0.6s cubic-bezier(0.23, 1, 0.32, 1);
 
 
 
 
 
276
  backface-visibility: hidden;
277
  }
278
+ .carousel-controls, .carousel-indicator { display: flex; gap: 16px; margin-top: 24px; }
 
 
 
 
279
  .carousel-controls button {
280
+ padding: 10px; width: 50px; height: 50px; border-radius: 50%;
281
+ display: flex; align-items: center; justify-content: center;
282
+ background: var(--bg-tertiary); color: var(--accent-primary);
283
+ border: 1px solid var(--accent-primary);
284
  }
285
+ .carousel-controls button:hover { background: var(--accent-primary); color: var(--bg-primary); }
286
  .carousel-dot {
287
+ width: 14px; height: 14px; background: var(--bg-tertiary);
288
+ border-radius: 50%; cursor: pointer; transition: all 0.3s ease;
289
+ border: 2px solid transparent;
290
+ }
291
+ .carousel-dot.active { background: var(--accent-primary); transform: scale(1.2); }
292
+ .carousel-dot:hover { border-color: var(--accent-primary); }
293
+ #applyPromptBtn { margin-top: 24px; }
294
+
295
+ .chip-container {
296
+ display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap;
297
+ padding: 0 24px;
298
+ }
299
+ .chip {
300
+ padding: 10px 18px; background: var(--bg-tertiary);
301
+ border: 1px solid var(--glass-border); border-radius: 20px;
302
+ font-size: 0.85rem; font-weight: 500; cursor: pointer;
303
+ transition: all 0.3s ease; color: var(--text-secondary);
304
+ }
305
+ .chip:hover {
306
+ background: var(--accent-orange); color: var(--bg-primary);
307
+ transform: translateY(-2px); box-shadow: 0 6px 15px var(--shadow-color-orange);
308
  }
 
309
 
 
310
  .floating-buttons {
311
+ position: absolute; top: 20px; right: 20px; display: flex;
312
+ gap: 12px; z-index: 100;
 
 
 
 
 
313
  }
314
  .floating-btn {
315
+ width: 48px; height: 48px; border-radius: 50%;
316
+ background: var(--bg-secondary); backdrop-filter: blur(10px);
317
+ border: 1px solid var(--glass-border); display: flex;
318
+ align-items: center; justify-content: center;
319
+ transition: all 0.3s ease; padding: 0; color: var(--text-primary);
 
 
 
 
 
 
320
  }
321
  .floating-btn:hover {
322
+ background: var(--accent-green); color: var(--bg-primary);
323
+ transform: scale(1.1); box-shadow: 0 0 15px var(--accent-green);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  }
325
+
326
+ #chatMode { display: none; flex: 1; flex-direction: column; }
327
+ #carouselMode { display: flex; flex: 1; flex-direction: column; }
328
+
329
  @media (max-width: 768px) {
330
+ .app-container { flex-direction: column; height: 100vh; border-radius: 0; }
331
+ .settings-panel { width: 100%; max-height: 40vh; border-right: none; border-bottom: 1px solid var(--glass-border); }
332
+ .settings-panel.collapsed { max-height: 0; }
333
+ .carousel { height: 250px; }
334
+ .floating-buttons { top: 15px; right: 15px; }
 
335
  }
336
 
337
  </style>
338
  </head>
339
  <body>
340
  <div class="app-container" id="appContainer">
341
+ <div class="settings-panel" id="settingsPanel">
 
342
  <h2>⚙️ ตั้งค่า</h2>
343
  <div class="form-field">
344
  <label>🤖 โมเดลหลัก:</label>
 
347
  <option value="Qwen/Qwen2-VL-72B-Instruct">Qwen2-VL 72B (Image)</option>
348
  <option value="microsoft/Florence-2-large">Florence-2 Large (Image)</option>
349
  </select>
350
+ <p style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 8px;">
351
+ 💡 เลือกโมเดล (VL/Florence สำหรับรูปภาพ)
352
+ </p>
353
  </div>
354
  <div class="form-field">
355
  <label>👁️ โมเดล OCR:</label>
 
359
  </select>
360
  </div>
361
  <button id="saveSettingsBtn">💾 บันทึก</button>
 
 
 
362
  </div>
363
 
364
+ <div class="main-content">
365
+ <div class="floating-buttons">
366
+ <button id="settingsToggle" class="floating-btn"><span class="material-icons">settings</span></button>
367
+ <button id="fullscreenToggle" class="floating-btn"><span class="material-icons">fullscreen</span></button>
368
+ <button id="refreshBtn" class="floating-btn"><span class="material-icons">refresh</span></button>
369
  </div>
370
+
371
+ <div class="mode-toggle">
372
+ <button id="carouselModeBtn" class="active">🎛️ Prompt Editor</button>
373
+ <button id="chatModeBtn">💬 แชท</button>
374
+ <div class="mode-toggle-slider" id="modeToggleSlider"></div>
 
375
  </div>
 
 
376
 
377
+ <div id="carouselMode">
378
+ <div class="carousel-container">
379
+ <div class="carousel" id="promptCarousel">
380
+ </div>
381
+ <div class="carousel-controls">
382
+ <button id="prevCard"><span class="material-icons">chevron_left</span></button>
383
+ <button id="nextCard"><span class="material-icons">chevron_right</span></button>
384
  </div>
385
+ <div class="carousel-indicator" id="carouselIndicator">
386
+ </div>
387
+ <button id="applyPromptBtn">✅ ใช้ Prompt & ไปที่แชท</button>
388
  </div>
 
 
389
  </div>
 
 
 
 
 
 
 
 
 
 
 
390
 
391
+ <div id="chatMode">
392
+ <div class="chip-container" id="quickPrompts">
393
+ <span class="chip" data-prompt="อธิบายโค้ดนี้">🔍 อธิบาย</span>
394
+ <span class="chip" data-prompt="สร้างฟังก์ชัน">⚡ สร้าง</span>
395
+ <span class="chip" data-prompt="แก้บั๊ก">🐛 แก้บั๊ก</span>
396
+ <span class="chip" data-prompt="ปรับปรุง">🚀 ปรับปรุง</span>
397
+ </div>
398
+ <div class="chat-container" id="chatContainer">
399
+ <div class="message ai">สวัสดี! กลับมาแล้ว พร้อมช่วยเหลือคุณ.</div>
400
+ </div>
401
+ <div class="input-container">
402
+ <button id="attachButton" class="input-btn"><span class="material-icons">attach_file</span></button>
403
+ <input type="file" id="fileInput" multiple accept="image/*" style="display: none;">
404
+ <span id="fileName" style="font-size: 0.8rem; color: var(--accent-green); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; max-width: 100px;"></span>
405
+ <textarea id="userInput" placeholder="พิมพ์ข้อความ..."></textarea>
406
+ <button id="sendButton" class="input-btn"><span class="material-icons">send</span></button>
407
+ </div>
408
+ </div>
409
+ </div>
410
  </div>
411
 
412
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
 
417
  let attachedFiles = [];
418
  let currentCarouselIndex = 0;
419
  let isLoading = false;
420
+ let carouselElement;
 
 
 
 
421
 
422
  const prompts = [
423
+ { id: 'primarySystemPrompt', label: '🎯 Primary System Prompt', value: 'You are a powerful AI assistant that excels at understanding code, images, and technical content. Provide clear, accurate, and helpful responses. Focus on practical solutions and detailed explanations.' },
424
+ { id: 'ocrSystemPrompt', label: '👁️ OCR System Prompt', value: 'Extract all text from the provided image clearly and accurately. Preserve formatting, structure, and layout when possible. If text is unclear, indicate uncertain parts.' },
425
+ { id: 'codeTemplate', label: '💻 Code Template', value: '```javascript\n// Enhanced code implementation\nfunction solution() {\n // Your optimized code here\n return result;\n}\n```' },
426
+ { id: 'additionalInstructions', label: '📋 Additional Instructions', value: 'Always provide working, tested code examples. Include error handling and optimization suggestions. Explain complex concepts step by step. Use modern best practices.' },
427
+ { id: 'promptPrefix', label: '🚀 Prompt Prefix', value: 'Please analyze the following request carefully and provide a comprehensive solution:' },
428
+ { id: 'promptSuffix', label: '✨ Prompt Suffix', value: 'Ensure your response is complete, accurate, and includes practical examples where applicable.' }
429
  ];
430
 
431
  document.addEventListener('DOMContentLoaded', () => {
432
+ carouselElement = document.getElementById('promptCarousel');
 
433
  populateCarousel();
434
+ loadSettings();
435
  setupEventListeners();
436
+ updateCarouselPosition();
437
+ switchMode('carousel'); // Start with carousel
 
 
438
  });
439
 
440
  function populateCarousel() {
 
442
  const indicator = document.getElementById('carouselIndicator');
443
  carousel.innerHTML = '';
444
  indicator.innerHTML = '';
445
+ const totalCards = prompts.length;
446
+ const anglePerCard = 360 / totalCards;
447
+ const cardWidth = 500; // Based on max-width
448
+ const translateZ = (cardWidth / 2) / Math.tan((anglePerCard / 2) * (Math.PI / 180));
449
 
450
  prompts.forEach((prompt, index) => {
451
  const card = document.createElement('div');
452
  card.className = 'carousel-card';
453
+ const angle = index * anglePerCard;
454
+ card.style.transform = `rotateY(${angle}deg) translateZ(${translateZ}px)`;
455
  card.innerHTML = `
456
  <label>${prompt.label}</label>
457
  <textarea id="${prompt.id}" placeholder="Enter prompt...">${localStorage.getItem(prompt.id) || prompt.value}</textarea>
 
463
  dot.dataset.index = index;
464
  indicator.appendChild(dot);
465
  });
466
+ carousel.dataset.translateZ = translateZ; // Store for later use
467
  }
468
 
469
  function loadSettings() {
470
  document.getElementById('modelSelect').value = localStorage.getItem('selectedModel') || 'Qwen/Qwen2.5-Coder-32B-Instruct';
471
  document.getElementById('ocrModelSelect').value = localStorage.getItem('selectedOcrModel') || 'none';
472
+ // Values are loaded during populateCarousel now
 
 
 
473
  }
474
 
475
  function saveSettings() {
476
  localStorage.setItem('selectedModel', document.getElementById('modelSelect').value);
477
  localStorage.setItem('selectedOcrModel', document.getElementById('ocrModelSelect').value);
478
  prompts.forEach(p => localStorage.setItem(p.id, document.getElementById(p.id).value));
479
+ showNotification('บันทึกแล้ว!', 'success');
480
  }
481
 
482
  function setupEventListeners() {
483
+ document.getElementById('carouselModeBtn').addEventListener('click', () => switchMode('carousel'));
484
+ document.getElementById('chatModeBtn').addEventListener('click', () => switchMode('chat'));
485
  document.getElementById('saveSettingsBtn').addEventListener('click', saveSettings);
486
+ document.getElementById('settingsToggle').addEventListener('click', () => document.getElementById('settingsPanel').classList.toggle('collapsed'));
 
487
  document.getElementById('prevCard').addEventListener('click', () => rotateCarousel(-1));
488
  document.getElementById('nextCard').addEventListener('click', () => rotateCarousel(1));
489
+ document.getElementById('applyPromptBtn').addEventListener('click', applyPromptsAndSwitchToChat);
490
  document.getElementById('sendButton').addEventListener('click', sendMessage);
491
  document.getElementById('attachButton').addEventListener('click', () => document.getElementById('fileInput').click());
492
  document.getElementById('fileInput').addEventListener('change', handleFileSelect);
493
  document.getElementById('userInput').addEventListener('keydown', handleKeyDown);
 
494
  document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
495
+ document.getElementById('refreshBtn').addEventListener('click', () => location.reload());
496
 
497
+ document.querySelectorAll('.chip').forEach(chip => {
498
+ chip.addEventListener('click', (e) => {
499
+ document.getElementById('userInput').value = e.target.getAttribute('data-prompt');
500
+ document.getElementById('userInput').focus();
501
+ });
502
+ });
503
  document.querySelectorAll('.carousel-dot').forEach(dot => {
504
  dot.addEventListener('click', (e) => goToCarouselIndex(parseInt(e.target.dataset.index)));
505
  });
506
 
507
+ // Basic Touch Swipe for Carousel
508
+ let touchStartX = 0;
509
+ let touchEndX = 0;
510
+ carouselElement.addEventListener('touchstart', e => { touchStartX = e.changedTouches[0].screenX; }, { passive: true });
511
+ carouselElement.addEventListener('touchend', e => {
512
+ touchEndX = e.changedTouches[0].screenX;
513
+ handleSwipe();
514
+ }, { passive: true });
515
+
516
+ function handleSwipe() {
517
+ const swipeThreshold = 50; // Minimum distance for a swipe
518
+ if (touchEndX < touchStartX - swipeThreshold) rotateCarousel(1); // Swipe left -> Next
519
+ if (touchEndX > touchStartX + swipeThreshold) rotateCarousel(-1); // Swipe right -> Prev
 
 
 
 
 
520
  }
521
  }
522
 
523
+ function switchMode(mode) {
524
+ const carouselMode = document.getElementById('carouselMode');
525
+ const chatMode = document.getElementById('chatMode');
526
+ const carouselBtn = document.getElementById('carouselModeBtn');
527
+ const chatBtn = document.getElementById('chatModeBtn');
528
+ const slider = document.getElementById('modeToggleSlider');
529
+ const sliderWidth = carouselBtn.offsetWidth; // Get width based on button
530
+
531
+ if (mode === 'carousel') {
532
+ carouselMode.style.display = 'flex';
533
+ chatMode.style.display = 'none';
534
+ carouselBtn.classList.add('active');
535
+ chatBtn.classList.remove('active');
536
+ slider.style.width = `${sliderWidth}px`;
537
+ slider.style.left = '4px';
 
 
 
538
  } else {
539
+ carouselMode.style.display = 'none';
540
+ chatMode.style.display = 'flex';
541
+ carouselBtn.classList.remove('active');
542
+ chatBtn.classList.add('active');
543
+ slider.style.width = `${chatBtn.offsetWidth}px`;
544
+ slider.style.left = `${carouselBtn.offsetLeft + carouselBtn.offsetWidth - chatBtn.offsetWidth + 4}px`;
545
  }
546
  }
547
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  function rotateCarousel(direction) {
549
  const totalCards = prompts.length;
550
  currentCarouselIndex = (currentCarouselIndex + direction + totalCards) % totalCards;
 
556
  updateCarouselPosition();
557
  }
558
 
559
+ function updateCarouselPosition() {
560
+ const totalCards = prompts.length;
 
 
 
561
  const anglePerCard = 360 / totalCards;
562
+ const translateZ = parseFloat(carouselElement.dataset.translateZ || 350);
563
+ const rotationAngle = -currentCarouselIndex * anglePerCard;
 
 
 
564
 
565
+ carouselElement.style.transform = `translateZ(-${translateZ}px) rotateY(${rotationAngle}deg)`;
 
566
 
567
+ carouselElement.querySelectorAll('.carousel-card').forEach((card, index) => {
568
+ card.style.opacity = index === currentCarouselIndex ? '1' : '0.6';
 
 
569
  });
570
 
571
+ document.querySelectorAll('.carousel-dot').forEach((dot, index) => {
572
  dot.classList.toggle('active', index === currentCarouselIndex);
573
  });
574
  }
575
 
576
+ function applyPromptsAndSwitchToChat() {
 
577
  saveSettings();
578
+ switchMode('chat');
579
  showNotification('ใช้ Prompt แล้ว!', 'success');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
  }
581
 
582
  function handleFileSelect(e) {
583
+ attachedFiles = Array.from(e.target.files);
 
 
 
 
584
  updateFileDisplay();
 
585
  }
586
 
587
  function updateFileDisplay() {
 
598
  }
599
  }
600
 
601
+ async function sendMessage() {
602
  const userInput = document.getElementById('userInput');
603
  const message = userInput.value.trim();
604
  if (!message && attachedFiles.length === 0 || isLoading) return;
 
632
  async function callHuggingFaceAPI(message, files) {
633
  const model = document.getElementById('modelSelect').value;
634
  const ocrModel = document.getElementById('ocrModelSelect').value;
635
+ let fullPrompt = `${localStorage.getItem('primarySystemPrompt')}\n\n${localStorage.getItem('promptPrefix')}\n\nUser: ${message}`;
636
+
637
+ if (files.length > 0 && ocrModel !== 'none' && (model.includes('VL') || model.includes('Florence'))) {
638
+ try {
639
+ const ocrText = await performOCR(files[0], ocrModel);
640
+ fullPrompt += `\n\n[Image Content: ${ocrText}]`;
641
+ } catch (error) {
642
+ fullPrompt += `\n\n[Image attached, OCR failed]`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  }
644
+ } else if (files.length > 0) {
645
+ fullPrompt += `\n\n[Image attached]`;
646
  }
647
 
648
+ fullPrompt += `\n\n${localStorage.getItem('promptSuffix')}\n\nAI:`;
649
 
650
  const payload = {
651
  inputs: fullPrompt,
652
+ parameters: { max_new_tokens: 2048, temperature: 0.6, top_p: 0.9 }
653
  };
654
 
 
 
 
 
 
 
 
 
 
 
 
655
  const response = await fetch(`https://api-inference.huggingface.co/models/${model}`, {
656
  method: 'POST',
657
  headers: { 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}`, 'Content-Type': 'application/json' },
 
662
  const errorBody = await response.text();
663
  throw new Error(`HTTP ${response.status}: ${errorBody}`);
664
  }
 
665
  const data = await response.json();
666
  if (data.error) throw new Error(data.error);
667
  let text = data[0]?.generated_text || 'No response.';
668
+ return text.replace(fullPrompt, '').trim();
669
  }
670
 
671
+ async function performOCR(file, ocrModel) {
672
  const response = await fetch(`https://api-inference.huggingface.co/models/${ocrModel}`, {
673
  method: 'POST',
674
  headers: { 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}` },
 
684
  const chatContainer = document.getElementById('chatContainer');
685
  const messageDiv = document.createElement('div');
686
  messageDiv.className = `message ${sender}`;
687
+ if (isError) messageDiv.style.border = '1px solid var(--error)';
688
 
689
  if (sender === 'ai') {
690
  messageDiv.innerHTML = marked.parse(content);
 
696
  messageDiv.textContent = content;
697
  }
698
  chatContainer.appendChild(messageDiv);
 
699
  }
700
 
701
  function addImageMessage(imageUrl, sender) {
702
  const chatContainer = document.getElementById('chatContainer');
703
  const messageDiv = document.createElement('div');
704
  messageDiv.className = `message ${sender}`;
705
+ messageDiv.innerHTML = `<img src="${imageUrl}" alt="Uploaded" style="max-width: 100%; max-height: 250px; border-radius: 10px;">`;
706
  chatContainer.appendChild(messageDiv);
707
  }
708
 
709
+ function addCodeTools(preElement) {
710
  const toolsDiv = document.createElement('div');
711
  toolsDiv.className = 'code-tools';
712
  const copyBtn = document.createElement('button');
713
+ copyBtn.innerHTML = '<span class="material-icons" style="font-size: 16px;">content_copy</span>';
714
  copyBtn.onclick = () => {
715
  navigator.clipboard.writeText(preElement.querySelector('code').innerText)
716
  .then(() => showNotification('คัดลอกโค้ดแล้ว!', 'success'))
717
  .catch(() => showNotification('คัดลอกไม่สำเร็จ!', 'error'));
718
  };
719
  toolsDiv.appendChild(copyBtn);
 
720
  preElement.appendChild(toolsDiv);
721
  }
722
 
723
+
724
  function setLoading(loading) {
725
  isLoading = loading;
726
  const sendButton = document.getElementById('sendButton');
 
740
  }
741
 
742
  function showNotification(message, type = 'success') {
743
+ // Simple alert for now, can be replaced with a styled div
744
+ console.log(`Notification (${type}): ${message}`);
745
+ // alert(`(${type}) ${message}`);
 
 
746
  }
747
 
748
  function scrollToBottom(element) {