AnesKAM commited on
Commit
e52e0f4
ยท
verified ยท
1 Parent(s): a56402f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +335 -438
index.html CHANGED
@@ -5,7 +5,7 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>Genisi AI</title>
7
 
8
- <!-- ๐ŸŒŸ Lucide Icons - ู…ูƒุชุจุฉ ุฃูŠู‚ูˆู†ุงุช ุนุตุฑูŠุฉ ูˆู†ุธูŠูุฉ -->
9
  <script src="https://unpkg.com/lucide@latest"></script>
10
 
11
  <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
@@ -23,6 +23,8 @@
23
  --user-bubble: #1a2340; --bot-bubble: #161920;
24
  --danger: #f75f5f;
25
  --flash-color: #fbbf24; --pro-color: #a78bfa;
 
 
26
  --radius: 28px; --radius-sm: 18px; --radius-pill: 50px;
27
  --sidebar-w: 280px; --tr: 0.3s cubic-bezier(.4,0,.2,1);
28
  }
@@ -32,6 +34,8 @@
32
  --text: #1a1d2e; --text2: #4a5070; --text3: #8a90a8;
33
  --user-bubble: #dde6ff; --bot-bubble: #fff; --accent-soft: rgba(79,142,247,0.1);
34
  --flash-color: #d97706; --pro-color: #7c3aed;
 
 
35
  }
36
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
37
  html,body{height:100%;font-family:'Cairo',sans-serif;background:var(--bg);color:var(--text); scroll-behavior: smooth;}
@@ -40,13 +44,13 @@
40
 
41
  .app{display:flex;height:100vh;overflow:hidden}
42
 
43
- /* SIDEBAR */
44
  .sidebar{width:var(--sidebar-w);min-width:var(--sidebar-w);background:var(--surface);
45
  border-right:1px solid var(--border);display:flex;flex-direction:column;
46
  transition:width var(--tr),min-width var(--tr);overflow:hidden;
47
  border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius);}
48
  [dir="rtl"] .sidebar { border-right:none; border-left:1px solid var(--border); border-radius: 0 var(--radius) var(--radius) 0; }
49
- .sidebar.collapsed{width:0;min-width:0;border:none}
50
  .sidebar-header{padding:24px 20px 16px;display:flex;align-items:center;gap:12px;}
51
  .s-logo{display:flex;align-items:center;gap:12px;flex:1}
52
  .s-logo img{width:36px;height:36px;border-radius:12px;object-fit:cover; box-shadow: 0 4px 12px var(--accent-soft);}
@@ -56,7 +60,7 @@
56
  color:#fff;border:none;border-radius:var(--radius-pill);cursor:pointer;font-family:'Cairo',sans-serif;
57
  font-size:.95rem;font-weight:600;display:flex;align-items:center;gap:8px; justify-content:center;
58
  transition:all var(--tr); box-shadow: 0 4px 15px var(--accent-soft);}
59
- .btn-new i, .btn-new svg { width: 18px; height: 18px; }
60
  .btn-new:hover{opacity:.9;transform:translateY(-2px)}
61
  .s-label{padding:10px 20px;font-size:.75rem;font-weight:700;color:var(--text3);letter-spacing:.08em;text-transform:uppercase}
62
  .chats-list{flex:1;overflow-y:auto;padding:4px 12px;display:flex;flex-direction:column;gap:6px}
@@ -72,16 +76,35 @@
72
  .chat-item:hover .del-btn{opacity:1}
73
  .del-btn:hover{background:rgba(247,95,95,.15); transform:scale(1.1);}
74
  .del-btn svg { width: 16px; height: 16px; }
75
- .s-footer{padding:16px;display:flex;align-items:center;gap:12px; justify-content: center;}
 
76
  .btn-icon{width:42px;height:42px;background:var(--surface2);border:none;
77
  border-radius:var(--radius-pill);color:var(--text2);cursor:pointer;display:flex;
78
  align-items:center;justify-content:center;transition:all var(--tr)}
79
  .btn-icon svg { width: 20px; height: 20px; }
80
- .btn-icon:hover{background:var(--surface3);color:var(--text); transform:rotate(10deg);}
 
 
 
 
 
 
 
 
81
 
82
  /* MAIN */
83
  .main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0; background: var(--bg);}
84
  .topbar{height:70px;padding:0 24px;display:flex;align-items:center;gap:16px; flex-shrink:0}
 
 
 
 
 
 
 
 
 
 
85
  .topbar-title{flex:1;font-size:1.05rem;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
86
 
87
  /* CHAT AREA */
@@ -99,9 +122,9 @@
99
  .chip svg { width: 18px; height: 18px; color: var(--accent); }
100
  .chip:hover{background:var(--accent-soft);border-color:var(--accent);color:var(--accent); transform:translateY(-2px);}
101
 
102
- /* MESSAGES & ANIMATIONS */
103
  .msg-row{display:flex;padding:8px 30px;gap:16px; animation: popIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;}
104
- @keyframes popIn { from { opacity: 0; transform: translateY(15px) scale(0.98); filter: blur(4px); } to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); } }
105
  @keyframes fadeSlideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
106
  .msg-row.user{flex-direction:row-reverse}
