teszenofficial commited on
Commit
492c0f6
·
verified ·
1 Parent(s): 87719cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -502
app.py CHANGED
@@ -23,8 +23,9 @@ MODEL_REPO = "teszenofficial/mtptz"
23
  # ======================
24
  # DESCARGA DEL MODELO
25
  # ======================
26
- print(f"--- SISTEMA MTP 1.1 ---")
27
  print(f"Descargando/Verificando modelo desde {MODEL_REPO}...")
 
28
  repo_path = snapshot_download(
29
  repo_id=MODEL_REPO,
30
  repo_type="model",
@@ -40,6 +41,7 @@ from tokenizer import MTPTokenizer
40
  # CARGA DEL MODELO
41
  # ======================
42
  print("Cargando modelo en memoria...")
 
43
  with open(os.path.join(repo_path, "mtp_mini.pkl"), "rb") as f:
44
  model_data = pickle.load(f)
45
 
@@ -66,7 +68,7 @@ model.eval()
66
  print(f"🚀 MTP 1.1 listo y corriendo en: {DEVICE.upper()}")
67
 
68
  # ======================
69
- # PARÁMETROS AVANZADOS (AÑADIDOS)
70
  # ======================
71
  TEMPERATURE = 0.7
72
  TOP_K = 40
@@ -85,7 +87,7 @@ IDENTITY_QUERIES = [
85
  ]
86
 
87
  # ======================
88
- # UTILIDADES NUEVAS
89
  # ======================
90
  def apply_repetition_penalty(logits, input_ids, penalty):
91
  for token_id in set(input_ids.view(-1).tolist()):
@@ -96,13 +98,13 @@ def apply_repetition_penalty(logits, input_ids, penalty):
96
  return logits
97
 
98
 
99
- def generate_advanced(full_prompt: str):
100
- text_lower = full_prompt.lower().strip()
101
  max_tokens = MAX_TOKENS
102
 
103
- if any(x in text_lower for x in SHORT_RESPONSES):
104
  max_tokens = 50
105
- elif any(x in text_lower for x in IDENTITY_QUERIES):
106
  max_tokens = 80
107
 
108
  tokens = [tokenizer.bos_id()] + tokenizer.encode(full_prompt)
@@ -113,21 +115,18 @@ def generate_advanced(full_prompt: str):
113
  with torch.no_grad():
114
  for _ in range(max_tokens):
115
  input_cond = input_ids[:, -model.max_seq_len:]
116
-
117
  logits, _ = model(input_cond)
118
- next_logits = logits[:, -1, :].clone()
119
 
120
- next_logits = apply_repetition_penalty(
121
- next_logits, input_ids, REPETITION_PENALTY
122
- )
123
 
124
- next_logits /= TEMPERATURE
 
 
125
 
126
- if TOP_K > 0:
127
- min_val = torch.topk(next_logits, TOP_K)[0][..., -1, None]
128
- next_logits[next_logits < min_val] = -float("inf")
129
-
130
- sorted_logits, sorted_indices = torch.sort(next_logits, descending=True)
131
  probs = torch.softmax(sorted_logits, dim=-1)
132
  cum_probs = torch.cumsum(probs, dim=-1)
133
 
@@ -136,9 +135,9 @@ def generate_advanced(full_prompt: str):
136
  remove[..., 0] = False
137
 
138
  indices_remove = remove.scatter(1, sorted_indices, remove)
139
- next_logits[indices_remove] = -float("inf")
140
 
141
- probs = torch.softmax(next_logits, dim=-1)
142
  next_token = torch.multinomial(probs, 1)
143
 
144
  if next_token.item() == tokenizer.eos_id():
@@ -148,23 +147,25 @@ def generate_advanced(full_prompt: str):
148
  input_ids = torch.cat([input_ids, next_token], dim=1)
149
 
150
  if len(generated) > 30:
151
- last = tokenizer.decode(generated[-5:])
152
- if any(p in last for p in [".", "!", "?", "\n\n"]):
153
  break
154
 
155
  if not generated:
156
- return "No pude generar una respuesta."
157
 
158
  return tokenizer.decode(generated).strip()
159
 
 
160
  # ======================
161
- # API FASTAPI
162
  # ======================
163
  app = FastAPI(title="MTP 1.1 API")
164
 
165
  class Prompt(BaseModel):
166
  text: str
167
 
 
168
  @app.post("/generate")
169
  def generate(prompt: Prompt):
170
  user_input = prompt.text.strip()
@@ -172,7 +173,7 @@ def generate(prompt: Prompt):
172
  return {"reply": ""}
173
 
174
  full_prompt = f"### Instrucción:\n{user_input}\n\n### Respuesta:\n"
175
- response = generate_advanced(full_prompt)
176
 
177
  if "###" in response:
178
  response = response.split("###")[0].strip()
@@ -180,12 +181,11 @@ def generate(prompt: Prompt):
180
  return {"reply": response}
181
 
182
  # ======================
183
- # INTERFAZ WEB (HTML ORIGINAL COMPLETO)
184
  # ======================
185
  @app.get("/", response_class=HTMLResponse)
186
  def chat_ui():
187
- return """
188
- <!DOCTYPE html>
189
  <html lang="es">
190
  <head>
191
  <meta charset="UTF-8">
@@ -195,7 +195,6 @@ def chat_ui():
195
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
196
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
197
  <style>
198
- /* --- VARIABLES & THEME --- */
199
  :root {
200
  --bg-color: #131314;
201
  --surface-color: #1E1F20;
@@ -203,494 +202,53 @@ def chat_ui():
203
  --text-primary: #e3e3e3;
204
  --text-secondary: #9aa0a6;
205
  --user-bubble: #282a2c;
206
- --bot-actions-color: #c4c7c5;
207
  --logo-url: url('https://i.postimg.cc/yxS54PF3/IMG-3082.jpg');
208
  }
209
-
210
- * { box-sizing: border-box; outline: none; -webkit-tap-highlight-color: transparent; }
211
-
212
- body {
213
- margin: 0;
214
- background-color: var(--bg-color);
215
- font-family: 'Inter', sans-serif;
216
- color: var(--text-primary);
217
- height: 100dvh;
218
- display: flex;
219
- flex-direction: column;
220
- overflow: hidden;
221
- }
222
-
223
- /* --- HEADER --- */
224
- header {
225
- padding: 12px 20px;
226
- display: flex;
227
- align-items: center;
228
- justify-content: space-between;
229
- background: rgba(19, 19, 20, 0.85);
230
- backdrop-filter: blur(12px);
231
- position: fixed;
232
- top: 0;
233
- width: 100%;
234
- z-index: 50;
235
- border-bottom: 1px solid rgba(255,255,255,0.05);
236
- }
237
-
238
- .brand-wrapper {
239
- display: flex;
240
- align-items: center;
241
- gap: 12px;
242
- cursor: pointer;
243
- }
244
-
245
- .brand-logo {
246
- width: 32px;
247
- height: 32px;
248
- border-radius: 50%;
249
- background-image: var(--logo-url);
250
- background-size: cover;
251
- background-position: center;
252
- border: 1px solid rgba(255,255,255,0.1);
253
- }
254
-
255
- .brand-text {
256
- font-weight: 500;
257
- font-size: 1.05rem;
258
- display: flex;
259
- align-items: center;
260
- gap: 8px;
261
- }
262
-
263
- .version-badge {
264
- font-size: 0.75rem;
265
- background: rgba(74, 158, 255, 0.15);
266
- color: #8ab4f8;
267
- padding: 2px 8px;
268
- border-radius: 12px;
269
- font-weight: 600;
270
- }
271
-
272
- /* --- CHAT AREA --- */
273
- .chat-scroll {
274
- flex: 1;
275
- overflow-y: auto;
276
- padding: 80px 20px 40px 20px;
277
- display: flex;
278
- flex-direction: column;
279
- gap: 30px;
280
- max-width: 850px;
281
- margin: 0 auto;
282
- width: 100%;
283
- scroll-behavior: smooth;
284
- }
285
-
286
- /* Filas de Mensaje */
287
- .msg-row {
288
- display: flex;
289
- gap: 16px;
290
- width: 100%;
291
- opacity: 0;
292
- transform: translateY(10px);
293
- animation: slideUpFade 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
294
- }
295
-
296
- .msg-row.user { justify-content: flex-end; }
297
- .msg-row.bot { justify-content: flex-start; align-items: flex-start; }
298
-
299
- /* Contenido */
300
- .msg-content {
301
- line-height: 1.6;
302
- font-size: 1rem;
303
- word-wrap: break-word;
304
- max-width: 85%;
305
- }
306
-
307
- .user .msg-content {
308
- background-color: var(--user-bubble);
309
- padding: 10px 18px;
310
- border-radius: 18px;
311
- border-top-right-radius: 4px;
312
- color: #fff;
313
- }
314
-
315
- .bot .msg-content-wrapper {
316
- display: flex;
317
- flex-direction: column;
318
- gap: 8px;
319
- width: 100%;
320
- }
321
-
322
- .bot .msg-text {
323
- padding-top: 6px;
324
- color: var(--text-primary);
325
- }
326
-
327
- /* Avatar Bot */
328
- .bot-avatar {
329
- width: 34px;
330
- height: 34px;
331
- min-width: 34px;
332
- border-radius: 50%;
333
- background-image: var(--logo-url);
334
- background-size: cover;
335
- box-shadow: 0 2px 6px rgba(0,0,0,0.2);
336
- }
337
-
338
- /* Acciones Bot */
339
- .bot-actions {
340
- display: flex;
341
- gap: 10px;
342
- opacity: 0;
343
- transition: opacity 0.3s;
344
- margin-top: 5px;
345
- }
346
-
347
- .action-btn {
348
- background: transparent;
349
- border: none;
350
- color: var(--text-secondary);
351
- cursor: pointer;
352
- padding: 4px;
353
- border-radius: 4px;
354
- display: flex;
355
- align-items: center;
356
- transition: color 0.2s, background 0.2s;
357
- }
358
-
359
- .action-btn:hover {
360
- color: var(--text-primary);
361
- background: rgba(255,255,255,0.08);
362
- }
363
-
364
- .action-btn svg { width: 16px; height: 16px; fill: currentColor; }
365
-
366
- /* Efecto Escritura (BOLITA AZUL) */
367
- .typing-cursor::after {
368
- content: '';
369
- display: inline-block;
370
- width: 10px;
371
- height: 10px;
372
- background: var(--accent-color);
373
- border-radius: 50%;
374
- margin-left: 5px;
375
- vertical-align: middle;
376
- animation: blink 1s infinite;
377
- }
378
-
379
- /* --- FOOTER & INPUT --- */
380
- .footer-container {
381
- padding: 0 20px 20px 20px;
382
- background: linear-gradient(to top, var(--bg-color) 85%, transparent);
383
- position: relative;
384
- z-index: 60;
385
- }
386
-
387
- .input-box {
388
- max-width: 850px;
389
- margin: 0 auto;
390
- background: var(--surface-color);
391
- border-radius: 28px;
392
- padding: 8px 10px 8px 20px;
393
- display: flex;
394
- align-items: center;
395
- border: 1px solid rgba(255,255,255,0.1);
396
- transition: border-color 0.2s, box-shadow 0.2s;
397
- }
398
-
399
- .input-box:focus-within {
400
- border-color: rgba(74, 158, 255, 0.5);
401
- box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.1);
402
- }
403
-
404
- #userInput {
405
- flex: 1;
406
- background: transparent;
407
- border: none;
408
- color: white;
409
- font-size: 1rem;
410
- font-family: inherit;
411
- padding: 10px 0;
412
- }
413
-
414
- #mainBtn {
415
- background: white;
416
- color: black;
417
- border: none;
418
- width: 36px;
419
- height: 36px;
420
- border-radius: 50%;
421
- display: flex;
422
- align-items: center;
423
- justify-content: center;
424
- cursor: pointer;
425
- margin-left: 8px;
426
- transition: transform 0.2s;
427
- }
428
-
429
- #mainBtn:hover { transform: scale(1.05); }
430
-
431
- .disclaimer {
432
- text-align: center;
433
- font-size: 0.75rem;
434
- color: #666;
435
- margin-top: 12px;
436
- }
437
-
438
- /* --- ANIMACIONES --- */
439
- @keyframes slideUpFade {
440
- from { opacity: 0; transform: translateY(15px); }
441
- to { opacity: 1; transform: translateY(0); }
442
- }
443
-
444
- @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
445
-
446
- @keyframes pulseAvatar {
447
- 0% { box-shadow: 0 0 0 0 rgba(74, 158, 255, 0.4); }
448
- 70% { box-shadow: 0 0 0 8px rgba(74, 158, 255, 0); }
449
- 100% { box-shadow: 0 0 0 0 rgba(74, 158, 255, 0); }
450
- }
451
-
452
- .pulsing { animation: pulseAvatar 1.5s infinite; }
453
-
454
- ::-webkit-scrollbar { width: 8px; }
455
- ::-webkit-scrollbar-track { background: transparent; }
456
- ::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
457
-
458
  </style>
459
  </head>
460
  <body>
461
-
462
  <header>
463
- <div class="brand-wrapper" onclick="location.reload()">
464
- <div class="brand-logo"></div>
465
- <div class="brand-text">
466
- MTP <span class="version-badge">1.1</span>
467
- </div>
468
- </div>
469
  </header>
470
-
471
  <div id="chatScroll" class="chat-scroll">
472
- <!-- Bienvenida -->
473
- <div class="msg-row bot" style="animation-delay: 0.1s;">
474
- <div class="bot-avatar"></div>
475
- <div class="msg-content-wrapper">
476
- <div class="msg-text">
477
- ¡Hola! Soy MTP 1.1. ¿En qué puedo ayudarte hoy?
478
- </div>
479
- </div>
480
- </div>
481
  </div>
482
-
483
  <div class="footer-container">
484
- <div class="input-box">
485
- <input type="text" id="userInput" placeholder="Escribe un mensaje..." autocomplete="off">
486
- <button id="mainBtn" onclick="handleBtnClick()">
487
- <!-- Icono dinámico -->
488
- </button>
489
- </div>
490
- <div class="disclaimer">
491
- MTP puede cometer errores. Considera verificar la información importante.
492
- </div>
493
  </div>
494
-
495
  <script>
496
- const chatScroll = document.getElementById('chatScroll');
497
- const userInput = document.getElementById('userInput');
498
- const mainBtn = document.getElementById('mainBtn');
499
-
500
- // Variables de Estado
501
- let isGenerating = false;
502
- let abortController = null;
503
- let typingTimeout = null;
504
- let lastUserPrompt = "";
505
-
506
- // Iconos SVG
507
- 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>`;
508
- 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>`;
509
-
510
- // Inicial
511
- mainBtn.innerHTML = ICON_SEND;
512
-
513
- // --- UTILS ---
514
- function scrollToBottom() {
515
- chatScroll.scrollTop = chatScroll.scrollHeight;
516
  }
517
-
518
- function setBtnState(state) {
519
- if (state === 'sending') {
520
- mainBtn.innerHTML = ICON_STOP;
521
- isGenerating = true;
522
- } else {
523
- mainBtn.innerHTML = ICON_SEND;
524
- isGenerating = false;
525
- abortController = null;
526
- }
527
- }
528
-
529
- // --- CORE ---
530
-
531
- function handleBtnClick() {
532
- if (isGenerating) {
533
- stopGeneration();
534
- } else {
535
- sendMessage();
536
- }
537
- }
538
-
539
- function stopGeneration() {
540
- if (abortController) abortController.abort();
541
- if (typingTimeout) clearTimeout(typingTimeout);
542
-
543
- // UI Limpieza
544
- const activeCursor = document.querySelector('.typing-cursor');
545
- if (activeCursor) activeCursor.classList.remove('typing-cursor');
546
-
547
- const activeAvatar = document.querySelector('.pulsing');
548
- if (activeAvatar) activeAvatar.classList.remove('pulsing');
549
-
550
- setBtnState('idle');
551
- userInput.focus();
552
- }
553
-
554
- async function sendMessage(textOverride = null) {
555
- const text = textOverride || userInput.value.trim();
556
- if (!text) return;
557
-
558
- lastUserPrompt = text;
559
-
560
- if (!textOverride) {
561
- userInput.value = '';
562
- addMessage(text, 'user');
563
- }
564
-
565
- setBtnState('sending');
566
- abortController = new AbortController();
567
-
568
- // Bot Placeholder
569
- const botRow = document.createElement('div');
570
- botRow.className = 'msg-row bot';
571
-
572
- const avatar = document.createElement('div');
573
- avatar.className = 'bot-avatar pulsing';
574
-
575
- const wrapper = document.createElement('div');
576
- wrapper.className = 'msg-content-wrapper';
577
-
578
- const msgText = document.createElement('div');
579
- msgText.className = 'msg-text';
580
-
581
- wrapper.appendChild(msgText);
582
- botRow.appendChild(avatar);
583
- botRow.appendChild(wrapper);
584
- chatScroll.appendChild(botRow);
585
- scrollToBottom();
586
-
587
- try {
588
- const response = await fetch('/generate', {
589
- method: 'POST',
590
- headers: { 'Content-Type': 'application/json' },
591
- body: JSON.stringify({ text: text }),
592
- signal: abortController.signal
593
- });
594
-
595
- const data = await response.json();
596
-
597
- if (!isGenerating) return;
598
-
599
- avatar.classList.remove('pulsing');
600
- const reply = data.reply || "No entendí eso.";
601
-
602
- await typeWriter(msgText, reply);
603
-
604
- if (isGenerating) {
605
- addActions(wrapper, reply);
606
- setBtnState('idle');
607
- }
608
-
609
- } catch (error) {
610
- if (error.name === 'AbortError') {
611
- msgText.textContent += " [Detenido]";
612
- } else {
613
- avatar.classList.remove('pulsing');
614
- msgText.textContent = "Error de conexión.";
615
- msgText.style.color = "#ff8b8b";
616
- setBtnState('idle');
617
- }
618
- }
619
- }
620
-
621
- function addMessage(text, sender) {
622
- const row = document.createElement('div');
623
- row.className = `msg-row ${sender}`;
624
- const content = document.createElement('div');
625
- content.className = 'msg-content';
626
- content.textContent = text;
627
- row.appendChild(content);
628
- chatScroll.appendChild(row);
629
- scrollToBottom();
630
- }
631
-
632
- function typeWriter(element, text, speed = 12) {
633
- return new Promise(resolve => {
634
- let i = 0;
635
- element.classList.add('typing-cursor');
636
-
637
- function type() {
638
- if (!isGenerating) {
639
- element.classList.remove('typing-cursor');
640
- resolve();
641
- return;
642
- }
643
-
644
- if (i < text.length) {
645
- element.textContent += text.charAt(i);
646
- i++;
647
- scrollToBottom();
648
- typingTimeout = setTimeout(type, speed + Math.random() * 5);
649
- } else {
650
- element.classList.remove('typing-cursor');
651
- resolve();
652
- }
653
- }
654
- type();
655
- });
656
- }
657
-
658
- function addActions(wrapperElement, textToCopy) {
659
- const actionsDiv = document.createElement('div');
660
- actionsDiv.className = 'bot-actions';
661
-
662
- const copyBtn = document.createElement('button');
663
- copyBtn.className = 'action-btn';
664
- 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>`;
665
- copyBtn.onclick = () => {
666
- navigator.clipboard.writeText(textToCopy);
667
- };
668
-
669
- const regenBtn = document.createElement('button');
670
- regenBtn.className = 'action-btn';
671
- 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>`;
672
- regenBtn.onclick = () => {
673
- sendMessage(lastUserPrompt);
674
- };
675
-
676
- actionsDiv.appendChild(copyBtn);
677
- actionsDiv.appendChild(regenBtn);
678
- wrapperElement.appendChild(actionsDiv);
679
-
680
- requestAnimationFrame(() => actionsDiv.style.opacity = "1");
681
- scrollToBottom();
682
- }
683
-
684
- userInput.addEventListener('keydown', (e) => {
685
- if (e.key === 'Enter') handleBtnClick();
686
- });
687
-
688
- window.onload = () => userInput.focus();
689
-
690
  </script>
691
  </body>
692
- </html>
693
- """
694
 
695
  # ======================
696
  # ENTRYPOINT
 
23
  # ======================
24
  # DESCARGA DEL MODELO
25
  # ======================
26
+ print("--- SISTEMA MTP 1.1 ---")
27
  print(f"Descargando/Verificando modelo desde {MODEL_REPO}...")
28
+
29
  repo_path = snapshot_download(
30
  repo_id=MODEL_REPO,
31
  repo_type="model",
 
41
  # CARGA DEL MODELO
42
  # ======================
43
  print("Cargando modelo en memoria...")
44
+
45
  with open(os.path.join(repo_path, "mtp_mini.pkl"), "rb") as f:
46
  model_data = pickle.load(f)
47
 
 
68
  print(f"🚀 MTP 1.1 listo y corriendo en: {DEVICE.upper()}")
69
 
70
  # ======================
71
+ # PARÁMETROS DE GENERACIÓN (de mtp_chat.py)
72
  # ======================
73
  TEMPERATURE = 0.7
74
  TOP_K = 40
 
87
  ]
88
 
89
  # ======================
90
+ # GENERACIÓN REAL (FIX)
91
  # ======================
92
  def apply_repetition_penalty(logits, input_ids, penalty):
93
  for token_id in set(input_ids.view(-1).tolist()):
 
98
  return logits
99
 
100
 
101
+ def generate_response(full_prompt: str):
102
+ lower = full_prompt.lower()
103
  max_tokens = MAX_TOKENS
104
 
105
+ if any(x in lower for x in SHORT_RESPONSES):
106
  max_tokens = 50
107
+ elif any(x in lower for x in IDENTITY_QUERIES):
108
  max_tokens = 80
109
 
110
  tokens = [tokenizer.bos_id()] + tokenizer.encode(full_prompt)
 
115
  with torch.no_grad():
116
  for _ in range(max_tokens):
117
  input_cond = input_ids[:, -model.max_seq_len:]
 
118
  logits, _ = model(input_cond)
119
+ logits = logits[:, -1, :]
120
 
121
+ logits = apply_repetition_penalty(logits, input_ids, REPETITION_PENALTY)
122
+ logits /= TEMPERATURE
 
123
 
124
+ # Top-k
125
+ topk_vals, _ = torch.topk(logits, TOP_K)
126
+ logits[logits < topk_vals[:, -1].unsqueeze(1)] = -float("inf")
127
 
128
+ # Top-p
129
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
 
 
 
130
  probs = torch.softmax(sorted_logits, dim=-1)
131
  cum_probs = torch.cumsum(probs, dim=-1)
132
 
 
135
  remove[..., 0] = False
136
 
137
  indices_remove = remove.scatter(1, sorted_indices, remove)
138
+ logits[indices_remove] = -float("inf")
139
 
140
+ probs = torch.softmax(logits, dim=-1)
141
  next_token = torch.multinomial(probs, 1)
142
 
143
  if next_token.item() == tokenizer.eos_id():
 
147
  input_ids = torch.cat([input_ids, next_token], dim=1)
148
 
149
  if len(generated) > 30:
150
+ tail = tokenizer.decode(generated[-5:])
151
+ if any(p in tail for p in [".", "!", "?"]):
152
  break
153
 
154
  if not generated:
155
+ return "Lo siento, no pude generar una respuesta."
156
 
157
  return tokenizer.decode(generated).strip()
158
 
159
+
160
  # ======================
161
+ # FASTAPI
162
  # ======================
163
  app = FastAPI(title="MTP 1.1 API")
164
 
165
  class Prompt(BaseModel):
166
  text: str
167
 
168
+
169
  @app.post("/generate")
170
  def generate(prompt: Prompt):
171
  user_input = prompt.text.strip()
 
173
  return {"reply": ""}
174
 
175
  full_prompt = f"### Instrucción:\n{user_input}\n\n### Respuesta:\n"
176
+ response = generate_response(full_prompt)
177
 
178
  if "###" in response:
179
  response = response.split("###")[0].strip()
 
181
  return {"reply": response}
182
 
183
  # ======================
184
+ # UI (HTML COMPLETO)
185
  # ======================
186
  @app.get("/", response_class=HTMLResponse)
187
  def chat_ui():
188
+ return """<!DOCTYPE html>
 
189
  <html lang="es">
190
  <head>
191
  <meta charset="UTF-8">
 
195
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
196
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
197
  <style>
 
198
  :root {
199
  --bg-color: #131314;
200
  --surface-color: #1E1F20;
 
202
  --text-primary: #e3e3e3;
203
  --text-secondary: #9aa0a6;
204
  --user-bubble: #282a2c;
 
205
  --logo-url: url('https://i.postimg.cc/yxS54PF3/IMG-3082.jpg');
206
  }
207
+ *{box-sizing:border-box}
208
+ body{margin:0;background:var(--bg-color);font-family:Inter;color:var(--text-primary);height:100vh;display:flex;flex-direction:column}
209
+ header{padding:12px 20px;display:flex;align-items:center;gap:12px;border-bottom:1px solid rgba(255,255,255,.05)}
210
+ .brand-logo{width:32px;height:32px;border-radius:50%;background-image:var(--logo-url);background-size:cover}
211
+ .chat-scroll{flex:1;overflow-y:auto;padding:20px;max-width:850px;margin:auto;width:100%}
212
+ .msg-row{margin-bottom:18px}
213
+ .msg-row.user{text-align:right}
214
+ .msg-row.bot{text-align:left}
215
+ .msg-content{display:inline-block;padding:10px 16px;border-radius:18px;max-width:80%}
216
+ .user .msg-content{background:var(--user-bubble)}
217
+ .footer-container{padding:20px}
218
+ .input-box{display:flex;background:var(--surface-color);border-radius:30px;padding:10px}
219
+ #userInput{flex:1;background:transparent;border:none;color:white;font-size:16px}
220
+ #mainBtn{background:white;border:none;border-radius:50%;width:36px;height:36px}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  </style>
222
  </head>
223
  <body>
 
224
  <header>
225
+ <div class="brand-logo"></div>
226
+ <strong>MTP 1.1</strong>
 
 
 
 
227
  </header>
 
228
  <div id="chatScroll" class="chat-scroll">
229
+ <div class="msg-row bot"><div class="msg-content">¡Hola! Soy MTP 1.1. ¿En qué puedo ayudarte?</div></div>
 
 
 
 
 
 
 
 
230
  </div>
 
231
  <div class="footer-container">
232
+ <div class="input-box">
233
+ <input id="userInput" placeholder="Escribe un mensaje..." />
234
+ <button id="mainBtn" onclick="send()">➤</button>
235
+ </div>
 
 
 
 
 
236
  </div>
 
237
  <script>
238
+ async function send(){
239
+ const i=document.getElementById('userInput');
240
+ const t=i.value.trim();
241
+ if(!t)return;
242
+ i.value='';
243
+ chatScroll.innerHTML+=`<div class="msg-row user"><div class="msg-content">${t}</div></div>`;
244
+ const r=await fetch('/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:t})});
245
+ const d=await r.json();
246
+ chatScroll.innerHTML+=`<div class="msg-row bot"><div class="msg-content">${d.reply}</div></div>`;
247
+ chatScroll.scrollTop=chatScroll.scrollHeight;
 
 
 
 
 
 
 
 
 
 
248
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  </script>
250
  </body>
251
+ </html>"""
 
252
 
253
  # ======================
254
  # ENTRYPOINT