GPT-2 / app.py
teszenofficial's picture
Create app.py
6c3c71a verified
from flask import Flask, render_template_string, request, jsonify
from transformers import pipeline, set_seed
import threading
# --- CONFIGURACIÓN DEL MODELO ---
# Cargamos el modelo al inicio una sola vez
print("Cargando modelo GPT-2...")
generator = pipeline('text-generation', model='openai-community/gpt2')
set_seed(42)
print("Modelo cargado.")
app = Flask(__name__)
# --- PLANTILLA HTML/CSS/JS (Incrustada para tener un solo archivo manejable) ---
# Esto define la interfaz visual completa
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPT-2 Chat Clone</title>
<!-- Font Awesome para iconos -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* --- ESTILOS GENERALES (Reset & Colors) --- */
:root {
--bg-color: #343541;
--chat-input-bg: #40414F;
--user-msg-bg: #343541;
--bot-msg-bg: #444654;
--text-color: #ECECF1;
--border-color: rgba(32,33,35,0.5);
--sidebar-color: #202123;
}
body, html {
margin: 0;
padding: 0;
font-family: 'Söhne', 'Segoe UI', Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
height: 100%;
overflow: hidden; /* Evitar scroll en el body, solo en el chat */
}
/* --- LAYOUT PRINCIPAL --- */
.main-container {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 100%;
}
/* --- ÁREA DE CHAT --- */
#chat-history {
flex: 1;
overflow-y: auto;
scroll-behavior: smooth;
padding-bottom: 150px; /* Espacio para el input fijo abajo */
}
/* Filas de mensajes (Stripe style) */
.message-row {
width: 100%;
border-bottom: 1px solid rgba(0,0,0,0.1);
padding: 24px 0;
display: flex;
justify-content: center;
}
.message-row.user {
background-color: var(--user-msg-bg);
}
.message-row.bot {
background-color: var(--bot-msg-bg);
border-bottom: 1px solid var(--border-color);
}
.message-content {
max-width: 768px;
width: 90%;
display: flex;
gap: 20px;
font-size: 16px;
line-height: 1.6;
}
/* Avatares */
.avatar {
width: 30px;
height: 30px;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.avatar.user {
background-color: #5436DA;
}
.avatar.bot {
background-color: #19C37D; /* El verde clásico */
}
.text-content {
padding-top: 2px;
width: 100%;
white-space: pre-wrap; /* Mantiene saltos de línea */
}
/* --- ÁREA DE INPUT FIJA --- */
.input-area {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-image: linear-gradient(180deg,rgba(53,55,64,0),#353740 58.85%);
padding-bottom: 30px;
padding-top: 20px;
display: flex;
justify-content: center;
}
.input-container {
width: 90%;
max-width: 768px;
position: relative;
}
.chat-input {
width: 100%;
background-color: var(--chat-input-bg);
border: 1px solid rgba(32,33,35,0.5);
border-radius: 6px;
color: white;
padding: 12px 45px 12px 15px;
font-size: 16px;
font-family: inherit;
resize: none;
height: 50px;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
outline: none;
}
.chat-input:focus {
box-shadow: 0 0 15px rgba(0,0,0,0.3);
border-color: rgba(32,33,35,0);
}
.send-btn {
position: absolute;
right: 12px;
bottom: 12px;
background: transparent;
border: none;
color: #8e8ea0;
cursor: pointer;
transition: color 0.2s;
}
.send-btn:hover {
color: #d9d9e3;
}
.send-btn:disabled {
opacity: 0.5;
cursor: default;
}
/* --- PANTALLA DE BIENVENIDA --- */
#welcome-screen {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 80%;
text-align: center;
color: var(--text-color);
}
.logo-big {
font-size: 40px;
font-weight: bold;
margin-bottom: 40px;
}
.columns {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
}
.col {
display: flex;
flex-direction: column;
align-items: center;
width: 240px;
}
.icon-header {
margin-bottom: 10px;
font-size: 24px;
}
.card {
background-color: #3E3F4B;
padding: 12px;
border-radius: 6px;
margin-bottom: 12px;
width: 100%;
font-size: 14px;
}
/* --- ANIMACIONES --- */
.cursor::after {
content: "▋";
display: inline-block;
animation: blink 1s infinite;
color: var(--text-color);
margin-left: 2px;
font-size: 14px;
vertical-align: middle;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* Ocultar scrollbar pero permitir scroll */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #565869;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #8e8ea0;
}
</style>
</head>
<body>
<div class="main-container">
<!-- Contenedor del Historial -->
<div id="chat-history">
<!-- Pantalla de bienvenida -->
<div id="welcome-screen">
<div class="logo-big">GPT-2</div>
<div class="columns">
<div class="col">
<div class="icon-header"><i class="fa-regular fa-sun"></i></div>
<h3>Examples</h3>
<div class="card">"Explain quantum computing in simple terms"</div>
<div class="card">"Got any creative ideas for a 10 year old’s birthday?"</div>
</div>
<div class="col">
<div class="icon-header"><i class="fa-solid fa-bolt"></i></div>
<h3>Capabilities</h3>
<div class="card">Remembers what user said earlier in the conversation</div>
<div class="card">Allows user to provide follow-up corrections</div>
</div>
<div class="col">
<div class="icon-header"><i class="fa-solid fa-triangle-exclamation"></i></div>
<h3>Limitations</h3>
<div class="card">May occasionally generate incorrect information</div>
<div class="card">Limited knowledge of world and events after 2021</div>
</div>
</div>
</div>
<!-- Los mensajes se insertarán aquí -->
</div>
<!-- Input -->
<div class="input-area">
<div class="input-container">
<textarea id="user-input" class="chat-input" rows="1" placeholder="Send a message..."></textarea>
<button id="send-btn" class="send-btn"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
<script>
const chatHistory = document.getElementById('chat-history');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const welcomeScreen = document.getElementById('welcome-screen');
let isFirstMessage = true;
// Auto-resize del textarea
userInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
if(this.value === '') this.style.height = '50px';
});
// Enviar con Enter (sin Shift)
userInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
sendBtn.addEventListener('click', sendMessage);
async function sendMessage() {
const text = userInput.value.trim();
if (!text) return;
// 1. UI Updates
if (isFirstMessage) {
welcomeScreen.style.display = 'none';
isFirstMessage = false;
}
userInput.value = '';
userInput.style.height = '50px';
addMessage(text, 'user');
// Placeholder Bot (Loading)
const botMsgId = addMessage('', 'bot', true);
// 2. Fetch Backend
try {
const response = await fetch('/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: text })
});
const data = await response.json();
// 3. Typewriter Effect
const botContentDiv = document.getElementById(botMsgId);
// Quitamos cursor de carga si existiera y empezamos a escribir
botContentDiv.innerHTML = '<span class="cursor"></span>';
typeWriter(botContentDiv, data.response);
} catch (error) {
console.error(error);
document.getElementById(botMsgId).textContent = "Error: Could not connect to the server.";
}
}
function addMessage(text, role, isLoading = false) {
const row = document.createElement('div');
row.className = `message-row ${role}`;
const content = document.createElement('div');
content.className = 'message-content';
const avatar = document.createElement('div');
avatar.className = `avatar ${role}`;
avatar.innerHTML = role === 'user'
? '<i class="fa-solid fa-user" style="color: white; font-size: 14px;"></i>'
: '<i class="fa-solid fa-robot" style="color: white; font-size: 14px;"></i>';
const textDiv = document.createElement('div');
textDiv.className = 'text-content';
if (isLoading) {
// Generamos ID único para actualizar luego
const id = 'msg-' + Date.now();
textDiv.id = id;
textDiv.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i>'; // Loading icon
content.appendChild(avatar);
content.appendChild(textDiv);
row.appendChild(content);
chatHistory.appendChild(row);
chatHistory.scrollTop = chatHistory.scrollHeight;
return id;
} else {
textDiv.textContent = text;
content.appendChild(avatar);
content.appendChild(textDiv);
row.appendChild(content);
chatHistory.appendChild(row);
chatHistory.scrollTop = chatHistory.scrollHeight;
}
}
function typeWriter(element, text) {
let i = 0;
element.innerHTML = '<span class="cursor"></span>'; // Iniciar cursor
const speed = 20; // Velocidad de escritura ms
function type() {
if (i < text.length) {
// Insertamos el caracter ANTES del cursor
const char = text.charAt(i);
// Manipulamos el HTML para mantener el cursor al final
const currentHtml = element.innerHTML;
// Quitamos el span del cursor, agregamos letra, agregamos cursor
element.innerHTML = currentHtml.replace('<span class="cursor"></span>', '') + char + '<span class="cursor"></span>';
i++;
chatHistory.scrollTop = chatHistory.scrollHeight; // Auto scroll
setTimeout(type, speed);
} else {
// Al final, quitamos el cursor parpadeante
element.innerHTML = element.innerHTML.replace('<span class="cursor"></span>', '');
}
}
type();
}
</script>
</body>
</html>
"""
# --- RUTAS FLASK ---
@app.route('/')
def home():
return render_template_string(HTML_TEMPLATE)
@app.route('/generate', methods=['POST'])
def generate():
data = request.json
prompt_text = data.get('prompt', '')
if not prompt_text:
return jsonify({'response': 'Please write something.'})
try:
# Generar texto con GPT-2
# max_length es dinámico para no tardar una eternidad
input_len = len(prompt_text.split())
response = generator(
prompt_text,
max_length=input_len + 60, # Genera unas 60 palabras
num_return_sequences=1,
pad_token_id=50256,
no_repeat_ngram_size=2,
temperature=0.7,
top_k=50,
top_p=0.95
)
full_text = response[0]['generated_text']
# Limpieza básica: Quitamos el prompt original para simular un chat
# (Opcional, GPT-2 no es un modelo de chat instruccional, así que a veces ayuda ver todo)
bot_response = full_text.replace(prompt_text, "", 1).strip()
if not bot_response:
bot_response = "..."
return jsonify({'response': bot_response})
except Exception as e:
return jsonify({'response': f"Error: {str(e)}"})
# Configuración necesaria para Hugging Face Spaces
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860)