jade-code-api / app.py
Madras1's picture
Upload 3 files
ccbf17f verified
raw
history blame
8.73 kB
"""
Jade Code IDE - HuggingFace Space Backend
BYOK (Bring Your Own Key) Architecture
Cada usuário fornece sua própria API key via header.
O backend nunca armazena keys - só repassa pro LLM.
"""
import os
import sys
import io
import json
import base64
import traceback
import re
from contextlib import redirect_stdout, redirect_stderr
from typing import Optional
from fastapi import FastAPI, HTTPException, Request, Header
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import gradio as gr
# ============== App Setup ==============
app = FastAPI(title="Jade Code IDE API", version="2.0")
# CORS - permitir GitHub Pages e localhost para dev
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Em produção, restringir para seu domínio
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ============== Request Models ==============
class ChatRequest(BaseModel):
message: str
current_file_content: Optional[str] = ""
history: Optional[list] = []
class RunRequest(BaseModel):
code: str
class CompleteRequest(BaseModel):
prefix: str
suffix: str = ""
language: str = "python"
# ============== Helper: Get Groq Client ==============
def get_groq_client(api_key: str):
"""Cria cliente Groq com a key do usuário (não armazena)."""
from groq import Groq
return Groq(api_key=api_key)
# ============== System Prompt ==============
SYSTEM_PROMPT = """Você é CodeJade, um assistente de programação avançado.
Seu objetivo é ajudar o usuário a escrever código, corrigir bugs e explicar conceitos.
Regras:
- Seja direto e técnico
- Use código Python por padrão
- Explique o que está fazendo
- Se o usuário compartilhar código, analise e sugira melhorias
"""
# ============== Endpoints ==============
@app.get("/")
async def root():
"""Health check."""
return {"status": "ok", "message": "Jade Code IDE API v2.0 - BYOK Mode"}
@app.post("/chat")
async def chat(req: ChatRequest, x_api_key: str = Header(..., alias="X-API-Key")):
"""
Chat com o agente usando a API key do usuário.
"""
if not x_api_key or len(x_api_key) < 10:
raise HTTPException(status_code=401, detail="API Key inválida")
try:
client = get_groq_client(x_api_key)
# Monta mensagens
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
# Adiciona histórico se existir
for msg in (req.history or [])[-10:]: # Últimas 10 mensagens
messages.append(msg)
# Adiciona contexto do arquivo se existir
user_msg = req.message
if req.current_file_content:
user_msg = f"[Código atual do usuário]\n```\n{req.current_file_content[:3000]}\n```\n\n{req.message}"
messages.append({"role": "user", "content": user_msg})
# Chama o modelo
response = client.chat.completions.create(
messages=messages,
model="llama-3.3-70b-versatile",
temperature=0.3,
max_tokens=2000
)
return {"response": response.choices[0].message.content}
except Exception as e:
error_msg = str(e)
if "authentication" in error_msg.lower() or "api key" in error_msg.lower():
raise HTTPException(status_code=401, detail="API Key inválida ou expirada")
raise HTTPException(status_code=500, detail=f"Erro: {error_msg}")
@app.post("/run")
async def run_code(req: RunRequest):
"""
Executa código Python (sandbox limitado).
Não precisa de API key - é só execução local.
"""
code = req.code
# Captura stdout/stderr
f_out = io.StringIO()
f_err = io.StringIO()
# Variável para capturar matplotlib
image_base64 = None
try:
# Prepara ambiente
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
exec_globals = {
"__builtins__": __builtins__,
"__name__": "__main__",
"plt": plt,
}
# Importa libs comuns
try:
import numpy as np
exec_globals["np"] = np
exec_globals["numpy"] = np
except ImportError:
pass
try:
import pandas as pd
exec_globals["pd"] = pd
except ImportError:
pass
# Executa
with redirect_stdout(f_out), redirect_stderr(f_err):
exec(code, exec_globals)
# Captura figura matplotlib
if plt.get_fignums():
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight',
facecolor='#09090b', edgecolor='none')
buf.seek(0)
image_base64 = base64.b64encode(buf.read()).decode('utf-8')
plt.close('all')
return {
"output": f_out.getvalue(),
"error": f_err.getvalue() or None,
"image": image_base64
}
except Exception as e:
return {
"output": f_out.getvalue(),
"error": f"❌ {type(e).__name__}: {str(e)}",
"image": None
}
@app.post("/complete")
async def autocomplete(req: CompleteRequest, x_api_key: str = Header(..., alias="X-API-Key")):
"""
Autocomplete via LLM usando a key do usuário.
"""
if not x_api_key or len(x_api_key) < 10:
raise HTTPException(status_code=401, detail="API Key inválida")
try:
client = get_groq_client(x_api_key)
prompt = f"""Você é um autocomplete de código. Sugira completions para o código abaixo.
CÓDIGO ANTES DO CURSOR:
```{req.language}
{req.prefix[-500:]}
```
CÓDIGO DEPOIS DO CURSOR:
```{req.language}
{req.suffix[:200] if req.suffix else ''}
```
Responda APENAS com um JSON array:
[{{"label": "nome", "insertText": "código", "detail": "descrição", "kind": "Function"}}]
Máximo 5 sugestões. Tipos: Function, Variable, Class, Module, Keyword, Snippet"""
response = client.chat.completions.create(
messages=[
{"role": "system", "content": "Responda APENAS JSON válido."},
{"role": "user", "content": prompt}
],
model="llama-3.3-70b-versatile",
temperature=0.1,
max_tokens=500
)
response_text = response.choices[0].message.content
# Parse JSON
match = re.search(r'\[.*\]', response_text, re.DOTALL)
if match:
suggestions = json.loads(match.group(0))
return {"suggestions": suggestions}
return {"suggestions": []}
except Exception as e:
return {"suggestions": [], "error": str(e)}
# ============== Gradio Interface (for HF Spaces UI) ==============
def gradio_chat(message, api_key):
"""Interface Gradio simples para testar."""
if not api_key:
return "❌ Coloque sua API Key do Groq"
try:
client = get_groq_client(api_key)
response = client.chat.completions.create(
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": message}
],
model="llama-3.3-70b-versatile",
temperature=0.3
)
return response.choices[0].message.content
except Exception as e:
return f"❌ Erro: {str(e)}"
# Gradio UI
with gr.Blocks(title="Jade Code IDE API") as demo:
gr.Markdown("# 🟢 Jade Code IDE - API Backend")
gr.Markdown("Esta é a API do Jade Code IDE. Use a IDE web para a experiência completa.")
gr.Markdown("**API Docs**: `/docs`")
with gr.Row():
api_key_input = gr.Textbox(label="Sua API Key do Groq", type="password")
with gr.Row():
msg_input = gr.Textbox(label="Mensagem de teste", lines=3)
with gr.Row():
send_btn = gr.Button("Testar Chat")
output = gr.Textbox(label="Resposta", lines=10)
send_btn.click(gradio_chat, inputs=[msg_input, api_key_input], outputs=output)
# Mount Gradio app
app = gr.mount_gradio_app(app, demo, path="/ui")
# ============== Run ==============
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)