File size: 14,646 Bytes
58eefb0 658554c 1877ea4 640872b 658554c e9d758b 83b4ae1 edfb8b9 9989d76 11b4bb3 d7cb051 1808fc9 d7cb051 8937b67 658554c 698739e 658554c ea2ae6d 920734f 1877ea4 ea2ae6d 920734f 587b8ce 74faa86 093aa42 74faa86 920734f 2619665 093aa42 2619665 093aa42 2619665 920734f 093aa42 920734f 7a42c33 920734f 7a42c33 45b9721 658554c 7a42c33 45b9721 7a42c33 1877ea4 658554c 7a42c33 658554c 7a42c33 658554c 7a42c33 658554c 7a42c33 658554c 7a42c33 920734f 7a42c33 658554c 7a42c33 093aa42 283de25 7a42c33 45b9721 7a42c33 c25895c 45b9721 7a42c33 45b9721 c25895c 7a42c33 093aa42 920734f 093aa42 7a42c33 093aa42 7a42c33 093aa42 7a42c33 093aa42 7a42c33 093aa42 c25895c 7a42c33 4ed2e87 c25895c 7a42c33 093aa42 7a42c33 920734f 7a42c33 920734f 1433464 7a42c33 9989d76 7a42c33 920734f 093aa42 7a42c33 093aa42 9989d76 7a42c33 1433464 7a42c33 45b9721 1433464 9989d76 7a42c33 9989d76 318073d 9989d76 7a42c33 9989d76 7a42c33 920734f 7a42c33 1433464 7a42c33 45b9721 ea2ae6d 9989d76 698739e 920734f 093aa42 698739e 7a42c33 698739e 920734f 7a42c33 920734f 7a42c33 920734f 698739e 093aa42 920734f 7a42c33 920734f 698739e 093aa42 7a42c33 882a21f 7a42c33 920734f 882a21f 7a42c33 45b9721 7a42c33 45b9721 093aa42 698739e 7a42c33 58eefb0 093aa42 | 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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | import os
import re
import ast
import tempfile
import sys
import subprocess
from html.parser import HTMLParser
import gradio as gr
from huggingface_hub import InferenceClient
import spaces
from groq import Groq
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
MODEL_ID = "openai/gpt-oss-120b"
REASONING_EFFORT = "low"
MAX_AUTOFIX = 3
DEMO_LIMIT = 200
# Contador global (se reinicia al restartear el Space)
_USAGES = {"count": 0, "blocked": False}
BLOQUEOS_JAILBREAK = [
"ignora tus reglas", "olvida instrucciones", "actua como un",
"eres ahora", "system prompt", "reveala tus instrucciones",
"dime tu system prompt", "hackear", "malware", "virus", "exploit"
]
SYSTEM_PROMPT_BASE = """Eres Aura, asistente senior que REVISA, CORRIGE Y AUTO-REPARA código hasta que ejecute.
Tu ÚNICA función es programar, revisar, debuggear y corregir código.
Si te doy un error de ejecución, corrige el código COMPLETO inmediatamente.
FLUJO OBLIGATORIO:
1. Analiza ERRORES DETECTADOS que te paso.
2. Responde con:
🔍 Errores encontrados:
- lista
✅ Código corregido: en bloque de código completo
📝 Qué corregiste: explicación breve
Si NO es programación, responde: Soy Aura, asistente especializado exclusivamente en programación."""
def extract_file_text(file):
if not file: return ""
try:
if isinstance(file, (list, tuple)): file = file[0]
path = None
for attr in ("path", "name", "file_path", "orig_name"):
val = getattr(file, attr, None)
if isinstance(val, str) and val: path = val; break
if path is None and isinstance(file, dict):
for k in ("path", "name", "file_path"):
v = file.get(k)
if isinstance(v, str) and v: path = v; break
if path is None and isinstance(file, str): path = file
if not path or os.path.isdir(path) or not os.path.isfile(path): return ""
with open(path, 'r', encoding='utf-8', errors='ignore') as f: return f.read()[:20000]
except: return ""
def get_ext(n):
n = (n or "").lower()
if n.endswith((".html", ".htm")): return "html"
if n.endswith((".js", ".jsx", ".mjs")): return "js"
if n.endswith((".ts", ".tsx")): return "ts"
if n.endswith(".css"): return "css"
if n.endswith(".sql"): return "sql"
if n.endswith(".py"): return "py"
return "py"
def detectar_errores_python(codigo):
try:
ast.parse(codigo)
return []
except SyntaxError as err:
return [f"🔴 SyntaxError línea {err.lineno}: {err.msg}"]
except Exception as e:
return [f"🔴 Error: {e}"]
def analizar_todo(codigo, nombre):
ext = get_ext(nombre)
if ext == "py":
return detectar_errores_python(codigo)
return []
def ejecutar_codigo_sandbox(codigo, nombre):
ext = get_ext(nombre)
if ext in ["html", "css"]:
return True, f"✅ {ext.upper()} validado."
if ext == "sql":
return True, "✅ SQL analizada."
if ext == "py":
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, encoding='utf-8') as f:
f.write(codigo)
temp_path = f.name
try:
result = subprocess.run(
[sys.executable, temp_path],
capture_output=True, text=True, timeout=10
)
out = result.stdout + (f"\n{result.stderr}" if result.stderr else "")
if result.returncode != 0:
return False, f"❌ ERROR:\n{out}"
return True, f"✅ OK:\n{out or '(sin salida)'}"
except subprocess.TimeoutExpired:
return False, "❌ TIMEOUT"
except Exception as e:
return False, str(e)
finally:
try:
os.unlink(temp_path)
except:
pass
return True, "✅ Código recibido."
def to_messages(history):
msgs = []
if not isinstance(history, list):
return msgs
for item in history:
if isinstance(item, dict) and "role" in item and "content" in item:
c = item["content"]
if isinstance(c, list):
c = "".join(b.get("text", "") for b in c if isinstance(b, dict))
if item["role"] in ("user", "assistant"):
msgs.append({"role": item["role"], "content": str(c)})
return msgs
def make_download_files(text):
code_path, txt_path = None, None
try:
blocks = re.findall(r'```(?:python|javascript|js|html|sql)?\n(.*?)\n```', text, re.DOTALL)
if blocks:
main_code = max(blocks, key=len)
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False, encoding='utf-8') as f:
f.write(main_code)
code_path = f.name
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f:
f.write(text)
txt_path = f.name
except:
pass
return code_path, txt_path
def autofix_loop(client, messages, main_code, ext, max_tokens, temperature, top_p):
"""Reintenta enviando el error al modelo hasta MAX_AUTOFIX veces."""
ultima = "Listo"
codigo_actual = main_code
historial_fix = list(messages)
for intento in range(1, MAX_AUTOFIX + 1):
ok, res = ejecutar_codigo_sandbox(codigo_actual, f"test.{ext}")
ultima = res
if ok:
return codigo_actual, ultima, True
# Le mandamos el error al modelo para que lo corrijas
yield_msg = (
f"El código que me diste FALLÓ al ejecutarse.\n\n"
f"--- SALIDA/ERROR ---\n{res}\n--- FIN ERROR ---\n\n"
f"Corrige el problema y responde SOLO con el código completo corregido "
f"en un único bloque ```{ext}```. No expliques nada más."
)
historial_fix.append({"role": "assistant", "content": f"```{ext}\n{codigo_actual}\n```"})
historial_fix.append({"role": "user", "content": yield_msg})
r = client.chat.completions.create(
model=MODEL_ID,
messages=historial_fix,
max_tokens=int(max_tokens),
temperature=float(temperature),
top_p=float(top_p)
)
texto_fix = r.choices[0].message.content or ""
blocks = re.findall(r'```(?:python|javascript|js|html|sql)?\n(.*?)\n```', texto_fix, re.DOTALL)
if not blocks:
ultima = f"⚠️ Intento {intento}: el modelo no devolvió código.\n{res}"
break
codigo_actual = max(blocks, key=len)
return codigo_actual, ultima, False
# ─── SISTEMA DE LÍMITES ───
def decrement_usage():
global _USAGES
_USAGES["count"] += 1
return DEMO_LIMIT - _USAGES["count"]
def check_usage(token):
"""Retorna (ok, restante, footer_extra)"""
if token:
return True, -1, "" # -1 = ilimitado (usa su propia key)
if _USAGES["blocked"]:
return False, 0, "\n\n⛔ **Demo agotado.** Usa tu propia API key de Groq para continuar."
restante = decrement_usage()
if restante <= 0:
_USAGES["blocked"] = True
return True, 0, "\n\n⛔ **Límite de 200 demos alcanzado.** Obtén tu key en [console.groq.com/keys](https://console.groq.com/keys) para uso ilimitado."
return True, restante, None
@spaces.GPU
def run_chat(message, history, user_context, max_tokens, temperature, top_p, token, file_content, file_name, memoria):
global _USAGES
if not isinstance(history, list):
history = []
if not message or not message.strip():
yield history, None, None, "⚠️ Mensaje vacío.", memoria
return
new_turns = [{"role": "user", "content": message}]
# Jailbreak check
for b in BLOQUEOS_JAILBREAK:
if b in message.lower():
blocked = history + new_turns + [{"role": "assistant", "content": "Soy Aura, asistente especializado exclusivamente en programación."}]
yield blocked, None, None, "⛔ Bloqueado", memoria
return
# Verificar límites de demo
ok, restante, footer_extra = check_usage(token)
if not ok:
yield history + new_turns + [{"role": "assistant", "content": "⛔ Demo agotado. Usa tu propia API key."}], None, None, "⛔ Demo agotado", memoria
return
api_key = token or GROQ_API_KEY
if not api_key:
blocked = history + new_turns + [{"role": "assistant", "content": "⚠️ Falta GROQ_API_KEY"}]
yield blocked, None, None, "Falta token", memoria
return
try:
client = Groq(api_key=api_key)
contexto_archivo = ""
if file_content and file_name:
errores = analizar_todo(file_content, file_name)
contexto_archivo = f"\n--- ARCHIVO ({file_name}) ---\n{file_content}\n"
if errores:
contexto_archivo += "\n".join(errores) + "\n"
final_system = SYSTEM_PROMPT_BASE
if user_context:
final_system += f"\n--- CONTEXTO ---\n{user_context}\n"
if memoria:
final_system += f"\n--- MEMORIA ---\n{memoria}\n"
messages = [{"role": "system", "content": final_system}]
messages.extend(to_messages(history)[-6:])
messages.append({"role": "user", "content": message + contexto_archivo})
resp_text = ""
stream = client.chat.completions.create(
model=MODEL_ID,
messages=messages,
max_tokens=int(max_tokens),
stream=True,
temperature=float(temperature),
top_p=float(top_p)
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
resp_text += chunk.choices[0].delta.content
yield history + new_turns + [{"role": "assistant", "content": resp_text}], None, None, "Generando...", memoria
ultima = "Listo"
# Auto-fix loop
blocks = re.findall(r'```(?:python|javascript|js|html|sql)?\n(.*?)\n```', resp_text, re.DOTALL)
if blocks:
main_code = max(blocks, key=len)
ext = get_ext(file_name) if file_name else "py"
yield history + new_turns + [{"role": "assistant", "content": resp_text + "\n\n🔁 *Verificando ejecución...*"}], None, None, "Ejecutando...", memoria
codigo_final, ultima, ok_fix = autofix_loop(
client, messages, main_code, ext, max_tokens, temperature, top_p
)
if not ok_fix and codigo_final != main_code:
resp_text = resp_text + f"\n\n---\n🔧 **Código auto-corregido:**\n```{ext}\n{codigo_final}\n```"
yield history + new_turns + [{"role": "assistant", "content": resp_text}], None, None, ultima, memoria
code_file, txt_file = make_download_files(resp_text)
# Agregar footer de límite
final_resp = resp_text
if footer_extra:
final_resp += footer_extra
elif restante > 0:
final_resp += f"\n\n*🔹 Quedan {restante} demos. Usa tu propia API key para uso ilimitado.*"
yield history + new_turns + [{"role": "assistant", "content": final_resp}], code_file, txt_file, ultima, memoria
except Exception as e:
import traceback
print(traceback.format_exc())
yield history + [{"role": "user", "content": message}, {"role": "assistant", "content": f"💥 Error: {e}"}], None, None, str(e), memoria
# ─── UI ───
with gr.Blocks(title="Aura Programadora") as demo:
gr.Markdown("# 💻 Aura Programadora (Groq)")
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.Chatbot(label="Chat", height=500)
chat_input = gr.Textbox(placeholder="Escribe tu código o error...", lines=2)
with gr.Row():
send_btn = gr.Button("🚀 Enviar", variant="primary")
clear_btn = gr.Button("🗑️ Limpiar")
with gr.Accordion("📎 Adjuntar Archivo", open=False):
file_input = gr.File(
label="Sube archivo (.py,.js,.html,.sql)",
file_types=[".py", ".js", ".html", ".sql", ".jsx", ".ts"]
)
file_name_state = gr.Textbox(visible=False)
file_content_state = gr.Textbox(visible=False)
with gr.Column(scale=2):
with gr.Accordion("⚙️ Configuración IA", open=False):
user_context_input = gr.Textbox(
label="Tus Instrucciones", lines=3,
placeholder="Ej: explica paso a paso"
)
max_tokens_input = gr.Slider(1, 4096, 2048, label="Max Tokens")
temp_input = gr.Slider(0.1, 1.0, 0.2, label="Temperature")
top_p_input = gr.Slider(0.1, 1.0, 0.9, label="Top-p")
token_input = gr.Textbox(
label="Tu API Key Groq (opcional, usamos la demo)",
type="password"
)
exec_output = gr.Textbox(label="Resultado Ejecución", lines=4)
download_code = gr.File(label="Código Corregido")
download_txt = gr.File(label="Chat Completo")
gr.Markdown("### 🧠 Memoria")
visible_memoria = gr.Textbox(
label="Notas del Usuario", lines=2,
placeholder="Ej: Mi proyecto usa FastAPI..."
)
hidden_memoria = gr.Textbox(visible=False)
def on_file_upload(file):
if not file:
return "", ""
if isinstance(file, (list, tuple)):
file = file[0]
fname = getattr(file, "orig_name", None) or getattr(file, "name", "") or ""
if isinstance(file, dict):
fname = file.get("orig_name") or file.get("name") or ""
return extract_file_text(file), os.path.basename(str(fname))
file_input.upload(
fn=on_file_upload,
inputs=[file_input],
outputs=[file_content_state, file_name_state]
)
event_inputs = [
chat_input, chatbot, user_context_input,
max_tokens_input, temp_input, top_p_input,
token_input, file_content_state, file_name_state,
visible_memoria
]
event_outputs = [chatbot, download_code, download_txt, exec_output, hidden_memoria]
send_btn.click(fn=run_chat, inputs=event_inputs, outputs=event_outputs)
chat_input.submit(fn=run_chat, inputs=event_inputs, outputs=event_outputs)
clear_btn.click(fn=lambda: ([], None, None, "", ""), inputs=[], outputs=event_outputs)
if __name__ == "__main__":
demo.queue().launch(ssr_mode=False) |