107
  .msg-av{width:38px;height:38px;border-radius:14px;flex-shrink:0;display:flex;align-items:center;
@@ -113,77 +136,107 @@
113
  .msg-row.user .msg-av svg { width: 20px; height: 20px; }
114
 
115
  .bubble{max-width:min(700px,80vw);padding:16px 22px;border-radius:var(--radius);
116
- font-size:.98rem;line-height:1.75;word-break:break-word; box-shadow: 0 4px 15px rgba(0,0,0,0.03);}
117
- .msg-row.bot .bubble{background:transparent;border:none;padding:8px 4px;box-shadow:none;border-top-left-radius:var(--radius);}
118
- [dir="rtl"] .msg-row.bot .bubble { border-top-left-radius:var(--radius); border-top-right-radius:var(--radius); }
119
- .msg-row.user .bubble{background:var(--user-bubble);border:none; border-top-right-radius:6px}
120
- [dir="rtl"] .msg-row.user .bubble { border-top-right-radius:var(--radius); border-top-left-radius:6px; }
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- /* GENISI STREAMING EFFECT */
123
- .genisi-cursor { display: inline-block; width: 14px; height: 14px; margin-inline-start: 6px; background: linear-gradient(135deg, var(--accent), var(--accent2)); mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 0l2.5 9.5L24 12l-9.5 2.5L12 24l-2.5-9.5L0 12l9.5-2.5z"/></svg>'); -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 0l2.5 9.5L24 12l-9.5 2.5L12 24l-2.5-9.5L0 12l9.5-2.5z"/></svg>'); mask-size: cover; -webkit-mask-size: cover; animation: spinPulse 1s infinite linear; transform-origin: center; vertical-align: middle; }
124
- @keyframes spinPulse { 0% { transform: scale(0.8) rotate(0deg); opacity: 0.5; } 50% { transform: scale(1.2) rotate(90deg); opacity: 1; filter: drop-shadow(0 0 5px var(--accent)); } 100% { transform: scale(0.8) rotate(180deg); opacity: 0.5; } }
125
-
126
- /* IMAGE SKELETON LOADING */
127
- .skeleton-img {
128
- width: 100%; max-width: 400px; height: 350px;
129
- margin: 15px auto; border-radius: 18px;
130
- background: linear-gradient(90deg, var(--surface2) 25%, var(--surface3) 50%, var(--surface2) 75%);
131
- background-size: 200% 100%;
132
- animation: skeletonLoading 1.5s infinite linear;
133
- box-shadow: 0 8px 25px rgba(0,0,0,0.1);
134
- display: flex; align-items: center; justify-content: center; gap: 10px;
135
- color: var(--text3); font-size: 1.2rem;
136
  }
137
- @keyframes skeletonLoading {
138
- 0% { background-position: 200% 0; }
139
- 100% { background-position: -200% 0; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  }
141
 
 
 
 
 
142
  /* MARKDOWN STYLES */
143
  .bubble p{margin-bottom:12px}.bubble p:last-child{margin-bottom:0}
144
  .bubble ul,.bubble ol{padding-inline-start:24px;margin:12px 0}
145
  .bubble li {margin-bottom: 6px;}
146
- .bubble .table-wrapper{width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;margin:16px 0;border-radius:var(--radius-sm);border:1px solid var(--border);}
147
  .bubble table{width:100%;border-collapse:collapse;min-width:400px;}
148
- .bubble th,.bubble td{border:1px solid var(--border);padding:10px 14px;text-align:start;white-space:nowrap;}
149
  .bubble th{background:var(--surface3);font-weight:600;}
150
 
151
  /* Code Blocks */
152
- .code-container{position:relative;margin:16px 0;background:var(--surface3);border-radius:var(--radius-sm);border:1px solid var(--border);overflow:hidden;max-width:100%;}
153
- .code-header{display:flex;justify-content:space-between;align-items:center;background:#12141a;padding:8px 16px;font-size:0.8rem;color:var(--text2);font-family:'JetBrains Mono',monospace;}
154
- .copy-btn{background:var(--surface2);border:none;color:var(--text);cursor:pointer;display:flex;align-items:center;gap:6px;font-size:0.8rem;padding:4px 10px;border-radius:var(--radius-pill);transition:all 0.2s;}
155
- .copy-btn svg { width: 16px; height: 16px; }
156
  .copy-btn:hover{background:var(--accent);color:#fff;}
157
- .code-container pre{margin:0;padding:16px;overflow-x:auto;-webkit-overflow-scrolling:touch;font-family:'JetBrains Mono',monospace;font-size:0.85rem;max-width:100%;}
158
- .code-container pre code{white-space:pre;display:block;}
159
  .bubble code:not(pre code){font-family:'JetBrains Mono',monospace;font-size:.85rem;background:var(--surface3);padding:3px 6px;border-radius:6px;color:var(--accent);}
160
 
161
- /* KaTeX Math Styles */
162
- .katex-display{overflow-x:auto;overflow-y:hidden;padding:8px 0;-webkit-overflow-scrolling:touch;}
163
  .katex{font-size:1.05em;}
164
 
165
  /* Files */
166
  .file-chip {display:inline-flex; align-items:center; gap:6px; background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius-pill); padding:4px 12px; font-size:0.85rem; margin:0 4px 8px 0; color:var(--text);}
167
- .file-chip svg { width: 16px; height: 16px; }
168
- .file-chip button {background:none; border:none; color:var(--danger); cursor:pointer; font-size:1rem; padding:0; margin-left:4px; transition:transform 0.2s; display: flex; align-items: center;}
169
- .file-chip button svg { width: 14px; height: 14px; }
170
- .file-chip button:hover {transform:scale(1.2);}
171
  .file-prev {display:flex; align-items:center; gap:8px; background:var(--surface3); padding:8px 12px; border-radius:8px; margin-bottom:8px; font-size:0.85rem;}
172
- .file-prev svg { width: 18px; height: 18px; }
173
 
174
  .mem-badge{font-size:.75rem;color:var(--text3);padding:4px 30px 10px;display:flex;align-items:center;gap:6px; font-weight: 600;}
175
- .mem-badge svg { width: 14px; height: 14px; }
176
 
177
  /* INPUT */
178
  .input-area{padding:0 24px 30px; display:flex; flex-direction:column; align-items:center; flex-shrink:0;}
179
  .input-wrapper{width:100%; max-width:850px; position:relative;}
180
  .file-chips-container {width:100%; max-width:850px; display:flex; flex-wrap:wrap; margin-bottom:8px;}
181
  .input-box{display:flex;align-items:flex-end;gap:12px;background:var(--surface);
182
- border:1px solid var(--border);border-radius:32px;padding:12px 16px 12px 16px;
183
  transition:all var(--tr); box-shadow: 0 8px 30px rgba(0,0,0,0.15);}
184
- .input-box:focus-within{border-color:var(--accent); box-shadow: 0 8px 30px var(--accent-soft), 0 0 0 3px var(--accent-soft);}
185
 
186
- .btn-attach{background:none; border:none; color:var(--text3); cursor:pointer; padding:6px; border-radius:50%; transition:all var(--tr); display:flex; align-items:center; justify-content:center;}
187
  .btn-attach svg { width: 22px; height: 22px; }
188
  .btn-attach:hover{color:var(--accent); background:var(--surface2);}
189
 
@@ -191,137 +244,48 @@
191
  font-family:'Cairo',sans-serif;font-size:.98rem;resize:none;max-height:160px;line-height:1.6; padding: 6px 0;}
192
  .input-box textarea::placeholder{color:var(--text3)}
193
 
194
- .btn-send, .btn-stop {width:42px;height:42px; border:none;border-radius:50%;cursor:pointer;color:#fff;display:flex;align-items:center;justify-content:center;transition:all var(--tr); flex-shrink:0; margin-bottom: 2px;}
195
  .btn-send {background:linear-gradient(135deg,var(--accent),var(--accent2));}
196
  .btn-send svg { width: 20px; height: 20px; }
197
- .btn-send:hover{opacity:.9;transform:scale(1.08) rotate(-10deg);}[dir="rtl"] .btn-send:hover{transform:scale(1.08) rotate(10deg);}
198
  .btn-send:disabled{opacity:.4;cursor:not-allowed;transform:none;}
199
 
200
- /* Stop Button Styles */
201
  .btn-stop {background:var(--surface3); color:var(--text); border:1px solid var(--border);}
202
  .btn-stop svg { width: 20px; height: 20px; }
203
- .btn-stop:hover {background:var(--danger); color:#fff; transform:scale(1.05);}
204
 
205
- /* ๐ŸŒŸ MODEL SELECTOR IN INPUT BAR - ุฃูŠู‚ูˆู†ุฉ ูู‚ุท */
206
- .model-selector-input {
207
- position: relative;
208
- display: flex;
209
- align-items: center;
210
- }
211
-
212
  .model-toggle-btn {
213
- display: flex;
214
- align-items: center;
215
- justify-content: center;
216
- width: 36px;
217
- height: 36px;
218
- background: var(--surface2);
219
- border: 1px solid var(--border);
220
- border-radius: 50%;
221
- cursor: pointer;
222
- transition: all var(--tr);
223
- user-select: none;
224
- }
225
-
226
- .model-toggle-btn:hover {
227
- background: var(--surface3);
228
- border-color: var(--accent);
229
- transform: scale(1.05);
230
- }
231
-
232
- .model-toggle-btn svg {
233
- width: 18px;
234
- height: 18px;
235
- transition: all var(--tr);
236
- }
237
-
238
- .model-toggle-btn.flash-active svg {
239
- color: var(--flash-color);
240
- }
241
-
242
- .model-toggle-btn.pro-active svg {
243
- color: var(--pro-color);
244
  }
 
 
 
 
245
 
246
  .model-dropdown-input {
247
- position: absolute;
248
- bottom: calc(100% + 8px);
249
- left: 0;
250
- background: var(--surface);
251
- border: 1px solid var(--border);
252
- border-radius: var(--radius-sm);
253
- padding: 8px;
254
- min-width: 220px;
255
- box-shadow: 0 10px 40px rgba(0,0,0,0.4);
256
- z-index: 100;
257
- opacity: 0;
258
- visibility: hidden;
259
- transform: translateY(10px);
260
- transition: all var(--tr);
261
- }
262
-
263
- [dir="rtl"] .model-dropdown-input {
264
- left: auto;
265
- right: 0;
266
- }
267
-
268
- .model-dropdown-input.show {
269
- opacity: 1;
270
- visibility: visible;
271
- transform: translateY(0);
272
  }
 
 
273
 
274
  .model-option-input {
275
- display: flex;
276
- align-items: center;
277
- gap: 12px;
278
- padding: 12px 14px;
279
- border-radius: var(--radius-sm);
280
- cursor: pointer;
281
- transition: background var(--tr);
282
- }
283
-
284
- .model-option-input:hover {
285
- background: var(--surface2);
286
- }
287
-
288
- .model-option-input.active {
289
- background: var(--accent-soft);
290
- border: 1px solid var(--accent);
291
- }
292
-
293
- .model-option-input svg {
294
- width: 22px;
295
- height: 22px;
296
- flex-shrink: 0;
297
- }
298
-
299
- .model-info-input {
300
- flex: 1;
301
- }
302
-
303
- .model-name-input {
304
- font-weight: 600;
305
- font-size: 0.95rem;
306
- color: var(--text);
307
- display: flex;
308
- align-items: center;
309
- gap: 6px;
310
- }
311
-
312
- .model-desc-input {
313
- font-size: 0.75rem;
314
- color: var(--text3);
315
- margin-top: 2px;
316
- }
317
-
318
- .model-badge-input {
319
- padding: 2px 8px;
320
- border-radius: var(--radius-pill);
321
- font-size: 0.65rem;
322
- font-weight: 700;
323
- color: #fff;
324
  }
 
 
 
 
 
 
 
325
 
326
  /* MODAL */
327
  .overlay{position:fixed;inset:0;z-index:999;background:rgba(0,0,0,.6);display:flex;
@@ -334,7 +298,7 @@
334
  .overlay.open .modal{transform:translateY(0) scale(1)}
335
  .m-header{display:flex;align-items:center;gap:12px;margin-bottom:24px}
336
  .m-title{font-size:1.3rem;font-weight:700;flex:1}
337
- .m-close{background:var(--surface2);border:none;color:var(--text);cursor:pointer;width:36px;height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center; transition:background var(--tr);}
338
  .m-close svg { width: 20px; height: 20px; }
339
  .m-close:hover{background:var(--danger); color:#fff;}
340
  .s-row{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--border)}
@@ -342,41 +306,34 @@
342
  .s-desc{font-size:.8rem;color:var(--text3);margin-top:4px}
343
  .toggle{position:relative;width:48px;height:26px}
344
  .toggle input{opacity:0;width:0;height:0}
345
- .tslider{position:absolute;inset:0;background:var(--surface3);border-radius:var(--radius-pill);cursor:pointer;transition:all var(--tr)}
346
  .tslider::before{content:'';position:absolute;width:20px;height:20px;border-radius:50%;
347
- background:#fff;top:3px;left:4px;transition:all var(--tr); box-shadow: 0 2px 5px rgba(0,0,0,0.2);}
348
  .toggle input:checked+.tslider{background:var(--accent)}
349
  .toggle input:checked+.tslider::before{transform:translateX(20px)}
350
 
351
- select.s-input { width:auto; padding:8px 16px; background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius-pill); color:var(--text); cursor:pointer; font-family:'Cairo',sans-serif; font-weight:600;}
352
  .btn-save{width:100%;margin-top:24px;padding:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));
353
- color:#fff;border:none;border-radius:var(--radius-pill);cursor:pointer;font-family:'Cairo',sans-serif;font-size:1rem;font-weight:700; transition:opacity var(--tr); display: flex; align-items: center; justify-content: center; gap: 8px;}
354
  .btn-save svg { width: 18px; height: 18px; }
355
- .btn-save:hover{opacity:.9}
356
  .btn-danger{padding:8px 18px;background:rgba(247,95,95,.12);border:1px solid var(--danger);
357
- color:var(--danger);border-radius:var(--radius-pill);cursor:pointer;font-size:.9rem;font-weight:600;font-family:'Cairo',sans-serif; transition:all var(--tr); display: flex; align-items: center; gap: 6px;}
358
  .btn-danger svg { width: 16px; height: 16px; }
359
  .btn-danger:hover{background:var(--danger); color:#fff;}
360
 
361
- /* โ•โ• RESPONSIVE MOBILE โ•โ• */
362
  @media (max-width: 640px) {
363
  .msg-row { padding: 6px 12px; gap: 10px; }
364
  .bubble { max-width: 92vw; padding: 12px 14px; font-size: .93rem; }
365
- .msg-row.bot .bubble { padding: 6px 2px; }
366
  .topbar { height: 56px; padding: 0 14px; }
367
  .input-area { padding: 0 10px 18px; }
368
- .code-container pre { font-size: 0.78rem; padding: 12px; }
369
- .bubble th, .bubble td { font-size: 0.85rem; padding: 8px 10px; }
370
  .w-title { font-size: 1.8rem; }
371
- .chips { gap: 8px; }
372
- .chip { font-size: .82rem; padding: 8px 14px; }
373
  }
374
  </style>
375
  </head>
376
  <body>
377
  <div class="app">
378
 
379
- <aside class="sidebar" id="sidebar">
380
  <div class="sidebar-header">
381
  <div class="s-logo">
382
  <img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" alt="Genisi"/>
@@ -390,14 +347,23 @@
390
  <div class="s-label" id="i18n-chats-label">CHATS</div>
391
  <div class="chats-list" id="chats-list"></div>
392
  <div class="s-footer">
393
- <button class="btn-icon" onclick="openSettings()"><i data-lucide="settings"></i></button>
394
- <button class="btn-icon" id="theme-btn" onclick="toggleTheme()"><i data-lucide="moon"></i></button>
 
 
 
 
 
 
395
  </div>
396
  </aside>
397
 
398
  <main class="main">
399
  <div class="topbar">
400
- <button class="btn-icon" onclick="toggleSidebar()"><i data-lucide="menu"></i></button>
 
 
 
401
  <div class="topbar-title" id="topbar-title">Genisi AI</div>
402
  </div>
403
 
@@ -410,7 +376,6 @@
410
  <input type="file" id="file-input" multiple hidden onchange="handleFiles(this.files)" />
411
  <button class="btn-attach" onclick="document.getElementById('file-input').click()"><i data-lucide="paperclip"></i></button>
412
 
413
- <!-- ๐ŸŒŸ MODEL SELECTOR - ุฃูŠู‚ูˆู†ุฉ ูู‚ุท -->
414
  <div class="model-selector-input">
415
  <div class="model-toggle-btn" id="model-toggle-btn" onclick="toggleModelDropdownInput(event)">
416
  <i id="model-icon-input" data-lucide="zap"></i>
@@ -427,13 +392,13 @@
427
  </div>
428
  </div>
429
  <div class="model-option-input" data-model="pro" onclick="selectModelInput('pro', event)">
430
- <i data-lucide="crown" style="color: var(--pro-color);"></i>
431
  <div class="model-info-input">
432
  <div class="model-name-input">
433
  Genisi Pro
434
  <span class="model-badge-input" style="background: var(--pro-color);">๐Ÿ’Ž Pro</span>
435
  </div>
436
- <div class="model-desc-input">ู…ุชู‚ุฏู…ุŒ ุชุญู„ูŠู„ ุนู…ูŠู‚ ูˆุฅุจุฏุงุน</div>
437
  </div>
438
  </div>
439
  </div>
@@ -490,10 +455,11 @@ const i18n = {
490
  en: {
491
  newChat: "New Chat", chatsLabel: "CHATS", settings: "Settings", lang: "Language",
492
  dark: "Dark Mode", del: "Delete All Chats", delDesc: "This action cannot be undone",
493
- delBtn: "Delete", saveBtn: "Close", placeholder: "Type your message... (Enter to send)",
494
  welcomeTitle: "Welcome to Genisi", welcomeSub: "Your smart assistant by AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
495
  c1: "What is AI?", c2: "Write Python code", c3: "Summarize a topic", c4: "Design an image in space ๐Ÿš€",
496
- errConnect: "Connection Error", memBadge: "Memory: {c}/{t} messages", stopMsg: "[Generation stopped by user]"
 
497
  },
498
  ar: {
499
  newChat: "ู…ุญุงุฏุซุฉ ุฌุฏูŠุฏุฉ", chatsLabel: "ุงู„ู…ุญุงุฏุซุงุช", settings: "ุงู„ุฅุนุฏุงุฏุงุช", lang: "ุงู„ู„ุบุฉ",
@@ -501,7 +467,8 @@ const i18n = {
501
  delBtn: "ุญุฐู", saveBtn: "ุฅุบู„ุงู‚", placeholder: "ุงูƒุชุจ ุฑุณุงู„ุชูƒ... (Enter ู„ู„ุฅุฑุณุงู„)",
502
  welcomeTitle: "ู…ุฑุญุจู‹ุง ููŠ Genisi", welcomeSub: "ู…ุณุงุนุฏูƒ ุงู„ุฐูƒูŠ ู…ู† AnesNT โ€” ูˆู„ุงูŠุฉ ุจุงุชู†ุฉ ๐Ÿ‡ฉ๐Ÿ‡ฟ",
503
  c1: "ู…ุง ู‡ูˆ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†๏ฟฝ๏ฟฝุนูŠุŸ", c2: "ุงูƒุชุจ ูƒูˆุฏ ุจุงูŠุซูˆู†", c3: "ู„ุฎุต ู„ูŠ ู…ูˆุถูˆุนุงู‹", c4: "ุตู…ู… ุตูˆุฑุฉ ุจุงู„ูุถุงุก ๐Ÿš€",
504
- errConnect: "ุฎุทุฃ ููŠ ุงู„ุงุชุตุงู„", memBadge: "ุงู„ุฐุงูƒุฑุฉ: {c}/{t} ุฑุณุงุฆู„", stopMsg: "[ุชู… ุฅูŠู‚ุงู ุงู„ุชูˆู„ูŠุฏ]"
 
505
  },
506
  fr: {
507
  newChat: "Nouvelle Disc.", chatsLabel: "DISCUSSIONS", settings: "Paramรจtres", lang: "Langue",
@@ -509,7 +476,8 @@ const i18n = {
509
  delBtn: "Supprimer", saveBtn: "Fermer", placeholder: "ร‰crivez votre message...",
510
  welcomeTitle: "Bienvenue sur Genisi", welcomeSub: "Votre assistant intelligent par AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
511
  c1: "Qu'est-ce que l'IA?", c2: "Code Python", c3: "Rรฉsumer un sujet", c4: "Crรฉer une image ๐Ÿš€",
512
- errConnect: "Erreur de connexion", memBadge: "Mรฉmoire: {c}/{t} msgs", stopMsg: "[Gรฉnรฉration arrรชtรฉe]"
 
513
  },
514
  es: {
515
  newChat: "Nuevo Chat", chatsLabel: "CHATS", settings: "Ajustes", lang: "Idioma",
@@ -517,7 +485,8 @@ const i18n = {
517
  delBtn: "Borrar", saveBtn: "Cerrar", placeholder: "Escribe tu mensaje...",
518
  welcomeTitle: "Bienvenido a Genisi", welcomeSub: "Tu asistente por AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
519
  c1: "ยฟQuรฉ es la IA?", c2: "Cรณdigo Python", c3: "Resumir", c4: "Diseรฑar imagen ๐Ÿš€",
520
- errConnect: "Error de conexiรณn", memBadge: "Memoria: {c}/{t} msgs", stopMsg: "[Generaciรณn detenida]"
 
521
  }
522
  };
523
 
@@ -541,27 +510,17 @@ function applyI18n() {
541
 
542
  if(!activeChatId) renderWelcome();
543
  renderChatList();
544
-
545
- // ุฅุนุงุฏุฉ ุชู‡ูŠุฆุฉ ุงู„ุฃูŠู‚ูˆู†ุงุช ุจุนุฏ ุชุบูŠูŠุฑ ุงู„ู„ุบุฉ
546
  setTimeout(() => lucide.createIcons(), 10);
547
  }
548
 
549
- function changeLanguage(val) {
550
- currentLang = val;
551
- localStorage.setItem('genisi_lang', val);
552
- applyI18n();
553
- }
554
 
555
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
556
- // STATE & UTILS
557
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
558
  const genId = () => Date.now().toString(36) + Math.random().toString(36).substring(2, 6);
559
-
560
- let userId = localStorage.getItem('genisi_user_id');
561
- if (!userId) {
562
- userId = 'user_' + genId();
563
- localStorage.setItem('genisi_user_id', userId);
564
- }
565
 
566
  let chats = JSON.parse(localStorage.getItem('genisi_chats') || '[]');
567
  let activeChatId = null;
@@ -570,152 +529,83 @@ let pendingFiles = [];
570
  let currentAbortController = null;
571
  let currentModel = 'flash';
572
 
573
- // ๐ŸŒŸ ุฏูˆุงู„ ุฅุฏุงุฑุฉ ุงู„ู†ู…ูˆุฐุฌ - ุฃูŠู‚ูˆู†ุฉ ูู‚ุท
574
  function toggleModelDropdownInput(event) {
575
  event.stopPropagation();
576
  const dropdown = document.getElementById('model-dropdown-input');
577
  dropdown.classList.toggle('show');
578
-
579
  if (dropdown.classList.contains('show')) {
580
- setTimeout(() => {
581
- document.addEventListener('click', closeModelDropdownInput);
582
- }, 0);
583
  }
584
  }
585
-
586
  function closeModelDropdownInput() {
587
  document.getElementById('model-dropdown-input').classList.remove('show');
588
  document.removeEventListener('click', closeModelDropdownInput);
589
  }
590
-
591
  function selectModelInput(model, event) {
592
  event.stopPropagation();
593
  currentModel = model;
594
-
595
  const toggleBtn = document.getElementById('model-toggle-btn');
596
  const iconElement = document.getElementById('model-icon-input');
597
  const options = document.querySelectorAll('.model-option-input');
598
-
599
- // ุชุญุฏูŠุซ ุงู„ุฃูŠู‚ูˆู†ุฉ ูˆุงู„ูƒู„ุงุณุงุช
600
  toggleBtn.classList.remove('flash-active', 'pro-active');
601
-
602
  if (model === 'flash') {
603
  iconElement.setAttribute('data-lucide', 'zap');
604
  toggleBtn.classList.add('flash-active');
605
  } else {
606
- iconElement.setAttribute('data-lucide', 'crown');
607
  toggleBtn.classList.add('pro-active');
608
  }
609
-
610
- // ุฅุนุงุฏุฉ ุชู‡ูŠุฆุฉ ุงู„ุฃูŠู‚ูˆู†ุฉ
611
  lucide.createIcons();
612
-
613
- // ุชุญุฏูŠุซ ุงู„ุญุงู„ุฉ ุงู„ู†ุดุทุฉ
614
  options.forEach(opt => {
615
  opt.classList.remove('active');
616
- if (opt.dataset.model === model) {
617
- opt.classList.add('active');
618
- }
619
  });
620
-
621
- // ุญูุธ ุงู„ุชูุถูŠู„
622
  localStorage.setItem('genisi_model', model);
623
-
624
- // ุฅุบู„ุงู‚ ุงู„ู‚ุงุฆู…ุฉ
625
  closeModelDropdownInput();
626
  }
627
-
628
- // ุชุญู…ูŠู„ ุงู„ู†ู…ูˆุฐุฌ ุงู„ู…ุญููˆุธ
629
  function loadSavedModel() {
630
  const saved = localStorage.getItem('genisi_model') || 'flash';
631
  currentModel = saved;
632
-
633
  const toggleBtn = document.getElementById('model-toggle-btn');
634
  const iconElement = document.getElementById('model-icon-input');
635
  const options = document.querySelectorAll('.model-option-input');
636
-
637
  toggleBtn.classList.remove('flash-active', 'pro-active');
638
-
639
  if (saved === 'flash') {
640
  iconElement.setAttribute('data-lucide', 'zap');
641
  toggleBtn.classList.add('flash-active');
642
  } else {
643
- iconElement.setAttribute('data-lucide', 'crown');
644
  toggleBtn.classList.add('pro-active');
645
  }
646
-
647
  options.forEach(opt => {
648
  opt.classList.remove('active');
649
- if (opt.dataset.model === saved) {
650
- opt.classList.add('active');
651
- }
652
  });
653
  }
654
 
655
- // Markdown renderer
656
  const renderer = new marked.Renderer();
657
  renderer.code = function(token) {
658
  const codeText = typeof token === 'string' ? token : token.text || '';
659
  const lang = typeof token === 'string' ? arguments[1] : token.lang || 'text';
660
- const escapedCode = String(codeText).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
661
- return `
662
- <div class="code-container">
663
- <div class="code-header">
664
- <span>${lang}</span>
665
- <button class="copy-btn" onclick="copyCode(this, '${encodeURIComponent(codeText)}')">
666
- <i data-lucide="copy"></i> Copy
667
- </button>
668
- </div>
669
- <pre><code class="language-${lang}">${escapedCode}</code></pre>
670
- </div>`;
671
  };
672
- renderer.table = function(header, body) {
673
- if (typeof header === 'object' && header !== null && 'header' in header) {
674
- const token = header;
675
- const hdr = token.header.map(cell => `<th>${cell.tokens.map(t=>t.raw||'').join('')}</th>`).join('');
676
- const rows = (token.rows || []).map(row => `<tr>${row.map(cell => `<td>${cell.tokens.map(t=>t.raw||'').join('')}</td>`).join('')}</tr>`).join('');
677
- return `<div class="table-wrapper"><table><thead><tr>${hdr}</tr></thead><tbody>${rows}</tbody></table></div>`;
678
- }
679
- return `<div class="table-wrapper"><table><thead>${header}</thead><tbody>${body}</tbody></table></div>`;
680
- };
681
- renderer.html = function(html) { return html; };
682
- marked.setOptions({ renderer: renderer, breaks: true, gfm: true });
683
-
684
- function renderKaTeX(el) {
685
- if (window.renderMathInElement) {
686
- renderMathInElement(el, {
687
- delimiters: [
688
- {left: '$$', right: '$$', display: true},
689
- {left: '$', right: '$', display: false},
690
- {left: '\\(', right: '\\)', display: false},
691
- {left: '\\[', right: '\\]', display: true}
692
- ],
693
- throwOnError: false
694
- });
695
- }
696
- }
697
 
698
  function copyCode(btn, encodedCode) {
699
  navigator.clipboard.writeText(decodeURIComponent(encodedCode)).then(() => {
700
- const originalHTML = btn.innerHTML;
701
  btn.innerHTML = '<i data-lucide="check"></i> Copied!';
702
  lucide.createIcons();
703
- setTimeout(() => {
704
- btn.innerHTML = '<i data-lucide="copy"></i> Copy';
705
- lucide.createIcons();
706
- }, 2000);
707
  });
708
  }
709
 
710
  const saveChats = () => localStorage.setItem('genisi_chats', JSON.stringify(chats));
711
  const getChat = (id) => chats.find(c => c.id === (id ?? activeChatId));
712
-
713
  function esc(t){ return String(t).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
714
-
715
- function scrollToBottom() {
716
- const area = document.getElementById('chat-area');
717
- area.scrollTop = area.scrollHeight;
718
- }
719
 
720
  // Files
721
  async function handleFiles(files) {
@@ -728,12 +618,10 @@ async function handleFiles(files) {
728
  pendingFiles.push({ name: f.name, type: f.type || 'application/octet-stream', data: data, isText: isText });
729
  renderFileChips();
730
  };
731
- if (isText) reader.readAsText(f);
732
- else reader.readAsDataURL(f);
733
  }
734
  document.getElementById('file-input').value = '';
735
  }
736
-
737
  function renderFileChips() {
738
  const container = document.getElementById('file-chips');
739
  container.innerHTML = '';
@@ -743,29 +631,32 @@ function renderFileChips() {
743
  });
744
  lucide.createIcons();
745
  }
746
-
747
  function removeFile(idx) { pendingFiles.splice(idx, 1); renderFileChips(); }
748
 
749
  // Theme
750
  function applyTheme(t){
751
  document.documentElement.setAttribute('data-theme',t);
752
  localStorage.setItem('genisi_theme',t);
753
- const themeIcon = document.getElementById('theme-btn').querySelector('i');
754
- themeIcon.setAttribute('data-lucide', t === 'dark' ? 'moon' : 'sun');
755
  document.getElementById('dark-toggle').checked = t==='dark';
756
  lucide.createIcons();
757
  }
758
-
759
- function toggleTheme(){
760
- applyTheme(document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark');
761
- }
762
-
763
- function applyThemeToggle(){
764
- applyTheme(document.getElementById('dark-toggle').checked?'dark':'light');
 
 
 
 
 
 
765
  }
766
 
767
  // UI
768
- function toggleSidebar(){ document.getElementById('sidebar').classList.toggle('collapsed'); }
769
  function openSettings(){ document.getElementById('settings-modal').classList.add('open'); document.getElementById('lang-select').value=currentLang;}
770
  function closeSettings(){ document.getElementById('settings-modal').classList.remove('open'); }
771
 
@@ -775,8 +666,7 @@ function renderChatList(){
775
  [...chats].reverse().forEach(chat=>{
776
  const div=document.createElement('div');
777
  div.className='chat-item'+(chat.id===activeChatId?' active':'');
778
- div.innerHTML=`<i data-lucide="message-square"></i><span class="ct">${esc(chat.title||'Chat')}</span>
779
- <button class="del-btn" onclick="deleteChat('${chat.id}',event)"><i data-lucide="trash-2"></i></button>`;
780
  div.onclick = () => loadChat(chat.id);
781
  list.appendChild(div);
782
  });
@@ -787,10 +677,8 @@ function renderWelcome(){
787
  const t = i18n[currentLang];
788
  document.getElementById('topbar-title').textContent='Genisi AI';
789
  document.getElementById('chat-area').innerHTML=`
790
- <div class="welcome" id="welcome">
791
- <img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" class="w-logo" alt="Genisi"/>
792
- <div class="w-title">${t.welcomeTitle}</div>
793
- <div class="w-sub">${t.welcomeSub}</div>
794
  <div class="chips">
795
  <div class="chip" onclick="quickSend('${t.c1}')"><i data-lucide="brain"></i> ${t.c1}</div>
796
  <div class="chip" onclick="quickSend('${t.c2}')"><i data-lucide="code-2"></i> ${t.c2}</div>
@@ -801,79 +689,32 @@ function renderWelcome(){
801
  lucide.createIcons();
802
  }
803
 
804
- function newChat(){
805
- activeChatId=null;
806
- pendingFiles=[];
807
- renderFileChips();
808
- document.getElementById('msg-input').value='';
809
- renderWelcome();
810
- renderChatList();
811
- }
812
-
813
  function loadChat(id){
814
- activeChatId=id;
815
- const chat=getChat(id); if(!chat) return;
816
  document.getElementById('topbar-title').textContent=chat.title;
817
- const area=document.getElementById('chat-area');
818
- area.innerHTML='';
819
- chat.history.forEach((entry, idx)=>{
820
- appendBubble('user', entry.user, false, entry.uiFiles);
821
- appendBubble('bot', entry.bot, true);
822
- appendMemBadge(idx+1, chat.history.length);
823
- });
824
- scrollToBottom();
825
- renderChatList();
826
- lucide.createIcons();
827
- }
828
-
829
- function deleteChat(id,e){
830
- e.stopPropagation();
831
- chats=chats.filter(c=>c.id!==id);
832
- saveChats();
833
- if(activeChatId===id) newChat();
834
- else renderChatList();
835
- }
836
-
837
- function clearAllChats(){
838
- chats=[];
839
- saveChats();
840
- newChat();
841
- closeSettings();
842
- }
843
-
844
- function stopGeneration() {
845
- if (currentAbortController) {
846
- currentAbortController.abort();
847
- }
848
  }
 
 
 
849
 
850
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
851
- // MESSAGING
852
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
853
  function quickSend(text){ document.getElementById('msg-input').value=text; sendMessage(); }
854
-
855
- function handleKey(e){
856
- if(e.key==='Enter'&&!e.shiftKey){
857
- e.preventDefault();
858
- sendMessage();
859
- }
860
- }
861
-
862
- function autoResize(el){
863
- el.style.height='auto';
864
- el.style.height=Math.min(el.scrollHeight,160)+'px';
865
- }
866
 
867
  async function sendMessage(){
868
  if(isGenerating) return;
869
  const input = document.getElementById('msg-input');
870
  let text = input.value.trim();
871
-
872
  if(!text && pendingFiles.length === 0) return;
873
 
874
- const welcome = document.getElementById('welcome');
875
- if(welcome) welcome.remove();
876
-
877
  input.value=''; input.style.height='auto';
878
 
879
  if(!activeChatId){
@@ -886,15 +727,7 @@ async function sendMessage(){
886
  const chat = getChat();
887
  const uiFiles = pendingFiles.map(f => f.name);
888
  const binaryFiles =[];
889
-
890
- pendingFiles.forEach(f => {
891
- if(f.isText) {
892
- text += `\n\n\`\`\`${f.name.split('.').pop()}\n// File: ${f.name}\n${f.data}\n\`\`\``;
893
- } else {
894
- binaryFiles.push({ name: f.name, mime_type: f.type, data: f.data });
895
- }
896
- });
897
-
898
  appendBubble('user', text, false, uiFiles);
899
  pendingFiles =[]; renderFileChips();
900
 
@@ -902,28 +735,47 @@ async function sendMessage(){
902
  document.getElementById('send-btn').style.display = 'none';
903
  document.getElementById('stop-btn').style.display = 'flex';
904
 
 
905
  const botContentDiv = appendBubble('bot', '', true);
 
 
 
906
 
907
- const imageTriggers =["ุงุฑุณู…", "ุตู…ู…", "ุชุฎูŠู„", "ุตูˆุฑุฉ ู„", "draw", "generate", "imagine", "create an image", "ุชุตู…ูŠู…"];
908
- const isImageRequest = imageTriggers.some(trigger => text.toLowerCase().startsWith(trigger));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
909
 
910
  currentAbortController = new AbortController();
911
- let fullBotResponse = "";
 
 
 
912
 
913
  try {
914
  const response = await fetch('/chat', {
915
  method: 'POST',
916
  headers: {'Content-Type':'application/json'},
917
- body: JSON.stringify({
918
- user_id: userId,
919
- message: text,
920
- history: chat.history,
921
- files: binaryFiles,
922
- model: currentModel
923
- }),
924
  signal: currentAbortController.signal
925
  });
926
-
927
  if(!response.ok) throw new Error("Server Error");
928
 
929
  const reader = response.body.getReader();
@@ -934,56 +786,90 @@ async function sendMessage(){
934
  if(done) break;
935
 
936
  const chunk = decoder.decode(value, {stream: true});
937
- fullBotResponse += chunk;
938
 
939
- if (fullBotResponse.includes('<div style="text-align:center;')) {
940
- botContentDiv.innerHTML = fullBotResponse;
941
- } else {
942
- let htmlToRender = marked.parse(fullBotResponse);
943
- if (isImageRequest && !fullBotResponse.includes('<img')) {
944
- htmlToRender += '<div class="skeleton-img"><i data-lucide="palette"></i> ุฌุงุฑูŠ ุงู„ุชุฎูŠู„ ูˆุงู„ุชุตู…ูŠู…...</div>';
 
 
 
 
 
 
 
 
945
  }
946
- botContentDiv.innerHTML = htmlToRender + '<span class="genisi-cursor"></span>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
947
  }
 
948
  lucide.createIcons();
949
  scrollToBottom();
950
  }
951
 
 
952
  const cursor = botContentDiv.querySelector('.genisi-cursor');
953
  if (cursor) cursor.remove();
954
 
955
- if (fullBotResponse.includes('<div style="text-align:center;') && fullBotResponse.includes('<img')) {
956
- botContentDiv.innerHTML = fullBotResponse;
 
957
  } else {
958
  let parsedResponse = null;
959
- try { parsedResponse = JSON.parse(fullBotResponse); } catch(e) {}
960
-
961
  if (parsedResponse && (parsedResponse.image || parsedResponse.url)) {
962
- let imgHtml = '';
963
- if (parsedResponse.image) {
964
- imgHtml = `<img src="data:image/png;base64,${parsedResponse.image}" alt="Generated Image" style="max-width:100%; border-radius:18px; box-shadow:0 8px 25px rgba(0,0,0,0.1);"/>`;
965
- } else if (parsedResponse.url) {
966
- imgHtml = `<img src="${parsedResponse.url}" alt="Generated Image" style="max-width:100%; border-radius:18px; box-shadow:0 8px 25px rgba(0,0,0,0.1);"/>`;
967
- }
968
  botContentDiv.innerHTML = `<div style="text-align:center;">${imgHtml}</div>`;
969
  } else if (parsedResponse && parsedResponse.error) {
970
  botContentDiv.innerHTML = `<span style="color:var(--danger)"><i data-lucide="alert-triangle"></i> ${parsedResponse.error}</span>`;
 
 
971
  } else {
972
- botContentDiv.innerHTML = marked.parse(fullBotResponse);
973
- renderKaTeX(botContentDiv);
 
 
974
  }
975
  }
976
 
977
  lucide.createIcons();
978
- chat.history.push({ user: text, bot: String(fullBotResponse), uiFiles: uiFiles });
 
 
 
 
 
 
 
 
979
  saveChats();
980
  appendMemBadge(chat.history.length, chat.history.length);
981
 
982
  } catch(err){
983
  if (err.name === 'AbortError') {
984
- botContentDiv.innerHTML = marked.parse(fullBotResponse) + `<br><br><span style="color:var(--text3); font-style:italic;"><i data-lucide="ban"></i> ${i18n[currentLang].stopMsg}</span>`;
985
- renderKaTeX(botContentDiv);
986
- chat.history.push({ user: text, bot: String(fullBotResponse) + "\n\n*" + i18n[currentLang].stopMsg + "*", uiFiles: uiFiles });
987
  saveChats();
988
  } else {
989
  botContentDiv.innerHTML = `<span style="color:var(--danger)"><i data-lucide="alert-circle"></i> ${i18n[currentLang].errConnect}: ${err.message}</span>`;
@@ -998,58 +884,63 @@ async function sendMessage(){
998
  }
999
  }
1000
 
1001
- function appendBubble(role, text, isMarkdown, filesList =[]){
1002
  const area=document.getElementById('chat-area');
1003
  const row=document.createElement('div');
1004
  row.className=`msg-row ${role}`;
1005
-
1006
- const av=document.createElement('div');
1007
- av.className='msg-av';
1008
-
1009
- if(role==='bot'){
1010
- av.innerHTML=`<img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" alt="G"/>`;
1011
- } else {
1012
- av.innerHTML='<i data-lucide="user"></i>';
1013
- }
1014
-
1015
- const bub=document.createElement('div');
1016
- bub.className='bubble';
1017
 
1018
  if(filesList && filesList.length > 0) {
1019
- filesList.forEach(name => {
1020
- bub.innerHTML += `<div class="file-prev"><i data-lucide="file"></i> ${esc(name)}</div>`;
1021
- });
1022
  }
1023
 
1024
  const contentDiv = document.createElement('div');
1025
- if(text) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1026
  const safeText = (typeof text === 'object') ? JSON.stringify(text) : String(text);
1027
  if (safeText.includes('<div style="text-align:center;')) {
1028
  contentDiv.innerHTML = safeText;
1029
  } else {
1030
  contentDiv.innerHTML = isMarkdown ? marked.parse(safeText) : esc(safeText).replace(/\n/g, '<br/>');
1031
- if (isMarkdown) renderKaTeX(contentDiv);
1032
  }
1033
  }
1034
- bub.appendChild(contentDiv);
1035
 
1036
- row.appendChild(av);
1037
- row.appendChild(bub);
1038
  area.appendChild(row);
1039
  scrollToBottom();
1040
-
1041
  lucide.createIcons();
1042
  return contentDiv;
1043
  }
1044
 
1045
  function appendMemBadge(count, total){
1046
  const area=document.getElementById('chat-area');
1047
- const b=document.createElement('div');
1048
- b.className='mem-badge';
1049
  b.innerHTML=`<i data-lucide="database"></i> ${i18n[currentLang].memBadge.replace('{c}', count).replace('{t}', total)}`;
1050
- area.appendChild(b);
1051
- scrollToBottom();
1052
- lucide.createIcons();
1053
  }
1054
 
1055
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
@@ -1059,11 +950,9 @@ applyTheme(localStorage.getItem('genisi_theme')||'dark');
1059
  document.getElementById('lang-select').value = currentLang;
1060
  applyI18n();
1061
  loadSavedModel();
1062
-
1063
- // ุชู‡ูŠุฆุฉ Lucide Icons
1064
  lucide.createIcons();
1065
 
1066
- // ุฅุบู„ุงู‚ ุงู„ู‚ุงุฆู…ุฉ ุงู„ู…ู†ุณุฏู„ุฉ ุนู†ุฏ ุงู„ู†ู‚ุฑ ุฎุงุฑุฌู‡ุง
1067
  document.addEventListener('click', function(event) {
1068
  const selector = document.querySelector('.model-selector-input');
1069
  const dropdown = document.getElementById('model-dropdown-input');
@@ -1071,6 +960,14 @@ document.addEventListener('click', function(event) {
1071
  dropdown.classList.remove('show');
1072
  }
1073
  });
 
 
 
 
 
 
 
 
1074
  </script>
1075
  </body>
1076
  </html>
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
  <title>Genisi AI</title>
7
 
8
+ <!-- ๐ŸŒŸ Lucide Icons -->
9
  <script src="https://unpkg.com/lucide@latest"></script>
10
 
11
  <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"/>
 
23
  --user-bubble: #1a2340; --bot-bubble: #161920;
24
  --danger: #f75f5f;
25
  --flash-color: #fbbf24; --pro-color: #a78bfa;
26
+ --thinking-bg: rgba(124, 92, 247, 0.08);
27
+ --thinking-border: rgba(124, 92, 247, 0.2);
28
  --radius: 28px; --radius-sm: 18px; --radius-pill: 50px;
29
  --sidebar-w: 280px; --tr: 0.3s cubic-bezier(.4,0,.2,1);
30
  }
 
34
  --text: #1a1d2e; --text2: #4a5070; --text3: #8a90a8;
35
  --user-bubble: #dde6ff; --bot-bubble: #fff; --accent-soft: rgba(79,142,247,0.1);
36
  --flash-color: #d97706; --pro-color: #7c3aed;
37
+ --thinking-bg: rgba(124, 58, 237, 0.05);
38
+ --thinking-border: rgba(124, 58, 237, 0.15);
39
  }
40
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
41
  html,body{height:100%;font-family:'Cairo',sans-serif;background:var(--bg);color:var(--text); scroll-behavior: smooth;}
 
44
 
45
  .app{display:flex;height:100vh;overflow:hidden}
46
 
47
+ /* SIDEBAR - ู…ุฎููŠ ุงูุชุฑุงุถูŠุงู‹ */
48
  .sidebar{width:var(--sidebar-w);min-width:var(--sidebar-w);background:var(--surface);
49
  border-right:1px solid var(--border);display:flex;flex-direction:column;
50
  transition:width var(--tr),min-width var(--tr);overflow:hidden;
51
  border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius);}
52
  [dir="rtl"] .sidebar { border-right:none; border-left:1px solid var(--border); border-radius: 0 var(--radius) var(--radius) 0; }
53
+ .sidebar.collapsed{width:0;min-width:0;border:none;padding:0;}
54
  .sidebar-header{padding:24px 20px 16px;display:flex;align-items:center;gap:12px;}
55
  .s-logo{display:flex;align-items:center;gap:12px;flex:1}
56
  .s-logo img{width:36px;height:36px;border-radius:12px;object-fit:cover; box-shadow: 0 4px 12px var(--accent-soft);}
 
60
  color:#fff;border:none;border-radius:var(--radius-pill);cursor:pointer;font-family:'Cairo',sans-serif;
61
  font-size:.95rem;font-weight:600;display:flex;align-items:center;gap:8px; justify-content:center;
62
  transition:all var(--tr); box-shadow: 0 4px 15px var(--accent-soft);}
63
+ .btn-new svg { width: 18px; height: 18px; }
64
  .btn-new:hover{opacity:.9;transform:translateY(-2px)}
65
  .s-label{padding:10px 20px;font-size:.75rem;font-weight:700;color:var(--text3);letter-spacing:.08em;text-transform:uppercase}
66
  .chats-list{flex:1;overflow-y:auto;padding:4px 12px;display:flex;flex-direction:column;gap:6px}
 
76
  .chat-item:hover .del-btn{opacity:1}
77
  .del-btn:hover{background:rgba(247,95,95,.15); transform:scale(1.1);}
78
  .del-btn svg { width: 16px; height: 16px; }
79
+ .s-footer{padding:16px;display:flex;align-items:center;gap:12px; justify-content: space-between; border-top: 1px solid var(--border); margin-top: auto;}
80
+ .s-footer-actions {display: flex; align-items: center; gap: 8px;}
81
  .btn-icon{width:42px;height:42px;background:var(--surface2);border:none;
82
  border-radius:var(--radius-pill);color:var(--text2);cursor:pointer;display:flex;
83
  align-items:center;justify-content:center;transition:all var(--tr)}
84
  .btn-icon svg { width: 20px; height: 20px; }
85
+ .btn-icon:hover{background:var(--surface3);color:var(--text);}
86
+
87
+ .btn-collapse-sidebar {
88
+ width: 42px; height: 42px; background: var(--surface2); border: none;
89
+ border-radius: var(--radius-pill); color: var(--text2); cursor: pointer;
90
+ display: flex; align-items: center; justify-content: center; transition: all var(--tr);
91
+ }
92
+ .btn-collapse-sidebar svg { width: 20px; height: 20px; }
93
+ .btn-collapse-sidebar:hover { background: var(--surface3); color: var(--text); }
94
 
95
  /* MAIN */
96
  .main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0; background: var(--bg);}
97
  .topbar{height:70px;padding:0 24px;display:flex;align-items:center;gap:16px; flex-shrink:0}
98
+
99
+ .btn-show-sidebar {
100
+ width: 42px; height: 42px; background: var(--surface2); border: none;
101
+ border-radius: var(--radius-pill); color: var(--text2); cursor: pointer;
102
+ display: none; align-items: center; justify-content: center; transition: all var(--tr);
103
+ }
104
+ .btn-show-sidebar svg { width: 20px; height: 20px; }
105
+ .btn-show-sidebar:hover { background: var(--surface3); color: var(--text); }
106
+ .sidebar.collapsed ~ .main .btn-show-sidebar { display: flex; }
107
+
108
  .topbar-title{flex:1;font-size:1.05rem;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
109
 
110
  /* CHAT AREA */
 
122
  .chip svg { width: 18px; height: 18px; color: var(--accent); }
123
  .chip:hover{background:var(--accent-soft);border-color:var(--accent);color:var(--accent); transform:translateY(-2px);}
124
 
125
+ /* MESSAGES */
126
  .msg-row{display:flex;padding:8px 30px;gap:16px; animation: popIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;}
127
+ @keyframes popIn { from { opacity: 0; transform: translateY(15px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
128
  @keyframes fadeSlideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
129
  .msg-row.user{flex-direction:row-reverse}
130
  .msg-av{width:38px;height:38px;border-radius:14px;flex-shrink:0;display:flex;align-items:center;
 
136
  .msg-row.user .msg-av svg { width: 20px; height: 20px; }
137
 
138
  .bubble{max-width:min(700px,80vw);padding:16px 22px;border-radius:var(--radius);
139
+ font-size:.98rem;line-height:1.75;word-break:break-word;}
140
+ .msg-row.bot .bubble{background:transparent;border:none;padding:8px 4px;box-shadow:none;}
141
+ .msg-row.user .bubble{background:var(--user-bubble);border:none;}
142
+
143
+ /* ๐ŸŒŸ ุญุงูˆูŠุฉ ุงู„ุชููƒูŠุฑ ู„ู†ู…ูˆุฐุฌ Pro */
144
+ .thinking-container {
145
+ margin: 16px 0;
146
+ padding: 16px 18px;
147
+ background: var(--thinking-bg);
148
+ border: 1px solid var(--thinking-border);
149
+ border-radius: var(--radius-sm);
150
+ border-left: 3px solid var(--pro-color);
151
+ }
152
+ [dir="rtl"] .thinking-container {
153
+ border-left: 1px solid var(--thinking-border);
154
+ border-right: 3px solid var(--pro-color);
155
+ }
156
 
157
+ .thinking-header {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 10px;
161
+ margin-bottom: 12px;
162
+ color: var(--pro-color);
163
+ font-weight: 600;
164
+ font-size: 0.9rem;
 
 
 
 
 
 
165
  }
166
+
167
+ .thinking-header svg {
168
+ width: 18px;
169
+ height: 18px;
170
+ animation: pulse 1.5s ease-in-out infinite;
171
+ }
172
+
173
+ @keyframes pulse {
174
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
175
+ 50% { opacity: 1; transform: scale(1.1); }
176
+ }
177
+
178
+ .thinking-content {
179
+ color: var(--text2);
180
+ font-size: 0.9rem;
181
+ line-height: 1.7;
182
+ white-space: pre-wrap;
183
+ word-break: break-word;
184
+ }
185
+
186
+ .thinking-content p:last-child {
187
+ margin-bottom: 0;
188
+ }
189
+
190
+ /* ุฑุฏ Pro ุงู„ู†ู‡ุงุฆูŠ */
191
+ .pro-response {
192
+ margin-top: 8px;
193
  }
194
 
195
+ /* GENISI STREAMING EFFECT */
196
+ .genisi-cursor { display: inline-block; width: 12px; height: 12px; margin-inline-start: 6px; background: var(--accent); border-radius: 50%; animation: blink 1s infinite; }
197
+ @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }
198
+
199
  /* MARKDOWN STYLES */
200
  .bubble p{margin-bottom:12px}.bubble p:last-child{margin-bottom:0}
201
  .bubble ul,.bubble ol{padding-inline-start:24px;margin:12px 0}
202
  .bubble li {margin-bottom: 6px;}
203
+ .bubble .table-wrapper{width:100%;overflow-x:auto;margin:16px 0;border-radius:var(--radius-sm);border:1px solid var(--border);}
204
  .bubble table{width:100%;border-collapse:collapse;min-width:400px;}
205
+ .bubble th,.bubble td{border:1px solid var(--border);padding:10px 14px;text-align:start;}
206
  .bubble th{background:var(--surface3);font-weight:600;}
207
 
208
  /* Code Blocks */
209
+ .code-container{position:relative;margin:16px 0;background:var(--surface3);border-radius:var(--radius-sm);border:1px solid var(--border);overflow:hidden;}
210
+ .code-header{display:flex;justify-content:space-between;align-items:center;background:#12141a;padding:8px 16px;font-size:0.8rem;color:var(--text2);}
211
+ .copy-btn{background:var(--surface2);border:none;color:var(--text);cursor:pointer;display:flex;align-items:center;gap:6px;font-size:0.8rem;padding:4px 10px;border-radius:var(--radius-pill);}
212
+ .copy-btn svg { width: 14px; height: 14px; }
213
  .copy-btn:hover{background:var(--accent);color:#fff;}
214
+ .code-container pre{margin:0;padding:16px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:0.85rem;}
 
215
  .bubble code:not(pre code){font-family:'JetBrains Mono',monospace;font-size:.85rem;background:var(--surface3);padding:3px 6px;border-radius:6px;color:var(--accent);}
216
 
217
+ /* KaTeX */
218
+ .katex-display{overflow-x:auto;overflow-y:hidden;padding:8px 0;}
219
  .katex{font-size:1.05em;}
220
 
221
  /* Files */
222
  .file-chip {display:inline-flex; align-items:center; gap:6px; background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius-pill); padding:4px 12px; font-size:0.85rem; margin:0 4px 8px 0; color:var(--text);}
223
+ .file-chip svg { width: 14px; height: 14px; }
224
+ .file-chip button {background:none; border:none; color:var(--danger); cursor:pointer; padding:0; margin-left:4px; display: flex; align-items: center;}
225
+ .file-chip button svg { width: 12px; height: 12px; }
 
226
  .file-prev {display:flex; align-items:center; gap:8px; background:var(--surface3); padding:8px 12px; border-radius:8px; margin-bottom:8px; font-size:0.85rem;}
 
227
 
228
  .mem-badge{font-size:.75rem;color:var(--text3);padding:4px 30px 10px;display:flex;align-items:center;gap:6px; font-weight: 600;}
 
229
 
230
  /* INPUT */
231
  .input-area{padding:0 24px 30px; display:flex; flex-direction:column; align-items:center; flex-shrink:0;}
232
  .input-wrapper{width:100%; max-width:850px; position:relative;}
233
  .file-chips-container {width:100%; max-width:850px; display:flex; flex-wrap:wrap; margin-bottom:8px;}
234
  .input-box{display:flex;align-items:flex-end;gap:12px;background:var(--surface);
235
+ border:1px solid var(--border);border-radius:32px;padding:12px 16px;
236
  transition:all var(--tr); box-shadow: 0 8px 30px rgba(0,0,0,0.15);}
237
+ .input-box:focus-within{border-color:var(--accent); box-shadow: 0 8px 30px var(--accent-soft);}
238
 
239
+ .btn-attach{background:none; border:none; color:var(--text3); cursor:pointer; padding:6px; border-radius:50%; display:flex; align-items:center; justify-content:center;}
240
  .btn-attach svg { width: 22px; height: 22px; }
241
  .btn-attach:hover{color:var(--accent); background:var(--surface2);}
242
 
 
244
  font-family:'Cairo',sans-serif;font-size:.98rem;resize:none;max-height:160px;line-height:1.6; padding: 6px 0;}
245
  .input-box textarea::placeholder{color:var(--text3)}
246
 
247
+ .btn-send, .btn-stop {width:42px;height:42px; border:none;border-radius:50%;cursor:pointer;color:#fff;display:flex;align-items:center;justify-content:center;transition:all var(--tr); flex-shrink:0;}
248
  .btn-send {background:linear-gradient(135deg,var(--accent),var(--accent2));}
249
  .btn-send svg { width: 20px; height: 20px; }
250
+ .btn-send:hover{opacity:.9;transform:scale(1.08);}
251
  .btn-send:disabled{opacity:.4;cursor:not-allowed;transform:none;}
252
 
 
253
  .btn-stop {background:var(--surface3); color:var(--text); border:1px solid var(--border);}
254
  .btn-stop svg { width: 20px; height: 20px; }
255
+ .btn-stop:hover {background:var(--danger); color:#fff;}
256
 
257
+ /* MODEL SELECTOR */
258
+ .model-selector-input { position: relative; display: flex; align-items: center; }
 
 
 
 
 
259
  .model-toggle-btn {
260
+ display: flex; align-items: center; justify-content: center; width: 36px; height: 36px;
261
+ background: var(--surface2); border: 1px solid var(--border); border-radius: 50%;
262
+ cursor: pointer; transition: all var(--tr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
+ .model-toggle-btn:hover { background: var(--surface3); border-color: var(--accent); transform: scale(1.05); }
265
+ .model-toggle-btn svg { width: 18px; height: 18px; }
266
+ .model-toggle-btn.flash-active svg { color: var(--flash-color); }
267
+ .model-toggle-btn.pro-active svg { color: var(--pro-color); }
268
 
269
  .model-dropdown-input {
270
+ position: absolute; bottom: calc(100% + 8px); left: 0; background: var(--surface);
271
+ border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px;
272
+ min-width: 220px; box-shadow: 0 10px 40px rgba(0,0,0,0.4); z-index: 100;
273
+ opacity: 0; visibility: hidden; transform: translateY(10px); transition: all var(--tr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  }
275
+ [dir="rtl"] .model-dropdown-input { left: auto; right: 0; }
276
+ .model-dropdown-input.show { opacity: 1; visibility: visible; transform: translateY(0); }
277
 
278
  .model-option-input {
279
+ display: flex; align-items: center; gap: 12px; padding: 12px 14px;
280
+ border-radius: var(--radius-sm); cursor: pointer; transition: background var(--tr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  }
282
+ .model-option-input:hover { background: var(--surface2); }
283
+ .model-option-input.active { background: var(--accent-soft); border: 1px solid var(--accent); }
284
+ .model-option-input svg { width: 22px; height: 22px; flex-shrink: 0; }
285
+ .model-info-input { flex: 1; }
286
+ .model-name-input { font-weight: 600; font-size: 0.95rem; color: var(--text); display: flex; align-items: center; gap: 6px; }
287
+ .model-desc-input { font-size: 0.75rem; color: var(--text3); margin-top: 2px; }
288
+ .model-badge-input { padding: 2px 8px; border-radius: var(--radius-pill); font-size: 0.65rem; font-weight: 700; color: #fff; }
289
 
290
  /* MODAL */
291
  .overlay{position:fixed;inset:0;z-index:999;background:rgba(0,0,0,.6);display:flex;
 
298
  .overlay.open .modal{transform:translateY(0) scale(1)}
299
  .m-header{display:flex;align-items:center;gap:12px;margin-bottom:24px}
300
  .m-title{font-size:1.3rem;font-weight:700;flex:1}
301
+ .m-close{background:var(--surface2);border:none;color:var(--text);cursor:pointer;width:36px;height:36px; border-radius:50%; display:flex; align-items:center; justify-content:center;}
302
  .m-close svg { width: 20px; height: 20px; }
303
  .m-close:hover{background:var(--danger); color:#fff;}
304
  .s-row{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--border)}
 
306
  .s-desc{font-size:.8rem;color:var(--text3);margin-top:4px}
307
  .toggle{position:relative;width:48px;height:26px}
308
  .toggle input{opacity:0;width:0;height:0}
309
+ .tslider{position:absolute;inset:0;background:var(--surface3);border-radius:var(--radius-pill);cursor:pointer;}
310
  .tslider::before{content:'';position:absolute;width:20px;height:20px;border-radius:50%;
311
+ background:#fff;top:3px;left:4px;transition:all var(--tr);}
312
  .toggle input:checked+.tslider{background:var(--accent)}
313
  .toggle input:checked+.tslider::before{transform:translateX(20px)}
314
 
315
+ select.s-input { width:auto; padding:8px 16px; background:var(--surface2); border:1px solid var(--border); border-radius:var(--radius-pill); color:var(--text); cursor:pointer; font-family:'Cairo',sans-serif;}
316
  .btn-save{width:100%;margin-top:24px;padding:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));
317
+ color:#fff;border:none;border-radius:var(--radius-pill);cursor:pointer;font-size:1rem;font-weight:700; display: flex; align-items: center; justify-content: center; gap: 8px;}
318
  .btn-save svg { width: 18px; height: 18px; }
 
319
  .btn-danger{padding:8px 18px;background:rgba(247,95,95,.12);border:1px solid var(--danger);
320
+ color:var(--danger);border-radius:var(--radius-pill);cursor:pointer;font-size:.9rem;font-weight:600; display: flex; align-items: center; gap: 6px;}
321
  .btn-danger svg { width: 16px; height: 16px; }
322
  .btn-danger:hover{background:var(--danger); color:#fff;}
323
 
 
324
  @media (max-width: 640px) {
325
  .msg-row { padding: 6px 12px; gap: 10px; }
326
  .bubble { max-width: 92vw; padding: 12px 14px; font-size: .93rem; }
 
327
  .topbar { height: 56px; padding: 0 14px; }
328
  .input-area { padding: 0 10px 18px; }
 
 
329
  .w-title { font-size: 1.8rem; }
 
 
330
  }
331
  </style>
332
  </head>
333
  <body>
334
  <div class="app">
335
 
336
+ <aside class="sidebar collapsed" id="sidebar">
337
  <div class="sidebar-header">
338
  <div class="s-logo">
339
  <img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" alt="Genisi"/>
 
347
  <div class="s-label" id="i18n-chats-label">CHATS</div>
348
  <div class="chats-list" id="chats-list"></div>
349
  <div class="s-footer">
350
+ <div class="s-footer-actions">
351
+ <button class="btn-icon" onclick="openSettings()"><i data-lucide="settings"></i></button>
352
+ <button class="btn-icon" id="theme-btn" onclick="toggleTheme()"><i data-lucide="moon"></i></button>
353
+ </div>
354
+ <!-- ๐ŸŒŸ ุฒุฑ ุฅุฎูุงุก ุงู„ุณุงูŠุฏ ุจุงุฑ - ุฏุงุฎู„ ุงู„ุณุงูŠุฏ ุจุงุฑ -->
355
+ <button class="btn-collapse-sidebar" onclick="toggleSidebar()" title="ุฅุฎูุงุก ุงู„ู‚ุงุฆู…ุฉ">
356
+ <i data-lucide="panel-left-close"></i>
357
+ </button>
358
  </div>
359
  </aside>
360
 
361
  <main class="main">
362
  <div class="topbar">
363
+ <!-- ๐ŸŒŸ ุฒุฑ ุฅุธู‡ุงุฑ ุงู„ุณุงูŠุฏ ุจุงุฑ - ูŠุธู‡ุฑ ูู‚ุท ุนู†ุฏู…ุง ูŠูƒูˆู† ู…ุฎููŠุงู‹ -->
364
+ <button class="btn-show-sidebar" onclick="toggleSidebar()" title="ุฅุธู‡ุงุฑ ุงู„ู‚ุงุฆู…ุฉ">
365
+ <i data-lucide="panel-left-open"></i>
366
+ </button>
367
  <div class="topbar-title" id="topbar-title">Genisi AI</div>
368
  </div>
369
 
 
376
  <input type="file" id="file-input" multiple hidden onchange="handleFiles(this.files)" />
377
  <button class="btn-attach" onclick="document.getElementById('file-input').click()"><i data-lucide="paperclip"></i></button>
378
 
 
379
  <div class="model-selector-input">
380
  <div class="model-toggle-btn" id="model-toggle-btn" onclick="toggleModelDropdownInput(event)">
381
  <i id="model-icon-input" data-lucide="zap"></i>
 
392
  </div>
393
  </div>
394
  <div class="model-option-input" data-model="pro" onclick="selectModelInput('pro', event)">
395
+ <i data-lucide="brain" style="color: var(--pro-color);"></i>
396
  <div class="model-info-input">
397
  <div class="model-name-input">
398
  Genisi Pro
399
  <span class="model-badge-input" style="background: var(--pro-color);">๐Ÿ’Ž Pro</span>
400
  </div>
401
+ <div class="model-desc-input">ูŠููƒุฑ ุจุนู…ู‚ุŒ ุชุญู„ูŠู„ ูˆุฅุจุฏุงุน</div>
402
  </div>
403
  </div>
404
  </div>
 
455
  en: {
456
  newChat: "New Chat", chatsLabel: "CHATS", settings: "Settings", lang: "Language",
457
  dark: "Dark Mode", del: "Delete All Chats", delDesc: "This action cannot be undone",
458
+ delBtn: "Delete", saveBtn: "Close", placeholder: "Type a message... (Enter to send)",
459
  welcomeTitle: "Welcome to Genisi", welcomeSub: "Your smart assistant by AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
460
  c1: "What is AI?", c2: "Write Python code", c3: "Summarize a topic", c4: "Design an image in space ๐Ÿš€",
461
+ errConnect: "Connection Error", memBadge: "Memory: {c}/{t} messages", stopMsg: "[Generation stopped by user]",
462
+ thinking: "Genisi Pro is thinking..."
463
  },
464
  ar: {
465
  newChat: "ู…ุญุงุฏุซุฉ ุฌุฏูŠุฏุฉ", chatsLabel: "ุงู„ู…ุญุงุฏุซุงุช", settings: "ุงู„ุฅุนุฏุงุฏุงุช", lang: "ุงู„ู„ุบุฉ",
 
467
  delBtn: "ุญุฐู", saveBtn: "ุฅุบู„ุงู‚", placeholder: "ุงูƒุชุจ ุฑุณุงู„ุชูƒ... (Enter ู„ู„ุฅุฑุณุงู„)",
468
  welcomeTitle: "ู…ุฑุญุจู‹ุง ููŠ Genisi", welcomeSub: "ู…ุณุงุนุฏูƒ ุงู„ุฐูƒูŠ ู…ู† AnesNT โ€” ูˆู„ุงูŠุฉ ุจุงุชู†ุฉ ๐Ÿ‡ฉ๐Ÿ‡ฟ",
469
  c1: "ู…ุง ู‡ูˆ ุงู„ุฐูƒุงุก ุงู„ุงุตุทู†๏ฟฝ๏ฟฝุนูŠุŸ", c2: "ุงูƒุชุจ ูƒูˆุฏ ุจุงูŠุซูˆู†", c3: "ู„ุฎุต ู„ูŠ ู…ูˆุถูˆุนุงู‹", c4: "ุตู…ู… ุตูˆุฑุฉ ุจุงู„ูุถุงุก ๐Ÿš€",
470
+ errConnect: "ุฎุทุฃ ููŠ ุงู„ุงุชุตุงู„", memBadge: "ุงู„ุฐุงูƒุฑุฉ: {c}/{t} ุฑุณุงุฆู„", stopMsg: "[ุชู… ุฅูŠู‚ุงู ุงู„ุชูˆู„ูŠุฏ]",
471
+ thinking: "Genisi Pro ูŠููƒุฑ..."
472
  },
473
  fr: {
474
  newChat: "Nouvelle Disc.", chatsLabel: "DISCUSSIONS", settings: "Paramรจtres", lang: "Langue",
 
476
  delBtn: "Supprimer", saveBtn: "Fermer", placeholder: "ร‰crivez votre message...",
477
  welcomeTitle: "Bienvenue sur Genisi", welcomeSub: "Votre assistant intelligent par AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
478
  c1: "Qu'est-ce que l'IA?", c2: "Code Python", c3: "Rรฉsumer un sujet", c4: "Crรฉer une image ๐Ÿš€",
479
+ errConnect: "Erreur de connexion", memBadge: "Mรฉmoire: {c}/{t} msgs", stopMsg: "[Gรฉnรฉration arrรชtรฉe]",
480
+ thinking: "Genisi Pro rรฉflรฉchit..."
481
  },
482
  es: {
483
  newChat: "Nuevo Chat", chatsLabel: "CHATS", settings: "Ajustes", lang: "Idioma",
 
485
  delBtn: "Borrar", saveBtn: "Cerrar", placeholder: "Escribe tu mensaje...",
486
  welcomeTitle: "Bienvenido a Genisi", welcomeSub: "Tu asistente por AnesNT โ€” Batna ๐Ÿ‡ฉ๐Ÿ‡ฟ",
487
  c1: "ยฟQuรฉ es la IA?", c2: "Cรณdigo Python", c3: "Resumir", c4: "Diseรฑar imagen ๐Ÿš€",
488
+ errConnect: "Error de conexiรณn", memBadge: "Memoria: {c}/{t} msgs", stopMsg: "[Generaciรณn detenida]",
489
+ thinking: "Genisi Pro estรก pensando..."
490
  }
491
  };
492
 
 
510
 
511
  if(!activeChatId) renderWelcome();
512
  renderChatList();
 
 
513
  setTimeout(() => lucide.createIcons(), 10);
514
  }
515
 
516
+ function changeLanguage(val) { currentLang = val; localStorage.setItem('genisi_lang', val); applyI18n(); }
 
 
 
 
517
 
518
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
519
+ // STATE
520
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
521
  const genId = () => Date.now().toString(36) + Math.random().toString(36).substring(2, 6);
522
+ let userId = localStorage.getItem('genisi_user_id') || ('user_' + genId());
523
+ localStorage.setItem('genisi_user_id', userId);
 
 
 
 
524
 
525
  let chats = JSON.parse(localStorage.getItem('genisi_chats') || '[]');
526
  let activeChatId = null;
 
529
  let currentAbortController = null;
530
  let currentModel = 'flash';
531
 
532
+ // ๐ŸŒŸ ุฏูˆุงู„ ุงู„ู†ู…ูˆุฐุฌ
533
  function toggleModelDropdownInput(event) {
534
  event.stopPropagation();
535
  const dropdown = document.getElementById('model-dropdown-input');
536
  dropdown.classList.toggle('show');
 
537
  if (dropdown.classList.contains('show')) {
538
+ setTimeout(() => document.addEventListener('click', closeModelDropdownInput), 0);
 
 
539
  }
540
  }
 
541
  function closeModelDropdownInput() {
542
  document.getElementById('model-dropdown-input').classList.remove('show');
543
  document.removeEventListener('click', closeModelDropdownInput);
544
  }
 
545
  function selectModelInput(model, event) {
546
  event.stopPropagation();
547
  currentModel = model;
 
548
  const toggleBtn = document.getElementById('model-toggle-btn');
549
  const iconElement = document.getElementById('model-icon-input');
550
  const options = document.querySelectorAll('.model-option-input');
 
 
551
  toggleBtn.classList.remove('flash-active', 'pro-active');
 
552
  if (model === 'flash') {
553
  iconElement.setAttribute('data-lucide', 'zap');
554
  toggleBtn.classList.add('flash-active');
555
  } else {
556
+ iconElement.setAttribute('data-lucide', 'brain');
557
  toggleBtn.classList.add('pro-active');
558
  }
 
 
559
  lucide.createIcons();
 
 
560
  options.forEach(opt => {
561
  opt.classList.remove('active');
562
+ if (opt.dataset.model === model) opt.classList.add('active');
 
 
563
  });
 
 
564
  localStorage.setItem('genisi_model', model);
 
 
565
  closeModelDropdownInput();
566
  }
 
 
567
  function loadSavedModel() {
568
  const saved = localStorage.getItem('genisi_model') || 'flash';
569
  currentModel = saved;
 
570
  const toggleBtn = document.getElementById('model-toggle-btn');
571
  const iconElement = document.getElementById('model-icon-input');
572
  const options = document.querySelectorAll('.model-option-input');
 
573
  toggleBtn.classList.remove('flash-active', 'pro-active');
 
574
  if (saved === 'flash') {
575
  iconElement.setAttribute('data-lucide', 'zap');
576
  toggleBtn.classList.add('flash-active');
577
  } else {
578
+ iconElement.setAttribute('data-lucide', 'brain');
579
  toggleBtn.classList.add('pro-active');
580
  }
 
581
  options.forEach(opt => {
582
  opt.classList.remove('active');
583
+ if (opt.dataset.model === saved) opt.classList.add('active');
 
 
584
  });
585
  }
586
 
587
+ // Markdown
588
  const renderer = new marked.Renderer();
589
  renderer.code = function(token) {
590
  const codeText = typeof token === 'string' ? token : token.text || '';
591
  const lang = typeof token === 'string' ? arguments[1] : token.lang || 'text';
592
+ const escapedCode = String(codeText).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
593
+ return `<div class="code-container"><div class="code-header"><span>${lang}</span><button class="copy-btn" onclick="copyCode(this, '${encodeURIComponent(codeText)}')"><i data-lucide="copy"></i> Copy</button></div><pre><code>${escapedCode}</code></pre></div>`;
 
 
 
 
 
 
 
 
 
594
  };
595
+ marked.setOptions({ renderer, breaks: true, gfm: true });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
  function copyCode(btn, encodedCode) {
598
  navigator.clipboard.writeText(decodeURIComponent(encodedCode)).then(() => {
 
599
  btn.innerHTML = '<i data-lucide="check"></i> Copied!';
600
  lucide.createIcons();
601
+ setTimeout(() => { btn.innerHTML = '<i data-lucide="copy"></i> Copy'; lucide.createIcons(); }, 2000);
 
 
 
602
  });
603
  }
604
 
605
  const saveChats = () => localStorage.setItem('genisi_chats', JSON.stringify(chats));
606
  const getChat = (id) => chats.find(c => c.id === (id ?? activeChatId));
 
607
  function esc(t){ return String(t).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
608
+ function scrollToBottom() { const area = document.getElementById('chat-area'); area.scrollTop = area.scrollHeight; }
 
 
 
 
609
 
610
  // Files
611
  async function handleFiles(files) {
 
618
  pendingFiles.push({ name: f.name, type: f.type || 'application/octet-stream', data: data, isText: isText });
619
  renderFileChips();
620
  };
621
+ if (isText) reader.readAsText(f); else reader.readAsDataURL(f);
 
622
  }
623
  document.getElementById('file-input').value = '';
624
  }
 
625
  function renderFileChips() {
626
  const container = document.getElementById('file-chips');
627
  container.innerHTML = '';
 
631
  });
632
  lucide.createIcons();
633
  }
 
634
  function removeFile(idx) { pendingFiles.splice(idx, 1); renderFileChips(); }
635
 
636
  // Theme
637
  function applyTheme(t){
638
  document.documentElement.setAttribute('data-theme',t);
639
  localStorage.setItem('genisi_theme',t);
640
+ document.getElementById('theme-btn').querySelector('i').setAttribute('data-lucide', t === 'dark' ? 'moon' : 'sun');
 
641
  document.getElementById('dark-toggle').checked = t==='dark';
642
  lucide.createIcons();
643
  }
644
+ function toggleTheme(){ applyTheme(document.documentElement.getAttribute('data-theme')==='dark'?'light':'dark'); }
645
+ function applyThemeToggle(){ applyTheme(document.getElementById('dark-toggle').checked?'dark':'light'); }
646
+
647
+ // ๐ŸŒŸ Sidebar toggle
648
+ function toggleSidebar(){
649
+ const sidebar = document.getElementById('sidebar');
650
+ sidebar.classList.toggle('collapsed');
651
+ // ุชุญุฏูŠุซ ุฃูŠู‚ูˆู†ุฉ ุงู„ุฒุฑ ุฏุงุฎู„ ุงู„ุณุงูŠุฏ ุจุงุฑ
652
+ const collapseBtn = sidebar.querySelector('.btn-collapse-sidebar i');
653
+ if (collapseBtn) {
654
+ collapseBtn.setAttribute('data-lucide', sidebar.classList.contains('collapsed') ? 'panel-left-open' : 'panel-left-close');
655
+ lucide.createIcons();
656
+ }
657
  }
658
 
659
  // UI
 
660
  function openSettings(){ document.getElementById('settings-modal').classList.add('open'); document.getElementById('lang-select').value=currentLang;}
661
  function closeSettings(){ document.getElementById('settings-modal').classList.remove('open'); }
662
 
 
666
  [...chats].reverse().forEach(chat=>{
667
  const div=document.createElement('div');
668
  div.className='chat-item'+(chat.id===activeChatId?' active':'');
669
+ div.innerHTML=`<i data-lucide="message-square"></i><span class="ct">${esc(chat.title||'Chat')}</span><button class="del-btn" onclick="deleteChat('${chat.id}',event)"><i data-lucide="trash-2"></i></button>`;
 
670
  div.onclick = () => loadChat(chat.id);
671
  list.appendChild(div);
672
  });
 
677
  const t = i18n[currentLang];
678
  document.getElementById('topbar-title').textContent='Genisi AI';
679
  document.getElementById('chat-area').innerHTML=`
680
+ <div class="welcome"><img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" class="w-logo" alt="Genisi"/>
681
+ <div class="w-title">${t.welcomeTitle}</div><div class="w-sub">${t.welcomeSub}</div>
 
 
682
  <div class="chips">
683
  <div class="chip" onclick="quickSend('${t.c1}')"><i data-lucide="brain"></i> ${t.c1}</div>
684
  <div class="chip" onclick="quickSend('${t.c2}')"><i data-lucide="code-2"></i> ${t.c2}</div>
 
689
  lucide.createIcons();
690
  }
691
 
692
+ function newChat(){ activeChatId=null; pendingFiles=[]; renderFileChips(); document.getElementById('msg-input').value=''; renderWelcome(); renderChatList(); }
 
 
 
 
 
 
 
 
693
  function loadChat(id){
694
+ activeChatId=id; const chat=getChat(id); if(!chat) return;
 
695
  document.getElementById('topbar-title').textContent=chat.title;
696
+ const area=document.getElementById('chat-area'); area.innerHTML='';
697
+ chat.history.forEach((entry, idx)=>{ appendBubble('user', entry.user, false, entry.uiFiles); appendBubble('bot', entry.bot, true, null, entry.thinking); appendMemBadge(idx+1, chat.history.length); });
698
+ scrollToBottom(); renderChatList(); lucide.createIcons();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
699
  }
700
+ function deleteChat(id,e){ e.stopPropagation(); chats=chats.filter(c=>c.id!==id); saveChats(); if(activeChatId===id) newChat(); else renderChatList(); }
701
+ function clearAllChats(){ chats=[]; saveChats(); newChat(); closeSettings(); }
702
+ function stopGeneration() { if (currentAbortController) currentAbortController.abort(); }
703
 
704
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
705
+ // MESSAGING ู…ุน ุฏุนู… ุงู„ุชููƒูŠุฑ ู„ู€ Pro
706
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
707
  function quickSend(text){ document.getElementById('msg-input').value=text; sendMessage(); }
708
+ function handleKey(e){ if(e.key==='Enter'&&!e.shiftKey){ e.preventDefault(); sendMessage(); } }
709
+ function autoResize(el){ el.style.height='auto'; el.style.height=Math.min(el.scrollHeight,160)+'px'; }
 
 
 
 
 
 
 
 
 
 
710
 
711
  async function sendMessage(){
712
  if(isGenerating) return;
713
  const input = document.getElementById('msg-input');
714
  let text = input.value.trim();
 
715
  if(!text && pendingFiles.length === 0) return;
716
 
717
+ const welcome = document.getElementById('welcome'); if(welcome) welcome.remove();
 
 
718
  input.value=''; input.style.height='auto';
719
 
720
  if(!activeChatId){
 
727
  const chat = getChat();
728
  const uiFiles = pendingFiles.map(f => f.name);
729
  const binaryFiles =[];
730
+ pendingFiles.forEach(f => { if(f.isText) text += `\n\n\`\`\`${f.name.split('.').pop()}\n// File: ${f.name}\n${f.data}\n\`\`\``; else binaryFiles.push({ name: f.name, mime_type: f.type, data: f.data }); });
 
 
 
 
 
 
 
 
731
  appendBubble('user', text, false, uiFiles);
732
  pendingFiles =[]; renderFileChips();
733
 
 
735
  document.getElementById('send-btn').style.display = 'none';
736
  document.getElementById('stop-btn').style.display = 'flex';
737
 
738
+ // ุฅู†ุดุงุก ูู‚ุงุนุฉ ุงู„ุจูˆุช
739
  const botContentDiv = appendBubble('bot', '', true);
740
+ let thinkingContainer = null;
741
+ let thinkingContent = null;
742
+ let responseContainer = null;
743
 
744
+ // ุฅุฐุง ูƒุงู† ุงู„ู†ู…ูˆุฐุฌ ProุŒ ู†ุถูŠู ุญุงูˆูŠุฉ ุงู„ุชููƒูŠุฑ
745
+ if (currentModel === 'pro') {
746
+ const t = i18n[currentLang];
747
+ thinkingContainer = document.createElement('div');
748
+ thinkingContainer.className = 'thinking-container';
749
+ thinkingContainer.innerHTML = `
750
+ <div class="thinking-header">
751
+ <i data-lucide="brain"></i>
752
+ <span>${t.thinking}</span>
753
+ </div>
754
+ <div class="thinking-content" id="thinking-content"></div>
755
+ `;
756
+ botContentDiv.appendChild(thinkingContainer);
757
+
758
+ responseContainer = document.createElement('div');
759
+ responseContainer.className = 'pro-response';
760
+ botContentDiv.appendChild(responseContainer);
761
+
762
+ thinkingContent = document.getElementById('thinking-content');
763
+ lucide.createIcons();
764
+ }
765
 
766
  currentAbortController = new AbortController();
767
+ let fullResponse = "";
768
+ let thinkingText = "";
769
+ let finalResponse = "";
770
+ let isThinkingPhase = currentModel === 'pro';
771
 
772
  try {
773
  const response = await fetch('/chat', {
774
  method: 'POST',
775
  headers: {'Content-Type':'application/json'},
776
+ body: JSON.stringify({ user_id: userId, message: text, history: chat.history, files: binaryFiles, model: currentModel }),
 
 
 
 
 
 
777
  signal: currentAbortController.signal
778
  });
 
779
  if(!response.ok) throw new Error("Server Error");
780
 
781
  const reader = response.body.getReader();
 
786
  if(done) break;
787
 
788
  const chunk = decoder.decode(value, {stream: true});
789
+ fullResponse += chunk;
790
 
791
+ // ๐ŸŒŸ ู…ุนุงู„ุฌุฉ ุฎุงุตุฉ ู„ู†ู…ูˆุฐุฌ Pro: ูุตู„ ุงู„ุชููƒูŠุฑ ุนู† ุงู„ุฑุฏ ุงู„ู†ู‡ุงุฆูŠ
792
+ if (currentModel === 'pro') {
793
+ // ุงู„ุจุญุซ ุนู† ุนู„ุงู…ุฉ ู†ู‡ุงูŠุฉ ุงู„ุชููƒูŠุฑ (ูŠู…ูƒู† ุชุฎุตูŠุตู‡ุง ุญุณุจ ุงู„ุจุงูƒ-ุฅู†ุฏ)
794
+ const thinkingEndMarker = "";
795
+ const markerIndex = fullResponse.indexOf(thinkingEndMarker);
796
+
797
+ if (markerIndex !== -1 && isThinkingPhase) {
798
+ // ุงู†ุชู‡ู‰ ุงู„ุชููƒูŠุฑุŒ ู†ุนุฑุถ ุงู„ุฑุฏ ุงู„ู†ู‡ุงุฆูŠ
799
+ thinkingText = fullResponse.substring(0, markerIndex);
800
+ finalResponse = fullResponse.substring(markerIndex + thinkingEndMarker.length);
801
+ isThinkingPhase = false;
802
+
803
+ if (thinkingContent) {
804
+ thinkingContent.innerHTML = marked.parse(thinkingText);
805
  }
806
+ if (responseContainer) {
807
+ responseContainer.innerHTML = marked.parse(finalResponse) + '<span class="genisi-cursor"></span>';
808
+ }
809
+ } else if (isThinkingPhase) {
810
+ // ู„ุง ูŠุฒุงู„ ููŠ ู…ุฑุญู„ุฉ ุงู„ุชููƒูŠุฑ
811
+ thinkingText = fullResponse;
812
+ if (thinkingContent) {
813
+ thinkingContent.innerHTML = marked.parse(thinkingText) + '<span class="genisi-cursor"></span>';
814
+ }
815
+ } else {
816
+ // ู…ุฑุญู„ุฉ ุงู„ุฑุฏ ุงู„ู†ู‡ุงุฆูŠ
817
+ finalResponse = fullResponse.substring(fullResponse.indexOf(thinkingEndMarker) + thinkingEndMarker.length);
818
+ if (responseContainer) {
819
+ responseContainer.innerHTML = marked.parse(finalResponse) + '<span class="genisi-cursor"></span>';
820
+ }
821
+ }
822
+ } else {
823
+ // ู†ู…ูˆุฐุฌ Flash - ุนุฑุถ ู…ุจุงุดุฑ
824
+ botContentDiv.innerHTML = marked.parse(fullResponse) + '<span class="genisi-cursor"></span>';
825
  }
826
+
827
  lucide.createIcons();
828
  scrollToBottom();
829
  }
830
 
831
+ // ุฅุฒุงู„ุฉ ุงู„ู…ุคุดุฑ
832
  const cursor = botContentDiv.querySelector('.genisi-cursor');
833
  if (cursor) cursor.remove();
834
 
835
+ // ู…ุนุงู„ุฌุฉ ุงู„ุตูˆุฑ
836
+ if (fullResponse.includes('<div style="text-align:center;') && fullResponse.includes('<img')) {
837
+ botContentDiv.innerHTML = fullResponse;
838
  } else {
839
  let parsedResponse = null;
840
+ try { parsedResponse = JSON.parse(fullResponse); } catch(e) {}
 
841
  if (parsedResponse && (parsedResponse.image || parsedResponse.url)) {
842
+ let imgHtml = parsedResponse.image ? `<img src="data:image/png;base64,${parsedResponse.image}" style="max-width:100%; border-radius:18px;"/>` : `<img src="${parsedResponse.url}" style="max-width:100%; border-radius:18px;"/>`;
 
 
 
 
 
843
  botContentDiv.innerHTML = `<div style="text-align:center;">${imgHtml}</div>`;
844
  } else if (parsedResponse && parsedResponse.error) {
845
  botContentDiv.innerHTML = `<span style="color:var(--danger)"><i data-lucide="alert-triangle"></i> ${parsedResponse.error}</span>`;
846
+ } else if (currentModel !== 'pro') {
847
+ botContentDiv.innerHTML = marked.parse(fullResponse);
848
  } else {
849
+ // ู„ู„ู†ู…ูˆุฐุฌ ProุŒ ู†ุถู…ู† ุฃู† ุงู„ุฑุฏ ุงู„ู†ู‡ุงุฆูŠ ู…ุนุฑูˆุถ ุจุดูƒู„ ุตุญูŠุญ
850
+ if (responseContainer) {
851
+ responseContainer.innerHTML = marked.parse(finalResponse || fullResponse);
852
+ }
853
  }
854
  }
855
 
856
  lucide.createIcons();
857
+
858
+ // ุญูุธ ุงู„ู…ุญุงุฏุซุฉ ู…ุน ู…ุนู„ูˆู…ุงุช ุงู„ุชููƒูŠุฑ
859
+ const historyEntry = {
860
+ user: text,
861
+ bot: String(fullResponse),
862
+ uiFiles: uiFiles,
863
+ thinking: currentModel === 'pro' ? thinkingText : null
864
+ };
865
+ chat.history.push(historyEntry);
866
  saveChats();
867
  appendMemBadge(chat.history.length, chat.history.length);
868
 
869
  } catch(err){
870
  if (err.name === 'AbortError') {
871
+ botContentDiv.innerHTML = marked.parse(fullResponse) + `<br><br><span style="color:var(--text3);"><i data-lucide="ban"></i> ${i18n[currentLang].stopMsg}</span>`;
872
+ chat.history.push({ user: text, bot: String(fullResponse) + "\n\n*" + i18n[currentLang].stopMsg + "*", uiFiles: uiFiles });
 
873
  saveChats();
874
  } else {
875
  botContentDiv.innerHTML = `<span style="color:var(--danger)"><i data-lucide="alert-circle"></i> ${i18n[currentLang].errConnect}: ${err.message}</span>`;
 
884
  }
885
  }
886
 
887
+ function appendBubble(role, text, isMarkdown, filesList = [], thinkingText = null){
888
  const area=document.getElementById('chat-area');
889
  const row=document.createElement('div');
890
  row.className=`msg-row ${role}`;
891
+ const av=document.createElement('div'); av.className='msg-av';
892
+ if(role==='bot'){ av.innerHTML=`<img src="https://copilot.microsoft.com/th/id/BCO.f29916dd-b0c1-4089-87cd-43d099a7d1a6.png" alt="G"/>`; }
893
+ else { av.innerHTML='<i data-lucide="user"></i>'; }
894
+ const bub=document.createElement('div'); bub.className='bubble';
 
 
 
 
 
 
 
 
895
 
896
  if(filesList && filesList.length > 0) {
897
+ filesList.forEach(name => { bub.innerHTML += `<div class="file-prev"><i data-lucide="file"></i> ${esc(name)}</div>`; });
 
 
898
  }
899
 
900
  const contentDiv = document.createElement('div');
901
+
902
+ // ๐ŸŒŸ ุฅุฐุง ูƒุงู† ู‡ู†ุงูƒ ุชููƒูŠุฑ (ู†ู…ูˆุฐุฌ Pro)ุŒ ู†ุนุฑุถู‡ ุฃูˆู„ุงู‹
903
+ if (thinkingText) {
904
+ const t = i18n[currentLang];
905
+ const thinkingContainer = document.createElement('div');
906
+ thinkingContainer.className = 'thinking-container';
907
+ thinkingContainer.innerHTML = `
908
+ <div class="thinking-header">
909
+ <i data-lucide="brain"></i>
910
+ <span>${t.thinking}</span>
911
+ </div>
912
+ <div class="thinking-content">${marked.parse(thinkingText)}</div>
913
+ `;
914
+ contentDiv.appendChild(thinkingContainer);
915
+
916
+ // ุฅุถุงูุฉ ุงู„ุฑุฏ ุงู„ู†ู‡ุงุฆูŠ
917
+ const responseDiv = document.createElement('div');
918
+ responseDiv.className = 'pro-response';
919
+ const mainResponse = String(text).replace(thinkingText + " instant", "").trim();
920
+ responseDiv.innerHTML = marked.parse(mainResponse || text);
921
+ contentDiv.appendChild(responseDiv);
922
+ } else if(text) {
923
  const safeText = (typeof text === 'object') ? JSON.stringify(text) : String(text);
924
  if (safeText.includes('<div style="text-align:center;')) {
925
  contentDiv.innerHTML = safeText;
926
  } else {
927
  contentDiv.innerHTML = isMarkdown ? marked.parse(safeText) : esc(safeText).replace(/\n/g, '<br/>');
 
928
  }
929
  }
 
930
 
931
+ bub.appendChild(contentDiv);
932
+ row.appendChild(av); row.appendChild(bub);
933
  area.appendChild(row);
934
  scrollToBottom();
 
935
  lucide.createIcons();
936
  return contentDiv;
937
  }
938
 
939
  function appendMemBadge(count, total){
940
  const area=document.getElementById('chat-area');
941
+ const b=document.createElement('div'); b.className='mem-badge';
 
942
  b.innerHTML=`<i data-lucide="database"></i> ${i18n[currentLang].memBadge.replace('{c}', count).replace('{t}', total)}`;
943
+ area.appendChild(b); scrollToBottom(); lucide.createIcons();
 
 
944
  }
945
 
946
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
 
950
  document.getElementById('lang-select').value = currentLang;
951
  applyI18n();
952
  loadSavedModel();
 
 
953
  lucide.createIcons();
954
 
955
+ // ุฅุบู„ุงู‚ ุงู„ู‚ุงุฆู…ุฉ ุงู„ู…ู†ุณุฏู„ุฉ
956
  document.addEventListener('click', function(event) {
957
  const selector = document.querySelector('.model-selector-input');
958
  const dropdown = document.getElementById('model-dropdown-input');
 
960
  dropdown.classList.remove('show');
961
  }
962
  });
963
+
964
+ // ๐ŸŒŸ ุงู„ุชุฃูƒุฏ ู…ู† ุฃู† ุงู„ุณุงูŠุฏ ุจุงุฑ ู…ุฎููŠ ุงูุชุฑุงุถูŠุงู‹
965
+ document.addEventListener('DOMContentLoaded', function() {
966
+ document.getElementById('sidebar').classList.add('collapsed');
967
+ const collapseBtn = document.querySelector('.btn-collapse-sidebar i');
968
+ if (collapseBtn) collapseBtn.setAttribute('data-lucide', 'panel-left-open');
969
+ lucide.createIcons();
970
+ });
971
  </script>
972
  </body>
973
  </html>