Spaces:
Sleeping
Sleeping
| """ | |
| 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 ============== | |
| async def root(): | |
| """Health check.""" | |
| return {"status": "ok", "message": "Jade Code IDE API v2.0 - BYOK Mode"} | |
| 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}") | |
| 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 | |
| } | |
| 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) | |