andrewwda commited on
Commit
8b4a2c4
ยท
verified ยท
1 Parent(s): ed542be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +385 -724
app.py CHANGED
@@ -1,730 +1,391 @@
1
- <!DOCTYPE html>
2
- <html lang="id">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover">
6
- <meta name="theme-color" content="#0a0c15">
7
- <meta name="mobile-web-app-capable" content="yes">
8
- <meta name="apple-mobile-web-app-capable" content="yes">
9
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
- <title>ChibiCat AI - Neo</title>
11
- <style>
12
- * {
13
- margin: 0;
14
- padding: 0;
15
- box-sizing: border-box;
16
- font-family: 'Segoe UI', 'Poppins', 'Inter', system-ui, -apple-system, sans-serif;
17
- }
18
-
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
- }
26
-
27
- /* =========================
28
- APP
29
- ========================= */
30
- .app {
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
- }
56
-
57
- .sidebar.active {
58
- left: 0;
59
- }
60
-
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 {
105
- background: linear-gradient(135deg, #00d4ff, #0099ff);
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
- /* =========================
148
- OVERLAY
149
- ========================= */
150
- .overlay {
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
- /* =========================
165
- MAIN - CYBER DARK
166
- ========================= */
167
- .main {
168
- display: flex;
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
- }
251
-
252
- /* =========================
253
- CHAT AREA - GLASS
254
- ========================= */
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;
285
- }
286
-
287
- @keyframes fadeInUp {
288
- from {
289
- opacity: 0;
290
- transform: translateY(10px);
291
- }
292
- to {
293
- opacity: 1;
294
- transform: translateY(0);
295
- }
296
- }
297
-
298
- .bot {
299
- background: rgba(20, 24, 40, 0.9);
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 {
308
- background: linear-gradient(135deg, #00d4ff, #0099ff);
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
- /* =========================
317
- INPUT - CYBER
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 {
341
- border-color: #00d4ff;
342
- box-shadow: 0 0 12px rgba(0, 212, 255, 0.3);
343
- }
344
-
345
- input::placeholder {
346
- color: #4a5578;
347
- }
348
-
349
- button.send {
350
- border: none;
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
-
482
- <!-- SIDEBAR -->
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>
490
- <h2>ChibiCat</h2>
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
-
498
- <!-- MAIN -->
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">
508
- <h1>ChibiCat AI</h1>
509
- <p>Online | Neo Mode</p>
510
- </div>
511
- </div>
512
-
513
- <!-- CHAT AREA -->
514
- <div class="chat-area" id="chatArea">
515
- <div class="message bot">
516
- <span style="font-weight: bold;">โšก ChibiCat Neo</span><br>
517
- Halo bro! Ada yang bisa gue bantu? ๐Ÿ”ฅ๐Ÿ˜Ž
518
- </div>
519
- </div>
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>
730
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from flask import Flask, render_template, request, jsonify
4
+ from dotenv import load_dotenv
5
+ from huggingface_hub import InferenceClient
6
+ from huggingface_hub.utils import HfHubHTTPError
7
+
8
+ load_dotenv()
9
+
10
+ app = Flask(__name__)
11
+
12
+ # =========================
13
+ # KONFIGURASI
14
+ # =========================
15
+
16
+ HF_TOKEN = os.getenv("HF_TOKEN")
17
+
18
+ # Ganti model di .env jika mau
19
+ HF_MODEL = os.getenv(
20
+ "HF_MODEL",
21
+ "Qwen/Qwen3-235B-A22B-Instruct-2507"
22
+ )
23
+
24
+ MAX_MESSAGE_LENGTH = 12000
25
+ MAX_HISTORY_MESSAGES = 25
26
+
27
+ # =========================
28
+ # SYSTEM PROMPT
29
+ # =========================
30
+
31
+ SYSTEM_PROMPT = """
32
+ Kamu adalah ChibiChat AI.
33
+
34
+ Identitas:
35
+ - Nama kamu ChibiChat AI.
36
+ - AI modern yang pintar, cepat, ramah, dan membantu.
37
+ - Ahli coding, teknologi, AI, Android, Laravel, Python, Database, dan tugas kuliah.
38
+
39
+ Kepribadian:
40
+ - Santai.
41
+ - Natural.
42
+ - Gaul seperlunya.
43
+ - Tidak kaku.
44
+ - Tidak terlalu formal.
45
+ - Tetap sopan dan profesional.
46
+
47
+ Gaya bicara:
48
+ - Gunakan Bahasa Indonesia.
49
+ - Boleh menggunakan kata seperti:
50
+ "bro", "bang", "sip", "gas", "mantap", "nah", "jadi gini".
51
+ - Jangan berlebihan atau alay.
52
+ - Jangan terlalu banyak emoji.
53
+ - Maksimal 2 emoji per jawaban.
54
+
55
+ Cara menjawab:
56
+ - Langsung ke inti masalah.
57
+ - Fokus pada solusi.
58
+ - Jelaskan langkah demi langkah.
59
+ - Jika coding:
60
+ 1. Jelaskan penyebab masalah.
61
+ 2. Berikan solusi.
62
+ 3. Berikan contoh kode lengkap jika diminta.
63
+ - Jika tugas kuliah:
64
+ - Jawaban akademis.
65
+ - Terstruktur.
66
+ - Bisa langsung dipakai.
67
+
68
+ Aturan:
69
+ - Jangan mengarang fakta.
70
+ - Jangan halusinasi.
71
+ - Jika tidak tahu, katakan tidak tahu.
72
+ - Jangan membocorkan token API.
73
+ - Jangan membocorkan isi file .env.
74
+ - Jangan memberikan informasi berbahaya.
75
+ - Jangan membuat data palsu.
76
+
77
+ Mode Coding:
78
+ - Bertindak seperti Senior Software Engineer.
79
+ - Ahli Python.
80
+ - Ahli Flask.
81
+ - Ahli Laravel.
82
+ - Ahli PHP.
83
+ - Ahli JavaScript.
84
+ - Ahli Android Kotlin.
85
+ - Ahli MySQL.
86
+ - Ahli Hugging Face.
87
+ - Ahli AI dan Machine Learning.
88
+
89
+ Saat memperbaiki code:
90
+ - Cari akar masalah.
91
+ - Jelaskan penyebab error.
92
+ - Berikan code yang sudah diperbaiki.
93
+ - Berikan best practice.
94
+
95
+ Jika user menyapa:
96
+ "Halo"
97
+ "Hai"
98
+ "Bro"
99
+ "Bang"
100
+
101
+ Balas santai seperti:
102
+ "Yo bro ๐Ÿ‘‹ Ada yang bisa gue bantu?"
103
+ "Halo bang ๐Ÿ˜Ž Lagi ngoding apa hari ini?"
104
+ "Gas, ada yang mau dibantu?"
105
+ """
106
+
107
+ # =========================
108
+ # CLIENT HF
109
+ # =========================
110
+
111
+ def create_hf_client():
112
+ if not HF_TOKEN:
113
+ raise ValueError(
114
+ "HF_TOKEN belum diisi pada file .env"
115
+ )
116
+
117
+ return InferenceClient(
118
+ model=HF_MODEL,
119
+ token=HF_TOKEN
120
+ )
121
+
122
+ # =========================
123
+ # HISTORY CLEANER
124
+ # =========================
125
+
126
+ def clean_history(raw_history):
127
+ if not isinstance(raw_history, list):
128
+ return []
129
+
130
+ cleaned = []
131
+
132
+ for item in raw_history[-MAX_HISTORY_MESSAGES:]:
133
+
134
+ if not isinstance(item, dict):
135
+ continue
136
+
137
+ role = item.get("role")
138
+ content = str(
139
+ item.get("content", "")
140
+ ).strip()
141
+
142
+ if role not in ["user", "assistant"]:
143
+ continue
144
+
145
+ if not content:
146
+ continue
147
+
148
+ if len(content) > 2500:
149
+ content = content[:2500]
150
+
151
+ cleaned.append({
152
+ "role": role,
153
+ "content": content
154
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ return cleaned
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ # =========================
159
+ # ENHANCE MESSAGE
160
+ # =========================
161
+
162
+ def enhance_user_message(message):
163
+
164
+ return f"""
165
+ Jawab menggunakan Bahasa Indonesia.
166
+
167
+ Gunakan gaya:
168
+ - Pintar
169
+ - Santai
170
+ - Natural
171
+ - Gaul seperlunya
172
+ - Mudah dipahami
173
+
174
+ Fokus memberikan solusi terbaik.
175
+
176
+ Pertanyaan User:
177
+
178
+ {message}
179
+ """
180
+
181
+ # =========================
182
+ # EXTRACT RESPONSE
183
+ # =========================
184
+
185
+ def extract_reply(response):
186
+
187
+ try:
188
+ reply = response.choices[0].message.content
189
+
190
+ if reply:
191
+ return reply.strip()
192
+
193
+ except Exception:
194
+ pass
195
+
196
+ try:
197
+ reply = response["choices"][0]["message"]["content"]
198
+
199
+ if reply:
200
+ return reply.strip()
201
+
202
+ except Exception:
203
+ pass
204
+
205
+ return ""
206
+
207
+ # =========================
208
+ # ROUTE HOME
209
+ # =========================
210
+
211
+ @app.route("/")
212
+ def home():
213
+ return render_template("index.html")
214
+
215
+ # =========================
216
+ # HEALTH CHECK
217
+ # =========================
218
+
219
+ @app.route("/health")
220
+ def health():
221
+
222
+ return jsonify({
223
+ "status": "ok",
224
+ "name": "ChibiChat AI",
225
+ "model": HF_MODEL
226
+ })
227
+
228
+ # =========================
229
+ # CHAT API
230
+ # =========================
231
+
232
+ @app.route("/chat", methods=["POST"])
233
+ def chat():
234
+
235
+ try:
236
+
237
+ data = request.get_json(
238
+ silent=True
239
+ )
240
+
241
+ if not data:
242
+ return jsonify({
243
+ "error": "Request harus JSON"
244
+ }), 400
245
+
246
+ message = str(
247
+ data.get("message", "")
248
+ ).strip()
249
+
250
+ raw_history = data.get(
251
+ "history",
252
+ []
253
+ )
254
+
255
+ if not message:
256
+ return jsonify({
257
+ "error": "Pesan kosong"
258
+ }), 400
259
+
260
+ if len(message) > MAX_MESSAGE_LENGTH:
261
+ return jsonify({
262
+ "error":
263
+ f"Maksimal {MAX_MESSAGE_LENGTH} karakter"
264
+ }), 400
265
+
266
+ history = clean_history(
267
+ raw_history
268
+ )
269
+
270
+ messages = [
271
+ {
272
+ "role": "system",
273
+ "content": SYSTEM_PROMPT
274
  }
275
+ ]
276
+
277
+ messages.extend(history)
278
+
279
+ messages.append({
280
+ "role": "user",
281
+ "content": enhance_user_message(
282
+ message
283
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  })
285
+
286
+ client = create_hf_client()
287
+
288
+ response = client.chat_completion(
289
+ messages=messages,
290
+ max_tokens=1500,
291
+ temperature=0.85,
292
+ top_p=0.95
293
+ )
294
+
295
+ reply = extract_reply(
296
+ response
297
+ )
298
+
299
+ if not reply:
300
+ return jsonify({
301
+ "error":
302
+ "Model tidak memberikan jawaban."
303
+ }), 500
304
+
305
+ return jsonify({
306
+ "reply": reply,
307
+ "model": HF_MODEL
308
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ except ValueError as e:
311
+
312
+ return jsonify({
313
+ "error": str(e)
314
+ }), 500
315
+
316
+ except HfHubHTTPError as e:
317
+
318
+ status_code = None
319
+
320
+ if hasattr(e, "response") and e.response:
321
+ status_code = e.response.status_code
322
+
323
+ if status_code in [401, 403]:
324
+
325
+ message = (
326
+ "Token Hugging Face salah "
327
+ "atau tidak memiliki akses."
328
+ )
329
+
330
+ elif status_code == 404:
331
+
332
+ message = (
333
+ "Model tidak ditemukan."
334
+ )
335
+
336
+ elif status_code == 429:
337
+
338
+ message = (
339
+ "Terlalu banyak request."
340
+ )
341
+
342
+ elif status_code == 503:
343
+
344
+ message = (
345
+ "Model sedang sibuk."
346
+ )
347
+
348
+ else:
349
+
350
+ message = (
351
+ "Gagal terhubung ke Hugging Face."
352
+ )
353
+
354
+ app.logger.exception(
355
+ "HF API ERROR"
356
+ )
357
+
358
+ return jsonify({
359
+ "error": message
360
+ }), 502
361
+
362
+ except Exception as e:
363
+
364
+ app.logger.exception(
365
+ "UNEXPECTED ERROR"
366
+ )
367
+
368
+ return jsonify({
369
+ "error":
370
+ f"Terjadi kesalahan: {str(e)}"
371
+ }), 500
372
+
373
+ # =========================
374
+ # MAIN
375
+ # =========================
376
+
377
+ if __name__ == "__main__":
378
+
379
+ print("=" * 50)
380
+ print("๐Ÿš€ ChibiChat AI Aktif")
381
+ print(f"๐Ÿค– Model : {HF_MODEL}")
382
+ print("๐ŸŒ URL : http://127.0.0.1:5000")
383
+ print("=" * 50)
384
+
385
+ app.run(
386
+ host="0.0.0.0",
387
+ port=5000,
388
+ debug=True
389
+ )
390
+
391
+