Spaces:
Paused
Paused
| from fastapi import FastAPI, HTTPException | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| import requests | |
| import zipfile | |
| import io | |
| import os | |
| from typing import Dict, List | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # MEMÓRIA TEMPORÁRIA DO PROJETO | |
| projeto_atual = { | |
| "url": "", | |
| "arquivos": {}, # {"lib/main.dart": "conteúdo..."} | |
| "estrutura": [] | |
| } | |
| # Cliente de IA - MODELO LEVE QUE FUNCIONA EM CPU GRATUITA | |
| from huggingface_hub import InferenceClient | |
| client = InferenceClient() | |
| class LinkGitHub(BaseModel): | |
| url: str | |
| class Mensagem(BaseModel): | |
| mensagem: str | |
| class EditarArquivo(BaseModel): | |
| caminho: str | |
| novo_conteudo: str | |
| def baixar_github(url: str) -> tuple[Dict[str, str], List[str]]: | |
| """Baixa TODOS os arquivos de um repo GitHub público""" | |
| # Extrai owner/repo | |
| url = url.rstrip('/') | |
| if url.endswith('.git'): | |
| url = url[:-4] | |
| parts = url.replace('https://github.com/', '').split('/') | |
| if len(parts) < 2: | |
| raise ValueError("URL inválida") | |
| owner, repo = parts[0], parts[1] | |
| # Tenta main primeiro, depois master | |
| for branch in ['main', 'master']: | |
| api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1" | |
| resp = requests.get(api_url) | |
| if resp.status_code == 200: | |
| break | |
| else: | |
| raise ValueError("Repositório não encontrado ou privado") | |
| tree = resp.json().get('tree', []) | |
| arquivos = {} | |
| estrutura = [] | |
| for item in tree: | |
| path = item['path'] | |
| estrutura.append(path) | |
| # Ignora node_modules, .git, build, etc | |
| if any(x in path for x in ['node_modules', '.git/', 'build/', '.gradle', '.idea']): | |
| continue | |
| if item['type'] == 'blob': | |
| # Baixa arquivo | |
| raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}" | |
| try: | |
| file_resp = requests.get(raw_url, timeout=5) | |
| if file_resp.status_code == 200: | |
| # Tenta decodificar como texto | |
| try: | |
| arquivos[path] = file_resp.content.decode('utf-8') | |
| except: | |
| # Binário - ignora | |
| pass | |
| except: | |
| pass | |
| return arquivos, estrutura | |
| async def carregar_projeto(data: LinkGitHub): | |
| """Carrega projeto do GitHub""" | |
| try: | |
| arquivos, estrutura = baixar_github(data.url) | |
| projeto_atual['url'] = data.url | |
| projeto_atual['arquivos'] = arquivos | |
| projeto_atual['estrutura'] = estrutura | |
| return { | |
| "status": "sucesso", | |
| "total_arquivos": len(arquivos), | |
| "estrutura": estrutura | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| def get_estrutura(): | |
| """Retorna estrutura de pastas/arquivos""" | |
| return {"estrutura": projeto_atual['estrutura']} | |
| def get_arquivo(caminho: str): | |
| """Pega conteúdo de um arquivo""" | |
| if caminho in projeto_atual['arquivos']: | |
| return {"conteudo": projeto_atual['arquivos'][caminho]} | |
| raise HTTPException(status_code=404, detail="Arquivo não encontrado") | |
| def editar_arquivo(data: EditarArquivo): | |
| """Salva edição de arquivo""" | |
| projeto_atual['arquivos'][data.caminho] = data.novo_conteudo | |
| return {"status": "salvo"} | |
| async def chat(data: Mensagem): | |
| """Chat com IA que conhece o projeto""" | |
| if not projeto_atual['arquivos']: | |
| return {"resposta": "Carregue um projeto primeiro!"} | |
| # Monta contexto compacto com arquivos principais | |
| contexto = f"PROJETO: {projeto_atual['url']}\n\n" | |
| contexto += f"ESTRUTURA:\n{chr(10).join(projeto_atual['estrutura'][:30])}\n\n" | |
| contexto += "ARQUIVOS PRINCIPAIS:\n\n" | |
| # Adiciona apenas trechos dos arquivos mais relevantes | |
| count = 0 | |
| for caminho, conteudo in projeto_atual['arquivos'].items(): | |
| if caminho.endswith(('.dart', '.yaml', '.json')): | |
| if count < 8: | |
| contexto += f"--- {caminho} ---\n{conteudo[:1500]}\n\n" | |
| count += 1 | |
| # Limita tamanho total | |
| if len(contexto) > 12000: | |
| contexto = contexto[:12000] | |
| prompt = f"""{contexto} | |
| PERGUNTA: {data.mensagem} | |
| Responda de forma clara sobre o código Flutter/Dart.""" | |
| try: | |
| # Usa modelo pequeno e rápido que REALMENTE funciona em CPU gratuita | |
| resposta = client.text_generation( | |
| prompt, | |
| model="bigcode/starcoder2-3b", | |
| max_new_tokens=600, | |
| temperature=0.5, | |
| ) | |
| return {"resposta": resposta} | |
| except Exception as e: | |
| # Fallback para modelo ainda menor | |
| try: | |
| resposta = client.text_generation( | |
| prompt, | |
| model="Salesforce/codegen-350M-mono", | |
| max_new_tokens=400, | |
| ) | |
| return {"resposta": resposta} | |
| except: | |
| return {"resposta": f"IA indisponível no momento. Erro: {str(e)}"} | |
| def baixar_projeto(): | |
| """Gera ZIP do projeto modificado""" | |
| if not projeto_atual['arquivos']: | |
| raise HTTPException(status_code=400, detail="Nenhum projeto carregado") | |
| # Cria ZIP em memória | |
| zip_buffer = io.BytesIO() | |
| with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: | |
| for caminho, conteudo in projeto_atual['arquivos'].items(): | |
| zip_file.writestr(caminho, conteudo) | |
| zip_buffer.seek(0) | |
| # Salva temporariamente | |
| with open('/tmp/projeto.zip', 'wb') as f: | |
| f.write(zip_buffer.getvalue()) | |
| return FileResponse( | |
| '/tmp/projeto.zip', | |
| media_type='application/zip', | |
| filename='projeto_modificado.zip' | |
| ) | |
| def root(): | |
| return {"status": "IA Code Online", "projeto_carregado": bool(projeto_atual['arquivos'])} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |