File size: 6,456 Bytes
3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b a474bc1 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b a474bc1 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b cb12796 a474bc1 cb12796 a474bc1 e0b1502 cb12796 a474bc1 e0b1502 cb12796 a474bc1 e0b1502 cb12796 e0b1502 3754f8b a474bc1 cb12796 3754f8b e0b1502 3754f8b e0b1502 cb12796 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 3754f8b e0b1502 a474bc1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
import difflib
import json
import tempfile
from pathlib import Path
from typing import List, Tuple, Optional, Dict, Any
import gradio as gr
from config import settings
from ollama_utils import (
ensure_ollama_running,
is_model_available,
ask_ollama_stream,
pull_model_with_progress
)
from file_processing import read_uploaded_files, guess_lang_from_content
ChatMessage = Dict[str, str]
ChatHistory = List[ChatMessage]
LegacyHistory = List[Tuple[Optional[str], Optional[str]]]
def _normalize_history(history: Optional[Any]) -> ChatHistory:
"""Asegura que el historial sea una lista de mensajes con role/content."""
if not history:
return []
normalized: ChatHistory = []
for item in history:
if isinstance(item, dict) and "role" in item and "content" in item:
normalized.append(item)
elif isinstance(item, (list, tuple)) and len(item) == 2:
user, bot = item
if user:
normalized.append({"role": "user", "content": str(user)})
if bot:
normalized.append({"role": "assistant", "content": str(bot)})
return normalized
def _messages_to_pairs(messages: ChatHistory) -> LegacyHistory:
"""Convierte mensajes secuenciales a tuplas (usuario, asistente)."""
pairs: LegacyHistory = []
pending_user: Optional[str] = None
for msg in messages:
role = msg.get("role")
content = msg.get("content", "")
if role == "user":
if pending_user is not None:
pairs.append((pending_user, None))
pending_user = content
elif role == "assistant":
if pending_user is not None:
pairs.append((pending_user, content))
pending_user = None
else:
pairs.append((None, content))
if pending_user is not None:
pairs.append((pending_user, None))
return pairs
def _init_state():
"""Inicializa un estado de aplicación vacío."""
return {
"history": [],
"last_files": None,
"downloaded_models": [],
}
def build_prompt(user_text: str, files_blob: str, language: str) -> str:
"""Construye el prompt para el modelo."""
parts = []
if files_blob.strip():
lang_detected = guess_lang_from_content(files_blob) or language
parts.append(f"Basado en el siguiente contexto y archivos adjuntos (lenguaje: {lang_detected}):")
parts.append(files_blob)
parts.append("\n---")
parts.append("Responde a la siguiente instrucción del usuario:")
parts.append(user_text)
return "\n\n".join(parts)
def main_chat(
app_state: Dict[str, Any],
history: LegacyHistory, # Gradio pasa el historial como pares
user_text: str,
model: str,
files,
):
if not app_state:
app_state = _init_state()
downloaded_models: List[str] = app_state.setdefault("downloaded_models", [])
history_messages = _normalize_history(history)
if not user_text.strip() and not files:
yield app_state, history, user_text, files
return
# Check Ollama status
if not ensure_ollama_running():
gr.Warning("Ollama no está en ejecución. Por favor, inicia el servicio de Ollama.")
yield app_state, history, user_text, files
return
# Add user message + placeholder assistant so the UI updates at once
history_messages.append({"role": "user", "content": user_text})
assistant_message = {"role": "assistant", "content": "⏳ Preparando respuesta..."}
history_messages.append(assistant_message)
yield app_state, _messages_to_pairs(history_messages), "", files
model_ready = model in downloaded_models or is_model_available(model)
if not model_ready:
gr.Info(f"El modelo '{model}' no está disponible localmente. Intentando descargarlo...")
assistant_message["content"] = f"📥 Descargando modelo '{model}'..."
yield app_state, _messages_to_pairs(history_messages), "", files
pull_success = False
for status in pull_model_with_progress(model):
assistant_message["content"] = status
yield app_state, _messages_to_pairs(history_messages), "", files
if status.startswith("✅"):
pull_success = True
if not pull_success:
gr.Error(f"No se pudo descargar el modelo '{model}'. Por favor, verifica el nombre o hazlo manualmente.")
assistant_message["content"] = f"⚠️ No se pudo descargar '{model}'."
yield app_state, _messages_to_pairs(history_messages), user_text, files
return
if model not in downloaded_models:
downloaded_models.append(model)
gr.Info(f"Modelo '{model}' descargado con éxito.")
assistant_message["content"] = "⏳ Preparando respuesta..."
yield app_state, _messages_to_pairs(history_messages), "", files
# Prepare inputs
files_blob, preview, _ = read_uploaded_files(files, "")
user_prompt = build_prompt(user_text, files_blob, "Python") # Default language, can be improved
system_prompt = (
"Eres un asistente de IA servicial, experto en desarrollo de software y una amplia gama de temas. "
"Responde siempre en español, de forma clara y concisa. "
"Si se te pide código, formátéalo en bloques de markdown con la etiqueta del lenguaje correspondiente."
)
# Stream response
assistant_message["content"] = ""
# The history sent to the model should not include the latest empty assistant message
model_history_pairs = _messages_to_pairs(history_messages[:-1])
full_response = ""
for chunk in ask_ollama_stream(
model=model,
system_prompt=system_prompt,
history=model_history_pairs,
new_prompt=user_prompt,
temperature=0.4, # Sensible default
top_p=0.9, # Sensible default
max_tokens=4096, # Sensible default
):
full_response += chunk
assistant_message["content"] = full_response
yield app_state, _messages_to_pairs(history_messages), "", files
# Truncate history if too long
if len(history_messages) > settings.MAX_CHAT_TURNS * 2:
history_messages = history_messages[-(settings.MAX_CHAT_TURNS * 2):]
app_state["history"] = history_messages
yield app_state, _messages_to_pairs(history_messages), "", files
|