diff --git a/aios-platform/.dockerignore b/.dockerignore similarity index 100% rename from aios-platform/.dockerignore rename to .dockerignore diff --git a/aios-platform/.env.example b/.env.example similarity index 100% rename from aios-platform/.env.example rename to .env.example diff --git a/aios-platform/.env.local.example b/.env.local.example similarity index 100% rename from aios-platform/.env.local.example rename to .env.local.example diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f87075..342bf38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,122 +10,93 @@ env: NODE_VERSION: '20' jobs: - # ─── Next.js Dashboard ────────────────────────────────── - nextjs-typecheck: - name: 'Next.js: TypeCheck' + lint: + name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - run: npm ci - - run: npx tsc --noEmit + - name: Checkout repository + uses: actions/checkout@v4 - nextjs-build: - name: 'Next.js: Build' - runs-on: ubuntu-latest - needs: [nextjs-typecheck] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - - run: npm ci - - run: npm run build - # ─── Vite SPA (aios-platform) ────────────────────────── - spa-lint: - name: 'SPA: Lint' - runs-on: ubuntu-latest - defaults: - run: - working-directory: aios-platform - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - cache-dependency-path: aios-platform/package-lock.json - - run: npm ci - - run: npm run lint + - name: Install dependencies + run: npm ci - spa-typecheck: - name: 'SPA: TypeCheck' + - name: Run ESLint + run: npm run lint + + typecheck: + name: Type Check runs-on: ubuntu-latest - defaults: - run: - working-directory: aios-platform steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - cache-dependency-path: aios-platform/package-lock.json - - run: npm ci - - run: npx tsc --noEmit - spa-test: - name: 'SPA: Test (2453 tests)' + - name: Install dependencies + run: npm ci + + - name: Run TypeScript + run: npx tsc --noEmit + + test: + name: Test runs-on: ubuntu-latest - defaults: - run: - working-directory: aios-platform steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - cache-dependency-path: aios-platform/package-lock.json - - run: npm ci - - run: npx vitest run --project unit --reporter=verbose - - name: Upload coverage - if: always() + + - name: Install dependencies + run: npm ci + + - name: Run unit tests with coverage + run: npx vitest run --project unit --coverage + + - name: Upload coverage report uses: actions/upload-artifact@v4 with: - name: spa-coverage - path: aios-platform/coverage/ + name: coverage-report + path: coverage/ retention-days: 7 - spa-build: - name: 'SPA: Build' + build: + name: Build runs-on: ubuntu-latest - needs: [spa-lint, spa-typecheck, spa-test] - defaults: - run: - working-directory: aios-platform + needs: [lint, typecheck, test] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} cache: 'npm' - cache-dependency-path: aios-platform/package-lock.json - - run: npm ci - - run: npm run build - - name: Build size report - run: | - echo "### Build Size" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - du -sh dist/ >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - # ─── Engine (Bun/Hono) ───────────────────────────────── - engine-test: - name: 'Engine: Test (98 tests)' - runs-on: ubuntu-latest - defaults: - run: - working-directory: aios-platform/engine - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 with: - bun-version: latest - - run: bun install - - run: bun test tests/unit/ + name: build + path: dist/ + retention-days: 7 diff --git a/aios-platform/.github/workflows/deploy.yml b/.github/workflows/deploy.yml similarity index 100% rename from aios-platform/.github/workflows/deploy.yml rename to .github/workflows/deploy.yml diff --git a/aios-platform/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml similarity index 96% rename from aios-platform/.github/workflows/pr-check.yml rename to .github/workflows/pr-check.yml index efef0d8..7971da4 100644 --- a/aios-platform/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -74,8 +74,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Run tests - run: npm run test:coverage + - name: Run unit tests + run: npx vitest run --project unit --coverage - name: Test Summary uses: dorny/test-reporter@v2 diff --git a/aios-platform/.husky/pre-commit b/.husky/pre-commit similarity index 100% rename from aios-platform/.husky/pre-commit rename to .husky/pre-commit diff --git a/aios-platform/.prettierignore b/.prettierignore similarity index 100% rename from aios-platform/.prettierignore rename to .prettierignore diff --git a/aios-platform/.prettierrc b/.prettierrc similarity index 100% rename from aios-platform/.prettierrc rename to .prettierrc diff --git a/aios-platform/.storybook/api-mocks.ts b/.storybook/api-mocks.ts similarity index 100% rename from aios-platform/.storybook/api-mocks.ts rename to .storybook/api-mocks.ts diff --git a/aios-platform/.storybook/main.ts b/.storybook/main.ts similarity index 100% rename from aios-platform/.storybook/main.ts rename to .storybook/main.ts diff --git a/aios-platform/.storybook/preview.tsx b/.storybook/preview.tsx similarity index 100% rename from aios-platform/.storybook/preview.tsx rename to .storybook/preview.tsx diff --git a/aios-platform/.storybook/vitest.setup.ts b/.storybook/vitest.setup.ts similarity index 100% rename from aios-platform/.storybook/vitest.setup.ts rename to .storybook/vitest.setup.ts diff --git a/aios-platform/CLAUDE.md b/CLAUDE.md similarity index 100% rename from aios-platform/CLAUDE.md rename to CLAUDE.md diff --git a/aios-platform/Dockerfile b/Dockerfile similarity index 100% rename from aios-platform/Dockerfile rename to Dockerfile diff --git a/README.md b/README.md index 6499eb0..d2e7761 100644 --- a/README.md +++ b/README.md @@ -1,668 +1,73 @@ -# AIOS Dashboard: Observability Extension - -[![Synkra AIOS](https://img.shields.io/badge/Synkra-AIOS-blue.svg)](https://github.com/SynkraAI/aios-core) -[![Licença: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) -[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)]() -[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](https://github.com/SynkraAI/aios-dashboard/issues) - -**Interface visual para observar seu projeto AIOS em tempo real.** - -> 🚧 **FASE INICIAL DE DESENVOLVIMENTO** -> -> Este projeto está em construção ativa. Funcionalidades podem mudar, quebrar ou estar incompletas. -> **Colaborações são muito bem-vindas!** Veja as [issues abertas](https://github.com/SynkraAI/aios-dashboard/issues) ou abra uma nova para sugerir melhorias. - -> ⚠️ **Este projeto é uma extensão OPCIONAL.** O [Synkra AIOS](https://github.com/SynkraAI/aios-core) funciona 100% sem ele. O Dashboard existe apenas para **observar** o que acontece na CLI — ele nunca controla. - ---- - -## O que é o AIOS Dashboard? - -O AIOS Dashboard é uma **interface web** que permite visualizar em tempo real tudo que acontece no seu projeto AIOS: - -- 📋 **Stories** no formato Kanban (arrastar e soltar) -- 🤖 **Agentes** ativos e inativos -- 📡 **Eventos em tempo real** do Claude Code (qual tool está executando, prompts, etc) -- 🔧 **Squads** instalados com seus agentes, tasks e workflows -- 📊 **Insights** e estatísticas do projeto - -### Screenshot das Funcionalidades - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ AIOS Dashboard [Settings] │ -├─────────┬───────────────────────────────────────────────────────┤ -│ │ │ -│ Kanban │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ Monitor │ │ Backlog │ │ Doing │ │ Done │ │ -│ Agents │ ├─────────┤ ├─────────┤ ├─────────┤ │ -│ Squads │ │ Story 1 │ │ Story 3 │ │ Story 5 │ │ -│ Bob │ │ Story 2 │ │ Story 4 │ │ Story 6 │ │ -│ ... │ └─────────┘ └─────────┘ └─────────┘ │ -│ │ │ -└─────────┴───────────────────────────────────────────────────────┘ -``` - ---- - -## Funcionalidades - -| View | O que faz | -|------|-----------| -| **Kanban** | Board de stories com drag-and-drop entre colunas (Backlog, Doing, Done) | -| **Monitor** | Feed em tempo real de eventos do Claude Code (tools, prompts, erros) | -| **Agents** | Lista de agentes AIOS (@dev, @qa, @architect, etc) — ativos e em standby | -| **Squads** | Organograma visual dos squads instalados com drill-down para agentes e tasks | -| **Bob** | Acompanha execução do Bob Orchestrator (pipeline de desenvolvimento autônomo) | -| **Roadmap** | Visualização de features planejadas | -| **GitHub** | Integração com GitHub (PRs, issues) | -| **Insights** | Estatísticas e métricas do projeto | -| **Terminals** | Grid de múltiplos terminais | -| **Settings** | Configurações do dashboard | - ---- - -## Requisito: Projeto com AIOS Instalado - -O Dashboard **precisa estar dentro de um projeto com AIOS instalado** porque ele lê os documentos do framework. - -``` -meu-projeto/ # ← Você executa comandos daqui -├── .aios-core/ # Core do framework (OBRIGATÓRIO) -│ └── development/ -│ ├── agents/ # Agentes que aparecem na view "Agents" -│ ├── tasks/ # Tasks dos squads -│ └── templates/ -├── docs/ -│ └── stories/ # Stories que aparecem no "Kanban" -│ ├── active/ -│ └── completed/ -├── squads/ # Squads que aparecem na view "Squads" -│ ├── meu-squad/ -│ └── outro-squad/ -├── apps/ -│ └── dashboard/ # ← Dashboard instalado aqui -└── package.json -``` - -**Sem o AIOS instalado, o dashboard mostrará telas vazias.** - ---- - -## Instalação Passo a Passo - -> **IMPORTANTE:** Todos os comandos são executados a partir da **raiz do seu projeto** (`meu-projeto/`). - -### Pré-requisitos - -Antes de começar, você precisa ter: - -- ✅ [Node.js](https://nodejs.org/) versão 18 ou superior -- ✅ [Bun](https://bun.sh/) (para o servidor de eventos) -- ✅ [Synkra AIOS](https://github.com/SynkraAI/aios-core) instalado no projeto - -### Passo 1: Instale o AIOS (se ainda não tiver) - -```bash -# Opção A: Criar novo projeto com AIOS -npx aios-core init meu-projeto -cd meu-projeto - -# Opção B: Instalar em projeto existente -cd meu-projeto -npx aios-core install -``` - -### Passo 2: Clone o Dashboard - -```bash -# Cria a pasta apps/ e clona o dashboard -mkdir -p apps -git clone https://github.com/SynkraAI/aios-dashboard.git apps/dashboard -``` - -### Passo 3: Instale as dependências - -```bash -# Dependências do Dashboard (Next.js) -npm install --prefix apps/dashboard - -# Dependências do Server (Bun) -cd apps/dashboard/server -bun install -cd ../../.. -``` - -### Passo 4: Inicie o Server de Eventos - -O server captura eventos em tempo real do Claude Code. - -```bash -cd apps/dashboard/server -bun run dev -``` - -Você verá: -``` -🚀 Monitor Server running on http://localhost:4001 -``` - -> **Deixe este terminal aberto** e abra um novo para o próximo passo. - -### Passo 5: Inicie o Dashboard - -Em um **novo terminal**: - -```bash -npm run dev --prefix apps/dashboard -``` - -Você verá: -``` -▲ Next.js 14.x.x -- Local: http://localhost:3000 -``` - -### Passo 6: Acesse o Dashboard - -Abra no navegador: **http://localhost:3000** - -🎉 **Pronto!** Você verá o dashboard com suas stories, squads e agentes. - ---- - -## Passo Extra: Eventos em Tempo Real - -Para ver eventos do Claude Code em tempo real (qual ferramenta está executando, prompts, etc), instale os hooks: - -```bash -apps/dashboard/scripts/install-hooks.sh -``` - -Isso instala hooks em `~/.claude/hooks/` que enviam eventos para o dashboard. - -**Eventos capturados:** -- `PreToolUse` — Antes de executar uma ferramenta -- `PostToolUse` — Após executar (com resultado) -- `UserPromptSubmit` — Quando você envia um prompt -- `Stop` — Quando Claude para -- `SubagentStop` — Quando um subagent (Task) termina - ---- - -## Comandos Rápidos - -Cole estes comandos no terminal (execute da raiz do projeto): - -```bash -# ===== INSTALAÇÃO ===== -mkdir -p apps -git clone https://github.com/SynkraAI/aios-dashboard.git apps/dashboard -npm install --prefix apps/dashboard -cd apps/dashboard/server && bun install && cd ../../.. - -# ===== INICIAR (2 terminais) ===== - -# Terminal 1: Server de eventos -cd apps/dashboard/server && bun run dev - -# Terminal 2: Dashboard -npm run dev --prefix apps/dashboard - -# ===== EXTRAS ===== - -# Instalar hooks para eventos em tempo real -apps/dashboard/scripts/install-hooks.sh - -# Verificar se server está rodando -curl http://localhost:4001/health -``` - ---- - -## Estrutura do Projeto - -``` -apps/dashboard/ -├── src/ -│ ├── app/ # Páginas Next.js -│ ├── components/ -│ │ ├── kanban/ # Board de stories -│ │ ├── monitor/ # Feed de eventos em tempo real -│ │ ├── agents/ # Visualização de agentes -│ │ ├── squads/ # Organograma de squads -│ │ ├── bob/ # Orquestração Bob -│ │ └── ui/ # Componentes de UI -│ ├── hooks/ # React hooks customizados -│ ├── stores/ # Estado global (Zustand) -│ └── lib/ # Utilitários -├── server/ # Servidor de eventos (Bun) -│ ├── server.ts # Servidor principal -│ ├── db.ts # Banco SQLite -│ └── types.ts # Tipos TypeScript -└── scripts/ - └── install-hooks.sh # Instalador de hooks -``` - ---- - -## Posição na Arquitetura AIOS - -O Synkra AIOS segue uma hierarquia clara: - -``` -CLI First → Observability Second → UI Third -``` - -| Camada | Prioridade | O que faz | -| ----------------- | ---------- | --------------------------------------------------- | -| **CLI** | Máxima | Onde a inteligência vive. Toda execução e decisões. | -| **Observability** | Secundária | Observar o que acontece no CLI em tempo real. | -| **UI** | Terciária | Visualizações e gestão pontual. | - -**Este Dashboard opera na camada de Observability.** Ele observa, mas nunca controla. - ---- - -## API do Server - -O server expõe endpoints para o dashboard consumir: - -| Endpoint | Método | Descrição | -| -------------------------- | --------- | ------------------------------- | -| `POST /events` | POST | Recebe eventos dos hooks | -| `GET /events/recent` | GET | Últimos eventos | -| `GET /sessions` | GET | Lista sessões do Claude Code | -| `GET /stats` | GET | Estatísticas agregadas | -| `WS /stream` | WebSocket | Stream de eventos em tempo real | -| `GET /health` | GET | Verifica se server está ok | - ---- - -## Configuração - -Crie o arquivo `apps/dashboard/.env.local`: - -```bash -# Porta do server de eventos -MONITOR_PORT=4001 - -# Onde salvar o banco SQLite -MONITOR_DB=~/.aios/monitor/events.db - -# URL do WebSocket (usado pelo dashboard) -NEXT_PUBLIC_MONITOR_WS_URL=ws://localhost:4001/stream -``` - ---- - -## Troubleshooting - -### "Dashboard mostra telas vazias" - -O AIOS não está instalado. Verifique: - -```bash -ls -la .aios-core/ # Deve existir -ls -la docs/stories/ # Deve ter arquivos -ls -la squads/ # Deve ter squads -``` - -Se não existir, instale o AIOS: `npx aios-core install` - -### "Monitor não mostra eventos" - -1. Server está rodando? - ```bash - curl http://localhost:4001/health - # Deve retornar: {"status":"ok"} - ``` - -2. Hooks estão instalados? - ```bash - ls ~/.claude/hooks/ - # Deve ter arquivos .py - ``` - -3. Reinstale os hooks: - ```bash - apps/dashboard/scripts/install-hooks.sh - ``` - -### "Erro ao iniciar o server" - -Bun não está instalado. Instale em: https://bun.sh - -```bash -curl -fsSL https://bun.sh/install | bash -``` - -### "Porta 3000/4001 em uso" - -Encerre o processo que está usando a porta: - -```bash -# Descobrir qual processo -lsof -i :3000 -lsof -i :4001 - -# Matar o processo (substitua PID) -kill -9 -``` - ---- - -## QA: Verificando se Tudo Funciona - -Após a instalação, execute este checklist para garantir que tudo está funcionando: - -### ✅ Checklist de Verificação - -```bash -# 1. AIOS está instalado? -ls .aios-core/development/agents/ -# ✓ Deve listar arquivos .md (dev.md, qa.md, architect.md, etc) - -# 2. Server está rodando? -curl http://localhost:4001/health -# ✓ Deve retornar: {"status":"ok"} - -# 3. Dashboard está acessível? -curl -s http://localhost:3000 | head -5 -# ✓ Deve retornar HTML - -# 4. Hooks estão instalados? (opcional) -ls ~/.claude/hooks/*.py 2>/dev/null | wc -l -# ✓ Deve retornar número > 0 -``` - -### 🧪 Teste Manual - -1. **Kanban**: Acesse http://localhost:3000 → deve mostrar board com stories -2. **Agents**: Clique em "Agents" → deve listar agentes em standby -3. **Squads**: Clique em "Squads" → deve mostrar organograma de squads -4. **Monitor**: Clique em "Monitor" → deve mostrar status de conexão - -### ❌ Se algo não funcionar - -| Problema | Solução | -|----------|---------| -| Kanban vazio | Verifique se existe `docs/stories/` com arquivos `.md` | -| Agents vazio | Verifique se existe `.aios-core/development/agents/` | -| Squads vazio | Verifique se existe `squads/` com subpastas | -| Monitor desconectado | Verifique se o server está rodando na porta 4001 | - ---- - -## Contribuindo - -Contribuições são muito bem-vindas! Este é um projeto em fase inicial e há muito espaço para melhorias. - -### Tipos de Contribuição - -| Tipo | Descrição | Dificuldade | -|------|-----------|-------------| -| **Bug fixes** | Corrigir problemas reportados | Fácil | -| **Documentação** | Melhorar README, adicionar guias | Fácil | -| **UI/UX** | Melhorar interface, adicionar temas | Médio | -| **Novos componentes** | Adicionar visualizações | Médio | -| **Novas views** | Criar páginas novas no dashboard | Avançado | -| **Server features** | Adicionar endpoints, melhorar performance | Avançado | - -### Contribuindo com AIOS (Recomendado) - -Se você tem o AIOS instalado, use os agentes para ajudar no desenvolvimento: - -#### 🏗️ Para novas features — Use @architect + @dev - -```bash -# 1. Peça ao Architect para planejar -@architect Quero adicionar um gráfico de métricas na view Monitor. - Analise a estrutura atual e sugira a melhor abordagem. - -# 2. Depois peça ao Dev para implementar -@dev Implemente o gráfico de métricas seguindo o plano do Architect. - Use Recharts e siga os padrões existentes em src/components/monitor/ -``` - -#### 🎨 Para melhorias de UI — Use @ux-design-expert + @dev - -```bash -# 1. Peça ao UX Designer para analisar -@ux-design-expert Analise a view Kanban e sugira melhorias de usabilidade. - Considere acessibilidade e mobile. - -# 2. Depois implemente com o Dev -@dev Aplique as melhorias de UX sugeridas no Kanban. -``` - -#### 🐛 Para bugs — Use @qa + @dev - -```bash -# 1. Peça ao QA para investigar -@qa O WebSocket do Monitor desconecta após 5 minutos. - Investigue a causa raiz. - -# 2. Depois corrija com o Dev -@dev Corrija o problema de desconexão do WebSocket identificado pelo QA. -``` - -#### 🚀 Para deploy/PR — Use @devops - -```bash -# Quando terminar, peça ao DevOps para criar o PR -@devops Crie um PR para a branch atual com as mudanças do Monitor. - Inclua descrição detalhada e screenshots. +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) ``` - -#### ✅ Para validação final — Use @qa - -```bash -# Antes de submeter, peça ao QA para revisar -@qa Faça uma revisão completa das mudanças. - Verifique lint, types, testes e funcionamento visual. -``` - ---- - -### Contribuindo sem AIOS (Manual) - -Se preferir contribuir sem usar os agentes: - -#### 1. Fork e Clone - -```bash -# Fork pelo GitHub, depois clone seu fork -git clone https://github.com/SEU_USUARIO/aios-dashboard.git -cd aios-dashboard - -# Adicione o repositório original como upstream -git remote add upstream https://github.com/SynkraAI/aios-dashboard.git -``` - -#### 2. Crie uma Branch - -```bash -git checkout -b feature/minha-nova-feature -``` - -**Convenção de nomes:** - -| Prefixo | Uso | -|---------|-----| -| `feature/` | Nova funcionalidade | -| `fix/` | Correção de bug | -| `docs/` | Documentação | -| `refactor/` | Refatoração de código | -| `ui/` | Melhorias visuais | - -#### 3. Faça suas alterações - -Desenvolva sua feature seguindo os padrões do projeto: - -- **React**: Componentes funcionais com hooks -- **TypeScript**: Tipagem obrigatória -- **Tailwind CSS**: Para estilos -- **Zustand**: Para estado global - -#### 4. Teste localmente - -```bash -# Lint -npm run lint --prefix apps/dashboard - -# Type check -npm run typecheck --prefix apps/dashboard - -# Testes -npm test --prefix apps/dashboard - -# Rode o dashboard e verifique visualmente -npm run dev --prefix apps/dashboard -``` - -#### 5. Commit com mensagem clara - -Usamos [Conventional Commits](https://www.conventionalcommits.org/): - -```bash -# Formato -: - -# Exemplos -git commit -m "feat: add dark mode toggle" -git commit -m "fix: resolve websocket reconnection issue" -git commit -m "docs: improve installation instructions" -git commit -m "ui: improve kanban card hover state" -``` - -**Tipos de commit:** -- `feat` - Nova funcionalidade -- `fix` - Correção de bug -- `docs` - Documentação -- `ui` - Mudanças visuais -- `refactor` - Refatoração -- `test` - Testes -- `chore` - Manutenção - -#### 6. Push e crie o Pull Request - -```bash -# Push para seu fork -git push origin feature/minha-nova-feature -``` - -Depois, abra um Pull Request no GitHub: - -1. Vá para https://github.com/SynkraAI/aios-dashboard -2. Clique em "Pull Requests" → "New Pull Request" -3. Selecione "compare across forks" -4. Selecione seu fork e branch -5. Preencha o template do PR - -### Template de Pull Request - -```markdown -## Descrição - -O que este PR faz? Por que é necessário? - -## Tipo de mudança - -- [ ] Bug fix -- [ ] Nova feature -- [ ] Melhoria de UI -- [ ] Documentação -- [ ] Refatoração - -## Como testar - -1. Passo 1 -2. Passo 2 -3. Resultado esperado - -## Screenshots (se aplicável) - -[Adicione screenshots aqui] - -## Checklist - -- [ ] Meu código segue o estilo do projeto -- [ ] Testei localmente -- [ ] Lint passa sem erros -- [ ] TypeScript compila sem erros -``` - -### Estrutura do Código - -``` -src/ -├── app/ # Páginas (App Router) -├── components/ -│ ├── ui/ # Componentes base (Button, Card, etc) -│ ├── kanban/ # Componentes do Kanban -│ ├── monitor/ # Componentes do Monitor -│ ├── squads/ # Componentes de Squads -│ └── ... -├── hooks/ # React hooks customizados -├── stores/ # Estado global (Zustand) -├── lib/ # Utilitários -└── types/ # Tipos TypeScript -``` - -### Adicionando um Novo Componente - -```tsx -// src/components/meu-componente/MeuComponente.tsx - -'use client'; - -import { memo } from 'react'; -import { cn } from '@/lib/utils'; - -interface MeuComponenteProps { - className?: string; - // ... outras props -} - -export const MeuComponente = memo(function MeuComponente({ - className, - ...props -}: MeuComponenteProps) { - return ( -
- {/* conteúdo */} -
- ); -}); -``` - -### Adicionando uma Nova View - -1. Crie o componente em `src/components/minha-view/` -2. Adicione o case em `src/app/page.tsx` no `ViewContent` -3. Adicione o item na sidebar em `src/components/layout/Sidebar.tsx` -4. Adicione o tipo em `src/types/index.ts` - -### Dicas Importantes - -- **Não quebre o que funciona** — Teste suas mudanças -- **Mantenha PRs pequenos** — Mais fácil de revisar -- **Documente código complexo** — Ajuda outros contribuidores -- **Pergunte antes de grandes mudanças** — Abra uma issue primeiro - -### Obtendo Ajuda - -- **Issues**: [Abrir issue](https://github.com/SynkraAI/aios-dashboard/issues) -- **Discussões**: [Iniciar discussão](https://github.com/SynkraAI/aios-dashboard/discussions) -- **AIOS Core**: [Comunidade AIOS](https://github.com/SynkraAI/aios-core/discussions) - ---- - -## Licença - -MIT - ---- - -Parte do ecossistema [Synkra AIOS](https://github.com/SynkraAI/aios-core) — CLI First, Observability Second, UI Third diff --git a/aios-platform/.github/workflows/ci.yml b/aios-platform/.github/workflows/ci.yml deleted file mode 100644 index 3b4a018..0000000 --- a/aios-platform/.github/workflows/ci.yml +++ /dev/null @@ -1,124 +0,0 @@ -name: CI - -on: - push: - branches: [main, develop] - pull_request: - branches: [main, develop] - -env: - NODE_VERSION: '20' - -jobs: - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run ESLint - run: npm run lint - - typecheck: - name: Type Check - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run TypeScript - run: npx tsc --noEmit - - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Run tests with coverage - run: npm run test:coverage - - - name: Upload coverage report - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: coverage/ - retention-days: 7 - - registry: - name: Registry Sync Check - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Generate registry - run: npm run generate:registry - - - name: Check registry is in sync - run: git diff --exit-code src/data/aios-registry.generated.ts || (echo "::error::Registry is out of sync. Run 'npm run generate:registry' locally and commit." && exit 1) - - build: - name: Build - runs-on: ubuntu-latest - needs: [lint, typecheck, test, registry] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: build - path: dist/ - retention-days: 7 diff --git a/aios-platform/README.md b/aios-platform/README.md deleted file mode 100644 index d2e7761..0000000 --- a/aios-platform/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` diff --git a/aios-platform/package.json b/aios-platform/package.json deleted file mode 100644 index ed2028e..0000000 --- a/aios-platform/package.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "name": "@aios/dashboard", - "private": true, - "version": "0.5.0", - "description": "AIOS Platform Dashboard — Vite + React SPA for AI agent orchestration", - "type": "module", - "scripts": { - "dev": "vite", - "dev:full": "bash scripts/dev-full.sh", - "engine": "cd engine && bun src/index.ts", - "engine:dev": "cd engine && bun --watch src/index.ts", - "engine:project": "cd engine && bun bin/aios-engine.ts", - "aios": "bun packages/cli/bin/aios.ts", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview", - "test": "vitest", - "test:run": "vitest run", - "test:ui": "vitest --ui", - "test:coverage": "vitest run --coverage", - "lint:fix": "eslint . --fix", - "format": "prettier --write \"src/**/*.{ts,tsx,css,json}\"", - "prepare": "husky || true", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build", - "postinstall": "patch-package", - "test:e2e": "playwright test", - "test:e2e:ui": "playwright test --ui", - "test:e2e:headed": "playwright test --headed", - "test:e2e:chromium": "playwright test --project=chromium", - "test:e2e:report": "playwright show-report", - "generate:registry": "npx tsx scripts/generate-aios-registry.ts", - "check:registry": "bash scripts/check-registry-sync.sh" - }, - "lint-staged": { - "*.{ts,tsx}": [ - "eslint --fix", - "prettier --write" - ], - "*.{css,json,md}": [ - "prettier --write" - ] - }, - "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@supabase/supabase-js": "^2.98.0", - "@tanstack/react-query": "^5.90.20", - "@tanstack/react-virtual": "^3.13.18", - "@types/react-syntax-highlighter": "^15.5.13", - "ansi-to-html": "^0.7.2", - "autoprefixer": "^10.4.24", - "clsx": "^2.1.1", - "framer-motion": "^11.18.2", - "geist": "^1.7.0", - "lucide-react": "^0.575.0", - "mermaid": "^11.12.3", - "postcss": "^8.5.6", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-markdown": "^10.1.0", - "react-syntax-highlighter": "^16.1.0", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^3.4.19", - "zustand": "^4.5.7" - }, - "overrides": { - "serialize-javascript": "^7.0.4" - }, - "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", - "@eslint/js": "^9.39.1", - "@playwright/test": "^1.58.2", - "@storybook/addon-a11y": "^10.2.6", - "@storybook/addon-docs": "^10.2.6", - "@storybook/addon-onboarding": "^10.2.6", - "@storybook/addon-vitest": "^10.2.6", - "@storybook/react-vite": "^10.2.6", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^24.10.1", - "@types/react": "^19.2.5", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", - "@vitest/browser": "^4.0.18", - "@vitest/browser-playwright": "^4.0.18", - "@vitest/coverage-v8": "^4.0.18", - "@vitest/runner": "^4.0.18", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", - "eslint-plugin-storybook": "^10.2.6", - "globals": "^16.5.0", - "husky": "^9.1.7", - "jsdom": "^28.0.0", - "lint-staged": "^16.2.7", - "patch-package": "^8.0.1", - "playwright": "^1.58.1", - "prettier": "^3.8.1", - "sharp": "^0.34.5", - "storybook": "^10.2.6", - "typescript": "~5.9.3", - "typescript-eslint": "^8.46.4", - "vite": "^7.2.4", - "vite-plugin-pwa": "^1.2.0", - "vitest": "^4.0.18", - "vitest-axe": "^0.1.0", - "workbox-window": "^7.4.0" - } -} diff --git a/aios-platform/src/components/agents/AgentCard.tsx b/aios-platform/src/components/agents/AgentCard.tsx deleted file mode 100644 index 98c73ca..0000000 --- a/aios-platform/src/components/agents/AgentCard.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import { memo } from 'react'; -import { motion } from 'framer-motion'; -import { GlassCard, Avatar, Badge } from '../ui'; -import { cn, getTierTheme } from '../../lib/utils'; -import { getIconComponent } from '../../lib/icons'; -import { hasAgentAvatar } from '../../lib/agent-avatars'; -import { useFavoritesStore } from '../../hooks/useFavorites'; -import { useUIStore } from '../../stores/uiStore'; -import type { AgentSummary, AgentTier } from '../../types'; -import { getSquadType as getSquadTypeUtil } from '../../types'; - -// Star icon for favorites -const StarIcon = ({ filled }: { filled?: boolean }) => ( - - - -); - -interface AgentCardProps { - agent: AgentSummary; - selected?: boolean; - compact?: boolean; - showTier?: boolean; - highlight?: boolean; - onClick?: () => void; -} - -// Tier gradients are now accessed via getTierTheme().gradient from centralized theme - -export const AgentCard = memo(function AgentCard({ agent, selected, compact = false, showTier = false, highlight = false, onClick }: AgentCardProps) { - const squadType = getSquadTypeUtil(agent.squad); - const { isFavorite, toggleFavorite } = useFavoritesStore(); - // Normalize tier to valid value (0, 1, or 2) - const normalizedTier: AgentTier = (agent.tier === 0 || agent.tier === 1 || agent.tier === 2) ? agent.tier : 2; - const favorited = isFavorite(agent.id); - - const handleFavoriteClick = (e: React.MouseEvent) => { - e.stopPropagation(); - toggleFavorite({ - id: agent.id, - name: agent.name, - squad: agent.squad, - }); - }; - - if (compact) { - return ( - -
- {hasAgentAvatar(agent.name) || hasAgentAvatar(agent.id) ? ( - - ) : agent.icon ? ( -
- {(() => { const Icon = getIconComponent(agent.icon); return ; })()} -
- ) : ( - - )} -
-
-

