sahil239 commited on
Commit
2ee1461
·
verified ·
1 Parent(s): 7ed6d2a

Upload 5 files

Browse files
Files changed (5) hide show
  1. README.md +48 -8
  2. index.html +609 -19
  3. main.py +83 -0
  4. model.py +15 -0
  5. requirements.txt +9 -0
README.md CHANGED
@@ -1,11 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Chatbotv2
3
- emoji: 🌖
4
- colorFrom: red
5
- colorTo: green
6
- sdk: static
7
- pinned: false
8
- license: apache-2.0
 
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
+ # 🤖 Tap Bot – AI Chatbot App
2
+
3
+ Welcome to **Tap Bot**, a simple AI chatbot app built with FastAPI and deployed for free using **Deta Space**. This project serves a static HTML/CSS/JS frontend and handles backend chat requests via an API endpoint.
4
+
5
+ ---
6
+
7
+ ## ✨ Features
8
+
9
+ - 🎨 Beautiful animated chat UI
10
+ - 💬 Typing effects and copy-to-clipboard
11
+ - 🔄 Clear chat, open/close animations
12
+ - 🚀 Free 24/7 hosting with Deta (no hours limit)
13
+ - 🧠 Powered by your own backend logic (LLM optional)
14
+
15
+ ---
16
+
17
+ ## 🧱 Tech Stack
18
+
19
+ - **Frontend**: HTML, CSS, JavaScript (Vanilla)
20
+ - **Backend**: FastAPI (Python)
21
+ - **Hosting**: Deta Space
22
+
23
+ ---
24
+
25
+ ## 🔗 Live Demo
26
+
27
+ Once deployed, app will be available at:
28
+
29
+
30
  ---
31
+
32
+ ## 🧪 Local Development
33
+
34
+ ```bash
35
+ # Install dependencies
36
+ pip install -r requirements.txt
37
+
38
+ # Run server
39
+ uvicorn main:app --reload
40
+ ```
41
+ Access at: http://localhost:8000
42
+
43
  ---
44
 
