my-gemini-chat / app.py
Gir93's picture
Update app.py
2af2e3d verified
import gradio as gr
import google.generativeai as genai
import os
import time
import json
from datetime import datetime
from typing import List, Tuple, Optional, Dict, Any
# --- Configuración de la API ---
GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
if not GOOGLE_API_KEY:
raise gr.Error("La variable de entorno GOOGLE_API_KEY no está configurada. Por favor, añádela como un secreto en Hugging Face Spaces.")
try:
genai.configure(api_key=GOOGLE_API_KEY)
_ = genai.list_models() # Prueba básica de configuración
except Exception as e:
raise gr.Error(f"Error al configurar la API de Google Gemini o listar modelos: {e}. Verifica tu GOOGLE_API_KEY.")
# --- Gestión de Modelos Disponibles ---
def get_available_models() -> List[str]:
"""
Obtiene y filtra los modelos de Google Gemini disponibles que soportan 'generateContent'
y son adecuados para chat basados en texto.
"""
available_models = []
try:
for m in genai.list_models():
if 'generateContent' in m.supported_generation_methods:
if 'vision' not in m.name.lower() and 'tts' not in m.name.lower() and 'audio' not in m.name.lower():
available_models.append(m.name)
except Exception as e:
print(f"Advertencia: No se pudieron listar los modelos: {e}. Se intentará continuar sin una lista completa.")
return sorted(list(set(available_models)))
AVAILABLE_MODELS = get_available_models()
if not AVAILABLE_MODELS:
raise gr.Error("No se encontraron modelos compatibles con 'generateContent'. Verifica tu clave API y la disponibilidad de modelos.")
# --- Configuración de Almacenamiento de Historial ---
CHATS_DIR = "saved_chats"
CHATS_FILE_PATH = os.path.join(CHATS_DIR, "chats.jsonl")
# Asegurarse de que el directorio exista
os.makedirs(CHATS_DIR, exist_ok=True)
# --- Funciones de Gestión de Historial de Chats ---
def save_chat_history(current_chat_history: List[List[Optional[str]]]) -> str:
"""
Guarda el historial de chat actual en un archivo JSON Lines.
Genera un ID único basado en la marca de tiempo.
"""
if not current_chat_history:
return "No hay chat para guardar."
chat_id = datetime.now().strftime("%Y%m%d_%H%M%S") # ID basado en fecha y hora
timestamp = datetime.now().isoformat()
# Filtra el historial para guardar solo mensajes completos (no los [user, None] pendientes)
# Aunque la función de Gradio debería pasar el historial ya renderizado,
# esta es una precaución.
filtered_history = [[u, b] for u, b in current_chat_history if u is not None and b is not None]
if not filtered_history: # Si el chat está vacío después de filtrar, no lo guardamos
return "Chat vacío. No se guardó."
chat_data = {
"id": chat_id,
"timestamp": timestamp,
"history": filtered_history
}
try:
with open(CHATS_FILE_PATH, "a", encoding="utf-8") as f:
f.write(json.dumps(chat_data, ensure_ascii=False) + "\n")
return f"Chat '{chat_id}' guardado exitosamente."
except Exception as e:
return f"Error al guardar el chat: {e}"
def load_all_chat_summaries() -> List[Tuple[str, str]]:
"""
Carga los IDs y resúmenes de todos los chats guardados para mostrarlos en un Dropdown.
El formato de salida es [(valor, etiqueta), ...].
"""
summaries = []
try:
with open(CHATS_FILE_PATH, "r", encoding="utf-8") as f:
for line in f:
try:
chat_data = json.loads(line)
chat_id = chat_data.get("id", "ID_Desconocido")
timestamp = chat_data.get("timestamp", "Fecha_Desconocida")
# Puedes crear un resumen más descriptivo si lo deseas
first_message = chat_data.get("history", [])[0][0] if chat_data.get("history") else "Sin mensaje inicial"
summary = f"{timestamp} - {first_message[:50]}..." # Resumen del primer mensaje
summaries.append((chat_id, summary))
except json.JSONDecodeError:
print(f"Advertencia: Línea corrupta en el archivo de chats: {line.strip()}")
except FileNotFoundError:
pass # No hay chats guardados aún, no es un error
except Exception as e:
print(f"Error al cargar resúmenes de chat: {e}")
return summaries
def load_specific_chat(chat_id: str) -> List[List[Optional[str]]]:
"""
Carga un historial de chat específico dado su ID.
"""
try:
with open(CHATS_FILE_PATH, "r", encoding="utf-8") as f:
for line in f:
try:
chat_data = json.loads(line)
if chat_data.get("id") == chat_id:
return chat_data.get("history", [])
except json.JSONDecodeError:
continue # Saltar líneas corruptas
except FileNotFoundError:
pass
return [] # Devolver historial vacío si no se encuentra o hay error
# --- Función para convertir el historial de Gradio a formato de Gemini API ---
def format_history_for_gemini(
history: List[List[Optional[str]]]
) -> List[Dict[str, Any]]:
"""
Convierte el historial de chat de Gradio (lista de listas [mensaje_usuario, mensaje_bot])
al formato requerido por el historial de start_chat de la API de Gemini.
"""
gemini_history = []
for turn in history:
if isinstance(turn, (list, tuple)) and len(turn) >= 2:
user_message = turn[0]
bot_message = turn[1]
if user_message is not None:
gemini_history.append({
'role': 'user',
'parts': [{'text': str(user_message)}]
})
if bot_message is not None:
gemini_history.append({
'role': 'model',
'parts': [{'text': str(bot_message)}]
})
return gemini_history
# --- Función principal de respuesta del chatbot ---
def respond(
history: List[List[Optional[str]]],
model_name: str
) -> List[List[Optional[str]]]:
"""
Genera una respuesta del modelo Gemini seleccionado basada en el historial de chat.
"""
if not history or not isinstance(history[-1], (list, tuple)) or len(history[-1]) < 1 or history[-1][0] is None:
print("Advertencia: 'respond' llamada con historial vacío o mal formado. Abortando.")
yield history
return
current_user_message = history[-1][0]
if model_name not in AVAILABLE_MODELS:
error_message = f"Error: Modelo no válido seleccionado: {model_name}. Por favor, elige un modelo de la lista."
print(error_message)
history[-1][1] = error_message
yield history
return
# Formatear el historial previo (excluyendo el mensaje actual del usuario)
api_history_for_start = format_history_for_gemini(history[:-1])
full_response_text = ""
try:
model = genai.GenerativeModel(model_name=model_name)
chat = model.start_chat(history=api_history_for_start)
response_stream = chat.send_message(str(current_user_message), stream=True)
for chunk in response_stream:
if chunk.text:
full_response_text += chunk.text
history[-1][1] = full_response_text
yield history
time.sleep(0.02)
except Exception as e:
error_message = f"Ocurrió un error con el modelo '{model_name}': {e}"
print(f"Error de API: {e}")
if full_response_text:
history[-1][1] = full_response_text + "\n\n" + error_message
else:
history[-1][1] = error_message
yield history
# --- Función de ayuda para eventos de Gradio ---
def add_user_message_to_history(
message: str,
history: List[List[Optional[str]]]
) -> Tuple[str, List[List[Optional[str]]]]:
"""
Añade el mensaje del usuario al historial del chat y limpia el cuadro de entrada.
"""
if not message:
return "", history
return "", history + [[message, None]]
# --- Interfaz de Gradio ---
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("# Chatbot con Modelos Gemini/Gemma (Solo Texto)")
gr.Markdown("Selecciona un modelo y comienza a chatear. La API Key debe estar configurada como un secreto en Hugging Face (variable de entorno `GOOGLE_API_KEY`).")
with gr.Row():
model_selector = gr.Dropdown(
choices=AVAILABLE_MODELS,
value=AVAILABLE_MODELS[0] if AVAILABLE_MODELS else None,
label="Selecciona un modelo",
interactive=True
)
# Nota: El UserWarning sobre 'type' en Chatbot es una advertencia de Gradio
# para futuras versiones. Por ahora, el formato de tuplas sigue siendo compatible.
# Si quieres eliminar la advertencia y usar el nuevo formato, tendrías que
# adaptar 'format_history_for_gemini' y la forma en que 'respond' maneja el historial.
chatbot = gr.Chatbot(height=500, label="Chat")
msg = gr.Textbox(label="Tu mensaje", placeholder="Escribe tu mensaje aquí...")
with gr.Row():
# Componentes para guardar y cargar
with gr.Column(scale=1):
save_chat_btn = gr.Button("Guardar Chat Actual", variant="secondary")
new_chat_btn = gr.Button("Nuevo Chat (guarda y limpia)", variant="secondary") # Guarda el actual y empieza uno nuevo
with gr.Column(scale=2):
saved_chat_selector = gr.Dropdown(
choices=load_all_chat_summaries(), # Carga inicial de resúmenes
label="Cargar Chat Guardado",
interactive=True,
allow_custom_value=False # No permitir valores personalizados
)
load_chat_btn = gr.Button("Cargar Seleccionado", variant="secondary")
refresh_chats_btn = gr.Button("Actualizar Chats Guardados")
with gr.Row():
clear_current_chat_btn = gr.Button("Limpiar Chat Actual") # Renombrado para mayor claridad
send_btn = gr.Button("Enviar", variant="primary")
# --- Lógica de Eventos ---
# Eventos de envío de mensaje
submit_event = msg.submit(
add_user_message_to_history,
[msg, chatbot],
[msg, chatbot],
queue=False
).then(
respond,
inputs=[chatbot, model_selector],
outputs=[chatbot]
)
send_btn.click(
add_user_message_to_history,
[msg, chatbot],
[msg, chatbot],
queue=False
).then(
respond,
inputs=[chatbot, model_selector],
outputs=[chatbot]
)
# Evento para guardar el chat actual
save_chat_btn.click(
save_chat_history,
inputs=[chatbot],
outputs=[gr.Textbox(value="", interactive=False, visible=True, label="Mensaje del sistema")] # Mostrar mensaje de estado
).then(
lambda: gr.update(choices=load_all_chat_summaries()), # <--- CORREGIDO AQUÍ
outputs=[saved_chat_selector]
)
# Evento para iniciar un nuevo chat (guarda el actual y limpia)
new_chat_btn.click(
save_chat_history, # Primero guarda el chat actual
inputs=[chatbot],
outputs=[gr.Textbox(value="", interactive=False, visible=True, label="Mensaje del sistema")]
).then(
lambda: ["", [], gr.update(choices=load_all_chat_summaries())], # <--- CORREGIDO AQUÍ
outputs=[msg, chatbot, saved_chat_selector]
)
# Evento para cargar un chat seleccionado
load_chat_btn.click(
load_specific_chat,
inputs=[saved_chat_selector], # El valor del dropdown es el ID del chat
outputs=[chatbot]
).then(
lambda: "", # Limpiar el cuadro de mensaje después de cargar
outputs=[msg]
)
# Evento para actualizar la lista de chats guardados
refresh_chats_btn.click(
lambda: gr.update(choices=load_all_chat_summaries()), # <--- CORREGIDO AQUÍ
outputs=[saved_chat_selector]
)
# Evento: Limpiar solo el chat actual sin guardar
clear_current_chat_btn.click(
lambda: ["", []],
outputs=[msg, chatbot],
queue=False
)
# Inicializar el Dropdown de chats guardados al cargar la interfaz
demo.load(
lambda: gr.update(choices=load_all_chat_summaries()), # <--- CORREGIDO AQUÍ
outputs=[saved_chat_selector],
queue=False
)
# --- Lanzar la aplicación ---
demo.launch()