- {agent.name} -

- {showTier && ( - - T{normalizedTier} - - )} -
-

{agent.title || agent.description}

-
- {/* Favorite button */} - -
-
- ); - } - - return ( - - -
- {hasAgentAvatar(agent.name) || hasAgentAvatar(agent.id) ? ( - - ) : agent.icon ? ( -
- {(() => { const Icon = getIconComponent(agent.icon); return ; })()} -
- ) : ( - - )} - -
-
- {/* Favorite button - positioned top right */} - -
-

{agent.name}

- - {getTierTheme(normalizedTier).label} - -
-

{agent.title}

-
- - {/* When to use - Primary decision helper */} - {(() => { - const isPlaceholder = (text?: string) => - !text || text.startsWith('[') || text.includes('{{') || text.length < 10; - - if (agent.whenToUse) { - return ( -

- {agent.whenToUse} -

- ); - } else if (agent.description && !isPlaceholder(agent.description)) { - return ( -

- {agent.description} -

- ); - } - return null; - })()} - - {/* Commands count */} - {agent.commandCount !== undefined && agent.commandCount > 0 && ( -
- - {agent.commandCount} comandos - -
- )} -
-
-
-
- ); -}); - -// New detailed card for explorer -interface AgentExplorerCardProps { - agent: AgentSummary; - selected?: boolean; - onClick?: () => void; -} - -export const AgentExplorerCard = memo(function AgentExplorerCard({ agent, selected, onClick }: AgentExplorerCardProps) { - const squadType = getSquadTypeUtil(agent.squad); - const { isFavorite, toggleFavorite } = useFavoritesStore(); - const favorited = isFavorite(agent.id); - const isAiox = useUIStore((s) => s.theme) === 'aiox'; - // Normalize tier to valid value (0, 1, or 2) - const normalizedTier: AgentTier = (agent.tier === 0 || agent.tier === 1 || agent.tier === 2) ? agent.tier : 2; - - const handleFavoriteClick = (e: React.MouseEvent) => { - e.stopPropagation(); - toggleFavorite({ - id: agent.id, - name: agent.name, - squad: agent.squad, - }); - }; - - return ( - - {/* Tier indicator */} -
- -
- {/* Icon/Avatar — prioritize generated avatar over icon */} - {hasAgentAvatar(agent.name) || hasAgentAvatar(agent.id) ? ( - - ) : agent.icon ? ( -
- {(() => { const Icon = getIconComponent(agent.icon); return ; })()} -
- ) : ( - - )} - -
- {/* Header */} -
-
-

