teszenofficial commited on
Commit
19a3b5c
·
verified ·
1 Parent(s): 31ece54

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +632 -0
  2. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,632 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import torch
4
+ import pickle
5
+ from fastapi import FastAPI
6
+ from fastapi.responses import HTMLResponse
7
+ from pydantic import BaseModel
8
+ from huggingface_hub import snapshot_download
9
+ import uvicorn
10
+
11
+ # ======================
12
+ # CONFIGURACIÓN DE DISPOSITIVO (GPU/CPU)
13
+ # ======================
14
+ # Detectar automáticamente si hay una GPU NVIDIA disponible
15
+ if torch.cuda.is_available():
16
+ DEVICE = "cuda"
17
+ print("✅ GPU NVIDIA detectada. Usando CUDA.")
18
+ else:
19
+ DEVICE = "cpu"
20
+ print("⚠️ GPU no detectada. Usando CPU (puede ser más lento).")
21
+
22
+ MODEL_REPO = "teszenofficial/mtptz"
23
+
24
+ # ======================
25
+ # DESCARGA DEL MODELO
26
+ # ======================
27
+ print(f"--- SISTEMA MTP 1.1 ---")
28
+ print(f"Descargando/Verificando modelo desde {MODEL_REPO}...")
29
+ repo_path = snapshot_download(
30
+ repo_id=MODEL_REPO,
31
+ repo_type="model",
32
+ local_dir="mtptz_repo"
33
+ )
34
+
35
+ sys.path.insert(0, repo_path)
36
+
37
+ try:
38
+ from model import MTPMiniModel
39
+ from tokenizer import MTPTokenizer
40
+ except ImportError:
41
+ print("Advertencia: Verifica la estructura de archivos del modelo.")
42
+ pass
43
+
44
+ # ======================
45
+ # CARGA DEL MODELO
46
+ # ======================
47
+ print("Cargando modelo en memoria...")
48
+ with open(os.path.join(repo_path, "mtp_mini.pkl"), "rb") as f:
49
+ model_data = pickle.load(f)
50
+
51
+ tokenizer = MTPTokenizer(
52
+ os.path.join(repo_path, "mtp_tokenizer.model")
53
+ )
54
+
55
+ config = model_data["config"]
56
+
57
+ model = MTPMiniModel(
58
+ vocab_size=model_data["vocab_size"],
59
+ d_model=config["model"]["d_model"],
60
+ n_layers=config["model"]["n_layers"],
61
+ n_heads=config["model"]["n_heads"],
62
+ d_ff=config["model"]["d_ff"],
63
+ max_seq_len=config["model"]["max_seq_len"],
64
+ dropout=0.0
65
+ )
66
+
67
+ # Cargar pesos y mover a GPU
68
+ model.load_state_dict(model_data["model_state_dict"])
69
+ model.to(DEVICE)
70
+ model.eval()
71
+ print(f"🚀 MTP 1.1 listo y corriendo en: {DEVICE.upper()}")
72
+
73
+ # ======================
74
+ # API FASTAPI
75
+ # ======================
76
+ app = FastAPI(title="MTP 1.1 API")
77
+
78
+ class Prompt(BaseModel):
79
+ text: str
80
+
81
+ @app.post("/generate")
82
+ def generate(prompt: Prompt):
83
+ user_input = prompt.text.strip()
84
+ if not user_input:
85
+ return {"reply": ""}
86
+
87
+ full_prompt = f"### Instrucción:\n{user_input}\n\n### Respuesta:\n"
88
+ tokens = [tokenizer.bos_id()] + tokenizer.encode(full_prompt)
89
+
90
+ # IMPORTANTE: Mover los inputs también a la GPU
91
+ input_ids = torch.tensor([tokens], device=DEVICE)
92
+
93
+ with torch.no_grad():
94
+ output_ids = model.generate(
95
+ input_ids,
96
+ max_new_tokens=150,
97
+ temperature=0.7,
98
+ top_k=50,
99
+ top_p=0.9
100
+ )
101
+
102
+ gen_tokens = output_ids[0, len(tokens):].tolist()
103
+
104
+ if tokenizer.eos_id() in gen_tokens:
105
+ gen_tokens = gen_tokens[:gen_tokens.index(tokenizer.eos_id())]
106
+
107
+ response = tokenizer.decode(gen_tokens).strip()
108
+ if "###" in response:
109
+ response = response.split("###")[0].strip()
110
+
111
+ return {"reply": response}
112
+
113
+ # ======================
114
+ # INTERFAZ WEB (FRONTEND MEJORADO)
115
+ # ======================
116
+ @app.get("/", response_class=HTMLResponse)
117
+ def chat_ui():
118
+ return """
119
+ <!DOCTYPE html>
120
+ <html lang="es">
121
+ <head>
122
+ <meta charset="UTF-8">
123
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
124
+ <title>MTP 1.1</title>
125
+ <link rel="preconnect" href="https://fonts.googleapis.com">
126
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
127
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
128
+ <style>
129
+ /* --- VARIABLES & THEME --- */
130
+ :root {
131
+ --bg-color: #131314;
132
+ --surface-color: #1E1F20;
133
+ --accent-color: #4a9eff;
134
+ --text-primary: #e3e3e3;
135
+ --text-secondary: #9aa0a6;
136
+ --user-bubble: #282a2c;
137
+ --bot-actions-color: #c4c7c5;
138
+ --logo-url: url('https://i.postimg.cc/yxS54PF3/IMG-3082.jpg');
139
+ }
140
+
141
+ * { box-sizing: border-box; outline: none; -webkit-tap-highlight-color: transparent; }
142
+
143
+ body {
144
+ margin: 0;
145
+ background-color: var(--bg-color);
146
+ font-family: 'Inter', sans-serif;
147
+ color: var(--text-primary);
148
+ height: 100dvh;
149
+ display: flex;
150
+ flex-direction: column;
151
+ overflow: hidden;
152
+ }
153
+
154
+ /* --- HEADER --- */
155
+ header {
156
+ padding: 12px 20px;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: space-between;
160
+ background: rgba(19, 19, 20, 0.85);
161
+ backdrop-filter: blur(12px);
162
+ position: fixed;
163
+ top: 0;
164
+ width: 100%;
165
+ z-index: 50;
166
+ border-bottom: 1px solid rgba(255,255,255,0.05);
167
+ }
168
+
169
+ .brand-wrapper {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 12px;
173
+ cursor: pointer;
174
+ }
175
+
176
+ .brand-logo {
177
+ width: 32px;
178
+ height: 32px;
179
+ border-radius: 50%;
180
+ background-image: var(--logo-url);
181
+ background-size: cover;
182
+ background-position: center;
183
+ border: 1px solid rgba(255,255,255,0.1);
184
+ }
185
+
186
+ .brand-text {
187
+ font-weight: 500;
188
+ font-size: 1.05rem;
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 8px;
192
+ }
193
+
194
+ .version-badge {
195
+ font-size: 0.75rem;
196
+ background: rgba(74, 158, 255, 0.15);
197
+ color: #8ab4f8;
198
+ padding: 2px 8px;
199
+ border-radius: 12px;
200
+ font-weight: 600;
201
+ }
202
+
203
+ /* --- CHAT AREA --- */
204
+ .chat-scroll {
205
+ flex: 1;
206
+ overflow-y: auto;
207
+ padding: 80px 20px 40px 20px;
208
+ display: flex;
209
+ flex-direction: column;
210
+ gap: 30px;
211
+ max-width: 850px;
212
+ margin: 0 auto;
213
+ width: 100%;
214
+ scroll-behavior: smooth;
215
+ }
216
+
217
+ /* Filas de Mensaje */
218
+ .msg-row {
219
+ display: flex;
220
+ gap: 16px;
221
+ width: 100%;
222
+ opacity: 0;
223
+ transform: translateY(10px);
224
+ animation: slideUpFade 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
225
+ }
226
+
227
+ .msg-row.user { justify-content: flex-end; }
228
+ .msg-row.bot { justify-content: flex-start; align-items: flex-start; }
229
+
230
+ /* Contenido */
231
+ .msg-content {
232
+ line-height: 1.6;
233
+ font-size: 1rem;
234
+ word-wrap: break-word;
235
+ max-width: 85%;
236
+ }
237
+
238
+ .user .msg-content {
239
+ background-color: var(--user-bubble);
240
+ padding: 10px 18px;
241
+ border-radius: 18px;
242
+ border-top-right-radius: 4px;
243
+ color: #fff;
244
+ }
245
+
246
+ .bot .msg-content-wrapper {
247
+ display: flex;
248
+ flex-direction: column;
249
+ gap: 8px;
250
+ width: 100%;
251
+ }
252
+
253
+ .bot .msg-text {
254
+ padding-top: 6px;
255
+ color: var(--text-primary);
256
+ }
257
+
258
+ /* Avatar Bot */
259
+ .bot-avatar {
260
+ width: 34px;
261
+ height: 34px;
262
+ min-width: 34px;
263
+ border-radius: 50%;
264
+ background-image: var(--logo-url);
265
+ background-size: cover;
266
+ box-shadow: 0 2px 6px rgba(0,0,0,0.2);
267
+ }
268
+
269
+ /* Acciones Bot */
270
+ .bot-actions {
271
+ display: flex;
272
+ gap: 10px;
273
+ opacity: 0;
274
+ transition: opacity 0.3s;
275
+ margin-top: 5px;
276
+ }
277
+
278
+ .action-btn {
279
+ background: transparent;
280
+ border: none;
281
+ color: var(--text-secondary);
282
+ cursor: pointer;
283
+ padding: 4px;
284
+ border-radius: 4px;
285
+ display: flex;
286
+ align-items: center;
287
+ transition: color 0.2s, background 0.2s;
288
+ }
289
+
290
+ .action-btn:hover {
291
+ color: var(--text-primary);
292
+ background: rgba(255,255,255,0.08);
293
+ }
294
+
295
+ .action-btn svg { width: 16px; height: 16px; fill: currentColor; }
296
+
297
+ /* Efecto Escritura (BOLITA AZUL) */
298
+ .typing-cursor::after {
299
+ content: '';
300
+ display: inline-block;
301
+ width: 10px;
302
+ height: 10px;
303
+ background: var(--accent-color);
304
+ border-radius: 50%;
305
+ margin-left: 5px;
306
+ vertical-align: middle;
307
+ animation: blink 1s infinite;
308
+ }
309
+
310
+ /* --- FOOTER & INPUT --- */
311
+ .footer-container {
312
+ padding: 0 20px 20px 20px;
313
+ background: linear-gradient(to top, var(--bg-color) 85%, transparent);
314
+ position: relative;
315
+ z-index: 60;
316
+ }
317
+
318
+ .input-box {
319
+ max-width: 850px;
320
+ margin: 0 auto;
321
+ background: var(--surface-color);
322
+ border-radius: 28px;
323
+ padding: 8px 10px 8px 20px;
324
+ display: flex;
325
+ align-items: center;
326
+ border: 1px solid rgba(255,255,255,0.1);
327
+ transition: border-color 0.2s, box-shadow 0.2s;
328
+ }
329
+
330
+ .input-box:focus-within {
331
+ border-color: rgba(74, 158, 255, 0.5);
332
+ box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.1);
333
+ }
334
+
335
+ #userInput {
336
+ flex: 1;
337
+ background: transparent;
338
+ border: none;
339
+ color: white;
340
+ font-size: 1rem;
341
+ font-family: inherit;
342
+ padding: 10px 0;
343
+ }
344
+
345
+ #mainBtn {
346
+ background: white;
347
+ color: black;
348
+ border: none;
349
+ width: 36px;
350
+ height: 36px;
351
+ border-radius: 50%;
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: center;
355
+ cursor: pointer;
356
+ margin-left: 8px;
357
+ transition: transform 0.2s;
358
+ }
359
+
360
+ #mainBtn:hover { transform: scale(1.05); }
361
+
362
+ .disclaimer {
363
+ text-align: center;
364
+ font-size: 0.75rem;
365
+ color: #666;
366
+ margin-top: 12px;
367
+ }
368
+
369
+ /* --- ANIMACIONES --- */
370
+ @keyframes slideUpFade {
371
+ from { opacity: 0; transform: translateY(15px); }
372
+ to { opacity: 1; transform: translateY(0); }
373
+ }
374
+
375
+ @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
376
+
377
+ @keyframes pulseAvatar {
378
+ 0% { box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.4); }
379
+ 70% { box-shadow: 0 0 0 8px rgba(74, 158, 255, 0); }
380
+ 100% { box-shadow: 0 0 0 0 rgba(74, 158, 255, 0); }
381
+ }
382
+
383
+ .pulsing { animation: pulseAvatar 1.5s infinite; }
384
+
385
+ ::-webkit-scrollbar { width: 8px; }
386
+ ::-webkit-scrollbar-track { background: transparent; }
387
+ ::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
388
+
389
+ </style>
390
+ </head>
391
+ <body>
392
+
393
+ <header>
394
+ <div class="brand-wrapper" onclick="location.reload()">
395
+ <div class="brand-logo"></div>
396
+ <div class="brand-text">
397
+ MTP <span class="version-badge">1.1</span>
398
+ </div>
399
+ </div>
400
+ </header>
401
+
402
+ <div id="chatScroll" class="chat-scroll">
403
+ <!-- Bienvenida -->
404
+ <div class="msg-row bot" style="animation-delay: 0.1s;">
405
+ <div class="bot-avatar"></div>
406
+ <div class="msg-content-wrapper">
407
+ <div class="msg-text">
408
+ ¡Hola! Soy MTP 1.1. ¿En qué puedo ayudarte hoy?
409
+ </div>
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ <div class="footer-container">
415
+ <div class="input-box">
416
+ <input type="text" id="userInput" placeholder="Escribe un mensaje..." autocomplete="off">
417
+ <button id="mainBtn" onclick="handleBtnClick()">
418
+ <!-- Icono dinámico -->
419
+ </button>
420
+ </div>
421
+ <div class="disclaimer">
422
+ MTP puede cometer errores. Considera verificar la información importante.
423
+ </div>
424
+ </div>
425
+
426
+ <script>
427
+ const chatScroll = document.getElementById('chatScroll');
428
+ const userInput = document.getElementById('userInput');
429
+ const mainBtn = document.getElementById('mainBtn');
430
+
431
+ // Variables de Estado
432
+ let isGenerating = false;
433
+ let abortController = null;
434
+ let typingTimeout = null;
435
+ let lastUserPrompt = "";
436
+
437
+ // Iconos SVG
438
+ const ICON_SEND = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"></path></svg>`;
439
+ const ICON_STOP = `<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="0"><rect x="2" y="2" width="20" height="20" rx="4" ry="4"></rect></svg>`;
440
+
441
+ // Inicial
442
+ mainBtn.innerHTML = ICON_SEND;
443
+
444
+ // --- UTILS ---
445
+ function scrollToBottom() {
446
+ chatScroll.scrollTop = chatScroll.scrollHeight;
447
+ }
448
+
449
+ function setBtnState(state) {
450
+ if (state === 'sending') {
451
+ mainBtn.innerHTML = ICON_STOP;
452
+ isGenerating = true;
453
+ } else {
454
+ mainBtn.innerHTML = ICON_SEND;
455
+ isGenerating = false;
456
+ abortController = null;
457
+ }
458
+ }
459
+
460
+ // --- CORE ---
461
+
462
+ function handleBtnClick() {
463
+ if (isGenerating) {
464
+ stopGeneration();
465
+ } else {
466
+ sendMessage();
467
+ }
468
+ }
469
+
470
+ function stopGeneration() {
471
+ if (abortController) abortController.abort();
472
+ if (typingTimeout) clearTimeout(typingTimeout);
473
+
474
+ // UI Limpieza
475
+ const activeCursor = document.querySelector('.typing-cursor');
476
+ if (activeCursor) activeCursor.classList.remove('typing-cursor');
477
+
478
+ const activeAvatar = document.querySelector('.pulsing');
479
+ if (activeAvatar) activeAvatar.classList.remove('pulsing');
480
+
481
+ setBtnState('idle');
482
+ userInput.focus();
483
+ }
484
+
485
+ async function sendMessage(textOverride = null) {
486
+ const text = textOverride || userInput.value.trim();
487
+ if (!text) return;
488
+
489
+ lastUserPrompt = text;
490
+
491
+ if (!textOverride) {
492
+ userInput.value = '';
493
+ addMessage(text, 'user');
494
+ }
495
+
496
+ setBtnState('sending');
497
+ abortController = new AbortController();
498
+
499
+ // Bot Placeholder
500
+ const botRow = document.createElement('div');
501
+ botRow.className = 'msg-row bot';
502
+
503
+ const avatar = document.createElement('div');
504
+ avatar.className = 'bot-avatar pulsing';
505
+
506
+ const wrapper = document.createElement('div');
507
+ wrapper.className = 'msg-content-wrapper';
508
+
509
+ const msgText = document.createElement('div');
510
+ msgText.className = 'msg-text';
511
+
512
+ wrapper.appendChild(msgText);
513
+ botRow.appendChild(avatar);
514
+ botRow.appendChild(wrapper);
515
+ chatScroll.appendChild(botRow);
516
+ scrollToBottom();
517
+
518
+ try {
519
+ const response = await fetch('/generate', {
520
+ method: 'POST',
521
+ headers: { 'Content-Type': 'application/json' },
522
+ body: JSON.stringify({ text: text }),
523
+ signal: abortController.signal
524
+ });
525
+
526
+ const data = await response.json();
527
+
528
+ if (!isGenerating) return;
529
+
530
+ avatar.classList.remove('pulsing');
531
+ const reply = data.reply || "No entendí eso.";
532
+
533
+ await typeWriter(msgText, reply);
534
+
535
+ if (isGenerating) {
536
+ addActions(wrapper, reply);
537
+ setBtnState('idle');
538
+ }
539
+
540
+ } catch (error) {
541
+ if (error.name === 'AbortError') {
542
+ msgText.textContent += " [Detenido]";
543
+ } else {
544
+ avatar.classList.remove('pulsing');
545
+ msgText.textContent = "Error de conexión.";
546
+ msgText.style.color = "#ff8b8b";
547
+ setBtnState('idle');
548
+ }
549
+ }
550
+ }
551
+
552
+ function addMessage(text, sender) {
553
+ const row = document.createElement('div');
554
+ row.className = `msg-row ${sender}`;
555
+ const content = document.createElement('div');
556
+ content.className = 'msg-content';
557
+ content.textContent = text;
558
+ row.appendChild(content);
559
+ chatScroll.appendChild(row);
560
+ scrollToBottom();
561
+ }
562
+
563
+ function typeWriter(element, text, speed = 12) {
564
+ return new Promise(resolve => {
565
+ let i = 0;
566
+ element.classList.add('typing-cursor');
567
+
568
+ function type() {
569
+ if (!isGenerating) {
570
+ element.classList.remove('typing-cursor');
571
+ resolve();
572
+ return;
573
+ }
574
+
575
+ if (i < text.length) {
576
+ element.textContent += text.charAt(i);
577
+ i++;
578
+ scrollToBottom();
579
+ typingTimeout = setTimeout(type, speed + Math.random() * 5);
580
+ } else {
581
+ element.classList.remove('typing-cursor');
582
+ resolve();
583
+ }
584
+ }
585
+ type();
586
+ });
587
+ }
588
+
589
+ function addActions(wrapperElement, textToCopy) {
590
+ const actionsDiv = document.createElement('div');
591
+ actionsDiv.className = 'bot-actions';
592
+
593
+ const copyBtn = document.createElement('button');
594
+ copyBtn.className = 'action-btn';
595
+ copyBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>`;
596
+ copyBtn.onclick = () => {
597
+ navigator.clipboard.writeText(textToCopy);
598
+ };
599
+
600
+ const regenBtn = document.createElement('button');
601
+ regenBtn.className = 'action-btn';
602
+ regenBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"></path><path d="M1 20v-6h6"></path><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path></svg>`;
603
+ regenBtn.onclick = () => {
604
+ sendMessage(lastUserPrompt);
605
+ };
606
+
607
+ actionsDiv.appendChild(copyBtn);
608
+ actionsDiv.appendChild(regenBtn);
609
+ wrapperElement.appendChild(actionsDiv);
610
+
611
+ requestAnimationFrame(() => actionsDiv.style.opacity = "1");
612
+ scrollToBottom();
613
+ }
614
+
615
+ userInput.addEventListener('keydown', (e) => {
616
+ if (e.key === 'Enter') handleBtnClick();
617
+ });
618
+
619
+ window.onload = () => userInput.focus();
620
+
621
+ </script>
622
+ </body>
623
+ </html>
624
+ """
625
+
626
+ # ======================
627
+ # ENTRYPOINT
628
+ # ======================
629
+ if __name__ == "__main__":
630
+ uvicorn.run(app, host="0.0.0.0", port=7860)
631
+
632
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ torch
4
+ sentencepiece
5
+ huggingface_hub