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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +967 -1163
index.html CHANGED
@@ -1,1209 +1,1013 @@
1
  <!DOCTYPE html>
2
  <html lang="th">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AI Chatbot - ปรับปรุงแล้ว</title>
7
- <link rel="stylesheet" href="[https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css](https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/tokyo-night-dark.min.css)">
8
- <link href="[https://fonts.googleapis.com/icon?family=Material+Icons](https://fonts.googleapis.com/icon?family=Material+Icons)" rel="stylesheet">
9
- <style>
10
- :root {
11
- --bg-primary: #1a1a1a; /* สีน้ำตาลเข้ม/ดำ */
12
- --bg-secondary: #2c2c2c; /* สีน้ำตาลเข้ม/ดำ (สว่างขึ้น) */
13
- --bg-tertiary: #3d3d3d; /* สีน้ำตาลเข้ม/ดำ (สว่างสุด) */
14
- --text-primary: #f0e6d2; /* สีครีม/สว่าง */
15
- --text-secondary: #a0937d; /* สีครีม/น้ำตาล (หม่น) */
16
- --accent-primary: #33ff99; /* สีเขียว */
17
- --accent-secondary: #ff9933; /* สีส้ม */
18
- --accent-tertiary: #8b4513; /* สีน้ำตาล */
19
- --chat-bg: #f5f5dc; /* สีครีม */
20
- --chat-text: #333333; /* สีข้อความเข้มสำหรับพื้นหลังครีม */
21
- --error: #ff4757;
22
- --success: #2ed573;
23
- --warning: #ffa502;
24
- --glass-bg: rgba(61, 61, 61, 0.1);
25
- --glass-border: rgba(240, 230, 210, 0.15);
26
- --shadow-lg: 0 20px 40px rgba(0, 0, 0, 0.6);
27
- --shadow-xl: 0 25px 50px rgba(0, 0, 0, 0.8);
28
- --gradient-primary: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
29
- --gradient-secondary: linear-gradient(135deg, var(--accent-secondary), var(--accent-tertiary));
30
- }
31
-
32
- * {
33
- box-sizing: border-box;
34
- margin: 0;
35
- padding: 0;
36
- }
37
-
38
- body {
39
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
40
- background: var(--bg-primary);
41
- background-image:
42
- radial-gradient(ellipse at top left, rgba(51, 255, 153, 0.08) 0%, transparent 60%),
43
- radial-gradient(ellipse at bottom right, rgba(255, 153, 51, 0.08) 0%, transparent 60%);
44
- color: var(--text-primary);
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
- min-height: 100vh;
49
- overflow: hidden;
50
- -webkit-tap-highlight-color: transparent;
51
- }
52
-
53
- .app-container {
54
- width: 95%;
55
- max-width: 1400px;
56
- background: var(--glass-bg);
57
- backdrop-filter: blur(25px);
58
- -webkit-backdrop-filter: blur(25px);
59
- border: 1px solid var(--glass-border);
60
- border-radius: 25px;
61
- box-shadow: var(--shadow-xl);
62
- display: flex;
63
- overflow: hidden;
64
- height: 90vh;
65
- position: relative;
66
- animation: slideIn 0.9s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
67
- opacity: 0;
68
- transform: translateY(40px) scale(0.98);
69
- }
70
-
71
- @keyframes slideIn {
72
- to {
73
- opacity: 1;
74
- transform: translateY(0) scale(1);
75
- }
76
- }
77
-
78
- .settings-panel {
79
- width: 300px;
80
- padding: 30px;
81
- background: rgba(44, 44, 44, 0.7);
82
- backdrop-filter: blur(15px);
83
- -webkit-backdrop-filter: blur(15px);
84
- border-right: 1px solid var(--glass-border);
85
- transition: width 0.5s cubic-bezier(0.165, 0.84, 0.44, 1),
86
- padding 0.5s cubic-bezier(0.165, 0.84, 0.44, 1),
87
- opacity 0.5s cubic-bezier(0.165, 0.84, 0.44, 1);
88
- overflow-y: auto;
89
- scrollbar-width: thin;
90
- scrollbar-color: var(--accent-primary) transparent;
91
- }
92
-
93
- .settings-panel::-webkit-scrollbar { width: 6px; }
94
- .settings-panel::-webkit-scrollbar-track { background: transparent; }
95
- .settings-panel::-webkit-scrollbar-thumb { background: var(--accent-primary); border-radius: 3px; }
96
-
97
- .settings-panel.collapsed {
98
- width: 0;
99
- padding: 0;
100
- overflow: hidden;
101
- border-right: none;
102
- opacity: 0;
103
- }
104
-
105
- .main-content {
106
- flex: 1;
107
- display: flex;
108
- flex-direction: column;
109
- position: relative;
110
- background: transparent; /* Changed */
111
- }
112
-
113
- .form-field {
114
- margin-bottom: 25px;
115
- animation: fadeInUp 0.7s cubic-bezier(0.165, 0.84, 0.44, 1) both;
116
- }
117
-
118
- .form-field label {
119
- display: block;
120
- margin-bottom: 10px;
121
- font-size: 0.9rem;
122
- font-weight: 600;
123
- color: var(--text-secondary);
124
- text-transform: uppercase;
125
- letter-spacing: 0.8px;
126
- }
127
-
128
- .form-field select, .form-field textarea {
129
- width: 100%;
130
- padding: 14px 18px;
131
- border: 1px solid var(--glass-border);
132
- border-radius: 10px;
133
- background: var(--bg-tertiary);
134
- color: var(--text-primary);
135
- font-size: 1rem;
136
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
137
- backdrop-filter: blur(5px);
138
- }
139
-
140
- .form-field select:focus, .form-field textarea:focus {
141
- outline: none;
142
- border-color: var(--accent-primary);
143
- box-shadow: 0 0 25px rgba(51, 255, 153, 0.3);
144
- transform: translateY(-2px);
145
- }
146
-
147
- .form-field textarea { resize: vertical; min-height: 110px; }
148
-
149
- button {
150
- padding: 14px 28px;
151
- border: none;
152
- border-radius: 10px;
153
- background: var(--gradient-primary);
154
- color: #000; /* Changed for better contrast on gradient */
155
- cursor: pointer;
156
- font-size: 1rem;
157
- font-weight: 700;
158
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
159
- touch-action: manipulation;
160
- position: relative;
161
- overflow: hidden;
162
- }
163
-
164
- button::before {
165
- content: '';
166
- position: absolute;
167
- top: 0;
168
- left: -100%;
169
- width: 100%;
170
- height: 100%;
171
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
172
- transition: left 0.6s;
173
- }
174
-
175
- button:hover::before { left: 100%; }
176
- button:hover { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(51, 255, 153, 0.35); }
177
- button:active { transform: translateY(-1px); }
178
-
179
- .material-icons { font-family: 'Material Icons'; font-size: 26px; vertical-align: middle; }
180
-
181
- .chat-container {
182
- flex: 1;
183
- padding: 30px;
184
- overflow-y: auto;
185
- display: flex;
186
- flex-direction: column;
187
- gap: 20px;
188
- scrollbar-width: thin;
189
- scrollbar-color: var(--accent-secondary) transparent;
190
- background: var(--chat-bg); /* Cream Background */
191
- border-radius: 0 0 0 20px; /* Rounded corners */
192
- }
193
-
194
- .chat-container::-webkit-scrollbar { width: 8px; }
195
- .chat-container::-webkit-scrollbar-track { background: transparent; }
196
- .chat-container::-webkit-scrollbar-thumb { background: var(--accent-secondary); border-radius: 4px; }
197
-
198
- .message {
199
- max-width: 85%;
200
- padding: 18px 24px;
201
- border-radius: 22px;
202
- font-size: 1rem;
203
- line-height: 1.65;
204
- position: relative;
205
- animation: messageSlide 0.6s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
206
- opacity: 0;
207
- transform: translateY(25px);
208
- }
209
-
210
- @keyframes messageSlide {
211
- to { opacity: 1; transform: translateY(0); }
212
- }
213
-
214
- .message.user {
215
- background: var(--gradient-secondary);
216
- align-self: flex-end;
217
- box-shadow: 0 10px 25px rgba(255, 153, 51, 0.3);
218
- color: #fff; /* White text for user */
219
- }
220
-
221
- .message.ai {
222
- background: #ffffff; /* White AI messages */
223
- border: 1px solid #e0e0e0;
224
- align-self: flex-start;
225
- box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
226
- color: var(--chat-text); /* Dark text for AI */
227
- }
228
-
229
- .message.ai pre {
230
- background: #0d1117; /* Dark background for code */
231
- color: #c9d1d9; /* Light text for code */
232
- padding: 20px;
233
- border-radius: 10px;
234
- overflow-x: auto;
235
- border: 1px solid #30363d;
236
- margin-top: 15px;
237
- position: relative;
238
- }
239
- .message.ai pre code {
240
- font-family: 'Fira Code', 'Consolas', monospace;
241
- font-size: 0.9rem;
242
- background: none !important; /* Ensure no inner background */
243
- color: inherit !important; /* Ensure text color is inherited */
244
- }
245
-
246
- .input-container {
247
- display: flex;
248
- gap: 15px;
249
- padding: 25px;
250
- background: var(--bg-secondary);
251
- backdrop-filter: blur(10px);
252
- border-top: 1px solid var(--glass-border);
253
- align-items: center;
254
- border-radius: 0 0 20px 0; /* Match app container */
255
- }
256
-
257
- #userInput {
258
- flex: 1;
259
- padding: 18px 22px;
260
- border: 1px solid var(--glass-border);
261
- border-radius: 15px;
262
- background: var(--bg-tertiary);
263
- color: var(--text-primary);
264
- resize: none;
265
- min-height: 60px;
266
- max-height: 160px;
267
- font-size: 1rem;
268
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
269
- }
270
-
271
- #userInput:focus {
272
- outline: none;
273
- border-color: var(--accent-primary);
274
- box-shadow: 0 0 30px rgba(51, 255, 153, 0.35);
275
- transform: translateY(-2px);
276
- }
277
-
278
- #sendButton, #attachButton {
279
- display: flex;
280
- align-items: center;
281
- justify-content: center;
282
- width: 60px;
283
- height: 60px;
284
- border-radius: 50%;
285
- background: var(--gradient-primary);
286
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
287
- padding: 0; /* Reset padding */
288
- border: none; /* Reset border */
289
- }
290
-
291
- #sendButton:hover, #attachButton:hover {
292
- transform: translateY(-4px) scale(1.08);
293
- box-shadow: 0 15px 35px rgba(51, 255, 153, 0.45);
294
- }
295
-
296
- #sendButton:disabled {
297
- background: var(--bg-tertiary);
298
- cursor: not-allowed;
299
- transform: none;
300
- box-shadow: none;
301
- opacity: 0.6;
302
- }
303
-
304
- .spinner {
305
- width: 26px;
306
- height: 26px;
307
- border: 3px solid rgba(255, 255, 255, 0.3);
308
- border-top: 3px solid var(--accent-primary);
309
- border-radius: 50%;
310
- animation: spin 1s linear infinite;
311
- }
312
-
313
- @keyframes spin { to { transform: rotate(360deg); } }
314
-
315
- .notification {
316
- position: fixed;
317
- top: 30px;
318
- right: 30px;
319
- padding: 18px 26px;
320
- border-radius: 15px;
321
- color: #fff;
322
- font-size: 1rem;
323
- font-weight: 600;
324
- z-index: 1000;
325
- display: none;
326
- backdrop-filter: blur(18px);
327
- -webkit-backdrop-filter: blur(18px);
328
- animation: notificationSlide 0.6s cubic-bezier(0.165, 0.84, 0.44, 1) forwards;
329
- opacity: 0;
330
- transform: translateX(100px);
331
- }
332
-
333
- @keyframes notificationSlide {
334
- to { opacity: 1; transform: translateX(0); }
335
- }
336
-
337
- .error-message { background: linear-gradient(135deg, var(--error), #ff6b6b); box-shadow: 0 10px 25px rgba(255, 71, 87, 0.4); }
338
- .success-message { background: linear-gradient(135deg, var(--success), #5af78e); box-shadow: 0 10px 25px rgba(46, 213, 115, 0.4); }
339
-
340
- .carousel-container {
341
- perspective: 1500px;
342
- height: 100%;
343
- display: flex;
344
- flex-direction: column;
345
- align-items: center;
346
- justify-content: center;
347
- position: relative;
348
- user-select: none;
349
- padding: 30px;
350
- }
351
-
352
- .carousel {
353
- position: relative;
354
- width: 90%;
355
- max-width: 550px; /* Increased size */
356
- height: 350px; /* Increased size */
357
- transform-style: preserve-3d;
358
- transition: transform 0.8s cubic-bezier(0.19, 1, 0.22, 1); /* Smoother transition */
359
- cursor: grab;
360
- }
361
-
362
- .carousel-card {
363
- position: absolute;
364
- width: 100%;
365
- height: 100%;
366
- background: var(--bg-secondary); /* Darker cards */
367
- backdrop-filter: blur(15px);
368
- border: 1px solid var(--glass-border);
369
- border-radius: 20px;
370
- padding: 30px;
371
- box-shadow: var(--shadow-lg);
372
- display: flex;
373
- flex-direction: column;
374
- gap: 15px;
375
- transition: all 0.8s cubic-bezier(0.19, 1, 0.22, 1);
376
- backface-visibility: hidden; /* Hide back for smoother rotation */
377
- }
378
-
379
- .carousel-controls, .carousel-indicator {
380
- display: flex;
381
- gap: 20px;
382
- margin-top: 30px;
383
- }
384
-
385
- .carousel-dot {
386
- width: 16px;
387
- height: 16px;
388
- background: var(--bg-tertiary);
389
- border-radius: 50%;
390
- cursor: pointer;
391
- transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
392
- border: 2px solid transparent;
393
- }
394
-
395
- .carousel-dot.active {
396
- background: var(--accent-primary);
397
- box-shadow: 0 0 25px rgba(51, 255, 153, 0.5);
398
- transform: scale(1.3);
399
- }
400
-
401
- .carousel-dot:hover { transform: scale(1.15); border-color: var(--accent-primary); }
402
-
403
- .mode-toggle {
404
- display: flex;
405
- background: var(--bg-secondary);
406
- border-radius: 15px;
407
- padding: 5px;
408
- position: relative;
409
- margin: 20px; /* Added margin */
410
- border: 1px solid var(--glass-border);
411
- }
412
-
413
- .mode-toggle button {
414
- flex: 1;
415
- padding: 14px 22px;
416
- background: none;
417
- border: none;
418
- color: var(--text-secondary);
419
- cursor: pointer;
420
- z-index: 1;
421
- font-size: 1rem;
422
- font-weight: 600;
423
- border-radius: 12px;
424
- transition: color 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
425
- box-shadow: none; /* Remove button shadow */
426
- }
427
-
428
- .mode-toggle button.active { color: #fff; } /* White active text */
429
-
430
- .mode-toggle-slider {
431
- position: absolute;
432
- top: 5px;
433
- left: 5px;
434
- width: calc(50% - 5px); /* Adjusted width */
435
- height: calc(100% - 10px);
436
- background: var(--gradient-primary);
437
- border-radius: 12px;
438
- transition: all 0.5s cubic-bezier(0.19, 1, 0.22, 1);
439
- box-shadow: 0 5px 20px rgba(51, 255, 153, 0.3);
440
- }
441
-
442
- .dropzone {
443
- position: absolute;
444
- top: 0; left: 0; right: 0; bottom: 0;
445
- background: rgba(51, 255, 153, 0.1);
446
- backdrop-filter: blur(15px);
447
- border: 3px dashed var(--accent-primary);
448
- display: none;
449
- align-items: center;
450
- justify-content: center;
451
- color: var(--text-primary);
452
- font-size: 1.5rem;
453
- font-weight: 600;
454
- z-index: 100; /* Ensure it's on top */
455
- border-radius: 20px;
456
- }
457
- .dropzone.active { display: flex; animation: pulse 1.8s infinite; }
458
- @keyframes pulse { 0%, 100% { opacity: 0.7; transform: scale(1); } 50% { opacity: 1; transform: scale(1.02); } }
459
-
460
- .file-name {
461
- font-size: 0.9rem; /* Smaller */
462
- color: var(--accent-primary);
463
- margin-left: 15px;
464
- align-self: center;
465
- max-width: 180px;
466
- white-space: nowrap;
467
- overflow: hidden;
468
- text-overflow: ellipsis;
469
- font-weight: 600;
470
- }
471
-
472
- .chip-container {
473
- display: flex;
474
- gap: 15px;
475
- margin-bottom: 25px;
476
- flex-wrap: wrap;
477
- padding: 0 30px;
478
- }
479
-
480
- .chip {
481
- padding: 12px 20px;
482
- background: var(--bg-tertiary);
483
- border: 1px solid var(--glass-border);
484
- border-radius: 25px;
485
- font-size: 0.9rem;
486
- font-weight: 600;
487
- cursor: pointer;
488
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
489
- color: var(--text-secondary);
490
- }
491
-
492
- .chip:hover {
493
- background: var(--accent-secondary);
494
- color: #fff;
495
- transform: translateY(-3px);
496
- box-shadow: 0 8px 22px rgba(255, 153, 51, 0.4);
497
- border-color: transparent;
498
- }
499
-
500
- .floating-buttons {
501
- position: absolute;
502
- top: 25px;
503
- right: 25px;
504
- display: flex;
505
- gap: 15px;
506
- z-index: 200; /* Ensure above chat */
507
- }
508
-
509
- .floating-btn {
510
- width: 50px;
511
- height: 50px;
512
- border-radius: 50%;
513
- background: var(--bg-secondary);
514
- backdrop-filter: blur(10px);
515
- border: 1px solid var(--glass-border);
516
- display: flex;
517
- align-items: center;
518
- justify-content: center;
519
- transition: all 0.35s cubic-bezier(0.165, 0.84, 0.44, 1);
520
- padding: 0;
521
- color: var(--text-primary);
522
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
523
- }
524
-
525
- .floating-btn:hover {
526
- background: var(--accent-primary);
527
- color: #000;
528
- transform: translateY(-3px) scale(1.1);
529
- box-shadow: 0 10px 30px rgba(51, 255, 153, 0.4);
530
- }
531
-
532
- .code-tools {
533
- position: absolute;
534
- top: 15px;
535
- right: 15px;
536
- display: flex;
537
- gap: 10px;
538
- opacity: 0; /* Hidden by default */
539
- transition: opacity 0.3s ease;
540
- }
541
- pre:hover .code-tools { opacity: 1; } /* Show on hover */
542
-
543
- .code-tools button {
544
- padding: 8px 14px;
545
- font-size: 0.8rem;
546
- background: var(--bg-tertiary);
547
- backdrop-filter: blur(5px);
548
- border: 1px solid var(--glass-border);
549
- color: var(--text-primary);
550
- border-radius: 6px;
551
- box-shadow: none;
552
- }
553
- .code-tools button:hover {
554
- background: var(--accent-primary);
555
- color: #000;
556
- transform: translateY(-2px);
557
- box-shadow: 0 4px 10px rgba(51, 255, 153, 0.3);
558
- }
559
-
560
- .image-preview {
561
- max-width: 100%;
562
- max-height: 350px;
563
- border-radius: 15px;
564
- margin-top: 15px;
565
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
566
- border: 1px solid #ddd;
567
- }
568
-
569
- @keyframes fadeInUp {
570
- from { opacity: 0; transform: translateY(35px); }
571
- to { opacity: 1; transform: translateY(0); }
572
- }
573
-
574
- @media (max-width: 768px) {
575
- .app-container { flex-direction: column; height: 100vh; border-radius: 0; width: 100%; }
576
- .settings-panel { width: 100%; max-height: 35vh; border-right: none; border-bottom: 1px solid var(--glass-border); }
577
- .settings-panel.collapsed { max-height: 0; }
578
- .chat-container { max-height: 55vh; padding: 20px; border-radius: 0; }
579
- .input-container { padding: 20px; border-radius: 0; }
580
- #userInput { margin-bottom: 0; }
581
- .floating-buttons { top: 15px; right: 15px; }
582
- .floating-btn { width: 45px; height: 45px; }
583
- .material-icons { font-size: 24px; }
584
- }
585
- @media (max-width: 480px) {
586
- .chip { font-size: 0.85rem; padding: 10px 16px; }
587
- .form-field select, .form-field textarea, #userInput { font-size: 1rem; }
588
- .message { padding: 15px 20px; }
589
- }
590
- </style>
591
  </head>
592
  <body>
593
- <div class="app-container" id="appContainer">
594
- <div class="settings-panel" id="settingsPanel">
595
- <h2 style="font-size: 1.4rem; margin-bottom: 30px; background: var(--gradient-primary); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">⚙️ ตั้งค่า</h2>
596
- <div class="form-field">
597
- <label>🤖 โมเดลหลัก:</label>
598
- <select id="modelSelect">
599
- <option value="Qwen/Qwen2.5-Coder-32B-Instruct">Qwen2.5-Coder 32B (โค้ด/ข้อความ)</option>
600
- <option value="Qwen/Qwen2-VL-72B-Instruct">Qwen2-VL 72B (ข้อความ/รูปภาพ)</option>
601
- <option value="microsoft/Florence-2-large">Florence-2 Large (ข้อความ/รูปภาพ)</option>
602
- </select>
603
- <p style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 10px;">
604
- 💡 เลือกโมเดลที่ต้องการ ใช้ VL หรือ Florence สำหรับรูปภาพ
605
- </p>
606
- </div>
607
- <div class="form-field">
608
- <label>👁️ โมเดล OCR (สำหรับรูปภาพ):</label>
609
- <select id="ocrModelSelect">
610
- <option value="none">ไม่ใช้</option>
611
- <option value="microsoft/trocr-base-printed">TrOCR Base (แนะนำ)</option>
612
- </select>
613
- </div>
614
- <button id="saveSettingsBtn">💾 บันทึกการตั้งค่า</button>
615
- </div>
616
-
617
- <div class="main-content">
618
- <div class="floating-buttons">
619
- <button id="settingsToggle" class="floating-btn">
620
- <span class="material-icons">settings</span>
621
- </button>
622
- <button id="fullscreenToggle" class="floating-btn">
623
- <span class="material-icons">fullscreen</span>
624
- </button>
625
- <button id="refreshBtn" class="floating-btn">
626
- <span class="material-icons">refresh</span>
627
- </button>
628
- </div>
629
-
630
- <div class="mode-toggle">
631
- <button id="carouselModeBtn" class="active">🎛️ แก้ไข Prompt</button>
632
- <button id="chatModeBtn">💬 แชท</button>
633
- <div class="mode-toggle-slider" id="modeToggleSlider"></div>
634
- </div>
635
-
636
- <div id="carouselMode">
637
- <div class="carousel-container">
638
- <div class="carousel" id="promptCarousel">
639
- <div class="carousel-card" style="transform: rotateY(0deg) translateZ(350px);">
640
- <label>🎯 Primary System Prompt:</label>
641
- <textarea id="primarySystemPrompt" placeholder="ป้อน Prompt หลักสำหรับ AI...">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.</textarea>
642
- </div>
643
- <div class="carousel-card" style="transform: rotateY(60deg) translateZ(350px); opacity: 0.7;">
644
- <label>👁️ OCR System Prompt:</label>
645
- <textarea id="ocrSystemPrompt" placeholder="ป้อน Prompt สำหรับ OCR...">Extract all text from the provided image clearly and accurately. Preserve formatting, structure, and layout when possible. If text is unclear, indicate uncertain parts.</textarea>
646
- </div>
647
- <div class="carousel-card" style="transform: rotateY(120deg) translateZ(350px); opacity: 0.7;">
648
- <label>💻 Code Template:</label>
649
- <textarea id="codeTemplate" placeholder="ป้อน Template สำหรับโค้ด...">```javascript
650
- // Enhanced code implementation
651
- function solution() {
652
- // Your optimized code here
653
- return result;
654
- }
655
- ```</textarea>
656
  </div>
657
- <div class="carousel-card" style="transform: rotateY(180deg) translateZ(350px); opacity: 0.7;">
658
- <label>📋 Additional Instructions:</label>
659
- <textarea id="additionalInstructions" placeholder="ป้อนคำแนะนำเพิ่มเติม...">Always provide working, tested code examples. Include error handling and optimization suggestions. Explain complex concepts step by step. Use modern best practices.</textarea>
 
 
 
660
  </div>
661
- <div class="carousel-card" style="transform: rotateY(240deg) translateZ(350px); opacity: 0.7;">
662
- <label>🚀 Prompt Prefix:</label>
663
- <textarea id="promptPrefix" placeholder="ข้อความนำหน้า Prompt...">Please analyze the following request carefully and provide a comprehensive solution:</textarea>
 
 
 
 
 
 
664
  </div>
665
- <div class="carousel-card" style="transform: rotateY(300deg) translateZ(350px); opacity: 0.7;">
666
- <label>✨ Prompt Suffix:</label>
667
- <textarea id="promptSuffix" placeholder="ข้อความต่อท้าย Prompt...">Ensure your response is complete, accurate, and includes practical examples where applicable.</textarea>
 
 
 
668
  </div>
669
- </div>
670
- <div class="carousel-controls">
671
- <button id="prevCard"><span class="material-icons">chevron_left</span></button>
672
- <button id="nextCard"><span class="material-icons">chevron_right</span></button>
673
- </div>
674
- <div class="carousel-indicator" id="carouselIndicator">
675
- <div class="carousel-dot active" data-index="0"></div>
676
- <div class="carousel-dot" data-index="1"></div>
677
- <div class="carousel-dot" data-index="2"></div>
678
- <div class="carousel-dot" data-index="3"></div>
679
- <div class="carousel-dot" data-index="4"></div>
680
- <div class="carousel-dot" data-index="5"></div>
681
- </div>
682
- <button id="applyPromptBtn" style="margin-top: 30px;">✅ ใช้ Prompt & ไปที่แชท</button>
683
  </div>
684
- </div>
685
-
686
- <div id="chatMode" style="display: none; flex: 1; display: flex; flex-direction: column;">
687
- <div class="chip-container" id="quickPrompts">
688
- <span class="chip" data-prompt="อธิบายโค้ดนี้">🔍 อธิบายโค้ด</span>
689
- <span class="chip" data-prompt="สร้างฟังก์ชัน">⚡ สร้างฟังก์ชัน</span>
690
- <span class="chip" data-prompt="แก้บั๊กโค้ดนี้">🐛 แก้บั๊ก</span>
691
- <span class="chip" data-prompt="ปรับปรุงประสิทธิภาพ">🚀 ปรับปรุง</span>
692
- <span class="chip" data-prompt="เพิ่ม Error Handling">🛡️ จัดการข้อผิดพลาด</span>
693
- <span class="chip" data-prompt="แปลงเป็น Syntax ใหม่">✨ ทำให้ทันสมัย</span>
694
- </div>
695
- <div class="chat-container" id="chatContainer">
696
  </div>
697
- <div class="input-container">
698
- <button id="attachButton"><span class="material-icons">attach_file</span></button>
699
- <input type="file" id="fileInput" multiple accept="image/*" style="display: none;">
700
- <span class="file-name" id="fileName"></span>
701
- <textarea id="userInput" placeholder="พิมพ์ข้อความที่นี่... (Shift+Enter เพื่อขึ้นบรรทัดใหม่)"></textarea>
702
- <button id="sendButton"><span class="material-icons">send</span></button>
703
  </div>
704
- <div class="dropzone" id="dropzone">📁 วางไฟล์ที่นี่เพื่อแนบ</div>
705
- </div>
706
-
707
- <div class="notification error-message" id="errorMessage">
708
- <span id="errorText"></span>
709
- </div>
710
- <div class="notification success-message" id="successMessage">
711
- <span id="successText"></span>
712
- </div>
 
713
  </div>
714
- </div>
715
-
716
- <script src="[https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js](https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js)"></script>
717
- <script src="[https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js](https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js)"></script>
718
- <script>
719
- // Global variables
720
- let attachedFiles = [];
721
- let currentCarouselIndex = 0;
722
- let isLoading = false;
723
- const HUGGINGFACE_TOKEN = "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"; // <<< !!! ใส่ Hugging Face Token ของคุณที่นี่ !!!
724
-
725
- // Initialize the application
726
- document.addEventListener('DOMContentLoaded', function() {
727
- loadSettings();
728
- setupEventListeners();
729
- setupCarousel();
730
- setupDropzone();
731
- switchMode('carousel'); // Start in carousel mode
732
- updateCarouselPosition(); // Ensure initial position is set
733
- });
734
-
735
- // Settings management
736
- function loadSettings() {
737
- const settings = {
738
- model: localStorage.getItem('selectedModel') || 'Qwen/Qwen2.5-Coder-32B-Instruct',
739
- ocrModel: localStorage.getItem('selectedOcrModel') || 'none',
740
- primarySystemPrompt: localStorage.getItem('primarySystemPrompt') || '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.',
741
- ocrSystemPrompt: localStorage.getItem('ocrSystemPrompt') || 'Extract all text from the provided image clearly and accurately. Preserve formatting, structure, and layout when possible. If text is unclear, indicate uncertain parts.',
742
- codeTemplate: localStorage.getItem('codeTemplate') || '```javascript\n// Enhanced code implementation\nfunction solution() {\n // Your optimized code here\n return result;\n}\n```',
743
- additionalInstructions: localStorage.getItem('additionalInstructions') || 'Always provide working, tested code examples. Include error handling and optimization suggestions. Explain complex concepts step by step. Use modern best practices.',
744
- promptPrefix: localStorage.getItem('promptPrefix') || 'Please analyze the following request carefully and provide a comprehensive solution:',
745
- promptSuffix: localStorage.getItem('promptSuffix') || 'Ensure your response is complete, accurate, and includes practical examples where applicable.'
746
- };
747
-
748
- document.getElementById('modelSelect').value = settings.model;
749
- document.getElementById('ocrModelSelect').value = settings.ocrModel;
750
- document.getElementById('primarySystemPrompt').value = settings.primarySystemPrompt;
751
- document.getElementById('ocrSystemPrompt').value = settings.ocrSystemPrompt;
752
- document.getElementById('codeTemplate').value = settings.codeTemplate;
753
- document.getElementById('additionalInstructions').value = settings.additionalInstructions;
754
- document.getElementById('promptPrefix').value = settings.promptPrefix;
755
- document.getElementById('promptSuffix').value = settings.promptSuffix;
756
- }
757
-
758
- function saveSettings() {
759
- localStorage.setItem('selectedModel', document.getElementById('modelSelect').value);
760
- localStorage.setItem('selectedOcrModel', document.getElementById('ocrModelSelect').value);
761
- localStorage.setItem('primarySystemPrompt', document.getElementById('primarySystemPrompt').value);
762
- localStorage.setItem('ocrSystemPrompt', document.getElementById('ocrSystemPrompt').value);
763
- localStorage.setItem('codeTemplate', document.getElementById('codeTemplate').value);
764
- localStorage.setItem('additionalInstructions', document.getElementById('additionalInstructions').value);
765
- localStorage.setItem('promptPrefix', document.getElementById('promptPrefix').value);
766
- localStorage.setItem('promptSuffix', document.getElementById('promptSuffix').value);
767
- showNotification('บันทึกการตั้งค่าแล้ว!', 'success');
768
- }
769
-
770
- // Event listeners setup
771
- function setupEventListeners() {
772
- // Mode toggle
773
- document.getElementById('carouselModeBtn').addEventListener('click', () => switchMode('carousel'));
774
- document.getElementById('chatModeBtn').addEventListener('click', () => switchMode('chat'));
775
- // Settings
776
- document.getElementById('saveSettingsBtn').addEventListener('click', saveSettings);
777
- document.getElementById('settingsToggle').addEventListener('click', toggleSettings);
778
- // Carousel controls - Using 'click' for better cross-device compatibility
779
- document.getElementById('prevCard').addEventListener('click', () => rotateCarousel(-1));
780
- document.getElementById('nextCard').addEventListener('click', () => rotateCarousel(1));
781
- document.getElementById('applyPromptBtn').addEventListener('click', applyPromptsAndSwitchToChat);
782
- // Chat controls
783
- document.getElementById('sendButton').addEventListener('click', sendMessage);
784
- document.getElementById('attachButton').addEventListener('click', () => document.getElementById('fileInput').click());
785
- document.getElementById('fileInput').addEventListener('change', handleFileSelect);
786
- document.getElementById('userInput').addEventListener('keydown', handleKeyDown);
787
- // Other controls
788
- document.getElementById('fullscreenToggle').addEventListener('click', toggleFullscreen);
789
- document.getElementById('refreshBtn').addEventListener('click', refreshApp);
790
- // Quick prompts
791
- document.querySelectorAll('.chip').forEach(chip => {
792
- chip.addEventListener('click', (e) => {
793
- const prompt = e.target.getAttribute('data-prompt');
794
- document.getElementById('userInput').value = prompt;
795
- document.getElementById('userInput').focus();
796
- });
797
- });
798
- // Carousel dots
799
- document.querySelectorAll('.carousel-dot').forEach(dot => {
800
- dot.addEventListener('click', (e) => {
801
- const index = parseInt(e.target.getAttribute('data-index'));
802
- goToCarouselIndex(index);
803
- });
804
- });
805
- }
806
-
807
- // Mode switching
808
- function switchMode(mode) {
809
- const carouselMode = document.getElementById('carouselMode');
810
- const chatMode = document.getElementById('chatMode');
811
- const carouselBtn = document.getElementById('carouselModeBtn');
812
- const chatBtn = document.getElementById('chatModeBtn');
813
- const slider = document.getElementById('modeToggleSlider');
814
-
815
- if (mode === 'carousel') {
816
- carouselMode.style.display = 'flex'; // Use flex for centering
817
- chatMode.style.display = 'none';
818
- carouselBtn.classList.add('active');
819
- chatBtn.classList.remove('active');
820
- slider.style.left = '5px';
821
- slider.style.width = 'calc(50% - 5px)';
822
- } else {
823
- carouselMode.style.display = 'none';
824
- chatMode.style.display = 'flex'; // Use flex for layout
825
- carouselBtn.classList.remove('active');
826
- chatBtn.classList.add('active');
827
- slider.style.left = 'calc(50% + 0px)'; // Adjusted position
828
- slider.style.width = 'calc(50% - 5px)';
829
- }
830
- }
831
-
832
- // Carousel functionality
833
- function setupCarousel() {
834
- const carousel = document.getElementById('promptCarousel');
835
- const cards = carousel.querySelectorAll('.carousel-card');
836
- const cardWidth = cards[0].offsetWidth; // Get card width
837
- const totalCards = cards.length;
838
- const angle = 360 / totalCards;
839
- const translateZ = (cardWidth / 2) / Math.tan( (angle / 2) * (Math.PI / 180) ); // Calculate translateZ dynamically
840
-
841
- cards.forEach((card, index) => {
842
- const cardAngle = index * angle;
843
- card.style.transform = `rotateY(${cardAngle}deg) translateZ(${translateZ}px)`;
844
- });
845
-
846
- // Store translateZ for rotation
847
- carousel.dataset.translateZ = translateZ;
848
- updateCarouselPosition();
849
- }
850
-
851
-
852
- function rotateCarousel(direction) {
853
- const totalCards = 6;
854
- currentCarouselIndex = (currentCarouselIndex + direction + totalCards) % totalCards;
855
- updateCarouselPosition();
856
- }
857
-
858
- function goToCarouselIndex(index) {
859
- currentCarouselIndex = index;
860
- updateCarouselPosition();
861
- }
862
-
863
- function updateCarouselPosition() {
864
- const carousel = document.getElementById('promptCarousel');
865
- const cards = carousel.querySelectorAll('.carousel-card');
866
- const dots = document.querySelectorAll('.carousel-dot');
867
- const totalCards = cards.length;
868
- const anglePerCard = 360 / totalCards;
869
- const rotationAngle = -currentCarouselIndex * anglePerCard;
870
- const translateZ = carousel.dataset.translateZ || 350; // Use stored or default
871
-
872
- carousel.style.transform = `translateZ(-${translateZ}px) rotateY(${rotationAngle}deg)`;
873
-
874
- cards.forEach((card, index) => {
875
- const isActive = index === currentCarouselIndex;
876
- card.style.opacity = isActive ? '1' : '0.6';
877
- card.style.cursor = isActive ? 'default' : 'pointer'; // Make non-active cards clickable? Maybe not.
878
- // No need to change individual card transforms here, only opacity/etc.
879
- });
880
 
881
- dots.forEach((dot, index) => {
882
- dot.classList.toggle('active', index === currentCarouselIndex);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
883
  });
884
- }
885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
 
887
- function applyPromptsAndSwitchToChat() {
888
- saveSettings();
889
- switchMode('chat');
890
- showNotification('ใช้ Prompt แล้ว, เริ่มแชทได้เลย!', 'success');
891
- }
 
 
 
892
 
893
- // File handling
894
- function setupDropzone() {
895
- const dropzone = document.getElementById('dropzone');
896
- const appContainer = document.getElementById('appContainer'); // Listen on a higher level
 
 
897
 
898
- ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
899
- appContainer.addEventListener(eventName, preventDefaults, false);
900
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
901
 
902
- function preventDefaults(e) {
903
- e.preventDefault();
904
- e.stopPropagation();
905
- }
906
 
907
- ['dragenter', 'dragover'].forEach(eventName => {
908
- appContainer.addEventListener(eventName, () => {
909
- if (document.getElementById('chatMode').style.display !== 'none') {
910
- dropzone.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
911
  }
912
- }, false);
913
- });
914
-
915
- ['dragleave', 'drop'].forEach(eventName => {
916
- appContainer.addEventListener(eventName, () => dropzone.classList.remove('active'), false);
917
- });
918
-
919
- appContainer.addEventListener('drop', handleDrop, false);
920
- }
921
-
922
- function handleDrop(e) {
923
- if (document.getElementById('chatMode').style.display === 'none') return;
924
- const dt = e.dataTransfer;
925
- const files = dt.files;
926
- handleFiles(files);
927
- }
928
-
929
- function handleFileSelect(e) {
930
- const files = e.target.files;
931
- handleFiles(files);
932
- }
933
-
934
- function handleFiles(files) {
935
- attachedFiles.push(...Array.from(files).filter(f => f.type.startsWith('image/'))); // Only add images
936
- updateFileDisplay();
937
- if (attachedFiles.length > 0) {
938
- showNotification(`${attachedFiles.length} รูปถูกแนบแล้ว`, 'success');
939
- }
940
- }
941
-
942
- function updateFileDisplay() {
943
- const fileName = document.getElementById('fileName');
944
- if (attachedFiles.length === 0) {
945
- fileName.textContent = '';
946
- } else if (attachedFiles.length === 1) {
947
- fileName.textContent = attachedFiles[0].name;
948
- } else {
949
- fileName.textContent = `${attachedFiles.length} ไฟล์`;
950
- }
951
- }
952
-
953
- // Chat functionality
954
- function handleKeyDown(e) {
955
- if (e.key === 'Enter' && !e.shiftKey) {
956
- e.preventDefault();
957
- sendMessage();
958
- }
959
- }
960
-
961
- async function sendMessage() {
962
- const userInput = document.getElementById('userInput');
963
- const message = userInput.value.trim();
964
-
965
- if (!message && attachedFiles.length === 0) return;
966
- if (isLoading) return;
967
-
968
- setLoading(true);
969
- const chatContainer = document.getElementById('chatContainer');
970
- const userMessage = message; // Keep a copy
971
-
972
- // Add user message
973
- if (userMessage) {
974
- addMessage(userMessage, 'user');
975
- }
976
-
977
- // Add file previews and clear input/files
978
- const currentFiles = [...attachedFiles];
979
- attachedFiles = [];
980
- userInput.value = '';
981
- updateFileDisplay();
982
-
983
- for (const file of currentFiles) {
984
- const imageUrl = URL.createObjectURL(file);
985
- addImageMessage(imageUrl, 'user');
986
- }
987
-
988
- chatContainer.scrollTop = chatContainer.scrollHeight;
989
-
990
- try {
991
- const response = await callHuggingFaceAPI(userMessage, currentFiles);
992
- addMessage(response, 'ai');
993
- } catch (error) {
994
- console.error('Error:', error);
995
- addMessage('ขออภัย, เกิดข้อผิดพลาดในการประมวลผล โปรดลองอีกครั้ง', 'ai');
996
- showNotification('Error: ' + error.message, 'error');
997
- } finally {
998
- setLoading(false);
999
- chatContainer.scrollTop = chatContainer.scrollHeight;
1000
- }
1001
- }
1002
-
1003
- async function callHuggingFaceAPI(message, files) {
1004
- const model = document.getElementById('modelSelect').value;
1005
- const primaryPrompt = document.getElementById('primarySystemPrompt').value;
1006
- const prefix = document.getElementById('promptPrefix').value;
1007
- const suffix = document.getElementById('promptSuffix').value;
1008
- const ocrModel = document.getElementById('ocrModelSelect').value;
1009
-
1010
- let fullPrompt = `${primaryPrompt}\n\n${prefix}\n\nUser: ${message}`;
1011
-
1012
- // Handle OCR and image data based on model
1013
- let imageInput = null;
1014
- if (files.length > 0) {
1015
- const file = files[0]; // Assuming one image for simplicity now
1016
- const reader = new FileReader();
1017
- imageInput = await new Promise((resolve) => {
1018
- reader.onloadend = () => resolve(reader.result.split(',')[1]); // Base64
1019
- reader.readAsDataURL(file);
1020
- });
1021
 
1022
- if (ocrModel !== 'none') {
1023
- try {
1024
- const ocrText = await performOCR(file, ocrModel);
1025
- fullPrompt += `\n\n[Image Content: ${ocrText}]`;
1026
- } catch (error) {
1027
- console.warn('OCR failed:', error);
1028
- fullPrompt += `\n\n[Image attached, OCR failed or skipped]`;
1029
- }
 
 
 
 
 
 
 
 
 
 
1030
  } else {
1031
- fullPrompt += `\n\n[Image attached]`;
 
1032
  }
1033
  }
1034
 
1035
- fullPrompt += `\n\n${suffix}\n\nAI:`;
1036
 
1037
- const payload = {
1038
- inputs: fullPrompt,
1039
- parameters: { max_new_tokens: 2048, temperature: 0.6, top_p: 0.9, do_sample: true }
1040
- };
 
 
1041
 
1042
- // Adjust payload for Vision models if needed (this part needs refinement based on specific model docs)
1043
- // For many HF models, you might pass the image as part of the prompt or a separate field.
1044
- // This example focuses on text+OCR, a more complex vision integration might require different API calls or libraries.
 
 
 
1045
 
1046
- const response = await fetch(`https://api-inference.huggingface.co/models/${model}`, {
1047
- method: 'POST',
1048
- headers: {
1049
- 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}`,
1050
- 'Content-Type': 'application/json',
1051
- },
1052
- body: JSON.stringify(payload)
1053
- });
1054
 
1055
- if (!response.ok) {
1056
- const errorBody = await response.text();
1057
- console.error("API Error Body:", errorBody);
1058
- throw new Error(`HTTP error! status: ${response.status} - ${errorBody}`);
 
 
 
 
 
1059
  }
1060
 
1061
- const data = await response.json();
 
 
 
 
 
 
 
1062
 
1063
- if (data.error) throw new Error(data.error);
 
 
 
 
 
 
 
 
 
1064
 
1065
- let generatedText = data[0]?.generated_text || data.choices?.[0]?.message?.content || 'ไม่มีการตอบกลับ';
1066
-
1067
- // Clean up response - remove the prompt part if it's included
1068
- if(generatedText.startsWith(fullPrompt)) {
1069
- generatedText = generatedText.substring(fullPrompt.length).trim();
 
 
 
 
 
 
 
1070
  }
1071
 
1072
- return generatedText;
1073
- }
 
 
 
1074
 
1075
- async function performOCR(file, ocrModel) {
1076
- // OCR often needs raw file data, not base64
1077
- const response = await fetch(`https://api-inference.huggingface.co/models/${ocrModel}`, {
1078
- method: 'POST',
1079
- headers: {
1080
- 'Authorization': `Bearer ${HUGGINGFACE_TOKEN}`,
1081
- },
1082
- body: file // Send file directly
1083
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1084
 
1085
- if (!response.ok) {
1086
- const errorBody = await response.text();
1087
- throw new Error(`OCR failed: ${response.status} - ${errorBody}`);
1088
  }
1089
 
1090
- const data = await response.json();
1091
- // OCR models have varying outputs, check common ones
1092
- return data[0]?.generated_text || data.generated_text || data.text || 'ไม่สามารถอ่านข้อความได้';
1093
- }
1094
 
 
 
 
 
 
 
 
1095
 
1096
- function addMessage(content, sender) {
1097
- const chatContainer = document.getElementById('chatContainer');
1098
- const messageDiv = document.createElement('div');
1099
- messageDiv.className = `message ${sender}`;
1100
 
1101
- if (sender === 'ai') {
1102
- messageDiv.innerHTML = marked.parse(content);
1103
- messageDiv.querySelectorAll('pre').forEach(pre => {
1104
- hljs.highlightElement(pre.querySelector('code'));
1105
- addCodeTools(pre); // Add tools to 'pre' element
1106
- });
1107
- } else {
1108
- messageDiv.textContent = content; // Keep user message as plain text
1109
- }
1110
-
1111
- chatContainer.appendChild(messageDiv);
1112
- chatContainer.scrollTop = chatContainer.scrollHeight;
1113
- }
1114
-
1115
-
1116
- function addImageMessage(imageUrl, sender) {
1117
- const chatContainer = document.getElementById('chatContainer');
1118
- const messageDiv = document.createElement('div');
1119
- messageDiv.className = `message ${sender}`;
1120
-
1121
- const img = document.createElement('img');
1122
- img.src = imageUrl;
1123
- img.className = 'image-preview';
1124
- img.alt = 'รูปภาพที่อัปโหลด';
1125
- img.onload = () => chatContainer.scrollTop = chatContainer.scrollHeight; // Scroll after image loads
1126
-
1127
- messageDiv.appendChild(img);
1128
- chatContainer.appendChild(messageDiv);
1129
- chatContainer.scrollTop = chatContainer.scrollHeight;
1130
- }
1131
-
1132
- function addCodeTools(preElement) {
1133
- const toolsDiv = document.createElement('div');
1134
- toolsDiv.className = 'code-tools';
1135
-
1136
- const copyBtn = document.createElement('button');
1137
- copyBtn.innerHTML = '<span class="material-icons" style="font-size: 16px;">content_copy</span> Copy';
1138
- copyBtn.onclick = () => {
1139
- const code = preElement.querySelector('code').innerText;
1140
- navigator.clipboard.writeText(code)
1141
- .then(() => showNotification('คัดลอกโค้ดแล้ว!', 'success'))
1142
- .catch(err => showNotification('คัดลอกไม่สำเร็จ!', 'error'));
1143
- };
1144
-
1145
- toolsDiv.appendChild(copyBtn);
1146
- preElement.insertBefore(toolsDiv, preElement.firstChild); // Insert before code
1147
- }
1148
-
1149
- function setLoading(loading) {
1150
- isLoading = loading;
1151
- const sendButton = document.getElementById('sendButton');
1152
- const icon = sendButton.querySelector('.material-icons');
1153
-
1154
- if (loading) {
1155
- sendButton.innerHTML = '<div class="spinner"></div>';
1156
- sendButton.disabled = true;
1157
- } else {
1158
- sendButton.innerHTML = '<span class="material-icons">send</span>';
1159
- sendButton.disabled = false;
1160
- }
1161
- }
1162
-
1163
- // Utility functions
1164
- function toggleSettings() {
1165
- document.getElementById('settingsPanel').classList.toggle('collapsed');
1166
- }
1167
-
1168
- function toggleFullscreen() {
1169
- const elem = document.documentElement;
1170
- const icon = document.querySelector('#fullscreenToggle .material-icons');
1171
- if (!document.fullscreenElement) {
1172
- elem.requestFullscreen().catch(err => console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`));
1173
- icon.textContent = 'fullscreen_exit';
1174
- } else {
1175
- document.exitFullscreen();
1176
- icon.textContent = 'fullscreen';
1177
- }
1178
- }
1179
-
1180
- function refreshApp() {
1181
- location.reload();
1182
- }
1183
-
1184
- function showNotification(message, type) {
1185
- const notification = document.getElementById(type === 'error' ? 'errorMessage' : 'successMessage');
1186
- const textElement = document.getElementById(type === 'error' ? 'errorText' : 'successText');
1187
-
1188
- textElement.textContent = message;
1189
- notification.style.display = 'block';
1190
- notification.style.opacity = '0'; // Start faded out
1191
- notification.style.transform = 'translateX(100px)'; // Start off-screen
1192
-
1193
- // Force reflow before adding class
1194
- void notification.offsetWidth;
1195
-
1196
- requestAnimationFrame(() => {
1197
- notification.style.opacity = '1';
1198
- notification.style.transform = 'translateX(0)';
1199
- });
1200
-
1201
- setTimeout(() => {
1202
- notification.style.opacity = '0';
1203
- notification.style.transform = 'translateX(100px)';
1204
- setTimeout(() => { notification.style.display = 'none'; }, 600); // Wait for transition
1205
- }, 3000);
1206
- }
1207
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1208
  </body>
1209
  </html>
 
1
  <!DOCTYPE html>
2
  <html lang="th">
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
+ }
31
+
32
+ * {
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>
480
+ <select id="modelSelect">
481
+ <option value="Qwen/Qwen2.5-Coder-32B-Instruct">Qwen2.5-Coder 32B</option>
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>
488
+ <select id="ocrModelSelect">
489
+ <option value="none">ไม่ใช้</option>
490
+ <option value="microsoft/trocr-base-printed">TrOCR Base</option>
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>
540
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/9.1.6/marked.min.js"></script>
541
+ <script>
542
+ const HUGGINGFACE_TOKEN = "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"; // <<< !!! ใส่ Hugging Face Token ของคุณที่นี่ !!!
543
+
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() {
574
+ const carousel = document.getElementById('promptCarousel');
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>
585
+ `;
586
+ carousel.appendChild(card);
587
+
588
+ const dot = document.createElement('div');
589
+ dot.className = 'carousel-dot';
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;
738
+ updateCarouselPosition();
739
+ }
740
 
741
+ function goToCarouselIndex(index) {
742
+ currentCarouselIndex = index;
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() {
820
+ const fileNameEl = document.getElementById('fileName');
821
+ if (attachedFiles.length === 0) fileNameEl.textContent = '';
822
+ else if (attachedFiles.length === 1) fileNameEl.textContent = attachedFiles[0].name;
823
+ else fileNameEl.textContent = `${attachedFiles.length} ไฟล์`;
824
+ }
825
+
826
+ function handleKeyDown(e) {
827
+ if (e.key === 'Enter' && !e.shiftKey) {
828
+ e.preventDefault();
829
+ sendMessage();
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;
837
+
838
+ setLoading(true);
839
+ const chatContainer = document.getElementById('chatContainer');
840
+ const userMessage = message;
841
+ const currentFiles = [...attachedFiles];
842
+
843
+ userInput.value = '';
844
+ attachedFiles = [];
845
+ updateFileDisplay();
846
+
847
+ if (userMessage) addMessage(userMessage, 'user');
848
+ currentFiles.forEach(file => addImageMessage(URL.createObjectURL(file), 'user'));
849
+ scrollToBottom(chatContainer);
850
+
851
+ try {
852
+ const response = await callHuggingFaceAPI(userMessage, currentFiles);
853
+ addMessage(response, 'ai');
854
+ } catch (error) {
855
+ console.error('Error:', error);
856
+ addMessage(`ขออภัย, เกิดข้อผิดพลาด: ${error.message}`, 'ai', true);
857
+ showNotification(`Error: ${error.message}`, 'error');
858
+ } finally {
859
+ setLoading(false);
860
+ scrollToBottom(chatContainer);
861
+ }
862
+ }
863
+
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' },
913
+ body: JSON.stringify(payload)
914
+ });
915
+
916
+ if (!response.ok) {
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}` },
931
+ body: file
932
+ });
933
+ if (!response.ok) throw new Error(`OCR failed: ${response.statusText}`);
934
+ const data = await response.json();
935
+ return data[0]?.generated_text || 'Could not extract text.';
936
+ }
937
+
938
+
939
+ function addMessage(content, sender, isError = false) {
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);
947
+ messageDiv.querySelectorAll('pre code').forEach(block => {
948
+ hljs.highlightElement(block);
949
+ addCodeTools(block.parentElement);
950
+ });
951
+ } else {
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');
984
+ sendButton.innerHTML = loading ? '<div class="spinner"></div>' : '<span class="material-icons">send</span>';
985
+ sendButton.disabled = loading;
986
+ }
987
+
988
+ function toggleFullscreen() {
989
+ const icon = document.querySelector('#fullscreenToggle .material-icons');
990
+ if (!document.fullscreenElement) {
991
+ document.documentElement.requestFullscreen();
992
+ icon.textContent = 'fullscreen_exit';
993
+ } else {
994
+ document.exitFullscreen();
995
+ icon.textContent = 'fullscreen';
996
+ }
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) {
1008
+ element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' });
1009
+ }
1010
+
1011
+ </script>
1012
  </body>
1013
  </html>