{agent.name}

-

{agent.title}

-
-
- - {getTierTheme(normalizedTier).label} - - {/* Favorite button */} - -
-
- - {/* When to use - Primary decision text */} - {(() => { - // Helper to check if description is a template placeholder - const isPlaceholder = (text?: string) => - !text || text.startsWith('[') || text.includes('{{') || text.length < 10; - - if (agent.whenToUse) { - return ( -

- {agent.whenToUse} -

- ); - } else if (agent.description && !isPlaceholder(agent.description)) { - return ( -

- {agent.description} -

- ); - } - return null; - })()} - - {/* Footer */} -
- {agent.squad} - {agent.commandCount !== undefined && agent.commandCount > 0 && ( - - {agent.commandCount} cmds - - )} -
-
-
- - ); -}); diff --git a/aios-platform/src/components/bob/ExecutionLog.tsx b/aios-platform/src/components/bob/ExecutionLog.tsx deleted file mode 100644 index 61ccb97..0000000 --- a/aios-platform/src/components/bob/ExecutionLog.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect, useRef } from 'react'; -import { Badge } from '../ui'; -import type { ExecutionLogEntry } from '../../stores/bobStore'; - -const typeBadge: Record = { - info: { label: 'INFO', status: 'online' }, - action: { label: 'ACTION', status: 'success' }, - decision: { label: 'DECISION', status: 'warning' }, - error: { label: 'ERROR', status: 'error' }, -}; - -export default function ExecutionLog({ entries }: { entries: ExecutionLogEntry[] }) { - const scrollRef = useRef(null); - - useEffect(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [entries.length]); - - if (entries.length === 0) { - return ( -
-

No log entries yet

-
- ); - } - - return ( -
- {entries.map((entry) => { - const badge = typeBadge[entry.type]; - return ( -
- - {new Date(entry.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })} - - - {badge.label} - - {entry.agent} - {entry.message} -
- ); - })} -
- ); -} diff --git a/aios-platform/src/components/chat/ChatContainer.tsx b/aios-platform/src/components/chat/ChatContainer.tsx deleted file mode 100644 index 8d66679..0000000 --- a/aios-platform/src/components/chat/ChatContainer.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { useState, useRef, useEffect } from 'react'; -import { motion, AnimatePresence } from 'framer-motion'; -import { SmartMessageList } from './VirtualizedMessageList'; -import { ChatInput } from './ChatInput'; -import { ChatConversationPanel } from './ChatConversationPanel'; -import { ChatHeader } from './ChatHeader'; -import { WelcomeMessage } from './WelcomeMessage'; -import { EmptyChat } from './EmptyChat'; -import { useChat } from '../../hooks/useChat'; -import { useChatStore } from '../../stores/chatStore'; -import { useUIStore } from '../../stores/uiStore'; -import { ORCHESTRATION_TRIGGERS } from './chat-types'; - -export function ChatContainer() { - const { - activeSession, - selectedAgent, - isAgentLoading, - isStreaming, - sendMessage, - stopStreaming, - } = useChat(); - const { sessions, activeSessionId, setActiveSession, deleteSession } = useChatStore(); - const { selectedAgentId, setCurrentView } = useUIStore(); - const [chatSidebarOpen, setChatSidebarOpen] = useState(true); - - const messagesEndRef = useRef(null); - const scrollAreaRef = useRef(null); - const [showScrollBtn, setShowScrollBtn] = useState(false); - - // Auto-scroll to bottom on new messages - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [activeSession?.messages]); - - // Track scroll position for scroll-to-bottom button - useEffect(() => { - const el = scrollAreaRef.current; - if (!el) return; - const onScroll = () => { - const gap = el.scrollHeight - el.scrollTop - el.clientHeight; - setShowScrollBtn(gap > 200); - }; - el.addEventListener('scroll', onScroll, { passive: true }); - return () => el.removeEventListener('scroll', onScroll); - }, []); - - const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }; - - // Show loading while agent data is being fetched (prevents flash of EmptyChat) - if (!selectedAgent && isAgentLoading && selectedAgentId) { - return ( -
-
-
- ); - } - - if (!selectedAgent) { - return ; - } - - return ( -
- {/* Conversation Sidebar */} - setChatSidebarOpen(!chatSidebarOpen)} - onSelectSession={(sessionId) => { - const session = sessions.find(s => s.id === sessionId); - if (session) { - setActiveSession(sessionId); - useUIStore.setState({ - selectedSquadId: session.squadId, - selectedAgentId: session.agentId, - }); - } - }} - onDeleteSession={deleteSession} - onNewChat={() => { - useUIStore.setState({ selectedAgentId: null }); - }} - /> - - {/* Main Chat Area */} -
- {/* Chat Header */} - setChatSidebarOpen(!chatSidebarOpen)} - /> - - {/* Messages Area */} -
- {activeSession?.messages && activeSession.messages.length > 0 ? ( -
- -
-
- ) : ( - - )} - - {/* Scroll to bottom floating button */} - - {showScrollBtn && ( - - - - - - Novas mensagens - - )} - -
- - {/* Input Area */} -
- { - // Detect orchestration commands and redirect to TaskOrchestrator - if (ORCHESTRATION_TRIGGERS.some(re => re.test(message.trim()))) { - const demand = message.replace(/^(\/orquestrar|\/orchestrate|@bob)\s*/i, '').trim(); - setCurrentView('bob'); - if (demand) { - sessionStorage.setItem('orchestration-demand', demand); - } - // Store originating session ID so results can be injected back - if (activeSessionId) { - sessionStorage.setItem('orchestration-source-session', activeSessionId); - } - return; - } - sendMessage(message, attachments); - }} - onStop={stopStreaming} - disabled={isStreaming} - isStreaming={isStreaming} - agentName={selectedAgent.name} - /> -
-
- -
- ); -} diff --git a/aios-platform/src/components/chat/MarkdownRenderer.tsx b/aios-platform/src/components/chat/MarkdownRenderer.tsx deleted file mode 100644 index 25edebd..0000000 --- a/aios-platform/src/components/chat/MarkdownRenderer.tsx +++ /dev/null @@ -1,909 +0,0 @@ -import { useState, useCallback, memo, useMemo, lazy, Suspense, Fragment } from 'react'; -import { createPortal } from 'react-dom'; -import { motion, AnimatePresence } from 'framer-motion'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import rehypeRaw from 'rehype-raw'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import { cn } from '../../lib/utils'; -import { useUIStore } from '../../stores/uiStore'; - -// Lazy load mermaid diagram renderer (heavy dependency) -const MermaidDiagram = lazy(() => import('./MermaidDiagram')); - -interface MarkdownRendererProps { - content: string; - className?: string; -} - -// --- Pre-processing --- - -function preprocessContent(content: string): string { - let processed = content; - - // Collapse numbered list items where number is on its own line: - // "1.\n**name**" → "1. **name**" - processed = processed.replace(/^(\d+)\.\s*\n+(\s*\*\*)/gm, '$1. $2'); - - // Collapse numbered list items where number is on its own line (no bold): - // "1.\n some text" → "1. some text" - processed = processed.replace(/^(\d+)\.\s*\n+(\s*\S)/gm, '$1. $2'); - - return processed; -} - -// --- Utility: URL detection --- - -const YOUTUBE_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{11})(?:\S*)?/; -const VIDEO_EXT_REGEX = /\.(mp4|webm|mov|avi|mkv)(\?.*)?$/i; -const AUDIO_EXT_REGEX = /\.(mp3|wav|ogg|m4a|aac|flac)(\?.*)?$/i; -const LOOM_REGEX = /(?:https?:\/\/)?(?:www\.)?loom\.com\/share\/([\w-]+)/; -const SPOTIFY_REGEX = /(?:https?:\/\/)?open\.spotify\.com\/(track|album|playlist|episode)\/([\w]+)/; -const GIST_REGEX = /(?:https?:\/\/)?gist\.github\.com\/([\w-]+)\/([\w]+)/; - -function getYouTubeId(url: string): string | null { - const match = YOUTUBE_REGEX.exec(url); - return match ? match[1] : null; -} - -function getLoomId(url: string): string | null { - const match = LOOM_REGEX.exec(url); - return match ? match[1] : null; -} - -function isVideoUrl(url: string): boolean { - return VIDEO_EXT_REGEX.test(url); -} - -function isAudioUrl(url: string): boolean { - return AUDIO_EXT_REGEX.test(url); -} - -function getSpotifyInfo(url: string): { type: string; id: string } | null { - const match = SPOTIFY_REGEX.exec(url); - return match ? { type: match[1], id: match[2] } : null; -} - -function getGistInfo(url: string): { user: string; id: string } | null { - const match = GIST_REGEX.exec(url); - return match ? { user: match[1], id: match[2] } : null; -} - -// --- Inline content processing for @mentions and file paths --- - -const AGENT_NAMES = ['dev', 'qa', 'architect', 'pm', 'po', 'sm', 'analyst', 'devops', 'data-engineer', 'aios-master', 'ux-design-expert', 'squad-creator']; -const AGENT_COLORS: Record = { - dev: '#60A5FA', - qa: '#F472B6', - architect: '#A78BFA', - pm: '#34D399', - po: '#FBBF24', - sm: '#FB923C', - analyst: '#2DD4BF', - devops: '#D1FF00', - 'data-engineer': '#818CF8', - 'aios-master': '#F43F5E', - 'ux-design-expert': '#E879F9', - 'squad-creator': '#38BDF8', -}; - -const MENTION_PATTERN = AGENT_NAMES.map(n => n.replace('-', '\\-')).join('|'); -const INLINE_REGEX = new RegExp( - `(@(?:${MENTION_PATTERN})\\b)` + - `|((?:(?:src|docs|packages|scripts|public|\\.\\.)\\/)(?:[\\w./@-]+(?:\\.[\\w]+)?))`, - 'g' -); - -function AgentMention({ name }: { name: string }) { - const color = AGENT_COLORS[name] || '#D1FF00'; - const navigateToAgent = useUIStore((s) => s.navigateToRegistryAgent); - return ( - - ); -} - -function FilePathBadge({ path }: { path: string }) { - return ( - - - - - - {path} - - ); -} - -function processInlineContent(children: React.ReactNode): React.ReactNode { - if (typeof children === 'string') { - return processTextString(children); - } - if (Array.isArray(children)) { - return children.map((child, i) => { - if (typeof child === 'string') { - return {processTextString(child)}; - } - return child; - }); - } - return children; -} - -function processTextString(text: string): React.ReactNode { - const parts: React.ReactNode[] = []; - let lastIndex = 0; - - // Reset regex state - INLINE_REGEX.lastIndex = 0; - let match; - - while ((match = INLINE_REGEX.exec(text)) !== null) { - if (match.index > lastIndex) { - parts.push(text.slice(lastIndex, match.index)); - } - - if (match[1]) { - const agentName = match[1].slice(1); // remove @ - parts.push(); - } else if (match[2]) { - parts.push(); - } - - lastIndex = match.index + match[0].length; - } - - if (lastIndex < text.length) { - parts.push(text.slice(lastIndex)); - } - - return parts.length > 0 ? parts : text; -} - -// --- Copy button --- - -function CopyButton({ code }: { code: string }) { - const [copied, setCopied] = useState(false); - - const handleCopy = useCallback(async () => { - await navigator.clipboard.writeText(code); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }, [code]); - - return ( - - ); -} - -const CopyIcon = () => ( - - - - -); - -const CheckIcon = () => ( - - - -); - -// --- Code block with syntax highlighting --- - -const CodeBlock = memo(function CodeBlock({ - language, - value, -}: { - language: string; - value: string; -}) { - // Mermaid diagrams - if (language === 'mermaid') { - return ( - -
-
- Renderizando diagrama... -
-
- } - > - -
- ); - } - - // Diff blocks - if (language === 'diff') { - return ; - } - - return ( -
- {language && ( -
- {language} -
- )} - - - {value} - -
- ); -}); - -// --- Diff block renderer --- - -function DiffBlock({ value }: { value: string }) { - const lines = value.split('\n'); - - return ( -
-
- diff -
- -
- {lines.map((line, i) => { - let bg = 'transparent'; - let color = 'rgba(255,255,255,0.7)'; - let prefix = ' '; - - if (line.startsWith('+') && !line.startsWith('+++')) { - bg = 'rgba(34, 197, 94, 0.1)'; - color = '#4ade80'; - prefix = '+'; - } else if (line.startsWith('-') && !line.startsWith('---')) { - bg = 'rgba(239, 68, 68, 0.1)'; - color = '#f87171'; - prefix = '-'; - } else if (line.startsWith('@@')) { - bg = 'rgba(96, 165, 250, 0.08)'; - color = '#60A5FA'; - prefix = '@'; - } else if (line.startsWith('+++') || line.startsWith('---')) { - color = 'rgba(255,255,255,0.4)'; - } - - return ( -
- - {prefix !== ' ' ? '' : ''} - - {line} -
- ); - })} -
-
- ); -} - -// --- Inline code --- - -const InlineCode = ({ children }: { children: React.ReactNode }) => ( - - {children} - -); - -// --- YouTube embed --- - -function YouTubeEmbed({ videoId }: { videoId: string }) { - return ( -
-