|
|
import gradio as gr |
|
|
import google.generativeai as genai |
|
|
import os |
|
|
import re |
|
|
from typing import List, Tuple |
|
|
|
|
|
|
|
|
def configurar_gemini(): |
|
|
try: |
|
|
|
|
|
api_key = os.environ.get("GOOGLE_API_KEY") |
|
|
|
|
|
|
|
|
if not api_key: |
|
|
from dotenv import load_dotenv |
|
|
load_dotenv() |
|
|
api_key = os.environ.get("GOOGLE_API_KEY") |
|
|
|
|
|
if not api_key: |
|
|
return None, "❌ **ERROR**: GOOGLE_API_KEY no encontrada. Configúrala en los Secrets de Hugging Face." |
|
|
|
|
|
|
|
|
genai.configure(api_key=api_key) |
|
|
|
|
|
|
|
|
try: |
|
|
models = genai.list_models() |
|
|
model_names = [model.name for model in models] |
|
|
|
|
|
|
|
|
preferred_models = [ |
|
|
'models/gemini-1.5-pro', |
|
|
'models/gemini-1.5-flash', |
|
|
'models/gemini-pro', |
|
|
'models/gemini-pro-vision' |
|
|
] |
|
|
|
|
|
for model_name in preferred_models: |
|
|
if model_name in model_names: |
|
|
model = genai.GenerativeModel(model_name.split('/')[-1]) |
|
|
return model, f"✅ **Conectado**: Usando {model_name}" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
preferred_prefixes = [ |
|
|
'gemini-2.5-pro', |
|
|
'gemini-2.5-flash', |
|
|
'gemini-pro', |
|
|
'gemini-flash' |
|
|
] |
|
|
|
|
|
selected_model_name = None |
|
|
|
|
|
|
|
|
for prefix in preferred_prefixes: |
|
|
|
|
|
matching_models = [name for name in model_names if prefix in name] |
|
|
|
|
|
if matching_models: |
|
|
|
|
|
|
|
|
selected_model_name = sorted(matching_models, key=len, reverse=True)[0] |
|
|
break |
|
|
|
|
|
if selected_model_name: |
|
|
|
|
|
model_alias = selected_model_name.split('/')[-1] |
|
|
model = genai.GenerativeModel(model_alias) |
|
|
return model, f"✅ **Conectado**: Usando {selected_model_name}" |
|
|
|
|
|
|
|
|
return None, f"❌ **ERROR**: No se encontraron modelos Gemini compatibles. Disponibles: {', '.join(model_names[:5])}..." |
|
|
|
|
|
except Exception as e: |
|
|
|
|
|
return None, f"❌ **ERROR**: No se encontraron modelos Gemini. Disponibles: {', '.join(model_names)}" |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"❌ **ERROR al conectar con Gemini**: {str(e)}" |
|
|
|
|
|
except Exception as e: |
|
|
return None, f"❌ **ERROR de configuración**: {str(e)}" |
|
|
|
|
|
|
|
|
model, status_message = configurar_gemini() |
|
|
|
|
|
|
|
|
futurista_css = """ |
|
|
:root { |
|
|
--primary: #00ff88; |
|
|
--secondary: #0088ff; |
|
|
--accent: #ff0088; |
|
|
--dark-bg: #0a0a0f; |
|
|
--darker-bg: #050508; |
|
|
--card-bg: #111122; |
|
|
--text: #e0e0ff; |
|
|
--text-secondary: #a0a0c0; |
|
|
--border: #222244; |
|
|
} |
|
|
|
|
|
.gradio-container { |
|
|
background: linear-gradient(135deg, var(--dark-bg) 0%, var(--darker-bg) 100%) !important; |
|
|
min-height: 100vh !important; |
|
|
font-family: 'Segoe UI', system-ui, sans-serif; |
|
|
} |
|
|
|
|
|
.dark-bg { |
|
|
background: var(--darker-bg) !important; |
|
|
} |
|
|
|
|
|
.contain { |
|
|
background: var(--card-bg) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
border-radius: 15px !important; |
|
|
box-shadow: 0 0 20px rgba(0, 255, 136, 0.1) !important; |
|
|
} |
|
|
|
|
|
.chatbot { |
|
|
background: var(--darker-bg) !important; |
|
|
border: none !important; |
|
|
min-height: 400px !important; |
|
|
} |
|
|
|
|
|
.message { |
|
|
margin: 8px 0 !important; |
|
|
border: none !important; |
|
|
padding: 12px 16px !important; |
|
|
} |
|
|
|
|
|
.message.user { |
|
|
background: linear-gradient(135deg, #0088ff, #00ff88) !important; |
|
|
color: white !important; |
|
|
border-radius: 18px 18px 4px 18px !important; |
|
|
margin-left: 20% !important; |
|
|
} |
|
|
|
|
|
.message.bot { |
|
|
background: var(--card-bg) !important; |
|
|
color: var(--text) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
border-radius: 18px 18px 18px 4px !important; |
|
|
margin-right: 20% !important; |
|
|
} |
|
|
|
|
|
.textbox { |
|
|
background: var(--card-bg) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
border-radius: 25px !important; |
|
|
color: var(--text) !important; |
|
|
padding: 12px 20px !important; |
|
|
} |
|
|
|
|
|
.textbox:focus { |
|
|
border-color: var(--primary) !important; |
|
|
box-shadow: 0 0 10px var(--primary) !important; |
|
|
} |
|
|
|
|
|
.button { |
|
|
background: linear-gradient(135deg, var(--primary), var(--secondary)) !important; |
|
|
border: none !important; |
|
|
border-radius: 20px !important; |
|
|
color: white !important; |
|
|
font-weight: 600 !important; |
|
|
transition: all 0.3s ease !important; |
|
|
} |
|
|
|
|
|
.button:hover { |
|
|
transform: translateY(-2px) !important; |
|
|
box-shadow: 0 5px 15px rgba(0, 255, 136, 0.3) !important; |
|
|
} |
|
|
|
|
|
.button-secondary { |
|
|
background: var(--card-bg) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
color: var(--text) !important; |
|
|
} |
|
|
|
|
|
.header-gradient { |
|
|
background: linear-gradient(135deg, var(--primary), var(--secondary), var(--accent)) !important; |
|
|
-webkit-background-clip: text !important; |
|
|
-webkit-text-fill-color: transparent !important; |
|
|
text-align: center !important; |
|
|
font-weight: 800 !important; |
|
|
font-size: 2.2em !important; |
|
|
margin-bottom: 10px !important; |
|
|
text-shadow: 0 0 20px rgba(0, 255, 136, 0.3); |
|
|
} |
|
|
|
|
|
.status-banner { |
|
|
background: linear-gradient(135deg, #ff0088, #ff4444) !important; |
|
|
color: white !important; |
|
|
padding: 10px 15px !important; |
|
|
border-radius: 10px !important; |
|
|
margin: 10px 0 !important; |
|
|
text-align: center !important; |
|
|
font-weight: bold !important; |
|
|
} |
|
|
|
|
|
.status-success { |
|
|
background: linear-gradient(135deg, #00ff88, #00aaff) !important; |
|
|
} |
|
|
|
|
|
.code-block { |
|
|
background: var(--darker-bg) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
border-radius: 10px !important; |
|
|
padding: 15px !important; |
|
|
margin: 10px 0 !important; |
|
|
font-family: 'Courier New', monospace !important; |
|
|
color: var(--primary) !important; |
|
|
border-left: 4px solid var(--primary) !important; |
|
|
overflow-x: auto !important; |
|
|
} |
|
|
|
|
|
.examples-box { |
|
|
background: var(--card-bg) !important; |
|
|
border: 1px solid var(--border) !important; |
|
|
border-radius: 10px !important; |
|
|
padding: 15px !important; |
|
|
margin: 10px 0 !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
def detectar_lenguaje(codigo: str) -> str: |
|
|
"""Detecta el lenguaje de programación del código.""" |
|
|
patrones = { |
|
|
'python': [r'^def\s+', r'^class\s+', r'^import\s+', r'^from\s+', r'print\('], |
|
|
'javascript': [r'function\s*\w*\s*\(', r'const\s+|let\s+|var\s+', r'=>', r'console\.log'], |
|
|
'java': [r'public\s+class', r'private\s+', r'protected\s+', r'System\.out\.print'], |
|
|
'cpp': [r'#include\s+<', r'using\s+namespace', r'std::', r'cout\s*<<'], |
|
|
'html': [r'<!DOCTYPE html>', r'<html>', r'<head>', r'<body>'], |
|
|
} |
|
|
|
|
|
for lenguaje, patrones_list in patrones.items(): |
|
|
for patron in patrones_list: |
|
|
if re.search(patron, codigo, re.MULTILINE | re.IGNORECASE): |
|
|
return lenguaje |
|
|
return 'desconocido' |
|
|
|
|
|
def analizar_codigo_basico(codigo: str) -> dict: |
|
|
"""Análisis básico del código.""" |
|
|
problemas = [] |
|
|
sugerencias = [] |
|
|
lenguaje = detectar_lenguaje(codigo) |
|
|
|
|
|
|
|
|
lineas = codigo.split('\n') |
|
|
if len(lineas) > 50: |
|
|
problemas.append("Código potencialmente muy largo") |
|
|
sugerencias.append("Considera dividir en funciones más pequeñas") |
|
|
|
|
|
for i, linea in enumerate(lineas): |
|
|
if len(linea.strip()) > 0 and len(linea) > 100: |
|
|
problemas.append(f"Línea {i+1} muy larga ({len(linea)} caracteres)") |
|
|
sugerencias.append("Divide la línea para mejorar legibilidad") |
|
|
|
|
|
return { |
|
|
'lenguaje': lenguaje, |
|
|
'problemas': problemas, |
|
|
'sugerencias': sugerencias, |
|
|
'lineas': len(lineas), |
|
|
'caracteres': len(codigo) |
|
|
} |
|
|
|
|
|
def formatear_respuesta(respuesta: str) -> str: |
|
|
"""Formatea la respuesta con estilos HTML.""" |
|
|
|
|
|
respuesta = re.sub( |
|
|
r'```(\w+)?\n(.*?)```', |
|
|
r'<div class="code-block"><strong>Código \1:</strong><br><pre>\2</pre></div>', |
|
|
respuesta, |
|
|
flags=re.DOTALL |
|
|
) |
|
|
|
|
|
|
|
|
respuesta = re.sub(r'^- (.*?)$', r'• \1', respuesta, flags=re.MULTILINE) |
|
|
|
|
|
return respuesta |
|
|
|
|
|
def generar_respuesta_fallback(mensaje: str) -> str: |
|
|
"""Genera una respuesta cuando Gemini no está disponible.""" |
|
|
mensaje_lower = mensaje.lower() |
|
|
|
|
|
if "analiza" in mensaje_lower and "código" in mensaje_lower: |
|
|
codigo_match = re.search(r'```(?:\w+)?\n(.*?)```', mensaje, re.DOTALL) |
|
|
if codigo_match: |
|
|
codigo = codigo_match.group(1) |
|
|
analisis = analizar_codigo_basico(codigo) |
|
|
|
|
|
respuesta = f""" |
|
|
🔍 **Análisis Básico del Código** |
|
|
|
|
|
**Lenguaje detectado:** {analisis['lenguaje']} |
|
|
**Líneas de código:** {analisis['lineas']} |
|
|
**Caracteres:** {analisis['caracteres']} |
|
|
|
|
|
""" |
|
|
if analisis['problemas']: |
|
|
respuesta += "**⚠️ Problemas detectados:**\n" + "\n".join([f"• {p}" for p in analisis['problemas']]) |
|
|
respuesta += "\n\n**💡 Sugerencias:**\n" + "\n".join([f"• {s}" for s in analisis['sugerencias']]) |
|
|
else: |
|
|
respuesta += "✅ **No se detectaron problemas evidentes en el análisis básico.**" |
|
|
|
|
|
return respuesta |
|
|
|
|
|
|
|
|
respuestas_predefinidas = { |
|
|
"hola": "¡Hola! Soy Cyber-Coder 9000. Actualmente estoy en modo básico. ¿En qué puedo ayudarte con tu código?", |
|
|
"help": """ |
|
|
🤖 **Cyber-Coder 9000 - Modo Básico** |
|
|
|
|
|
**Funciones disponibles:** |
|
|
• 🔍 Análisis básico de código |
|
|
• 📝 Revisión de sintaxis simple |
|
|
• 💡 Sugerencias de estructura |
|
|
|
|
|
**Cómo usar:** |
|
|
1. Para analizar código: `analiza este código: [tu código]` |
|
|
2. Envía el código entre triple backticks: \\`\\`\\`python |
|
|
# tu código aquí |
|
|
\\`\\`\\` |
|
|
|
|
|
**Nota:** La funcionalidad completa estará disponible cuando se configure la API de Gemini. |
|
|
""", |
|
|
"python": "**💡 Tip Python:** Recuerda usar type hints, docstrings y manejar excepciones adecuadamente.", |
|
|
"javascript": "**💡 Tip JavaScript:** Usa 'const' y 'let' en lugar de 'var', y considera async/await para operaciones asíncronas.", |
|
|
"error": "**🔧 Para debugging:** Revisa la sintaxis, verifica los tipos de datos y usa print statements para seguimiento." |
|
|
} |
|
|
|
|
|
for clave, valor in respuestas_predefinidas.items(): |
|
|
if clave in mensaje_lower: |
|
|
return valor |
|
|
|
|
|
return """ |
|
|
🤖 **Cyber-Coder 9000 - Modo Básico** |
|
|
|
|
|
Actualmente estoy operando en modo limitado. Para funcionalidad completa: |
|
|
|
|
|
1. **Configura tu API Key de Gemini** en los Secrets de Hugging Face |
|
|
2. **Asegúrate** de que la variable se llame `GOOGLE_API_KEY` |
|
|
3. **Verifica** que la API key sea válida |
|
|
|
|
|
**Mientras tanto, puedo ayudarte con:** |
|
|
• Análisis básico de estructura de código |
|
|
• Sugerencias generales de programación |
|
|
• Revisión de sintaxis simple |
|
|
|
|
|
¿En qué aspecto de tu código necesitas ayuda? |
|
|
""" |
|
|
|
|
|
def asistente_codificacion(mensaje: str, historial: List[Tuple[str, str]]) -> str: |
|
|
"""Función principal del asistente.""" |
|
|
|
|
|
if not mensaje.strip(): |
|
|
return "Por favor, escribe un mensaje." |
|
|
|
|
|
|
|
|
if model: |
|
|
try: |
|
|
contexto = "Eres un asistente especializado en análisis, corrección y generación de código. Responde de manera técnica pero clara." |
|
|
|
|
|
if "analiza" in mensaje.lower() and "código" in mensaje.lower(): |
|
|
codigo_match = re.search(r'```(?:\w+)?\n(.*?)```', mensaje, re.DOTALL) |
|
|
if codigo_match: |
|
|
codigo = codigo_match.group(1) |
|
|
lenguaje = detectar_lenguaje(codigo) |
|
|
contexto += f" Analiza este código en {lenguaje} y proporciona sugerencias de mejora." |
|
|
|
|
|
response = model.generate_content(contexto + "\n\n" + mensaje) |
|
|
return formatear_respuesta(response.text) |
|
|
|
|
|
except Exception as e: |
|
|
return f"❌ **Error de Gemini**: {str(e)}\n\n" + generar_respuesta_fallback(mensaje) |
|
|
else: |
|
|
|
|
|
return generar_respuesta_fallback(mensaje) |
|
|
|
|
|
|
|
|
def crear_interfaz(): |
|
|
with gr.Blocks(css=futurista_css, theme=gr.themes.Soft()) as demo: |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
<div class="header-gradient"> |
|
|
🤖 CYBER-CODER 9000 |
|
|
</div> |
|
|
""", |
|
|
elem_id="header" |
|
|
) |
|
|
|
|
|
|
|
|
status_class = "status-success" if model else "status-banner" |
|
|
gr.Markdown( |
|
|
f"""<div class="{status_class}">{status_message}</div>""", |
|
|
elem_id="status" |
|
|
) |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
**Asistente Especializado en Codificación Avanzada** |
|
|
*Auto-completa • Analiza • Corrige • Perfecciona • Genera* |
|
|
""", |
|
|
elem_classes="description" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=3): |
|
|
chatbot = gr.Chatbot( |
|
|
label="🤖 Sesión de Codificación", |
|
|
height=400, |
|
|
show_copy_button=True, |
|
|
show_share_button=True, |
|
|
placeholder="Escribe tu consulta de código aquí..." |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
msg = gr.Textbox( |
|
|
label="💻 Instrucción de Código", |
|
|
placeholder="Ej: 'analiza este código', 'genera una función Python para...', 'corrige este error'", |
|
|
scale=4, |
|
|
lines=2, |
|
|
max_lines=5 |
|
|
) |
|
|
btn = gr.Button("🚀 Ejecutar", scale=1) |
|
|
|
|
|
with gr.Row(): |
|
|
clear_btn = gr.Button("🔄 Limpiar Chat", variant="secondary") |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### 🛠️ Acciones Rápidas") |
|
|
|
|
|
with gr.Column(): |
|
|
accion_analizar = gr.Button("🔍 Análisis de Código") |
|
|
accion_corregir = gr.Button("🔧 Corrección") |
|
|
accion_generar = gr.Button("⚡ Generar Código") |
|
|
accion_optimizar = gr.Button("🎯 Optimizar") |
|
|
accion_help = gr.Button("❓ Ayuda") |
|
|
|
|
|
gr.Markdown("---") |
|
|
gr.Markdown("### 📊 Estado") |
|
|
gr.Markdown(f"**Conexión Gemini:** {'✅ Conectado' if model else '❌ Sin conexión'}") |
|
|
gr.Markdown("**Modo actual:** Básico" if not model else "**Modo actual:** Completo") |
|
|
|
|
|
|
|
|
with gr.Accordion("📚 Ejemplos Rápidos", open=False): |
|
|
gr.Markdown(""" |
|
|
**Haz clic en cualquier ejemplo para probar:** |
|
|
""") |
|
|
|
|
|
ejemplos = gr.Examples( |
|
|
examples=[ |
|
|
["Analiza este código Python:\n```python\ndef factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n-1)\n```"], |
|
|
["Genera una función en Python que valide emails usando expresiones regulares"], |
|
|
["¿Cuáles son las mejores prácticas para manejar errores en JavaScript?"], |
|
|
["Corrige este código:\n```python\ndef sum_list(numbers):\n total = 0\n for i in range(len(numbers)):\n total += numbers[i]\n return total\n```"] |
|
|
], |
|
|
inputs=msg, |
|
|
label="" |
|
|
) |
|
|
|
|
|
|
|
|
def enviar_mensaje(mensaje, historial): |
|
|
if not mensaje.strip(): |
|
|
return "", historial |
|
|
respuesta = asistente_codificacion(mensaje, historial) |
|
|
historial.append((mensaje, respuesta)) |
|
|
return "", historial |
|
|
|
|
|
def accion_rapida(accion): |
|
|
mensajes = { |
|
|
"🔍 Análisis de Código": "Por favor, pega el código que quieres analizar entre triple backticks (```). Ejemplo: \\`\\`\\`python\n# tu código aquí\n\\`\\`\\`", |
|
|
"🔧 Corrección": "Pega el código con errores entre triple backticks para que pueda ayudarte a corregirlo.", |
|
|
"⚡ Generar Código": "Describe qué código necesitas generar. Sé específico sobre el lenguaje y funcionalidad.", |
|
|
"🎯 Optimizar": "Pega el código que quieres optimizar entre triple backticks para mejorar su rendimiento.", |
|
|
"❓ Ayuda": "help" |
|
|
} |
|
|
return mensajes.get(accion, "Selecciona una acción válida") |
|
|
|
|
|
|
|
|
btn.click(enviar_mensaje, [msg, chatbot], [msg, chatbot]) |
|
|
msg.submit(enviar_mensaje, [msg, chatbot], [msg, chatbot]) |
|
|
clear_btn.click(lambda: None, None, chatbot, queue=False) |
|
|
|
|
|
|
|
|
acciones_rapidas = [accion_analizar, accion_corregir, accion_generar, accion_optimizar, accion_help] |
|
|
for accion in acciones_rapidas: |
|
|
accion.click( |
|
|
accion_rapida, |
|
|
inputs=[accion], |
|
|
outputs=[msg] |
|
|
) |
|
|
|
|
|
return demo |
|
|
|
|
|
|
|
|
demo = crear_interfaz() |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=7860, |
|
|
share=True |
|
|
) |