diff --git a/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db b/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db index b1229db1e..c11e0bbea 100644 Binary files a/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db and b/backend/Agents/AppAI/CodeBackend/Sessions/session_1.db differ diff --git a/backend/Agents/AppAI/CodeBackend/ai.py b/backend/Agents/AppAI/CodeBackend/ai.py index b3568862d..fe7c6a00f 100644 --- a/backend/Agents/AppAI/CodeBackend/ai.py +++ b/backend/Agents/AppAI/CodeBackend/ai.py @@ -13,6 +13,11 @@ from dotenv import load_dotenv from openai import OpenAI +from typing_extensions import TypedDict, Any +from agents import Agent, ModelSettings, Runner, RunConfig, function_tool, FileSearchTool, WebSearchTool, Runner +from datetime import datetime +import requests +from agents.mcp import MCPServerStdio from Functions.autosave.autosave import autosave from Functions.autolistlocalproject.autolistlocalproject import autolistlocalproject @@ -23,22 +28,158 @@ logger = logging.getLogger("SprintsPlanner_logger") class CodeBackEndAgentOutput(BaseModel): - saved_files: List[str] - + saved_files: List[str] + +class ReflectionAgentAgentOutput(BaseModel): + should_retry: bool + suggested_changes: List[str] + confidence: float + +class ReflectionData(TypedDict): + task: str + last_output: str + history: List[str] +import asyncio +import json +import logging +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel +from agents import FunctionTool, RunContextWrapper + +logger = logging.getLogger("sequentialthinking") +logging.basicConfig(level=logging.ERROR) + + +class ThoughtArgs(BaseModel): + thought: str + nextThoughtNeeded: bool + thoughtNumber: int + totalThoughts: int + isRevision: Optional[bool] = None + revisesThought: Optional[int] = None + branchFromThought: Optional[int] = None + branchId: Optional[str] = None + needsMoreThoughts: Optional[bool] = None + + +class SequentialThinkingState: + def __init__(self): + self.thought_history: List[ThoughtArgs] = [] + self.branches: Dict[str, List[ThoughtArgs]] = {} + self.lock = asyncio.Lock() + + def _format_thought(self, t: ThoughtArgs) -> str: + header_parts = [] + if t.isRevision: + header_parts.append("🔄 Revision") + elif t.branchFromThought: + header_parts.append("🌿 Branch") + else: + header_parts.append("💭 Thought") + header = f"{header_parts[0]} {t.thoughtNumber}/{t.totalThoughts}" + if t.isRevision and t.revisesThought: + header += f" (revising thought {t.revisesThought})" + if t.branchFromThought and t.branchId: + header += f" (from thought {t.branchFromThought}, ID: {t.branchId})" + body = t.thought + width = max(len(header), len(body)) + 4 + border = "─" * width + formatted = f"\n┌{border}┐\n│ {header.ljust(width-2)} │\n├{border}┤\n│ {body.ljust(width-2)} │\n└{border}┘" + return formatted + + async def process(self, data: ThoughtArgs) -> Dict[str, Any]: + async with self.lock: + if data.thoughtNumber > data.totalThoughts: + data.totalThoughts = data.thoughtNumber + self.thought_history.append(data) + if data.branchFromThought and data.branchId: + if data.branchId not in self.branches: + self.branches[data.branchId] = [] + self.branches[data.branchId].append(data) + formatted = self._format_thought(data) + logger.error(formatted) + return { + "thoughtNumber": data.thoughtNumber, + "totalThoughts": data.totalThoughts, + "nextThoughtNeeded": data.nextThoughtNeeded, + "branches": list(self.branches.keys()), + "thoughtHistoryLength": len(self.thought_history), + } + + +_state = SequentialThinkingState() + + +async def _on_invoke(ctx: RunContextWrapper[Any], args_json: str) -> str: + parsed = ThoughtArgs.model_validate_json(args_json) + result = await _state.process(parsed) + return json.dumps(result, indent=2) + + +tool_sequentialthinking = FunctionTool( + name="sequentialthinking", + description=( + "Ferramenta para pensamento sequencial/reflexivo. Aceita um objeto com os campos:" + " thought, nextThoughtNeeded, thoughtNumber, totalThoughts, isRevision, revisesThought," + " branchFromThought, branchId, needsMoreThoughts" + ), + params_json_schema=ThoughtArgs.model_json_schema(), + on_invoke_tool=_on_invoke, +) + +# async def _sequential_thinking(): +# async with MCPServerStdio( +# name="sequentialthinking", +# params={ +# "command": "docker", +# "args": [ +# "run", +# "--rm", +# "-i", +# "mcp/sequentialthinking" +# ], +# }, +# client_session_timeout_seconds=45 +# ) as sequential_thinking: +# return sequential_thinking + +# async def _git_server(src): +# async with MCPServerStdio( +# name="git", +# params={ +# "command": "docker", +# "args": ["run", +# "--rm", +# "-i", +# "--mount", +# f"type=bind,src={src},dst={src}", +# "mcp/git" +# ], +# }, +# client_session_timeout_seconds=45 +# ) as git_: +# return git_ async def CodeBackEndAgent( - OPENAI_API_KEY, - user_id, - tipo_app, - descricao, - user_content, - commit_language = 'pt', - model = "gpt-5-nano", - local_to_save = "./", - - ): + OPENAI_API_KEY, + user_id, + tipo_app, + title, + description, + category, + price, + technologies, + early_bonus, + deadline, + commit_language = 'pt', + model = "gpt-5-nano", + local_to_save = "./", + + ): os.environ['OPENAI_API_KEY'] = OPENAI_API_KEY os.makedirs(os.path.join(os.path.dirname(__file__), 'Sessions'), exist_ok=True) + os.chdir(local_to_save) logger.info(f"CodeBackEndAgent Agent") name_chroma_store = "backend_skeleton" chroma_store = os.path.join(os.path.dirname(__file__), 'CodeKnowledge', 'chroma_store') @@ -55,180 +196,204 @@ async def CodeBackEndAgent( elif commit_language == 'pt': prompt_system_direct = f""" -# IDENTIDADE E CONTEXTO -Você é um desenvolvedor backend sênior especializado em Python/Flask, responsável por implementar tarefas de backend de forma autônoma e eficiente. - -## INFORMAÇÕES DO PROJETO -- **Tipo**: {tipo_app} -- **Descrição**: {descricao} -- **Diretório Base**: {local_to_save} -- **Stack Tecnológica**: Flask (blueprints), SQLAlchemy (PostgreSQL), MongoDB (logs), Celery+Redis, Pydantic Settings - ---- - -# FLUXO DE TRABALHO OBRIGATÓRIO - -## 1️⃣ ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO) -Antes de qualquer implementação, você DEVE: - -### A) Listar Estado Atual do Projeto - -{{ - "autolistlocalproject": {{ - "path_project": "{local_to_save}" - }} -}} -Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos. -B) Consultar Base de Conhecimento -json{{ - "retrieve_backend_context": {{ - "query": "[descreva claramente o que precisa: ex: 'padrões de autenticação JWT', 'estrutura de models SQLAlchemy']", - "k": 8, - "path": "{chroma_store}", - "name": "{name_chroma_store}" - }} -}} -Quando usar: - -Antes de criar novos endpoints -Ao definir schemas de banco de dados -Para decisões arquiteturais (autenticação, validação, etc.) - - -2️⃣ DESENVOLVIMENTO E SALVAMENTO -Regras de Implementação -✅ SEMPRE: - -Use nomenclatura clara e descritiva (snake_case para arquivos/funções) -Implemente tratamento de erros com logging adequado -Adicione docstrings em todas as funções/classes -Siga padrões RESTful para APIs -Use type hints (Python 3.10+) -Valide inputs com Pydantic models - -❌ NUNCA: - -Hardcode credenciais ou secrets -Crie arquivos fora de {local_to_save} -Sobrescreva arquivos sem verificar o conteúdo atual via autolistlocalproject - -Estrutura de Diretórios Padrão -{local_to_save}/ -├── app/ -│ ├── __init__.py -│ ├── models/ # SQLAlchemy models -│ ├── routes/ # Flask blueprints -│ ├── schemas/ # Pydantic schemas -│ ├── services/ # Business logic -│ └── utils/ # Helpers -├── tasks/ # Celery tasks -├── config/ # Settings (BaseSettings) -├── tests/ # Testes unitários -└── manifest.json # Metadata do projeto -Salvamento de Arquivos -Para cada arquivo implementado: -json{{ - "autosave": {{ - "code": "# Conteúdo completo do arquivo aqui\n# Inclua imports, docstrings, type hints\n\nfrom flask import Blueprint\n\nauth_bp = Blueprint('auth', __name__)\n\n@auth_bp.route('/login', methods=['POST'])\ndef login():\n \"\"\"Endpoint de autenticação.\"\"\"\n pass", - "path": "{local_to_save}/app/routes/auth.py" - }} -}} - -3️⃣ ATUALIZAÇÃO DO MANIFEST -Após salvar arquivos, atualize {local_to_save}/manifest.json: -json{{ - "autosave": {{ - "code": "{{\n \"project_name\": \"{tipo_app}\",\n \"last_update\": \"2025-10-07T10:30:00Z\",\n \"files\": [\n {{\n \"path\": \"app/routes/auth.py\",\n \"size_bytes\": 1024,\n \"created_at\": \"2025-10-07T10:30:00Z\"\n }}\n ]\n}}", - "path": "{local_to_save}/manifest.json" +Você é um desenvolvedor backend sênior especializado em Python/Flask, +responsável por implementar tarefas de backend de forma autônoma e eficiente.\n +# FLUXO DE TRABALHO OBRIGATÓRIO\n +1) ANÁLISE INICIAL (SEMPRE EXECUTAR PRIMEIRO)\n + 1.1 - Antes de qualquer implementação, você DEVE: + Listar Estado Atual do Projeto + {{ + "autolistlocalproject": {{ + "path_project": "{local_to_save}" + "show_contents" True + }} + }} + Objetivo: Mapear arquivos existentes, estrutura de diretórios e evitar conflitos.\n + 1.2 - sequentialthinking + Use o pensamento sequencial para pensar e planejar o desenvolvimento + +---\n +2) DESENVOLVIMENTO E SALVAMENTO\n + 2.1 - Regras de Implementação\n + Use o pensamento sequencial para cada codigo funcional e sem erros + 2.2 - Salvamento de Arquivos\n + Para cada arquivo implementado: + json{{ + "autosave": {{ + "code": "# Conteúdo completo do arquivo aqui\n# Inclua imports, docstrings, type hints\n\nfrom flask import Blueprint\n\nauth_bp = Blueprint('auth', __name__)\n\n@auth_bp.route('/login', methods=['POST'])\ndef login():\n \"\"\"Endpoint de autenticação.\"\"\"\n pass", + "path": "{local_to_save}/app/routes/auth.py" + }} }} -}} - -FORMATO DE RESPOSTA FINAL -Após concluir todas as etapas, retorne SOMENTE este JSON (sem texto adicional): -json{{ - "analysis_summary": {{ - "existing_files": ["lista de arquivos encontrados no autolistlocalproject"], - "knowledge_retrieved": "resumo breve do que foi consultado no retrieve_backend_context" - }}, - "implementation_details": {{ - "approach": "breve descrição da estratégia de implementação", - "stack_decisions": ["Flask blueprints", "SQLAlchemy models", "Pydantic validation"] - }}, - "saved_files": [ - "{local_to_save}/app/routes/auth.py", - "{local_to_save}/app/models/user.py", - "{local_to_save}/manifest.json" - ], - "next_steps": [ - "Configurar variáveis de ambiente no .env", - "Executar migrações do banco de dados" - ] -}} - -EXEMPLOS DE USO DAS FERRAMENTAS + \n + 2.3 - Regras de desing system\n + Use nomenclatura clara e descritiva (snake_case para arquivos/funções) + Implemente tratamento de erros com logging adequado + Adicione docstrings em todas as funções/classes + Siga padrões RESTful para APIs + Use type hints (Python 3.10+) + Valide inputs com Pydantic models\n + + Estrutura de Diretórios Padrão + {local_to_save}/ + ├── app/ + │ ├── __init__.py + │ ├── models/ # SQLAlchemy models + │ ├── routes/ # Flask blueprints + │ ├── schemas/ # Pydantic schemas + │ ├── services/ # Business logic + │ └── utils/ # Helpers + ├── tasks/ # Celery tasks + ├── config/ # Settings + ├── tests/ # Testes unitários + 2.4 - NUNCA FAÇA ISSO:\n + Hardcode credenciais ou secrets + Criar arquivos fora de {local_to_save} + Sobrescrever arquivos sem verificar o conteúdo atual via autolistlocalproject\n +---\n +3) FORMATO DE RESPOSTA FINAL\n + 3.1 - Após concluir todas as etapas, retorne SOMENTE este JSON (sem texto adicional): + json{{ + "analysis_summary": {{ + "existing_files": ["lista de arquivos encontrados no autolistlocalproject"], + }}, + "implementation_details": {{ + "approach": "breve descrição da estratégia de implementação", + "stack_decisions": ["Flask blueprints", "SQLAlchemy models", "Pydantic validation"] + }}, + "saved_files": [ + "{local_to_save}/app/routes/auth.py", + "{local_to_save}/manifest.json" + ], + "next_steps": [ + "Configurar variáveis de ambiente no .env", + "Executar migrações do banco de dados" + ] + }} + - o saved_files deve ser de arquivos salvos ou modificados + +EXEMPLOS DE USO DAS FERRAMENTAS\n Exemplo 1: Criar Endpoint de Autenticação Sequência: - autolistlocalproject → Verificar se já existe app/routes/auth.py -retrieve_backend_context → query: "melhores práticas JWT Flask" +sequentialthinking → pensamento em como implementar o app/routes/auth.py com blueprint de forma proativa e eficiente autosave → Criar app/routes/auth.py com blueprint +sequentialthinking → pensamento em como desenvolver o app/schemas/auth.py com Pydantic models de forma proativa e eficiente autosave → Criar app/schemas/auth.py com Pydantic models -autosave → Atualizar manifest.json - Exemplo 2: Implementar Model de Usuário Sequência: - autolistlocalproject → Mapear models existentes -retrieve_backend_context → query: "schema usuário autenticação PostgreSQL" +sequentialthinking → pensamento em como desenvolver o app/models/user.py com SQLAlchemy de forma proativa e eficiente autosave → Criar app/models/user.py com SQLAlchemy -autosave → Atualizar manifest.json - - +---\n CHECKLIST PRÉ-RESPOSTA Antes de enviar o JSON final, confirme: - Executei autolistlocalproject? - Consultei retrieve_backend_context para decisões importantes? + Consultei o pensamento sequencial para decisões importantes? Todos os arquivos foram salvos via autosave? - O manifest.json foi atualizado? O JSON de resposta está válido e completo? - -COMECE AGORA: Execute autolistlocalproject e retrieve_backend_context antes de qualquer implementação. - """ - - imported_tools = [autosave, retrieve_backend_context, autolistlocalproject] + + user_content = f""" +## INFORMAÇÕES DO PROJETO +- Tipo: {tipo_app} +- Titulo: {title} +- Descrição: {description} +- Categoria: {category} +- Stack Tecnológica: {technologies} +- Preço Pago Pela Solucao: {price} +- Data Limite De Entrega: {deadline} +- Bonus Recebido Em Caso De Entrega Antecipada: {early_bonus} +- Diretório Base: {local_to_save} + """ +# ---\n +# Conhecimento de Ferramentas de operacoes git\n +# Registra alterações no staging area +# Entradas: +# repo_path(string): Caminho para o repositório Git +# message(string): Mensagem de confirmação +# Retorna: Confirmação com novo hash de commit +# git_add +# \n +# Registra alterações no repositório local\n +# Entradas: +# repo_path(string): Caminho para o repositório Git +# target(string): Ramificação de destino ou commit para comparar com +# Retorna: Saída diferente comparando o estado atual com o alvo +# git_commit +# \n +# Criar nova branch\n +# Entradas: +# repo_path(string): Caminho para o repositório Git +# max_count(número, opcional): Número máximo de confirmações a serem exibidas (padrão: 10) +# Retorna: Matriz de entradas de confirmação com hash, autor, data e mensagem +# git_create_branch +# ---\n + imported_tools = [ + autosave, + autolistlocalproject, + tool_sequentialthinking + ] session = SQLiteSession("agent_session_backend_01", db_path=os.path.join(os.path.dirname(__file__), 'Sessions', f"session_{user_id}.db")) + + # git_server = await _git_server(local_to_save) + # sequential_thinking = await _sequential_thinking() + async with MCPServerStdio( + name="sequentialthinking", + params={ + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "mcp/sequentialthinking" + ], + }, + client_session_timeout_seconds=45 + ) as sequential_thinking: + agent = Agent( + name="Agent Code BackEnd", + instructions=prompt_system_direct, + model=model, + # mcp_servers=[ + # sequential_thinking, + # # git_server + # ], + output_type=CodeBackEndAgentOutput, + model_settings=ModelSettings( + tool_choice="sequentialthinking", + include_usage=True + ), + tools=imported_tools + ) + + result = await Runner.run( + agent, + user_content, + max_turns=300, + session=session, + run_config=RunConfig( + model_settings=ModelSettings( + tool_choice="sequentialthinking", + include_usage=True, + parallel_tool_calls=True + ) + ), + ) + final_output = result.final_output + saved_files = final_output.saved_files + + usage = result.context_wrapper.usage + total_usage["input"] = usage.input_tokens + total_usage["cached"] = usage.input_tokens_details.cached_tokens + total_usage["reasoning"] = usage.output_tokens_details.reasoning_tokens + total_usage["output"] = usage.output_tokens + total_usage["total"] = usage.total_tokens + + logger.info(f"Agent Final Usage: {total_usage['total']} total tokens.") + - agent = Agent( - name="Agent Code BackEnd", - instructions=prompt_system_direct, - model=model, - output_type=CodeBackEndAgentOutput, - model_settings=ModelSettings(include_usage=True), - tools=imported_tools - ) - result = await Runner.run( - agent, - user_content, - max_turns=300, - session=session - ) - final_output = result.final_output - saved_files = final_output.saved_files - - usage = result.context_wrapper.usage - total_usage["input"] = usage.input_tokens - total_usage["cached"] = usage.input_tokens_details.cached_tokens - total_usage["reasoning"] = usage.output_tokens_details.reasoning_tokens - total_usage["output"] = usage.output_tokens - total_usage["total"] = usage.total_tokens - - logger.info(f"Agent Final Usage: {total_usage['total']} total tokens.") - - - return total_usage["total"], saved_files + return total_usage["total"], saved_files diff --git a/backend/Dockerfile b/backend/Dockerfile index 1045f2848..9f5bf0b43 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -10,8 +10,30 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ linux-headers-amd64 \ libssl-dev \ + docker-compose \ && rm -rf /var/lib/apt/lists/* +# Instalar Docker CLI +RUN apt-get update && apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release && \ + mkdir -m 0755 -p /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \ + https://download.docker.com/linux/debian \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ + apt-get update && \ + apt-get install -y docker-ce-cli docker-compose-plugin + +RUN apt-get update && \ + apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ + apt-get install -y nodejs + + RUN apt-get update && apt-get install -y \ ca-certificates \ curl \ diff --git a/backend/Functions/Think/Think.py b/backend/Functions/Think/Think.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/Functions/autocreaterepo/autocreaterepo.py b/backend/Functions/autocreaterepo/autocreaterepo.py new file mode 100644 index 000000000..8fce6f034 --- /dev/null +++ b/backend/Functions/autocreaterepo/autocreaterepo.py @@ -0,0 +1,45 @@ +from typing_extensions import TypedDict, Any +from agents import Agent, ModelSettings, function_tool, FileSearchTool, WebSearchTool, Runner +from datetime import datetime +import requests + +class autocreaterepoData(TypedDict): + repo_owner: str + repo_name: str + description: str + githubtoken: str + private: bool + +@function_tool +def autocreaterepo(data: autocreaterepoData): + try: + data_FINAL = data["data"] + repo_owner = data_FINAL["repo_owner"] + repo_name = data_FINAL["repo_name"] + description = data_FINAL["description"] + githubtoken = data_FINAL["githubtoken"] + private = data_FINAL["private"] + except Exception as eroo1: + print(eroo1) + repo_owner = data["repo_owner"] + repo_name = data["repo_name"] + description = data["description"] + githubtoken = data["githubtoken"] + private = data["private"] + repo_url = f"https://api.github.com/orgs/{repo_owner}/repos" + headers = { + "Authorization": f"token {githubtoken}", + "Accept": "application/vnd.github.v3+json" + } + repo_data = { + "name": repo_name, + "description": description, + "private": private + } + response = requests.post(repo_url, json=repo_data, headers=headers) + if response.status_code == 201: + print(f"Repositório {repo_name} criado com sucesso na organização {repo_owner}") + return {"status": "success", "message": f"Repositório {repo_name} criado com sucesso na organização {repo_owner}"} + else: + print(f"Falha ao criar o repositório. Status: {response.status_code}, Resposta: {response.json()}") + return {"status": "error", "message": response.json()} \ No newline at end of file diff --git a/backend/Functions/autolistlocalproject/autolistlocalproject.py b/backend/Functions/autolistlocalproject/autolistlocalproject.py index a78f7602d..ec36c825b 100644 --- a/backend/Functions/autolistlocalproject/autolistlocalproject.py +++ b/backend/Functions/autolistlocalproject/autolistlocalproject.py @@ -29,19 +29,28 @@ def ler_conteudos_arquivos(caminhos_arquivos): class FunctionData(TypedDict): path_project: str + show_contents: bool @function_tool def autolistlocalproject(data: FunctionData): try: data_FINAL = data["data"] path_project = data_FINAL["path_project"] + show_contents = data_FINAL["show_contents"] except Exception as eroo1: print(eroo1) path_project = data["path_project"] + show_contents = data["show_contents"] caminhos_arquivos = listar_arquivos(path_project) conteudos_arquivos = ler_conteudos_arquivos(caminhos_arquivos) - data = { - "paths": caminhos_arquivos, - "contents": conteudos_arquivos, - } + if show_contents == False: + data = { + "paths": caminhos_arquivos, + } + else: + + data = { + "paths": caminhos_arquivos, + "contents": conteudos_arquivos, + } return data \ No newline at end of file diff --git a/backend/Functions/gitcommit/gitcommit.py b/backend/Functions/gitcommit/gitcommit.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/Models/postgreSQL.py b/backend/Models/postgreSQL.py index 077c2ea76..29710ae00 100644 --- a/backend/Models/postgreSQL.py +++ b/backend/Models/postgreSQL.py @@ -8,6 +8,7 @@ from enum import Enum as PyEnum from sqlalchemy import Text from sqlalchemy.sql import func +from sqlalchemy.dialects.postgresql import JSON TOKEN_DEFAULT_EXPIRES_DAYS = 30 @@ -51,23 +52,58 @@ class TaskStatus(PyEnum): DONE = "done" FAILED = "failed" +class TaskLog(db.Model): + __tablename__ = "task_logs" + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + task_id = db.Column(db.Integer, db.ForeignKey("backend_tasks.id"), nullable=False) + type = db.Column(db.String(32), default="info") # info, success, warning, error + message = db.Column(db.Text, nullable=False) + task_metadata = db.Column(JSON, nullable=True) # Qualquer dado extra, opcional + created_at = db.Column(db.DateTime(timezone=True), default=func.now()) + +class ApprovedProject(db.Model): + __tablename__ = "approved_projects" + + id = db.Column(db.Integer, primary_key=True, autoincrement=True) + task_id = db.Column(db.Integer, db.ForeignKey("backend_tasks.id"), nullable=False) + agent_id = db.Column(db.Integer, nullable=False) + approved_at = db.Column(db.DateTime(timezone=True), default=func.now()) + amount_paid = db.Column(db.Float, nullable=False) + bonus_paid = db.Column(db.Float, default=0.0) + status = db.Column(db.String(32), default="completed") # completed, pending, failed + + class BackendTask(db.Model): __tablename__ = "backend_tasks" id = db.Column(db.Integer, primary_key=True, autoincrement=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) - user_content = db.Column(db.Text, nullable=False) - category = db.Column(db.String(128), default="desenvolvimento") - commit_language = db.Column(db.String(128), default="pt") - priority = db.Column(db.Integer, default=1) + status = db.Column(db.String(128), default=TaskStatus.PENDING) created_at = db.Column(db.DateTime(timezone=True), default=func.now()) completed_at = db.Column(db.DateTime(timezone=True), default=func.now()) + commit_language = db.Column(db.String(128), default="pt") + + title = db.Column(db.Text, nullable=True) + description = db.Column(db.Text, nullable=True) + category = db.Column(db.String(128), default="desenvolvimento") + price = db.Column(db.String(128), nullable=True) + priority = db.Column(db.Integer, default=1) + technologies = db.Column(db.String(128), nullable=True) + deadline = db.Column(db.String(128), nullable=True) + early_bonus = db.Column(db.String(128), nullable=True) estimated_hours = db.Column(db.String(128), nullable=True) + progress = db.Column(db.Integer, default=0) + allocated_agent = db.Column(db.String(128), nullable=True) + project_files = db.Column(JSON, nullable=True, default=list) + + total_tokens = db.Column(db.String(128), nullable=True) result = db.Column(db.Text, nullable=True) eta_str = db.Column(db.String(328), nullable=True) + updated_at = db.Column(db.DateTime, default=datetime.utcnow) class CommitMessage(db.Model): __tablename__ = 'commit_messages' diff --git a/backend/Modules/Savers/create_task.py b/backend/Modules/Savers/create_task.py index 462e40c71..1890ab07e 100644 --- a/backend/Modules/Savers/create_task.py +++ b/backend/Modules/Savers/create_task.py @@ -3,7 +3,14 @@ from datetime import datetime import pytz -def create_task(user_id, content, priority=1, hours="1.2", lang="pt", eta_str='', EMPLOYER_CATEGORY=''): +def create_task(user_id, + content, + priority=1, + hours="1.2", + lang="pt", + eta_str='', + EMPLOYER_CATEGORY='' + ): """ Cria uma tarefa no banco Flask-SQLAlchemy. """ diff --git a/backend/TestDiscovery/teste_mcp.py b/backend/TestDiscovery/teste_mcp.py deleted file mode 100644 index 519bc5dc6..000000000 --- a/backend/TestDiscovery/teste_mcp.py +++ /dev/null @@ -1,33 +0,0 @@ -from pathlib import Path -from agents import Agent, Runner -from agents.mcp import MCPServerStdio -import asyncio -import os -from dotenv import load_dotenv - -os.chdir(os.path.join(os.path.dirname(__file__))) -load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "Keys", 'keys.env')) - -import asyncio - -from agents import Agent, HostedMCPTool, Runner - -async def main() -> None: - agent = Agent( - name="Assistant", - tools=[ - HostedMCPTool( - tool_config={ - "type": "mcp", - "server_label": "gitmcp", - "server_url": "https://gitmcp.io/openai/codex", - "require_approval": "never", - } - ) - ], - ) - - result = await Runner.run(agent, "https://github.com/openai/openai-chatkit-advanced-samples Which language is this repository written in?") - print(result.final_output) - -asyncio.run(main()) \ No newline at end of file diff --git a/backend/_task_allocate_logs.py b/backend/_task_allocate_logs.py new file mode 100644 index 000000000..199d0676a --- /dev/null +++ b/backend/_task_allocate_logs.py @@ -0,0 +1,33 @@ +from datetime import datetime +from api import db, app +from Models.postgreSQL import BackendTask, TaskLog + +TASK_ID = 3 +NEW_LOGS = [ + {"id": "log1", "type": "info", "message": "Tarefa iniciada pelo agente CodAgent", "timestamp": datetime.utcnow()}, + {"id": "log2", "type": "success", "message": "Estrutura inicial do projeto carregada", "timestamp": datetime.utcnow()}, +] + +def allocate_logs(task_id: int, logs: list): + with app.app_context(): + task = db.session.query(BackendTask).filter_by(id=task_id).first() + if not task: + print(f"Tarefa {task_id} não encontrada.") + return + + for log in logs: + new_log = TaskLog( + task_id=task.id, + type=log["type"], + message=log["message"], + task_metadata={}, # se precisar de dados extras + created_at=log["timestamp"] + ) + db.session.add(new_log) + + task.updated_at = datetime.utcnow() if hasattr(task, "updated_at") else None + db.session.commit() + print(f"Logs alocados com sucesso na tarefa {task_id}!") + +if __name__ == "__main__": + allocate_logs(TASK_ID, NEW_LOGS) diff --git a/backend/_task_allocate_task.py b/backend/_task_allocate_task.py new file mode 100644 index 000000000..05d10b7c9 --- /dev/null +++ b/backend/_task_allocate_task.py @@ -0,0 +1,38 @@ +import os +import json +from datetime import datetime +from api import db, app +from Models.postgreSQL import BackendTask # importe seu modelo correto + +# Configurações iniciais +TASK_ID = 3 # ID da tarefa que você quer atualizar +AGENT_NAME = "CodAgent" # Nome do agente a ser alocado + +# Exemplo de project_files compatível com FileItem do front-end +PROJECT_FILES = [ + {"name": "server.js", "language": "javascript", "lines": 120, "status": "completed"}, + {"name": "auth.js", "language": "javascript", "lines": 45, "status": "in_progress"}, + {"name": "README.md", "language": "markdown", "lines": 10, "status": "completed"}, +] + +def allocate_task(task_id: int, agent_name: str, project_files: list): + with app.app_context(): + + task = db.session.query(BackendTask).filter_by(id=task_id).first() + if not task: + print(f"Tarefa {task_id} não encontrada.") + return + + task.allocated_agent = agent_name + task.project_files = project_files + task.status = "running" + task.progress = 20 + task.updated_at = datetime.utcnow() if hasattr(task, "updated_at") else None + + db.session.commit() + print(f"Tarefa {task_id} atualizada com sucesso!") + print(f"Agente: {agent_name}") + print(f"Arquivos: {json.dumps(project_files, indent=2)}") + +if __name__ == "__main__": + allocate_task(TASK_ID, AGENT_NAME, PROJECT_FILES) diff --git a/backend/api.py b/backend/api.py index 21e1e16ba..b7bffaad4 100644 --- a/backend/api.py +++ b/backend/api.py @@ -3,7 +3,10 @@ import threading import requests import json +import pytz import logging +import re +from sqlalchemy import or_ from dotenv import load_dotenv import asyncio import stripe @@ -43,7 +46,7 @@ from Modules.Geters.plans_data import get_plans_data from Modules.Resolvers.verify_signature import verify_signature from Modules.Resolvers.git_contex_layer_process import process_git_context_layer -from Modules.Savers.create_task import create_task +# from Modules.Savers.create_task import create_task diretorio_script = os.path.dirname(os.path.abspath(__file__)) logger = logging.getLogger(__name__) @@ -1471,35 +1474,67 @@ def add_task(): """ Endpoint para criar uma tarefa na fila process_dynamic_queue """ + data = request.get_json() if not data: return jsonify({"error": "Nenhum dado fornecido"}), 400 + + email = data.get("email") + password = data.get("password") + user, _, status = auth_user(logs_collection, + app, + email=email, + password=password + ) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 - user_id = data.get('user_id') - content = data.get('content') - EMPLOYER_CATEGORY = data.get('category') + tz = pytz.timezone("America/Sao_Paulo") priority = data.get('priority', 1) - hours = data.get('hours', "1.0") lang = data.get('lang', "pt") - eta_str = data.get('eta') # Espera string ISO - - if not user_id or not content: - return jsonify({"error": "Campos 'user_id' e 'content' são obrigatórios"}), 400 + numeric_user_id = user.id + title = data.get('title') + description = data.get('description') + category = data.get('category') + price = data.get('price') + technologies = data.get('technologies') + deadline = data.get('deadline') + early_bonus = data.get('early_bonus') + eta_str = data.get('deadline') try: - - - task_id = create_task(user_id, content, priority, hours, lang, eta_str, EMPLOYER_CATEGORY) - - - return jsonify({"task_id": task_id, "status": "created"}), 201 + task = BackendTask( + user_id=numeric_user_id, + title=title, + description=description, + category=category, + price=price, + priority=priority, + technologies=technologies, + deadline=deadline, + early_bonus=early_bonus, + commit_language=lang, + status=TaskStatus.PENDING.value, + created_at=datetime.now(tz), + eta_str=eta_str + ) + db.session.add(task) + db.session.commit() + print(f"Tarefa criada: {task.id}") + return jsonify({"task_id": task.id, "status": "created"}), 201 except Exception as e: + print(f"error Tarefa : {e}") return jsonify({"error": str(e)}), 500 @app.route('/api/tasks/list', methods=['GET']) def list_tasks(): - """Listar tarefas criadas pelo usuário, com filtros e paginação""" - user, _, status = auth_user(logs_collection, app) + email = request.args.get("email") + password = request.args.get("password") + user, _, status = auth_user(logs_collection, + app, + email=email, + password=password + ) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1513,7 +1548,9 @@ def list_tasks(): query = BackendTask.query.filter_by(user_id=numeric_user_id) if search_term: - query = query.filter(BackendTask.user_content.ilike(f'%{search_term}%')) + like = f"%{search_term}%" + query = query.filter(or_(BackendTask.title.ilike(like), BackendTask.description.ilike(like))) + if status_filter: query = query.filter(BackendTask.status == status_filter) @@ -1521,19 +1558,66 @@ def list_tasks(): page=page, per_page=limit, error_out=False ) + def parse_price(p): + if not p: + return None + nums = re.findall(r"[0-9]+(?:[.,][0-9]+)?", p.replace(',', '.')) + if not nums: + return None + try: + val = float(nums[0]) + if val.is_integer(): + return int(val) + return val + except: + return None + + def map_status(s): + if not s: + return "pending" + s = s.lower() + if s in ("running",): + return "in_progress" + if s in ("sheduled", "scheduled"): + return "scheduled" + if s == "done": + return "done" + if s == "failed": + return "failed" + return s + formatted_tasks = [] for task in tasks_paginated.items: + techs = [] + if task.technologies: + techs = [t.strip() for t in re.split(r"[;,]", task.technologies) if t.strip()] + deadline_iso = None + if task.deadline: + try: + deadline_iso = datetime.fromisoformat(task.deadline).isoformat() + except: + try: + parsed = datetime.strptime(task.deadline, "%Y-%m-%d") + deadline_iso = parsed.isoformat() + except: + deadline_iso = task.deadline formatted_tasks.append({ - 'id': str(task.id), - 'content': task.user_content, - 'priority': task.priority, - 'status': task.status, - 'createdAt': task.created_at.isoformat(), - 'completedAt': task.completed_at.isoformat() if task.completed_at else None, - 'eta_str': task.eta_str if task.eta_str else None, - 'estimatedCapacity': task.estimated_hours, - 'total_tokens': task.total_tokens, - 'commitLanguage': task.commit_language + "id": str(task.id), + "title": task.title or "", + "description": task.description or "", + "status": map_status(task.status), + "createdAt": task.created_at.isoformat() if task.created_at else None, + "completedAt": task.completed_at.isoformat() if task.completed_at else None, + "eta_str": task.eta_str or None, + "estimatedHours": task.estimated_hours or None, + "total_tokens": task.total_tokens or None, + "commitLanguage": task.commit_language or None, + "price": parse_price(task.price), + "priority": task.priority, + "technologies": techs, + "deadline": deadline_iso, + "progress": task.progress or 0, + "agentName": task.allocated_agent or None }) log_action(logs_collection, 'tasks_listed', { @@ -1544,7 +1628,12 @@ def list_tasks(): 'total_found': tasks_paginated.total }, user=numeric_user_id) - return jsonify(formatted_tasks) + return jsonify({ + "tasks": formatted_tasks, + "total": tasks_paginated.total, + "page": tasks_paginated.page, + "per_page": tasks_paginated.per_page + }) except Exception as e: log_action(logs_collection, 'tasks_list_error', {'error': str(e)}, user=numeric_user_id, level='error') @@ -1552,7 +1641,6 @@ def list_tasks(): @app.route('/api/tasks/details/', methods=['GET']) def get_task_details(task_id): - """Obter detalhes de uma tarefa específica""" user, _, status = auth_user(logs_collection, app) if status != "success" or not user: return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 @@ -1562,36 +1650,170 @@ def get_task_details(task_id): try: task = BackendTask.query.filter_by(id=task_id, user_id=numeric_user_id).first_or_404() + def parse_price(p): + if not p: + return None + nums = re.findall(r"[0-9]+(?:[.,][0-9]+)?", p.replace(',', '.')) + if not nums: + return None + try: + val = float(nums[0]) + return int(val) if val.is_integer() else val + except: + return None + + def map_status(s): + if not s: + return "pending" + s = s.lower() + return { + "running": "in_progress", + "sheduled": "scheduled", + "scheduled": "scheduled", + "done": "done", + "failed": "failed" + }.get(s, s) + + techs = [t.strip() for t in re.split(r"[;,]", task.technologies or "") if t.strip()] + + try: + deadline_iso = datetime.fromisoformat(task.deadline).isoformat() + except: + try: + deadline_iso = datetime.strptime(task.deadline, "%Y-%m-%d").isoformat() + except: + deadline_iso = task.deadline + + project_files = [] + if isinstance(task.project_files, list): + for f in task.project_files: + if isinstance(f, dict): + project_files.append({ + "name": f.get("name", "arquivo_desconhecido"), + "language": f.get("language", "txt"), + "lines": f.get("lines") if isinstance(f.get("lines"), int) else 0, + "status": "completed" if f.get("status") in ["completed", "done"] else "in_progress" + }) + task_details = { - 'id': str(task.id), - 'content': task.user_content, - 'priority': task.priority, - 'status': task.status, - 'createdAt': task.created_at.isoformat(), - 'completedAt': task.completed_at.isoformat() if task.completed_at else None, - 'eta_str': task.eta_str if task.eta_str else None, - 'estimatedHours': task.estimated_hours, - 'total_tokens': task.total_tokens, - 'commitLanguage': task.commit_language, - 'result': task.result if hasattr(task, 'result') else None # caso você armazene resultado + "id": str(task.id), + "title": task.title or "", + "description": task.description or "", + "status": map_status(task.status), + "createdAt": task.created_at.isoformat() if task.created_at else None, + "completedAt": task.completed_at.isoformat() if task.completed_at else None, + "eta_str": task.eta_str or None, + "estimatedHours": task.estimated_hours or None, + "total_tokens": task.total_tokens or None, + "commitLanguage": task.commit_language or None, + "price": parse_price(task.price), + "priority": task.priority, + "technologies": techs, + "deadline": deadline_iso, + "progress": task.progress or 0, + "agentName": task.allocated_agent or None, + "earlyBonus": parse_price(task.early_bonus), + "category": task.category or None, + "result": task.result or None, + "project_files": project_files } - log_action(logs_collection, 'task_details_accessed', { - 'task_id': task_id - }, user=numeric_user_id) + # Buscar logs do projeto + logs = TaskLog.query.filter_by(task_id=task.id).order_by(TaskLog.created_at.asc()).all() + logs_json = [ + { + "id": str(log.id), + "type": log.type, + "message": log.message, + "timestamp": log.created_at.isoformat(), + "metadata": log.task_metadata or {} + } + for log in logs + ] + task_details["logs"] = logs_json - return jsonify(task_details) + log_action(logs_collection, 'task_details_accessed', {'task_id': task_id}, user=numeric_user_id) + return jsonify(task_details), 200 except Exception as e: logger.info(f"task_details_error {str(e)}") - log_action(logs_collection, 'task_details_error', {'task_id': task_id, 'error': str(e)}, user=numeric_user_id, level='error') + log_action( + logs_collection, + 'task_details_error', + {'task_id': task_id, 'error': str(e)}, + user=numeric_user_id, + level='error' + ) return jsonify({'error': 'Falha ao buscar detalhes da tarefa'}), 500 - +@app.route('/api/projects/approve', methods=['POST']) +def approve_project(): + user, _, status = auth_user(logs_collection, app) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 + + data = request.get_json() + if not data: + return jsonify({"error": "Nenhum dado fornecido"}), 400 + + task_id = data.get("task_id") + bonus = float(data.get("bonus", 0)) + + try: + task = BackendTask.query.filter_by(id=task_id).first_or_404() + if task.status.lower() != "done": + return jsonify({"error": "Tarefa ainda não concluída"}), 400 + + # registrar aprovação + approved = ApprovedProject( + task_id=task.id, + agent_id=task.user_id, + amount_paid=float(task.price) if task.price else 0, + bonus_paid=bonus, + status="completed" + ) + db.session.add(approved) + db.session.commit() + + log_action(logs_collection, "project_approved", {"task_id": task.id, "amount": task.price, "bonus": bonus}, user=user.id) + + return jsonify({"status": "approved", "task_id": task.id}), 200 + + except Exception as e: + log_action(logs_collection, "project_approval_error", {"error": str(e)}, user=user.id, level="error") + return jsonify({"error": str(e)}), 500 +@app.route('/api/projects/approved', methods=['GET']) +def list_approved_projects(): + user, _, status = auth_user(logs_collection, app) + if status != "success" or not user: + return jsonify({"error": "Usuário não autenticado ou inválido"}), 401 + + try: + # Buscar todas as tarefas do usuário + user_tasks = BackendTask.query.filter_by(user_id=user.id).order_by(BackendTask.created_at.desc()).all() + approved_map = {ap.task_id: ap for ap in ApprovedProject.query.filter(ApprovedProject.task_id.in_([t.id for t in user_tasks])).all()} + + projects_list = [] + for task in user_tasks: + approved = approved_map.get(task.id) + projects_list.append({ + "task_id": task.id, + "title": task.title, + "agent_id": approved.agent_id if approved else None, + "amount_paid": task.price or 0, + "bonus_paid": approved.bonus_paid if approved else 0, + "approved_at": approved.approved_at.isoformat() if approved else None, + "status": "completed" if approved else "pending" + }) + log_action(logs_collection, "approved_projects_listed", {"total": len(projects_list)}, user=user.id) + return jsonify({"approved_projects": projects_list, "total": len(projects_list)}), 200 + except Exception as e: + log_action(logs_collection, "approved_projects_error", {"error": str(e)}, user=user.id, level="error") + return jsonify({"error": str(e)}), 500 diff --git a/backend/celery_app.py b/backend/celery_app.py index 1848f95c4..554f89887 100644 --- a/backend/celery_app.py +++ b/backend/celery_app.py @@ -88,7 +88,12 @@ async def execute_task(): if task.category == "desenvolvimento": result = run_backend_agent.apply_async( - args=(task.id, task.user_id, task.user_content, task.commit_language), + args=( + task.id, + task.user_id, + task.user_content, + task.commit_language + ), eta=eta_dt ) print(f"Tarefa #{task.id} enviada com sucesso.") diff --git a/backend/celerybeat-schedule b/backend/celerybeat-schedule index 1d683fe28..6d705f15b 100644 Binary files a/backend/celerybeat-schedule and b/backend/celerybeat-schedule differ diff --git a/backend/run_codeagent.py b/backend/run_codeagent.py new file mode 100644 index 000000000..6161ee851 --- /dev/null +++ b/backend/run_codeagent.py @@ -0,0 +1,52 @@ +import os +import threading +import requests +import json +import logging +from dotenv import load_dotenv +import asyncio +import stripe +from decimal import Decimal +from bson.json_util import dumps +from datetime import datetime, timedelta, timezone +import hmac +import hashlib +from flask import g, Flask, Response, request, jsonify, send_file, abort, redirect +from flask_cors import CORS +from asgiref.wsgi import WsgiToAsgi +from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address + +os.chdir(os.path.join(os.path.dirname(__file__))) +load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), 'Keys', 'keys.env')) +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +from Agents.AppAI.CodeBackend.ai import CodeBackEndAgent + +tipo_app = "automação" +title = "Automação de Backup de Arquivos para Nuvem" +description = "Criar uma aplicação em Python que monitore uma pasta local e faça backup automático de arquivos novos ou modificados para um serviço de nuvem (como Google Drive ou Dropbox). O sistema deve gerar logs das transferências, enviar notificações por e-mail em caso de erro, e permitir configuração de periodicidade e pastas monitoradas. Deve ser fácil de instalar e executar em segundo plano." +category = "automação e manutenção de sistemas" +price = "650" +technologies = "python, watchdog, smtplib, google-api-python-client" +early_bonus = "50" +deadline = "2025-10-19 18:12:00" +user_id = 1 +local_to_save = os.path.join(os.path.dirname(__file__), 'WorkEnv') +model = "gpt-5-nano" + +total_usage, saved_files = asyncio.run(CodeBackEndAgent( + OPENAI_API_KEY, + user_id, + tipo_app, + title, + description, + category, + price, + technologies, + early_bonus, + deadline, + model=model, + local_to_save = local_to_save + )) +print(f"saved_files {saved_files}") diff --git a/backend/teste_mcp.py b/backend/teste_mcp.py new file mode 100644 index 000000000..d13f10de5 --- /dev/null +++ b/backend/teste_mcp.py @@ -0,0 +1,64 @@ +from pathlib import Path +from agents import Agent, Runner +from agents.mcp import MCPServerStdio +import asyncio +import os +from dotenv import load_dotenv + +os.chdir(os.path.join(os.path.dirname(__file__))) +load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "Keys", 'keys.env')) + +import asyncio +from pathlib import Path +from agents import Agent, Runner +from agents.mcp import MCPServerStdio + +current_dir = Path(__file__).parent +samples_dir = current_dir / "sample_files" + +from agents import Agent, HostedMCPTool, Runner + +async def gitmcp() -> None: + agent = Agent( + name="Assistant", + tools=[ + HostedMCPTool( + tool_config={ + "type": "mcp", + "server_label": "gitmcp", + "server_url": "https://gitmcp.io/openai/codex", + "require_approval": "never", + } + ) + ], + ) + + result = await Runner.run(agent, "https://github.com/openai/openai-chatkit-advanced-samples Which language is this repository written in?") + print(result.final_output) + +# asyncio.run(gitmcp()) + + +async def thinking() -> None: + async with MCPServerStdio( + name="sequential-thinking", + params={ + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "mcp/sequentialthinking" + ], + }, + client_session_timeout_seconds=45 + ) as server: + agent = Agent( + name="Assistant", + instructions="use o pensamento sequencial para responder o usuario", + mcp_servers=[server], + ) + result = await Runner.run(agent, "quanto é 900 divido por 7") + print(result.final_output) + +asyncio.run(thinking()) \ No newline at end of file diff --git a/build_in_windows.py b/build_in_windows.py index 027485d3f..5e17b0841 100644 --- a/build_in_windows.py +++ b/build_in_windows.py @@ -10,5 +10,5 @@ def executar_comando(comando): subprocess.run(comando, shell=True) -executar_comando("docker-compose up --build -d ") +executar_comando("docker-compose up --build -d pipeline-api") diff --git a/frontend/devflow-co b/frontend/devflow-co new file mode 160000 index 000000000..eb48a2f53 --- /dev/null +++ b/frontend/devflow-co @@ -0,0 +1 @@ +Subproject commit eb48a2f5342666dcef87e4ee16f3d20499b63df1