45
+ 📝 License
46
+
47
+ This project is open-source and free to use.
48
+
49
+
50
+
51
+ Built by sahildesai.dev
index.html CHANGED
@@ -1,19 +1,609 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Tap Bot</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/showdown.min.js"></script>
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: 'Segoe UI', sans-serif;
13
+ background: linear-gradient(-45deg, #1f1c2c, #928dab, #2b5876, #4e4376);
14
+ background-size: 400% 400%;
15
+ animation: gradientBG 15s ease infinite;
16
+ color: #fff;
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ justify-content: center;
21
+ min-height: 100vh;
22
+ text-align: center;
23
+ }
24
+
25
+ @keyframes gradientBG {
26
+ 0% {
27
+ background-position: 0% 50%;
28
+ }
29
+ 50% {
30
+ background-position: 100% 50%;
31
+ }
32
+ 100% {
33
+ background-position: 0% 50%;
34
+ }
35
+ }
36
+
37
+ h1 {
38
+ font-size: 3em;
39
+ margin-bottom: 0.3em;
40
+ }
41
+
42
+ .store-buttons {
43
+ display: flex;
44
+ gap: 20px;
45
+ flex-wrap: wrap;
46
+ justify-content: center;
47
+ margin: 20px 0;
48
+ }
49
+
50
+ .store-buttons img {
51
+ width: 160px;
52
+ height: auto;
53
+ }
54
+
55
+ .try-btn {
56
+ background-color: #fff;
57
+ color: #2980b9;
58
+ border: none;
59
+ padding: 10px 20px;
60
+ font-size: 1.2em;
61
+ border-radius: 8px;
62
+ cursor: pointer;
63
+ transition: 0.3s ease;
64
+ }
65
+
66
+ .try-btn:hover {
67
+ background-color: #e0e0e0;
68
+ }
69
+
70
+ .chat-container {
71
+ position: fixed;
72
+ bottom: 20px;
73
+ right: 20px;
74
+ background: white;
75
+ color: black;
76
+ border-radius: 12px;
77
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
78
+ width: 350px;
79
+ height: 500px;
80
+ max-height: 90vh;
81
+ overflow: hidden;
82
+ display: none;
83
+ flex-direction: column;
84
+ /* Animation styles */
85
+ transform: scale(0);
86
+ opacity: 0;
87
+ transition: transform 0.3s ease, opacity 0.3s ease;
88
+ pointer-events: none;
89
+ }
90
+
91
+ .chat-container.open {
92
+ transform: scale(1);
93
+ opacity: 1;
94
+ pointer-events: auto;
95
+ }
96
+
97
+ .chat-header {
98
+ background-color: #2980b9;
99
+ color: white;
100
+ padding: 10px;
101
+ font-weight: bold;
102
+ display: flex;
103
+ justify-content: space-between;
104
+ align-items: center;
105
+ }
106
+
107
+ .chat-body {
108
+ padding: 10px;
109
+ overflow-y: auto;
110
+ flex-grow: 1;
111
+ }
112
+
113
+ .chat-input {
114
+ display: flex;
115
+ border-top: 1px solid #ccc;
116
+ }
117
+
118
+ .chat-input input {
119
+ flex-grow: 1;
120
+ border: none;
121
+ padding: 10px;
122
+ font-size: 1em;
123
+ }
124
+
125
+ .chat-input button {
126
+ border: none;
127
+ padding: 10px 15px;
128
+ background-color: #2980b9;
129
+ color: white;
130
+ cursor: pointer;
131
+ }
132
+
133
+ .msg.user {
134
+ text-align: right;
135
+ margin: 5px 0;
136
+ }
137
+
138
+ .msg.bot {
139
+ text-align: left;
140
+ margin: 5px 0;
141
+ }
142
+
143
+ .msg .bubble {
144
+ display: inline-block;
145
+ padding: 10px;
146
+ border-radius: 10px;
147
+ max-width: 85%;
148
+ }
149
+
150
+ .msg.user .bubble {
151
+ background: #d1eaff;
152
+ color: #000;
153
+ }
154
+
155
+ .msg.bot .bubble {
156
+ background: #f1f1f1;
157
+ color: #000;
158
+ }
159
+
160
+ .bubble.typing {
161
+ font-style: italic;
162
+ }
163
+
164
+ .copy-btn {
165
+ background: none;
166
+ border: none;
167
+ cursor: pointer;
168
+ font-size: 1em;
169
+ margin-left: 6px;
170
+ color: #555;
171
+ float: right;
172
+ position: relative;
173
+ }
174
+
175
+ .copy-btn::after {
176
+ content: "Copy";
177
+ position: absolute;
178
+ bottom: 100%;
179
+ left: 50%;
180
+ transform: translateX(-50%);
181
+ background: #000;
182
+ color: #fff;
183
+ padding: 4px 8px;
184
+ font-size: 0.75em;
185
+ border-radius: 4px;
186
+ opacity: 0;
187
+ white-space: nowrap;
188
+ pointer-events: none;
189
+ transition: opacity 0.2s;
190
+ }
191
+
192
+ .copy-btn:hover::after {
193
+ opacity: 1;
194
+ }
195
+
196
+ .toast {
197
+ visibility: hidden;
198
+ min-width: 120px;
199
+ background-color: #333;
200
+ color: #fff;
201
+ text-align: center;
202
+ border-radius: 6px;
203
+ padding: 10px;
204
+ position: fixed;
205
+ z-index: 1;
206
+ left: 50%;
207
+ bottom: 30px;
208
+ transform: translateX(-50%);
209
+ font-size: 0.9em;
210
+ opacity: 0;
211
+ transition: opacity 0.5s, bottom 0.5s;
212
+ }
213
+
214
+ .toast.show {
215
+ visibility: visible;
216
+ opacity: 1;
217
+ bottom: 50px;
218
+ }
219
+
220
+ #typing {
221
+ display: flex;
222
+ padding: 5px;
223
+ font-style: italic;
224
+ color: #555;
225
+ align-items: center;
226
+ }
227
+
228
+ #typing span.dot {
229
+ height: 10px;
230
+ width: 10px;
231
+ margin: 0 3px;
232
+ background-color: #999;
233
+ border-radius: 50%;
234
+ display: inline-block;
235
+ animation: blink 1.4s infinite;
236
+ }
237
+
238
+ #typing span.dot:nth-child(2) {
239
+ animation-delay: 0.2s;
240
+ }
241
+
242
+ #typing span.dot:nth-child(3) {
243
+ animation-delay: 0.4s;
244
+ }
245
+
246
+ @keyframes blink {
247
+ 0%, 80%, 100% {
248
+ opacity: 0.2;
249
+ transform: scale(0.8);
250
+ }
251
+ 40% {
252
+ opacity: 1;
253
+ transform: scale(1);
254
+ }
255
+ }
256
+
257
+ footer {
258
+ margin-top: 40px;
259
+ font-size: 0.9em;
260
+ }
261
+
262
+ @media (max-width: 600px) {
263
+ .store-buttons {
264
+ flex-direction: column;
265
+ align-items: center;
266
+ }
267
+ }
268
+ </style>
269
+ </head>
270
+ <div id="toast" class="toast">Copied!</div>
271
+ <body>
272
+
273
+ <h1 id="welcomeTitle"></h1>
274
+ <p id="welcomeSub"></p>
275
+ <div class="store-buttons">
276
+ <a href="#"><img
277
+ src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Google_Play_Store_badge_EN.svg/512px-Google_Play_Store_badge_EN.svg.png"
278
+ alt="Google Play"></a>
279
+ <a href="#"><img src="https://developer.apple.com/assets/elements/badges/download-on-the-app-store.svg"
280
+ alt="App Store"></a>
281
+ </div>
282
+
283
+ <button class="try-btn" onclick="openChat()">Try Now</button>
284
+
285
+ <footer>
286
+ <p><strong>Disclaimer:</strong> Tap Bot is powered by a fine-tuned language model trained using publicly available
287
+ data. </p>
288
+ <p> We do not claim ownership of the original data sources used for training. </p>
289
+ <p> Developed by <a href="https://sahildesai.dev" target="_blank" style="color:#fff;text-decoration:underline;">sahildesai.dev</a>
290
+ </p>
291
+ </footer>
292
+
293
+ <div class="chat-container" id="chatBox">
294
+ <div class="chat-header"><span id="chatTitle"></span>
295
+ <div>
296
+ <button onclick="clearChat()" style="background:none; border:none; cursor:pointer;">
297
+ <svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" fill="white" viewBox="0 0 24 24">
298
+ <path d="M3 6h18v2H3V6zm2 3h14l-1.5 12.5H6.5L5 9zm5.5-5h3v2h-3V4z"/>
299
+ </svg>
300
+ </button>
301
+ <button onclick="toggleChat()"
302
+ style="background:none;border:none;color:white;font-size:1.2em;cursor:pointer;">X
303
+ </button>
304
+ </div>
305
+ </div>
306
+ <div class="chat-body" id="chatMessages">
307
+ <div class="msg bot" id="welcomeMessage">
308
+ <div class="bubble">Hi there! I’m Tap Bot. How can I help you today?</div>
309
+ </div>
310
+ </div>
311
+ <div class="chat-input">
312
+ <input type="text" id="userInput" placeholder="What's on your mind?" oninput="toggleSendButton()"
313
+ onkeydown="handleEnter(event)"/>
314
+ <button id="sendBtn" onclick="submitMessage()" disabled>Send</button>
315
+ </div>
316
+ </div>
317
+
318
+ <script>
319
+ const botName = "Tap Bot";
320
+
321
+ document.addEventListener("DOMContentLoaded", () => {
322
+ document.getElementById("welcomeTitle").textContent = `Welcome to ${botName}!`;
323
+ document.getElementById("welcomeSub").textContent = `Your AI-powered assistant is live.`;
324
+ document.getElementById("chatTitle").textContent = botName;
325
+ });
326
+
327
+ function copyToClipboard(button) {
328
+ const text = button.previousElementSibling.textContent;
329
+ navigator.clipboard.writeText(text).then(() => {
330
+ button.textContent = "✅";
331
+ setTimeout(() => (button.textContent = "📋"), 1500);
332
+ });
333
+ }
334
+
335
+ function openChat() {
336
+ const box = document.getElementById("chatBox");
337
+ box.classList.add("open");
338
+ document.getElementById("chatMessages").innerHTML = "";
339
+ const chatMessages = document.getElementById("chatMessages");
340
+ box.style.display = "flex";
341
+ chatMessages.innerHTML = ''; // Clear on open
342
+
343
+ // Add typing dots
344
+ const botDiv = document.createElement("div");
345
+ botDiv.className = "msg bot";
346
+ const typingDiv = document.createElement("div");
347
+ typingDiv.className = "bubble typing";
348
+ typingDiv.innerHTML = `<div id="typing"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>`;
349
+ botDiv.appendChild(typingDiv);
350
+ chatMessages.appendChild(botDiv);
351
+
352
+ chatMessages.scrollTop = chatMessages.scrollHeight;
353
+
354
+ // After delay, show welcome message with typing
355
+ setTimeout(() => {
356
+ const welcomeText = "Hi there! I’m Tap Bot. How can I help you today?";
357
+ let i = 0;
358
+ typingDiv.innerHTML = "";
359
+ const textNode = document.createElement("div");
360
+ typingDiv.appendChild(textNode);
361
+
362
+ function typeChar() {
363
+ if (i < welcomeText.length) {
364
+ textNode.textContent += welcomeText[i++];
365
+ chatMessages.scrollTop = chatMessages.scrollHeight;
366
+ setTimeout(typeChar, 30);
367
+ }
368
+ }
369
+
370
+ typeChar();
371
+ }, 500);
372
+ }
373
+
374
+ function toggleChat() {
375
+ const box = document.getElementById("chatBox");
376
+ box.classList.toggle("open");
377
+ }
378
+
379
+ function clearChat() {
380
+ const chatMessages = document.getElementById("chatMessages");
381
+ chatMessages.innerHTML = "";
382
+ const welcome = document.createElement("div");
383
+ welcome.className = "msg bot";
384
+ welcome.id = "welcomeMessage";
385
+ welcome.innerHTML = `<div class="bubble">Hi there! I’m Tap Bot. How can I help you today?</div>`;
386
+ chatMessages.appendChild(welcome);
387
+ chatMessages.scrollTop = chatMessages.scrollHeight;
388
+
389
+ setTimeout(() => {
390
+ const welcomeText = "Hi there! I’m Tap Bot. How can I help you today?";
391
+ let i = 0;
392
+ typingDiv.innerHTML = "";
393
+
394
+ function typeChar() {
395
+ if (i < welcomeText.length) {
396
+ typingDiv.innerHTML += welcomeText[i++];
397
+ chatMessages.scrollTop = chatMessages.scrollHeight;
398
+ setTimeout(typeChar, 30);
399
+ }
400
+ }
401
+
402
+ typeChar();
403
+ }, 500);
404
+ }
405
+
406
+ function toggleSendButton() {
407
+ const btn = document.getElementById("sendBtn");
408
+ const input = document.getElementById("userInput");
409
+ btn.disabled = input.value.trim().length === 0;
410
+ }
411
+
412
+ function handleEnter(e) {
413
+ if (e.key === "Enter") {
414
+ e.preventDefault();
415
+ if (!document.getElementById("sendBtn").disabled) {
416
+ submitMessage();
417
+ }
418
+ }
419
+ }
420
+
421
+ function showToast(message = "Copied!") {
422
+ const toast = document.getElementById("toast");
423
+ toast.textContent = message;
424
+ toast.classList.add("show");
425
+ setTimeout(() => {
426
+ toast.classList.remove("show");
427
+ }, 2000); // 2 seconds
428
+ }
429
+
430
+ /* async function submitMessage() {
431
+ const input = document.getElementById("userInput");
432
+ const msg = input.value.trim();
433
+ if (!msg) return;
434
+
435
+ const chatMessages = document.getElementById("chatMessages");
436
+ const converter = new showdown.Converter();
437
+
438
+ // Append user message
439
+ const userDiv = document.createElement("div");
440
+ userDiv.className = "msg user";
441
+ userDiv.innerHTML = `<div class="bubble">${msg}</div>`;
442
+ chatMessages.appendChild(userDiv);
443
+
444
+ // Bot typing placeholder with animated dots
445
+ const botDiv = document.createElement("div");
446
+ botDiv.className = "msg bot";
447
+ const typingDiv = document.createElement("div");
448
+ typingDiv.className = "bubble typing";
449
+
450
+ const typingIndicator = document.createElement("div");
451
+ typingIndicator.id = "typing";
452
+ typingIndicator.innerHTML = `
453
+ <span class="dot"></span>
454
+ <span class="dot"></span>
455
+ <span class="dot"></span>
456
+ `;
457
+ typingDiv.appendChild(typingIndicator);
458
+ botDiv.appendChild(typingDiv);
459
+ chatMessages.appendChild(botDiv);
460
+ chatMessages.scrollTop = chatMessages.scrollHeight;
461
+
462
+ input.value = "";
463
+ toggleSendButton();
464
+
465
+ try {
466
+ const res = await fetch("/chat", {
467
+ method: "POST",
468
+ headers: {"Content-Type": "application/json"},
469
+ body: JSON.stringify({prompt: msg})
470
+ });
471
+
472
+ const data = await res.json();
473
+ const text = data.response;
474
+
475
+ // Replace the dots with actual typing text
476
+ typingDiv.removeChild(typingIndicator);
477
+
478
+ const textNode = document.createElement("div");
479
+ typingDiv.appendChild(textNode);
480
+
481
+ let i = 0;
482
+
483
+ function typeChar() {
484
+ if (i < text.length) {
485
+ textNode.textContent += text[i++];
486
+ chatMessages.scrollTop = chatMessages.scrollHeight;
487
+ setTimeout(typeChar, 20);
488
+ } else {
489
+ typingDiv.classList.remove("typing");
490
+
491
+ // Copy button
492
+ const copyBtn = document.createElement("button");
493
+ copyBtn.className = "copy-btn";
494
+ copyBtn.innerHTML = `
495
+ <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
496
+ <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
497
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
498
+ </svg>`;
499
+ copyBtn.onclick = function () {
500
+ navigator.clipboard.writeText(textNode.textContent).then(() => {
501
+ showToast("Copied!")
502
+ copyBtn.textContent = "✅";
503
+ setTimeout(() => (copyBtn.innerHTML = `
504
+ <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
505
+ <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
506
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
507
+ </svg>`), 1500);
508
+ });
509
+ };
510
+ typingDiv.appendChild(copyBtn);
511
+ }
512
+ }
513
+
514
+ setTimeout(typeChar, 200); // slight delay after removing dots
515
+ } catch (e) {
516
+ typingDiv.textContent = "Something went wrong. " + e.message;
517
+ typingDiv.classList.remove("typing");
518
+ }
519
+ }*/
520
+
521
+ async function submitMessage() {
522
+ const input = document.getElementById("userInput");
523
+ const msg = input.value.trim();
524
+ if (!msg) return;
525
+
526
+ const chatMessages = document.getElementById("chatMessages");
527
+
528
+ // Append user message
529
+ const userDiv = document.createElement("div");
530
+ userDiv.className = "msg user";
531
+ userDiv.innerHTML = `<div class="bubble">${msg}</div>`;
532
+ chatMessages.appendChild(userDiv);
533
+
534
+ // Bot typing placeholder
535
+ const botDiv = document.createElement("div");
536
+ botDiv.className = "msg bot";
537
+ const typingDiv = document.createElement("div");
538
+ typingDiv.className = "bubble typing";
539
+
540
+ const typingIndicator = document.createElement("div");
541
+ typingIndicator.id = "typing";
542
+ typingIndicator.innerHTML = `<span class="dot"></span><span class="dot"></span><span class="dot"></span>`;
543
+ typingDiv.appendChild(typingIndicator);
544
+ botDiv.appendChild(typingDiv);
545
+ chatMessages.appendChild(botDiv);
546
+ chatMessages.scrollTop = chatMessages.scrollHeight;
547
+
548
+ input.value = "";
549
+ toggleSendButton();
550
+
551
+ try {
552
+ const res = await fetch("/chat", {
553
+ method: "POST",
554
+ headers: {"Content-Type": "application/json"},
555
+ body: JSON.stringify({prompt: msg})
556
+ });
557
+
558
+ const data = await res.json();
559
+ const fullText = data.response;
560
+
561
+ // Split text into complete sentences using full stop
562
+ const sentences = fullText.split(/(?<=\.)\s+/);
563
+ let selectedSentences = [];
564
+
565
+ for (const sentence of sentences) {
566
+ selectedSentences.push(sentence.trim());
567
+ }
568
+
569
+ // === END TOKEN TRIM LOGIC ===
570
+
571
+ // Replace typing dots with final output
572
+ const converter = new showdown.Converter();
573
+ const markdownOutput = selectedSentences.join("\n");
574
+ typingDiv.innerHTML = converter.makeHtml(markdownOutput);
575
+ typingDiv.classList.remove("typing");
576
+
577
+
578
+ // Optional copy button
579
+ const copyBtn = document.createElement("button");
580
+ copyBtn.className = "copy-btn";
581
+ copyBtn.innerHTML = `
582
+ <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
583
+ <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
584
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
585
+ </svg>`;
586
+ copyBtn.onclick = function () {
587
+ navigator.clipboard.writeText(selectedSentences.join("\n")).then(() => {
588
+ showToast("Copied!");
589
+ copyBtn.textContent = "✅";
590
+ setTimeout(() => {
591
+ copyBtn.innerHTML = `
592
+ <svg xmlns="http://www.w3.org/2000/svg" height="18" width="18" viewBox="0 0 24 24" fill="white" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
593
+ <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
594
+ <rect x="8" y="2" width="8" height="4" rx="1" ry="1"/>
595
+ </svg>`;
596
+ }, 1500);
597
+ });
598
+ };
599
+ typingDiv.appendChild(copyBtn);
600
+
601
+ } catch (e) {
602
+ typingDiv.textContent = "Something went wrong. " + e.message;
603
+ typingDiv.classList.remove("typing");
604
+ }
605
+ }
606
+
607
+ </script>
608
+ </body>
609
+ </html>
main.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+
4
+ from fastapi import FastAPI
5
+ from pydantic import BaseModel
6
+ from starlette.middleware.cors import CORSMiddleware
7
+ from starlette.responses import HTMLResponse, FileResponse
8
+ from starlette.staticfiles import StaticFiles
9
+ from transformers import AutoTokenizer, AutoModelForCausalLM
10
+ import torch
11
+ from peft import PeftModel
12
+
13
+ # Load once at startup
14
+ base_model_name = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
15
+ adapter_repo = "sahil239/chatbot-v2"
16
+
17
+ tokenizer = AutoTokenizer.from_pretrained(base_model_name, trust_remote_code=True)
18
+ if tokenizer.pad_token is None:
19
+ tokenizer.pad_token = tokenizer.eos_token
20
+
21
+ base_model = AutoModelForCausalLM.from_pretrained(
22
+ base_model_name,
23
+ device_map="auto",
24
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
25
+ trust_remote_code=True
26
+ )
27
+ model = PeftModel.from_pretrained(base_model, adapter_repo)
28
+ model.eval()
29
+
30
+ def generate_response(user_message: str) -> str:
31
+ # Proper chat formatting
32
+ chat = [{"role": "user", "content": user_message}]
33
+ inputs = tokenizer.apply_chat_template(
34
+ chat,
35
+ tokenize=True,
36
+ return_tensors="pt"
37
+ ).to(model.device)
38
+
39
+ outputs = model.generate(
40
+ input_ids=inputs,
41
+ max_new_tokens=256,
42
+ temperature=0.1,
43
+ top_p=0.95,
44
+ do_sample=False,
45
+ eos_token_id=tokenizer.eos_token_id
46
+ )
47
+
48
+ decoded = tokenizer.decode(outputs[0], skip_special_tokens=True)
49
+
50
+ # Optional: strip past conversation if using chat template
51
+ # You might want to extract just the assistant's last response.
52
+ assistant_prefix = "<|assistant|>"
53
+ if assistant_prefix in decoded:
54
+ reply = decoded.split(assistant_prefix)[-1].strip()
55
+ else: reply = decoded.strip()
56
+
57
+ stop_strings = ["<|user|>", "<|end|>", "</s>"]
58
+ for stop in stop_strings:
59
+ if stop in reply:
60
+ reply = reply.split(stop)[0].strip()
61
+
62
+ return reply
63
+
64
+ app = FastAPI()
65
+ app.add_middleware(
66
+ CORSMiddleware,
67
+ allow_origins=["*"],
68
+ allow_methods=["*"],
69
+ allow_headers=["*"],
70
+ )
71
+ class ChatRequest(BaseModel):
72
+ prompt: str
73
+
74
+ @app.get("/", response_class=HTMLResponse)
75
+ async def homepage():
76
+ html_path = Path(__file__).parent / "index.html"
77
+ return HTMLResponse(content=html_path.read_text(), status_code=200)
78
+
79
+
80
+ @app.post("/chat")
81
+ def chat_endpoint(req: ChatRequest):
82
+ reply = generate_response(req.prompt)
83
+ return {"response": reply}
model.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoTokenizer, AutoModelForCausalLM
2
+ from peft import PeftModel
3
+ import torch
4
+
5
+ BASE_MODEL = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
6
+ LORA_REPO = "sahil239/chatbot-v2"
7
+
8
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
9
+ model = AutoModelForCausalLM.from_pretrained(
10
+ BASE_MODEL,
11
+ torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
12
+ device_map="auto"
13
+ )
14
+ model = PeftModel.from_pretrained(model, LORA_REPO)
15
+ model.eval()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi~=0.116.1
2
+ uvicorn
3
+ transformers~=4.55.2
4
+ peft~=0.17.0
5
+ torch~=2.8.0
6
+ myapplication~=0.1.0
7
+ pydantic~=2.11.7
8
+ starlette~=0.47.2
9
+ jinja2