andrewwda commited on
Commit
ed542be
·
verified ·
1 Parent(s): f3ffbbd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +298 -438
app.py CHANGED
@@ -19,6 +19,7 @@
19
  body {
20
  background: #0a0c15;
21
  overflow: hidden;
 
22
  -webkit-tap-highlight-color: transparent;
23
  touch-action: pan-x pan-y;
24
  }
@@ -30,26 +31,25 @@
30
  display: flex;
31
  height: 100vh;
32
  width: 100%;
33
- position: relative;
34
  }
35
 
36
  /* =========================
37
- SIDEBAR - DARK NEO (MOBILE FIRST)
38
  ========================= */
39
  .sidebar {
40
- width: 280px;
41
  background: linear-gradient(180deg, #0f111a 0%, #080a10 100%);
42
- backdrop-filter: blur(20px);
43
  color: #e8edff;
44
  display: flex;
45
  flex-direction: column;
46
- padding: 20px 16px;
47
  position: fixed;
48
- left: -100%;
49
  top: 0;
50
  height: 100vh;
51
- z-index: 1000;
52
- transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
53
  border-right: 1px solid rgba(0, 255, 255, 0.15);
54
  box-shadow: 4px 0 30px rgba(0, 0, 0, 0.5);
55
  }
@@ -61,52 +61,44 @@
61
  .sidebar-header {
62
  display: flex;
63
  align-items: center;
64
- margin-bottom: 25px;
65
- padding-bottom: 15px;
66
  border-bottom: 1px solid rgba(0, 212, 255, 0.2);
67
  }
68
 
69
  .sidebar-logo {
70
- width: 50px;
71
- height: 50px;
72
- border-radius: 14px;
73
  background: linear-gradient(135deg, #00d4ff, #0099ff);
74
  display: flex;
75
  align-items: center;
76
  justify-content: center;
77
- font-size: 28px;
78
- margin-right: 12px;
79
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
80
- flex-shrink: 0;
81
  }
82
 
 
83
  .sidebar-logo-img {
84
- width: 100%;
85
- height: 100%;
86
- border-radius: 14px;
87
  object-fit: cover;
88
  }
89
 
90
- .sidebar-header div:last-child {
91
- min-width: 0;
92
- flex: 1;
93
- }
94
-
95
  .sidebar h2 {
96
- font-size: 22px;
97
  letter-spacing: -0.5px;
98
  background: linear-gradient(135deg, #00d4ff, #4d9eff);
99
  -webkit-background-clip: text;
100
  background-clip: text;
101
  color: transparent;
102
- white-space: nowrap;
103
- overflow: hidden;
104
- text-overflow: ellipsis;
105
  }
106
 
107
  .sidebar p {
108
  color: #6c7a9e;
109
- font-size: 11px;
110
  }
111
 
112
  .new-chat {
@@ -114,43 +106,42 @@
114
  border: none;
115
  color: #0a0c15;
116
  font-weight: bold;
117
- padding: 12px;
118
- border-radius: 12px;
119
- font-size: 14px;
120
  cursor: pointer;
121
- margin-bottom: 20px;
122
  transition: all 0.2s;
123
  box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
124
- width: 100%;
125
  }
126
 
127
- .new-chat:active {
128
- transform: scale(0.98);
 
129
  }
130
 
131
  .chat-history {
132
  flex: 1;
133
  overflow-y: auto;
134
- -webkit-overflow-scrolling: touch;
135
  }
136
 
137
  .history-item {
138
  background: rgba(18, 22, 40, 0.7);
139
  backdrop-filter: blur(5px);
140
- padding: 12px 14px;
141
- border-radius: 12px;
142
- margin-bottom: 10px;
143
  cursor: pointer;
144
  transition: 0.2s;
145
  border: 1px solid rgba(0, 212, 255, 0.1);
146
  font-weight: 500;
147
- font-size: 13px;
148
- word-break: break-word;
149
  }
150
 
151
- .history-item:active {
152
- background: rgba(0, 212, 255, 0.2);
153
- transform: translateX(4px);
 
154
  }
155
 
156
  /* =========================
@@ -160,17 +151,14 @@
160
  position: fixed;
161
  width: 100%;
162
  height: 100vh;
163
- background: rgba(0, 0, 0, 0.7);
164
- backdrop-filter: blur(4px);
165
- z-index: 999;
166
  display: none;
167
- opacity: 0;
168
- transition: opacity 0.3s ease;
169
  }
170
 
171
  .overlay.active {
172
  display: block;
173
- opacity: 1;
174
  }
175
 
176
  /* =========================
@@ -181,95 +169,82 @@
181
  flex-direction: column;
182
  width: 100%;
183
  background: #0a0c15;
184
- position: relative;
185
  }
186
 
187
  /* =========================
188
  TOPBAR - NEO MASCULINE
189
  ========================= */
190
  .topbar {
191
- height: 65px;
192
  background: rgba(10, 12, 21, 0.95);
193
  backdrop-filter: blur(10px);
194
  display: flex;
195
  align-items: center;
196
- padding: 0 16px;
197
  color: #e8edff;
198
  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
199
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3);
200
- position: sticky;
201
- top: 0;
202
- z-index: 10;
203
  }
204
 
205
  .menu-btn {
206
- font-size: 24px;
207
- margin-right: 12px;
208
  cursor: pointer;
209
  transition: 0.2s;
210
  color: #00d4ff;
211
- padding: 8px;
212
- border-radius: 8px;
213
  }
214
 
215
- .menu-btn:active {
216
- background: rgba(0, 212, 255, 0.1);
217
- transform: scale(0.95);
218
  }
219
 
 
220
  .logo {
221
- width: 42px;
222
- height: 42px;
223
- border-radius: 12px;
224
  background: linear-gradient(135deg, #00d4ff, #0099ff);
225
  display: flex;
226
  align-items: center;
227
  justify-content: center;
228
- margin-right: 12px;
229
  box-shadow: 0 0 12px rgba(0, 212, 255, 0.5);
230
  overflow: hidden;
231
- flex-shrink: 0;
232
  }
233
 
234
  .logo-img {
235
  width: 100%;
236
  height: 100%;
237
  object-fit: cover;
238
- border-radius: 12px;
239
  }
240
 
 
241
  .logo-icon {
242
- font-size: 24px;
243
- }
244
-
245
- .title {
246
- min-width: 0;
247
- flex: 1;
248
  }
249
 
250
  .title h1 {
251
- font-size: 18px;
252
  letter-spacing: -0.5px;
253
  background: linear-gradient(135deg, #e8edff, #00d4ff);
254
  -webkit-background-clip: text;
255
  background-clip: text;
256
  color: transparent;
257
- white-space: nowrap;
258
- overflow: hidden;
259
- text-overflow: ellipsis;
260
  }
261
 
262
  .title p {
263
- font-size: 10px;
264
  color: #00d4ff;
265
  display: flex;
266
  align-items: center;
267
- gap: 4px;
268
  }
269
 
270
  .title p::before {
271
  content: "●";
272
- font-size: 8px;
273
  color: #00ff88;
274
  text-shadow: 0 0 5px #00ff88;
275
  }
@@ -280,31 +255,30 @@
280
  .chat-area {
281
  flex: 1;
282
  overflow-y: auto;
283
- padding: 16px;
284
  background: linear-gradient(180deg, #0a0c15 0%, #0f111a 100%);
 
285
  -webkit-overflow-scrolling: touch;
286
  }
287
 
288
  .chat-area::-webkit-scrollbar {
289
- width: 4px;
290
  }
291
-
292
  .chat-area::-webkit-scrollbar-track {
293
  background: #1a1f2e;
294
  border-radius: 10px;
295
  }
296
-
297
  .chat-area::-webkit-scrollbar-thumb {
298
  background: #00d4ff;
299
  border-radius: 10px;
300
  }
301
 
302
  .message {
303
- max-width: 85%;
304
- padding: 12px 16px;
305
- border-radius: 18px;
306
- margin-bottom: 12px;
307
- font-size: 14px;
308
  line-height: 1.5;
309
  word-wrap: break-word;
310
  animation: fadeInUp 0.3s ease;
@@ -326,9 +300,8 @@
326
  backdrop-filter: blur(10px);
327
  border: 1px solid rgba(0, 212, 255, 0.2);
328
  color: #d0d9ff;
329
- border-bottom-left-radius: 4px;
330
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
331
- margin-right: auto;
332
  }
333
 
334
  .user {
@@ -336,8 +309,8 @@
336
  color: #0a0c15;
337
  font-weight: 600;
338
  margin-left: auto;
339
- border-bottom-right-radius: 4px;
340
- box-shadow: 0 2px 8px rgba(0, 212, 255, 0.3);
341
  }
342
 
343
  /* =========================
@@ -345,26 +318,23 @@
345
  ========================= */
346
  .input-area {
347
  display: flex;
348
- padding: 12px 16px;
349
- gap: 10px;
350
  background: rgba(10, 12, 21, 0.95);
351
  backdrop-filter: blur(10px);
352
  border-top: 1px solid rgba(0, 212, 255, 0.2);
353
- position: sticky;
354
- bottom: 0;
355
  }
356
 
357
  input {
358
  flex: 1;
359
  border: 1px solid rgba(0, 212, 255, 0.3);
360
  background: rgba(18, 22, 40, 0.8);
361
- border-radius: 24px;
362
- padding: 12px 18px;
363
- font-size: 14px;
364
  outline: none;
365
  color: #e8edff;
366
  transition: all 0.2s;
367
- -webkit-appearance: none;
368
  }
369
 
370
  input:focus {
@@ -381,150 +351,131 @@
381
  background: linear-gradient(135deg, #00d4ff, #0099ff);
382
  color: #0a0c15;
383
  font-weight: bold;
384
- padding: 0 20px;
385
- border-radius: 24px;
386
- font-size: 14px;
387
  cursor: pointer;
388
  transition: 0.2s;
389
  display: flex;
390
  align-items: center;
391
- gap: 6px;
392
- white-space: nowrap;
393
  }
394
 
395
- button.send:active {
396
- transform: scale(0.97);
 
397
  }
398
 
399
  /* =========================
400
- RESPONSIVE UNTUK TABLET & DESKTOP
401
  ========================= */
402
- @media (min-width: 768px) {
403
  .sidebar {
404
- width: 300px;
405
- left: -320px;
406
  }
407
-
408
  .message {
409
- max-width: 70%;
410
  font-size: 15px;
411
  }
412
-
413
  .topbar {
414
- height: 72px;
 
 
 
 
 
415
  padding: 0 24px;
416
  }
417
-
 
 
 
 
 
 
 
418
  .menu-btn {
419
- font-size: 28px;
420
- margin-right: 20px;
421
  }
422
-
423
  .logo {
424
- width: 50px;
425
- height: 50px;
426
  }
427
-
428
  .title h1 {
429
- font-size: 22px;
430
  }
431
-
432
  .title p {
433
- font-size: 12px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  }
435
  }
436
 
437
- /* Landscape mode optimization */
438
  @media (max-width: 900px) and (orientation: landscape) {
439
- .sidebar {
440
- width: 260px;
441
- }
442
-
443
  .topbar {
444
  height: 55px;
445
  }
446
-
447
- .logo {
448
- width: 36px;
449
- height: 36px;
450
- }
451
-
452
- .title h1 {
453
- font-size: 16px;
454
- }
455
-
456
  .chat-area {
457
- padding: 10px;
458
  }
459
-
460
  .message {
461
- margin-bottom: 8px;
462
- padding: 8px 12px;
463
  }
464
-
465
  .input-area {
466
- padding: 8px 12px;
467
  }
468
-
469
  input {
470
- padding: 8px 14px;
471
  }
472
-
473
  button.send {
474
- padding: 0 16px;
475
  }
476
  }
477
-
478
- /* Loading animation */
479
- .loading-dots {
480
- display: inline-flex;
481
- gap: 4px;
482
- align-items: center;
483
- }
484
-
485
- .loading-dots span {
486
- width: 6px;
487
- height: 6px;
488
- border-radius: 50%;
489
- background: #00d4ff;
490
- animation: bounce 1.4s infinite ease-in-out both;
491
- }
492
-
493
- .loading-dots span:nth-child(1) { animation-delay: -0.32s; }
494
- .loading-dots span:nth-child(2) { animation-delay: -0.16s; }
495
-
496
- @keyframes bounce {
497
- 0%, 80%, 100% { transform: scale(0); }
498
- 40% { transform: scale(1); }
499
- }
500
-
501
- /* Toast notification */
502
- .toast {
503
- position: fixed;
504
- bottom: 80px;
505
- left: 50%;
506
- transform: translateX(-50%) translateY(100px);
507
- background: rgba(0, 212, 255, 0.9);
508
- color: #0a0c15;
509
- padding: 10px 20px;
510
- border-radius: 30px;
511
- font-size: 13px;
512
- font-weight: bold;
513
- z-index: 1001;
514
- transition: transform 0.3s ease;
515
- white-space: nowrap;
516
- pointer-events: none;
517
- }
518
-
519
- .toast.show {
520
- transform: translateX(-50%) translateY(0);
521
- }
522
  </style>
523
  </head>
524
  <body>
525
 
526
- <div class="overlay" id="overlay"></div>
527
- <div class="toast" id="toast"></div>
528
 
529
  <div class="app">
530
 
@@ -532,6 +483,7 @@
532
  <div class="sidebar" id="sidebar">
533
  <div class="sidebar-header">
534
  <div class="sidebar-logo" id="sidebarLogo">
 
535
  🐱‍👤
536
  </div>
537
  <div>
@@ -539,7 +491,7 @@
539
  <p>Neo Intelligence</p>
540
  </div>
541
  </div>
542
- <button class="new-chat" onclick="createNewChat()"> New Chat</button>
543
  <div class="chat-history" id="chatHistory"></div>
544
  </div>
545
 
@@ -547,8 +499,9 @@
547
  <div class="main">
548
  <!-- TOPBAR -->
549
  <div class="topbar">
550
- <div class="menu-btn" onclick="toggleSidebar()">☰</div>
551
  <div class="logo" id="mainLogo">
 
552
  🐱‍👤
553
  </div>
554
  <div class="title">
@@ -567,303 +520,210 @@
567
 
568
  <!-- INPUT -->
569
  <div class="input-area">
570
- <input type="text" id="messageInput" placeholder="Ketik pesan..." autocomplete="off" />
571
  <button class="send" onclick="sendMessage()">
572
- 📤 Kirim
573
  </button>
574
  </div>
575
  </div>
576
  </div>
577
 
578
  <script>
579
- let currentChatId = null;
580
- let isLoading = false;
581
 
582
  // =========================================
583
  // KONFIGURASI LOGO
584
  // =========================================
 
 
 
 
 
585
  const LOGO_CONFIG = {
586
- mode: "emoji", // "emoji", "image", atau "svg"
587
- emoji: "🐱‍🚀",
588
- imageUrl: "/static/logo.png",
589
- imageUrl2: "https://cdn-icons-png.flaticon.com/512/1998/1998592.png",
590
- svgIcon: "⚡🐱⚡"
591
  }
592
 
593
  function initLogos() {
594
- const sidebarLogo = document.getElementById("sidebarLogo");
595
- const mainLogo = document.getElementById("mainLogo");
596
 
597
  if (LOGO_CONFIG.mode === "emoji") {
598
- sidebarLogo.innerHTML = LOGO_CONFIG.emoji;
599
- sidebarLogo.style.fontSize = "28px";
600
- sidebarLogo.style.background = "linear-gradient(135deg, #00d4ff, #0099ff)";
601
- mainLogo.innerHTML = LOGO_CONFIG.emoji;
602
- mainLogo.style.fontSize = "24px";
603
- mainLogo.style.background = "linear-gradient(135deg, #00d4ff, #0099ff)";
 
 
 
604
  } else if (LOGO_CONFIG.mode === "image") {
605
- sidebarLogo.innerHTML = `<img src="${LOGO_CONFIG.imageUrl}" class="sidebar-logo-img" onerror="this.src='${LOGO_CONFIG.imageUrl2}'">`;
606
- sidebarLogo.style.background = "transparent";
607
- mainLogo.innerHTML = `<img src="${LOGO_CONFIG.imageUrl}" class="logo-img" onerror="this.src='${LOGO_CONFIG.imageUrl2}'">`;
608
- mainLogo.style.background = "transparent";
609
- } else {
610
- sidebarLogo.innerHTML = LOGO_CONFIG.svgIcon;
611
- sidebarLogo.style.fontSize = "26px";
612
- mainLogo.innerHTML = LOGO_CONFIG.svgIcon;
613
- mainLogo.style.fontSize = "22px";
614
- }
615
- }
616
-
617
- function showToast(message, isError = false) {
618
- const toast = document.getElementById("toast");
619
- toast.textContent = message;
620
- toast.style.background = isError ? "rgba(255, 68, 68, 0.95)" : "rgba(0, 212, 255, 0.95)";
621
- toast.classList.add("show");
622
- setTimeout(() => {
623
- toast.classList.remove("show");
624
- }, 2000);
625
- }
626
-
627
- function toggleSidebar() {
628
- const sidebar = document.getElementById("sidebar");
629
- const overlay = document.getElementById("overlay");
630
-
631
- if (sidebar.classList.contains("active")) {
632
- closeSidebar();
633
  } else {
634
- openSidebar();
 
 
 
 
 
 
 
 
635
  }
636
  }
637
 
638
  function openSidebar() {
639
- document.getElementById("sidebar").classList.add("active");
640
- document.getElementById("overlay").classList.add("active");
641
  }
642
 
643
  function closeSidebar() {
644
- document.getElementById("sidebar").classList.remove("active");
645
- document.getElementById("overlay").classList.remove("active");
646
  }
647
 
648
- // Close sidebar when clicking overlay
649
- document.getElementById("overlay").addEventListener("click", closeSidebar);
650
-
651
  async function createNewChat() {
652
- try {
653
- const response = await fetch("/new_chat", { method: "POST" });
654
- const data = await response.json();
655
- currentChatId = data.chat_id;
656
- document.getElementById("chatArea").innerHTML = `
657
- <div class="message bot">
658
- <span style="font-weight: bold;">⚡ ChibiCat Neo</span><br>
659
- Halo bro! Ada yang bisa gue bantu? 🔥😎
660
- </div>
661
- `;
662
- await loadChats();
663
- closeSidebar();
664
- showToast("Chat baru dibuat!");
665
- } catch (error) {
666
- console.error("Error creating new chat:", error);
667
- showToast("Gagal membuat chat baru", true);
668
- }
669
  }
670
 
671
  async function loadChats() {
672
- try {
673
- const response = await fetch("/get_chats");
674
- const chats = await response.json();
675
- const history = document.getElementById("chatHistory");
676
-
677
- if (chats.length === 0) {
678
- history.innerHTML = `<div style="text-align: center; color: #6c7a9e; padding: 20px;">Belum ada chat</div>`;
679
- return;
680
- }
681
-
682
- history.innerHTML = "";
683
- chats.forEach(chat => {
684
- const div = document.createElement("div");
685
- div.className = "history-item";
686
- div.innerHTML = `💬 ${escapeHtml(chat.title)}`;
687
- div.onclick = () => loadChat(chat.chat_id);
688
- history.appendChild(div);
689
- });
690
- } catch (error) {
691
- console.error("Error loading chats:", error);
692
- }
693
  }
694
 
695
  async function loadChat(chatId) {
696
- try {
697
- currentChatId = chatId;
698
- const response = await fetch(`/load_chat/${chatId}`);
699
- const messages = await response.json();
700
- const chatArea = document.getElementById("chatArea");
701
- chatArea.innerHTML = "";
702
- messages.forEach(msg => {
703
- const role = msg.role === "user" ? "user" : "bot";
704
- chatArea.innerHTML += `<div class="message ${role}">${escapeHtml(msg.content)}</div>`;
705
- });
706
- chatArea.scrollTop = chatArea.scrollHeight;
707
- closeSidebar();
708
- } catch (error) {
709
- console.error("Error loading chat:", error);
710
- showToast("Gagal memuat chat", true);
711
- }
712
- }
713
-
714
- function addLoadingIndicator() {
715
- const chatArea = document.getElementById("chatArea");
716
- const loadingDiv = document.createElement("div");
717
- loadingDiv.className = "message bot";
718
- loadingDiv.id = "loadingIndicator";
719
- loadingDiv.innerHTML = `<span style="font-weight: bold;">⚡ ChibiCat Neo</span><br><div class="loading-dots"><span></span><span></span><span></span></div>`;
720
- chatArea.appendChild(loadingDiv);
721
- chatArea.scrollTop = chatArea.scrollHeight;
722
- }
723
-
724
- function removeLoadingIndicator() {
725
- const indicator = document.getElementById("loadingIndicator");
726
- if (indicator) indicator.remove();
727
  }
728
 
729
  async function sendMessage() {
730
- if (isLoading) return;
731
-
732
- const input = document.getElementById("messageInput");
733
- const message = input.value.trim();
734
- if (message === "") return;
735
 
736
- isLoading = true;
737
- const chatArea = document.getElementById("chatArea");
738
 
739
- // Add user message
740
- chatArea.innerHTML += `<div class="message user">${escapeHtml(message)}</div>`;
741
- input.value = "";
742
- chatArea.scrollTop = chatArea.scrollHeight;
743
-
744
- // Add loading indicator
745
- addLoadingIndicator();
746
 
747
  try {
748
  const response = await fetch("/chat", {
749
  method: "POST",
750
  headers: { "Content-Type": "application/json" },
751
  body: JSON.stringify({ message: message })
752
- });
753
-
754
- if (!response.ok) throw new Error("Network response was not ok");
755
-
756
- const data = await response.json();
757
- removeLoadingIndicator();
758
- chatArea.innerHTML += `<div class="message bot">${escapeHtml(data.reply)}</div>`;
759
- chatArea.scrollTop = chatArea.scrollHeight;
760
- await loadChats();
761
  } catch (error) {
762
- console.error("Error sending message:", error);
763
- removeLoadingIndicator();
764
- chatArea.innerHTML += `<div class="message bot">⚠️ Server error, coba lagi nanti bro 😭</div>`;
765
- chatArea.scrollTop = chatArea.scrollHeight;
766
- showToast("Gagal mengirim pesan", true);
767
- } finally {
768
- isLoading = false;
769
  }
770
  }
771
 
 
772
  function escapeHtml(text) {
773
- if (!text) return "";
774
- const div = document.createElement('div');
775
- div.textContent = text;
776
- return div.innerHTML;
777
  }
778
 
779
- // Fix untuk keyboard di mobile
780
- function fixMobileKeyboard() {
781
- const input = document.getElementById("messageInput");
782
- const chatArea = document.querySelector('.chat-area');
783
-
784
- input.addEventListener("focus", () => {
785
- setTimeout(() => {
786
- chatArea.scrollTop = chatArea.scrollHeight;
787
- }, 300);
788
- });
789
-
790
- // Prevent zoom on input focus (iOS)
791
- input.style.fontSize = "16px";
792
  }
793
 
794
- // Swipe gesture untuk Android
795
- let touchStartX = 0;
796
- let touchEndX = 0;
797
-
798
- document.addEventListener('touchstart', (e) => {
799
- touchStartX = e.changedTouches[0].screenX;
800
- }, { passive: true });
801
-
802
- document.addEventListener('touchend', (e) => {
803
- touchEndX = e.changedTouches[0].screenX;
804
- const swipeDistance = touchEndX - touchStartX;
805
- const sidebar = document.getElementById("sidebar");
806
 
807
- if (swipeDistance > 50 && touchStartX < 50 && !sidebar.classList.contains("active")) {
808
- openSidebar();
809
- } else if (swipeDistance < -50 && sidebar.classList.contains("active")) {
810
- closeSidebar();
811
- }
812
- }, { passive: true });
813
-
814
- // Enter to send
815
- document.getElementById("messageInput").addEventListener("keypress", (e) => {
816
- if (e.key === "Enter" && !e.shiftKey) {
817
- e.preventDefault();
818
- sendMessage();
819
- }
820
- });
821
-
822
- // Prevent body scroll when sidebar is open
823
- function preventBodyScroll(shouldPrevent) {
824
- if (shouldPrevent) {
825
- document.body.style.overflow = 'hidden';
826
- } else {
827
- document.body.style.overflow = '';
828
  }
829
  }
830
 
831
- // Observer for sidebar state
832
- const observer = new MutationObserver((mutations) => {
833
- mutations.forEach((mutation) => {
834
- if (mutation.attributeName === 'class') {
835
- const sidebar = document.getElementById("sidebar");
836
- preventBodyScroll(sidebar.classList.contains("active"));
837
- }
838
- });
839
- });
840
-
841
- observer.observe(document.getElementById("sidebar"), { attributes: true });
842
 
843
- // Detect device and add appropriate classes
844
- function detectDevice() {
845
- const ua = navigator.userAgent.toLowerCase();
846
- const isAndroid = ua.includes("android");
847
- const isIOS = /iphone|ipad|ipod/.test(ua);
 
 
 
 
 
 
848
 
849
- if (isAndroid) {
850
- document.body.classList.add("android-device");
851
- } else if (isIOS) {
852
- document.body.classList.add("ios-device");
853
  }
854
- }
 
 
 
 
 
855
 
856
- // Initialize everything
857
- initLogos();
858
- loadChats();
859
- detectDevice();
860
- fixMobileKeyboard();
861
-
862
- // Scroll to bottom on initial load
863
- setTimeout(() => {
864
- const chatArea = document.getElementById("chatArea");
865
- chatArea.scrollTop = chatArea.scrollHeight;
866
- }, 100);
867
  </script>
868
 
869
  </body>
 
19
  body {
20
  background: #0a0c15;
21
  overflow: hidden;
22
+ /* TAMBAHAN UNTUK ANDROID */
23
  -webkit-tap-highlight-color: transparent;
24
  touch-action: pan-x pan-y;
25
  }
 
31
  display: flex;
32
  height: 100vh;
33
  width: 100%;
 
34
  }
35
 
36
  /* =========================
37
+ SIDEBAR - DARK NEO
38
  ========================= */
39
  .sidebar {
40
+ width: 300px;
41
  background: linear-gradient(180deg, #0f111a 0%, #080a10 100%);
42
+ backdrop-filter: blur(10px);
43
  color: #e8edff;
44
  display: flex;
45
  flex-direction: column;
46
+ padding: 24px 20px;
47
  position: fixed;
48
+ left: -320px;
49
  top: 0;
50
  height: 100vh;
51
+ z-index: 999;
52
+ transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
53
  border-right: 1px solid rgba(0, 255, 255, 0.15);
54
  box-shadow: 4px 0 30px rgba(0, 0, 0, 0.5);
55
  }
 
61
  .sidebar-header {
62
  display: flex;
63
  align-items: center;
64
+ margin-bottom: 35px;
65
+ padding-bottom: 20px;
66
  border-bottom: 1px solid rgba(0, 212, 255, 0.2);
67
  }
68
 
69
  .sidebar-logo {
70
+ width: 55px;
71
+ height: 55px;
72
+ border-radius: 16px;
73
  background: linear-gradient(135deg, #00d4ff, #0099ff);
74
  display: flex;
75
  align-items: center;
76
  justify-content: center;
77
+ font-size: 32px;
78
+ margin-right: 15px;
79
  box-shadow: 0 0 15px rgba(0, 212, 255, 0.4);
 
80
  }
81
 
82
+ /* GAMBAR LOGO UNTUK SIDEBAR */
83
  .sidebar-logo-img {
84
+ width: 55px;
85
+ height: 55px;
86
+ border-radius: 16px;
87
  object-fit: cover;
88
  }
89
 
 
 
 
 
 
90
  .sidebar h2 {
91
+ font-size: 26px;
92
  letter-spacing: -0.5px;
93
  background: linear-gradient(135deg, #00d4ff, #4d9eff);
94
  -webkit-background-clip: text;
95
  background-clip: text;
96
  color: transparent;
 
 
 
97
  }
98
 
99
  .sidebar p {
100
  color: #6c7a9e;
101
+ font-size: 13px;
102
  }
103
 
104
  .new-chat {
 
106
  border: none;
107
  color: #0a0c15;
108
  font-weight: bold;
109
+ padding: 14px;
110
+ border-radius: 14px;
111
+ font-size: 16px;
112
  cursor: pointer;
113
+ margin-bottom: 30px;
114
  transition: all 0.2s;
115
  box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
 
116
  }
117
 
118
+ .new-chat:hover {
119
+ transform: translateY(-2px);
120
+ box-shadow: 0 6px 20px rgba(0, 212, 255, 0.5);
121
  }
122
 
123
  .chat-history {
124
  flex: 1;
125
  overflow-y: auto;
 
126
  }
127
 
128
  .history-item {
129
  background: rgba(18, 22, 40, 0.7);
130
  backdrop-filter: blur(5px);
131
+ padding: 14px 18px;
132
+ border-radius: 14px;
133
+ margin-bottom: 12px;
134
  cursor: pointer;
135
  transition: 0.2s;
136
  border: 1px solid rgba(0, 212, 255, 0.1);
137
  font-weight: 500;
138
+ font-size: 14px;
 
139
  }
140
 
141
+ .history-item:hover {
142
+ background: rgba(0, 212, 255, 0.15);
143
+ border-color: rgba(0, 212, 255, 0.4);
144
+ transform: translateX(5px);
145
  }
146
 
147
  /* =========================
 
151
  position: fixed;
152
  width: 100%;
153
  height: 100vh;
154
+ background: rgba(0, 0, 0, 0.6);
155
+ backdrop-filter: blur(3px);
156
+ z-index: 998;
157
  display: none;
 
 
158
  }
159
 
160
  .overlay.active {
161
  display: block;
 
162
  }
163
 
164
  /* =========================
 
169
  flex-direction: column;
170
  width: 100%;
171
  background: #0a0c15;
 
172
  }
173
 
174
  /* =========================
175
  TOPBAR - NEO MASCULINE
176
  ========================= */
177
  .topbar {
178
+ height: 80px;
179
  background: rgba(10, 12, 21, 0.95);
180
  backdrop-filter: blur(10px);
181
  display: flex;
182
  align-items: center;
183
+ padding: 0 30px;
184
  color: #e8edff;
185
  border-bottom: 1px solid rgba(0, 212, 255, 0.3);
186
  box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3);
 
 
 
187
  }
188
 
189
  .menu-btn {
190
+ font-size: 28px;
191
+ margin-right: 20px;
192
  cursor: pointer;
193
  transition: 0.2s;
194
  color: #00d4ff;
 
 
195
  }
196
 
197
+ .menu-btn:hover {
198
+ transform: scale(1.1);
199
+ text-shadow: 0 0 8px #00d4ff;
200
  }
201
 
202
+ /* LOGO AREA - DENGAN GAMBAR */
203
  .logo {
204
+ width: 50px;
205
+ height: 50px;
206
+ border-radius: 14px;
207
  background: linear-gradient(135deg, #00d4ff, #0099ff);
208
  display: flex;
209
  align-items: center;
210
  justify-content: center;
211
+ margin-right: 15px;
212
  box-shadow: 0 0 12px rgba(0, 212, 255, 0.5);
213
  overflow: hidden;
 
214
  }
215
 
216
  .logo-img {
217
  width: 100%;
218
  height: 100%;
219
  object-fit: cover;
220
+ border-radius: 14px;
221
  }
222
 
223
+ /* Fallback jika gambar tidak ada - pake icon keren */
224
  .logo-icon {
225
+ font-size: 28px;
 
 
 
 
 
226
  }
227
 
228
  .title h1 {
229
+ font-size: 24px;
230
  letter-spacing: -0.5px;
231
  background: linear-gradient(135deg, #e8edff, #00d4ff);
232
  -webkit-background-clip: text;
233
  background-clip: text;
234
  color: transparent;
 
 
 
235
  }
236
 
237
  .title p {
238
+ font-size: 13px;
239
  color: #00d4ff;
240
  display: flex;
241
  align-items: center;
242
+ gap: 6px;
243
  }
244
 
245
  .title p::before {
246
  content: "●";
247
+ font-size: 10px;
248
  color: #00ff88;
249
  text-shadow: 0 0 5px #00ff88;
250
  }
 
255
  .chat-area {
256
  flex: 1;
257
  overflow-y: auto;
258
+ padding: 30px;
259
  background: linear-gradient(180deg, #0a0c15 0%, #0f111a 100%);
260
+ /* TAMBAHAN UNTUK ANDROID */
261
  -webkit-overflow-scrolling: touch;
262
  }
263
 
264
  .chat-area::-webkit-scrollbar {
265
+ width: 6px;
266
  }
 
267
  .chat-area::-webkit-scrollbar-track {
268
  background: #1a1f2e;
269
  border-radius: 10px;
270
  }
 
271
  .chat-area::-webkit-scrollbar-thumb {
272
  background: #00d4ff;
273
  border-radius: 10px;
274
  }
275
 
276
  .message {
277
+ max-width: 75%;
278
+ padding: 14px 20px;
279
+ border-radius: 20px;
280
+ margin-bottom: 20px;
281
+ font-size: 16px;
282
  line-height: 1.5;
283
  word-wrap: break-word;
284
  animation: fadeInUp 0.3s ease;
 
300
  backdrop-filter: blur(10px);
301
  border: 1px solid rgba(0, 212, 255, 0.2);
302
  color: #d0d9ff;
303
+ border-bottom-left-radius: 6px;
304
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
 
305
  }
306
 
307
  .user {
 
309
  color: #0a0c15;
310
  font-weight: 600;
311
  margin-left: auto;
312
+ border-bottom-right-radius: 6px;
313
+ box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
314
  }
315
 
316
  /* =========================
 
318
  ========================= */
319
  .input-area {
320
  display: flex;
321
+ padding: 20px 30px;
322
+ gap: 15px;
323
  background: rgba(10, 12, 21, 0.95);
324
  backdrop-filter: blur(10px);
325
  border-top: 1px solid rgba(0, 212, 255, 0.2);
 
 
326
  }
327
 
328
  input {
329
  flex: 1;
330
  border: 1px solid rgba(0, 212, 255, 0.3);
331
  background: rgba(18, 22, 40, 0.8);
332
+ border-radius: 18px;
333
+ padding: 16px 22px;
334
+ font-size: 16px;
335
  outline: none;
336
  color: #e8edff;
337
  transition: all 0.2s;
 
338
  }
339
 
340
  input:focus {
 
351
  background: linear-gradient(135deg, #00d4ff, #0099ff);
352
  color: #0a0c15;
353
  font-weight: bold;
354
+ padding: 0 32px;
355
+ border-radius: 18px;
356
+ font-size: 16px;
357
  cursor: pointer;
358
  transition: 0.2s;
359
  display: flex;
360
  align-items: center;
361
+ gap: 8px;
 
362
  }
363
 
364
+ button.send:hover {
365
+ transform: scale(1.02);
366
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.5);
367
  }
368
 
369
  /* =========================
370
+ RESPONSIVE UNTUK ANDROID
371
  ========================= */
372
+ @media (max-width: 768px) {
373
  .sidebar {
374
+ width: 85%;
 
375
  }
 
376
  .message {
377
+ max-width: 90%;
378
  font-size: 15px;
379
  }
 
380
  .topbar {
381
+ padding: 0 20px;
382
+ }
383
+ .input-area {
384
+ padding: 15px 20px;
385
+ }
386
+ button.send {
387
  padding: 0 24px;
388
  }
389
+ }
390
+
391
+ /* TAMBAHAN KHUSUS UNTUK ANDROID */
392
+ @media (max-width: 480px) {
393
+ .topbar {
394
+ height: 65px;
395
+ padding: 0 15px;
396
+ }
397
  .menu-btn {
398
+ font-size: 24px;
399
+ margin-right: 12px;
400
  }
 
401
  .logo {
402
+ width: 40px;
403
+ height: 40px;
404
  }
 
405
  .title h1 {
406
+ font-size: 18px;
407
  }
 
408
  .title p {
409
+ font-size: 10px;
410
+ }
411
+ .chat-area {
412
+ padding: 15px;
413
+ }
414
+ .message {
415
+ padding: 10px 14px;
416
+ font-size: 14px;
417
+ max-width: 95%;
418
+ }
419
+ .input-area {
420
+ padding: 10px 15px;
421
+ gap: 10px;
422
+ }
423
+ input {
424
+ padding: 12px 16px;
425
+ font-size: 14px;
426
+ }
427
+ button.send {
428
+ padding: 0 18px;
429
+ font-size: 14px;
430
+ }
431
+ .sidebar {
432
+ width: 80%;
433
+ padding: 20px 15px;
434
+ }
435
+ .sidebar-logo {
436
+ width: 45px;
437
+ height: 45px;
438
+ font-size: 24px;
439
+ }
440
+ .sidebar h2 {
441
+ font-size: 22px;
442
+ }
443
+ .new-chat {
444
+ padding: 12px;
445
+ font-size: 14px;
446
+ }
447
+ .history-item {
448
+ padding: 12px 15px;
449
+ font-size: 13px;
450
  }
451
  }
452
 
453
+ /* TAMBAHAN UNTUK LANDSCAPE MODE ANDROID */
454
  @media (max-width: 900px) and (orientation: landscape) {
 
 
 
 
455
  .topbar {
456
  height: 55px;
457
  }
 
 
 
 
 
 
 
 
 
 
458
  .chat-area {
459
+ padding: 10px 20px;
460
  }
 
461
  .message {
462
+ margin-bottom: 10px;
 
463
  }
 
464
  .input-area {
465
+ padding: 8px 15px;
466
  }
 
467
  input {
468
+ padding: 10px 16px;
469
  }
 
470
  button.send {
471
+ padding: 0 20px;
472
  }
473
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  </style>
475
  </head>
476
  <body>
477
 
478
+ <div class="overlay" id="overlay" onclick="closeSidebar()"></div>
 
479
 
480
  <div class="app">
481
 
 
483
  <div class="sidebar" id="sidebar">
484
  <div class="sidebar-header">
485
  <div class="sidebar-logo" id="sidebarLogo">
486
+ <!-- Logo akan diisi JS -->
487
  🐱‍👤
488
  </div>
489
  <div>
 
491
  <p>Neo Intelligence</p>
492
  </div>
493
  </div>
494
+ <button class="new-chat" onclick="createNewChat()">+ New Chat</button>
495
  <div class="chat-history" id="chatHistory"></div>
496
  </div>
497
 
 
499
  <div class="main">
500
  <!-- TOPBAR -->
501
  <div class="topbar">
502
+ <div class="menu-btn" onclick="openSidebar()">☰</div>
503
  <div class="logo" id="mainLogo">
504
+ <!-- Logo akan diisi JS -->
505
  🐱‍👤
506
  </div>
507
  <div class="title">
 
520
 
521
  <!-- INPUT -->
522
  <div class="input-area">
523
+ <input type="text" id="messageInput" placeholder="Ketik pesan bro..." />
524
  <button class="send" onclick="sendMessage()">
525
+ <span></span> Kirim
526
  </button>
527
  </div>
528
  </div>
529
  </div>
530
 
531
  <script>
532
+ let currentChatId = null
 
533
 
534
  // =========================================
535
  // KONFIGURASI LOGO
536
  // =========================================
537
+ // Pilih salah satu mode logo:
538
+ // mode: "emoji" = pake emoji keren
539
+ // mode: "image" = pake gambar dari URL
540
+ // mode: "svg" = pake karakter ASCII keren
541
+
542
  const LOGO_CONFIG = {
543
+ mode: "image", // "emoji", "image", atau "svg"
544
+ emoji: "🐱‍🚀", // emoji untuk mode emoji
545
+ imageUrl: "/static/logo.png", // cyber cat image
546
+ imageUrl2: "https://cdn-icons-png.flaticon.com/512/1998/1998592.png", // alternative robot cat
547
+ svgIcon: "⚡🐱⚡" // untuk mode svg/ascii
548
  }
549
 
550
  function initLogos() {
551
+ const sidebarLogo = document.getElementById("sidebarLogo")
552
+ const mainLogo = document.getElementById("mainLogo")
553
 
554
  if (LOGO_CONFIG.mode === "emoji") {
555
+ // Mode Emoji - simple tapi keren
556
+ sidebarLogo.innerHTML = LOGO_CONFIG.emoji
557
+ sidebarLogo.style.fontSize = "32px"
558
+ sidebarLogo.style.background = "linear-gradient(135deg, #00d4ff, #0099ff)"
559
+
560
+ mainLogo.innerHTML = LOGO_CONFIG.emoji
561
+ mainLogo.style.fontSize = "28px"
562
+ mainLogo.style.background = "linear-gradient(135deg, #00d4ff, #0099ff)"
563
+
564
  } else if (LOGO_CONFIG.mode === "image") {
565
+ // Mode Gambar - lebih profesional
566
+ sidebarLogo.innerHTML = `<img src="${LOGO_CONFIG.imageUrl}" class="sidebar-logo-img" style="width:100%;height:100%;border-radius:16px;object-fit:cover;" onerror="this.src='${LOGO_CONFIG.imageUrl2}'">`
567
+ sidebarLogo.style.background = "transparent"
568
+ sidebarLogo.style.padding = "0"
569
+ sidebarLogo.style.boxShadow = "0 0 15px rgba(0,212,255,0.4)"
570
+
571
+ mainLogo.innerHTML = `<img src="${LOGO_CONFIG.imageUrl}" class="logo-img" style="width:100%;height:100%;border-radius:14px;object-fit:cover;" onerror="this.src='${LOGO_CONFIG.imageUrl2}'">`
572
+ mainLogo.style.background = "transparent"
573
+ mainLogo.style.padding = "0"
574
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  } else {
576
+ // Mode SVG/ASCII - modern tech look
577
+ sidebarLogo.innerHTML = LOGO_CONFIG.svgIcon
578
+ sidebarLogo.style.fontSize = "28px"
579
+ sidebarLogo.style.fontWeight = "bold"
580
+ sidebarLogo.style.letterSpacing = "2px"
581
+
582
+ mainLogo.innerHTML = LOGO_CONFIG.svgIcon
583
+ mainLogo.style.fontSize = "24px"
584
+ mainLogo.style.fontWeight = "bold"
585
  }
586
  }
587
 
588
  function openSidebar() {
589
+ document.getElementById("sidebar").classList.add("active")
590
+ document.getElementById("overlay").classList.add("active")
591
  }
592
 
593
  function closeSidebar() {
594
+ document.getElementById("sidebar").classList.remove("active")
595
+ document.getElementById("overlay").classList.remove("active")
596
  }
597
 
 
 
 
598
  async function createNewChat() {
599
+ const response = await fetch("/new_chat", { method: "POST" })
600
+ const data = await response.json()
601
+ currentChatId = data.chat_id
602
+ document.getElementById("chatArea").innerHTML = `
603
+ <div class="message bot">
604
+ <span style="font-weight: bold;">⚡ ChibiCat Neo</span><br>
605
+ Halo bro! Ada yang bisa gue bantu? 🔥😎
606
+ </div>
607
+ `
608
+ loadChats()
609
+ closeSidebar()
 
 
 
 
 
 
610
  }
611
 
612
  async function loadChats() {
613
+ const response = await fetch("/get_chats")
614
+ const chats = await response.json()
615
+ const history = document.getElementById("chatHistory")
616
+ history.innerHTML = ""
617
+ chats.forEach(chat => {
618
+ history.innerHTML += `
619
+ <div class="history-item" onclick="loadChat('${chat.chat_id}')">
620
+ 💬 ${chat.title}
621
+ </div>
622
+ `
623
+ })
 
 
 
 
 
 
 
 
 
 
624
  }
625
 
626
  async function loadChat(chatId) {
627
+ currentChatId = chatId
628
+ const response = await fetch(`/load_chat/${chatId}`)
629
+ const messages = await response.json()
630
+ const chatArea = document.getElementById("chatArea")
631
+ chatArea.innerHTML = ""
632
+ messages.forEach(msg => {
633
+ const role = msg.role === "user" ? "user" : "bot"
634
+ chatArea.innerHTML += `<div class="message ${role}">${msg.content}</div>`
635
+ })
636
+ closeSidebar()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  }
638
 
639
  async function sendMessage() {
640
+ const input = document.getElementById("messageInput")
641
+ const message = input.value.trim()
642
+ if (message === "") return
 
 
643
 
644
+ const chatArea = document.getElementById("chatArea")
 
645
 
646
+ chatArea.innerHTML += `<div class="message user">${escapeHtml(message)}</div>`
647
+ input.value = ""
648
+ chatArea.scrollTop = chatArea.scrollHeight
 
 
 
 
649
 
650
  try {
651
  const response = await fetch("/chat", {
652
  method: "POST",
653
  headers: { "Content-Type": "application/json" },
654
  body: JSON.stringify({ message: message })
655
+ })
656
+ const data = await response.json()
657
+ chatArea.innerHTML += `<div class="message bot">${escapeHtml(data.reply)}</div>`
658
+ chatArea.scrollTop = chatArea.scrollHeight
659
+ loadChats()
 
 
 
 
660
  } catch (error) {
661
+ chatArea.innerHTML += `<div class="message bot">⚠️ Server error, coba lagi bro 😭</div>`
 
 
 
 
 
 
662
  }
663
  }
664
 
665
+ // TAMBAHAN: Fungsi untuk menghindari XSS dan support emoji di Android
666
  function escapeHtml(text) {
667
+ const div = document.createElement('div')
668
+ div.textContent = text
669
+ return div.innerHTML
 
670
  }
671
 
672
+ // TAMBAHAN: Fix untuk keyboard Android
673
+ function fixAndroidKeyboard() {
674
+ const input = document.getElementById("messageInput")
675
+ if (input) {
676
+ input.addEventListener("focus", function() {
677
+ setTimeout(() => {
678
+ document.querySelector('.chat-area').scrollTop = document.querySelector('.chat-area').scrollHeight
679
+ }, 300)
680
+ })
681
+ }
 
 
 
682
  }
683
 
684
+ // TAMBAHAN: Deteksi Android dan tambah kelas khusus
685
+ function detectAndroid() {
686
+ const userAgent = navigator.userAgent.toLowerCase()
687
+ const isAndroid = userAgent.indexOf("android") > -1
 
 
 
 
 
 
 
 
688
 
689
+ if (isAndroid) {
690
+ document.body.classList.add("android-device")
691
+ console.log("✅ Android device detected - optimized layout")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  }
693
  }
694
 
695
+ document.getElementById("messageInput").addEventListener("keypress", function(e) {
696
+ if (e.key === "Enter") sendMessage()
697
+ })
 
 
 
 
 
 
 
 
698
 
699
+ // TAMBAHAN: Swipe gesture untuk Android (geser kanan buka sidebar)
700
+ let touchStartX = 0
701
+ let touchEndX = 0
702
+
703
+ document.addEventListener('touchstart', function(e) {
704
+ touchStartX = e.changedTouches[0].screenX
705
+ }, false)
706
+
707
+ document.addEventListener('touchend', function(e) {
708
+ touchEndX = e.changedTouches[0].screenX
709
+ const swipeDistance = touchEndX - touchStartX
710
 
711
+ // Geser kanan lebih dari 50px untuk buka sidebar
712
+ if (swipeDistance > 50 && touchStartX < 50) {
713
+ openSidebar()
 
714
  }
715
+
716
+ // Geser kiri lebih dari 50px untuk tutup sidebar
717
+ if (swipeDistance < -50) {
718
+ closeSidebar()
719
+ }
720
+ }, false)
721
 
722
+ // Inisialisasi logo saat halaman load
723
+ initLogos()
724
+ loadChats()
725
+ detectAndroid()
726
+ fixAndroidKeyboard()
 
 
 
 
 
 
727
  </script>
728
 
729
  </body>