🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
A professional workspace for AI coding agents with: - Real-time streaming chat with SSE - Session management (fork, clone, tree navigation) - MCP tools browser & metadata panel - Skills browser (global, project, package sources) - Voice input & STT provider configuration - Integrated terminal & file explorer - Model selector with capability metadata - Keyboard-first UI with command palette - 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi) - Tauri 2 desktop app - React 19 + TypeScript + Vite 8 + Tailwind CSS 4 - Pi Agent extension runtime Originally forked from OpenCodeUI, now an independent project. License: GPL-3.0-only
This commit is contained in:
commit
b3489e1ede
983 files changed
+158475
No files matched your search
@@ -0,0 +1,66 @@
|
||||
# Especificação Técnica: Migração do PiAgentUi para Extensão do Pi Agent
|
||||
|
||||
Esta especificação define o desenho arquitetônico, a segurança, as estruturas de dados e a integração desktop necessários para migrar o frontend `PiAgentUi` (originalmente construído para o OpenCode) para o ecossistema do **Pi Agent**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Arquitetura e Fluxo de Dados
|
||||
|
||||
A arquitetura do `PiAgentUi` será descentralizada do OpenCode e integrada diretamente ao processo do Pi Agent através do modelo de extensões.
|
||||
|
||||
1. **Loopback Server (Porta Dinâmica):** A extensão `piagentui-server.ts` se conectará ao ciclo de vida do Pi e abrirá um servidor local seguro escutando em `127.0.0.1` em uma porta livre selecionada pelo sistema operacional.
|
||||
2. **Same-Origin Boot Data:** A extensão servirá o HTML principal e injetará no próprio corpo do `index.html` um bloco JSON contendo as informações de boot (URL base, porta, e o token bearer gerado na inicialização).
|
||||
3. **Ponte de Comunicação:** Toda chamada HTTP e WebSocket será autenticada usando o token em memória.
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌────────────────────────┐ ┌────────────────┐
|
||||
│ Tauri (Rust) │ ───> │ Pi Agent (Processo) │ <───> │ Webview (UI) │
|
||||
│ Port Check & │ │ - piagentui-server │ │ - React 19 │
|
||||
│ Launch State │ │ - HTTP + WS Server │ │ - Local Token │
|
||||
└─────────────────┘ └────────────────────────┘ └────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Matriz de Endpoints e Recursos
|
||||
|
||||
Para o MVP, a API do `PiAgentUi` responderá nos mesmos paths esperados pelo frontend, mapeando-os para chamadas da extensão e mockando recursos incompatíveis.
|
||||
|
||||
| Recurso / Funcionalidade | Endpoint Frontend | Ação no Backend (Pi Agent Extension) | Status no MVP |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Health Check** | `/global/health` | Retorna status da extensão do Pi | **Suportado** |
|
||||
| **Event Stream** | `/global/event` | SSE/WebSocket que espelha os eventos do Pi | **Suportado** |
|
||||
| **Modelos Ativos** | `/api/models` | Consulta o registro de modelos configurado no Pi | **Suportado** |
|
||||
| **Multi-Sessão (CRUD)** | `/api/sessions` | Gerencia sessões locais da pasta de sessões do Pi | **Suportado** |
|
||||
| **Enviar Mensagem** | `/api/messages/send` | Dispara `pi.sendUserMessage(...)` assincronamente | **Suportado** |
|
||||
| **Parar Mensagem** | `/api/messages/abort` | Aborta o turno ativo do Pi Agent se disponível | **Suportado** |
|
||||
| **PTY Terminal** | `/api/pty` | Retorna erro 501 / Desativado visualmente | **Stubbed** |
|
||||
| **MCP / LSP** | `/api/mcp` / `/api/lsp`| Retorna lista vazia ou erro 501 | **Stubbed** |
|
||||
| **Worktrees / VCS** | `/api/worktrees` | Retorna erro 501 / Desativado visualmente | **Stubbed** |
|
||||
|
||||
---
|
||||
|
||||
## 3. Segurança e Tokens
|
||||
|
||||
1. **Porta Dinâmica:** Impedir conflitos de porta iniciando sempre em portas dinâmicas alocadas pelo SO (`port: 0`).
|
||||
2. **Bearer Token:** Gerar um UUID/token seguro de uso único na inicialização do servidor.
|
||||
3. **Sem Persistência de Senhas:** Nenhuma credencial do usuário será armazenada fisicamente ou em `localStorage`. O token de autorização residirá unicamente na memória da sessão React.
|
||||
4. **Validação de Host/Origin:** Rejeitar qualquer requisição com cabeçalho `Host` ou `Origin` que não seja estritamente local (`127.0.0.1` ou `localhost`).
|
||||
5. **Prevenção de Traversal:** Servir arquivos estáticos validando canonicalização de caminhos para evitar vulnerabilidade de directory traversal no Windows.
|
||||
|
||||
---
|
||||
|
||||
## 4. Integração Tauri Desktop (Track B)
|
||||
|
||||
Com a decisão de **incluir o Tauri já no primeiro marco**, o app desktop sofrerá as seguintes modificações:
|
||||
* **Renomeação de Comandos:** Todos os comandos Tauri registrados no Rust e no frontend serão renomeados de `*opencode*` para correspondentes de `*pi*` ou `*pi_agent*`.
|
||||
* **Subprocesso do Pi:** Em vez de iniciar `opencode serve`, o Tauri iniciará o processo do Pi (ex: `pi --mode rpc`) adicionando dinamicamente o parâmetro `--extension` apontando para o nosso servidor local se necessário, ou confiando no auto-load das extensões instaladas.
|
||||
* **Filtro de URL Restrito:** O Rust bridge do Tauri terá uma validação explícita em tempo de execução para rejeitar qualquer tráfego que tente se conectar a servidores externos, limitando a ponte estritamente a adaptadores de loopback permitidos.
|
||||
|
||||
---
|
||||
|
||||
## 5. Referências do Projeto
|
||||
|
||||
Esta migração baseia-se diretamente no comportamento implementado em:
|
||||
`D:/Documents/Github/PiAgentUI/better-tau/extensions/mirror-server.ts`
|
||||
utilizando o modelo de ciclo de vida e roteamento nativo lá desenvolvidos como garantia de estabilidade sob o runtime do Pi.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Inventário de Campos e Dependências do SDK do OpenCode no PiAgentUi
|
||||
|
||||
Este documento registra todas as dependências da biblioteca `@opencode-ai/sdk` encontradas no `PiAgentUi`, detalhando as estruturas (DTOs) que o frontend consome e como elas serão mapeadas para a nova estrutura de compatibilidade com o Pi Agent.
|
||||
|
||||
---
|
||||
|
||||
## 1. Arquivos com Acoplamento ao SDK do OpenCode
|
||||
|
||||
Os seguintes arquivos realizam importações diretas do cliente SDK (`@opencode-ai/sdk`) ou utilizam tipos derivados dele:
|
||||
|
||||
### Arquivos em `src/api/`
|
||||
* `agent.ts`
|
||||
* `client.ts`
|
||||
* `command.ts`
|
||||
* `config.ts`
|
||||
* `file.ts`
|
||||
* `global.ts`
|
||||
* `lsp.ts`
|
||||
* `mcp.ts`
|
||||
* `message.ts`
|
||||
* `permission.ts`
|
||||
* `pty.ts`
|
||||
* `session.ts`
|
||||
* `skill.ts`
|
||||
* `todo.ts`
|
||||
* `tool.ts`
|
||||
* `vcs.ts`
|
||||
* `worktree.ts`
|
||||
* `sdk.ts`
|
||||
|
||||
### Arquivos em `src/types/api/`
|
||||
* `agent.ts`
|
||||
* `common.ts`
|
||||
* `config.ts`
|
||||
* `event.ts`
|
||||
* `file.ts`
|
||||
* `mcp.ts`
|
||||
* `message.ts`
|
||||
* `model.ts`
|
||||
* `permission.ts`
|
||||
* `project.ts`
|
||||
* `pty.ts`
|
||||
* `session.ts`
|
||||
* `skill.ts`
|
||||
* `tool.ts`
|
||||
* `vcs.ts`
|
||||
* `worktree.ts`
|
||||
|
||||
---
|
||||
|
||||
## 2. Inventário de Estruturas Consumidas pela UI
|
||||
|
||||
Para que a migração não quebre o frontend, a API da extensão Pi deve produzir e aceitar dados que correspondam a estas chaves essenciais:
|
||||
|
||||
### A. Estrutura de Mensagem (`MessageWithParts` / `Message`)
|
||||
O frontend lê de `message.info` e de `message.parts`:
|
||||
* **`info`** (`UserMessage` ou `AssistantMessage`):
|
||||
* `id`: string (UUID da mensagem).
|
||||
* `sessionID`: string (UUID da sessão).
|
||||
* `role`: `'user' | 'assistant'`.
|
||||
* `time`: `{ created?: number; completed?: number }`.
|
||||
* `summary`: string (breve descrição se disponível).
|
||||
* `modelID`: string (somente assistente).
|
||||
* `providerID`: string (somente assistente).
|
||||
* **`parts`** (Array de blocos que compõem a mensagem):
|
||||
* **`TextPart`**: `{ type: 'text', text: string }`.
|
||||
* **`ReasoningPart`**: `{ type: 'reasoning', text: string }`.
|
||||
* **`ToolPart`**: `{ type: 'tool', tool: string, state: { status: 'pending' | 'running' | 'completed' | 'error', input?: any, output?: any, error?: string } }`.
|
||||
* **`FilePart`**: `{ type: 'file', filename?: string, mime?: string, url?: string }`.
|
||||
* **`StepStartPart`**: `{ type: 'step-start' }`.
|
||||
* **`StepFinishPart`**: `{ type: 'step-finish' }`.
|
||||
|
||||
### B. Estrutura de Sessão (`Session`)
|
||||
Utilizada no menu lateral de histórico e criação:
|
||||
* `id`: string (UUID).
|
||||
* `title`: string (nome da sessão).
|
||||
* `directory`: string (diretório de trabalho correspondente).
|
||||
* `time`: `{ created?: number; updated?: number }`.
|
||||
* `summary`: `{ title?: string; category?: string; difficulty?: string; tokens?: number; cost?: number }`.
|
||||
* `status`: `'idle' | 'running' | 'error'`.
|
||||
|
||||
### C. Envelopes de Eventos Globais (`GlobalEvent`)
|
||||
Processados pelo pipeline de SSE em `events.ts`:
|
||||
* `{ directory?: string, payload: { type: string, properties: any } }`
|
||||
* Eventos chave:
|
||||
* `server.connected`
|
||||
* `session.updated`
|
||||
* `session.status`
|
||||
* `session.idle`
|
||||
* `session.error`
|
||||
* `message.updated`
|
||||
* `message.part.updated`
|
||||
* `message.part.delta`
|
||||
|
||||
---
|
||||
|
||||
## 3. Estratégia de Mapeamento no MVP
|
||||
|
||||
| Módulo Original | Ação / Novo Mapeamento |
|
||||
| :--- | :--- |
|
||||
| **`session`** | Consultas diretas ao Pi Session Manager via rotas REST locais da extensão. Mapeado para sessões nativas do Pi. |
|
||||
| **`message`** | Mapeado para o envio assíncrono de prompts ao Pi (`pi.sendUserMessage`). Eventos nativos do Pi traduzidos em deltas de partes. |
|
||||
| **`tool`** | Retorna ferramentas padrões disponíveis do Pi. |
|
||||
| **`pty` / `lsp` / `mcp`** | Desativados visualmente / stubs de array vazio / erro 501. |
|
||||
| **`worktree` / `vcs`** | Desativados visualmente. |
|
||||
@@ -0,0 +1,140 @@
|
||||
# PiAgentUI Active Session Switch Design
|
||||
|
||||
## Goal
|
||||
|
||||
When the user opens an existing session in PiAgentUI and sends a message, the message must be delivered to that selected Pi session, not to whichever session was active when the Pi TUI launched.
|
||||
|
||||
## Current finding
|
||||
|
||||
- The frontend route/store correctly loads selected sessions and their messages.
|
||||
- `src/hooks/useChatSession.ts` sends the selected `sessionId` to `sendMessageAsync()`.
|
||||
- `extensions/piagentui-server.ts` receives `sessionId`, but still calls `pi.sendUserMessage(parsed.text)`.
|
||||
- `pi.sendUserMessage()` always targets the current active Pi runtime session.
|
||||
- Pi exposes `switchSession()` only on `ExtensionCommandContext`, not on normal event `ExtensionContext` used by `session_start` and the HTTP server.
|
||||
- `AgentSessionRuntime.switchSession()` intentionally emits `session_shutdown`, disposes the old session, creates the replacement session, rebinds extensions, then runs `withSession(replacedCtx)`.
|
||||
- The existing PiAgentUI extension currently stops the web server on every `session_shutdown`, so a normal Pi session switch can close the HTTP/SSE server and force reconnects.
|
||||
|
||||
## Selected approach: activate and send on the backend
|
||||
|
||||
The backend should become authoritative for message routing:
|
||||
|
||||
1. Resolve requested `sessionId` to the JSONL session file in the current session directory.
|
||||
2. If the requested session differs from the active Pi session, switch the real Pi runtime to that session.
|
||||
3. Send the user message inside the replacement session callback so the message cannot land in the old session.
|
||||
4. Keep the existing frontend model: route/session selection still works as today; `sendMessageAsync()` keeps passing `sessionId`.
|
||||
|
||||
This preserves the current UI while making the server guarantee correctness.
|
||||
|
||||
## Required changes
|
||||
|
||||
### Pi core
|
||||
|
||||
Expose a safe session-switch capability to extension event contexts, or a narrowly-scoped equivalent that can be used by long-lived extension services.
|
||||
|
||||
Preferred API shape:
|
||||
|
||||
```ts
|
||||
interface ExtensionContext {
|
||||
switchSession(
|
||||
sessionPath: string,
|
||||
options?: { withSession?: (ctx: ReplacedSessionContext) => Promise<void> },
|
||||
): Promise<{ cancelled: boolean }>
|
||||
}
|
||||
```
|
||||
|
||||
Implementation should reuse the existing `switchSessionHandler` already used by `createCommandContext()`. The important behavior is not a new session implementation; it is making the already-existing runtime switch available to the PiAgentUI HTTP server context.
|
||||
|
||||
### PiAgentUI extension server
|
||||
|
||||
Add helpers in `extensions/piagentui-server.ts`:
|
||||
|
||||
- `getSessionFileForId(sessionId)` scans `latestCtx.sessionManager.getSessionDir()` for `*_${sessionId}.jsonl`.
|
||||
- `sendUserMessageToSession(parsed)` validates `sessionId`, resolves the session file, switches if needed, and sends in the correct context.
|
||||
- `session_shutdown` handling must not tear down the HTTP server on `reason === 'resume'` if the server is intentionally switching sessions for the web UI. The server should keep the port stable and update `latestCtx` on the following `session_start`.
|
||||
|
||||
Target send flow:
|
||||
|
||||
```ts
|
||||
if (requestedSessionId && requestedSessionId !== activeSessionId) {
|
||||
const sessionFile = getSessionFileForId(requestedSessionId)
|
||||
const result = await latestCtx.switchSession(sessionFile, {
|
||||
withSession: async replacedCtx => {
|
||||
await maybeSetModel(replacedCtx, parsed.model)
|
||||
await replacedCtx.sendUserMessage(parsed.text)
|
||||
},
|
||||
})
|
||||
if (result.cancelled) return HTTP 409
|
||||
return HTTP 202
|
||||
}
|
||||
|
||||
await maybeSetModel(latestCtx, parsed.model)
|
||||
pi.sendUserMessage(parsed.text)
|
||||
```
|
||||
|
||||
If `sessionId` is missing or already active, keep the existing path.
|
||||
|
||||
### Frontend
|
||||
|
||||
Keep frontend mostly unchanged.
|
||||
|
||||
Small additions only if needed:
|
||||
|
||||
- `sendMessageAsync()` continues sending `sessionId`.
|
||||
- Optional: handle HTTP `409`/`404` with a clear toast if target session cannot be activated.
|
||||
- Optional: add `activeSessionId` from `server.connected`/`session.status` later, but not required for the core fix.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Activate on session selection
|
||||
|
||||
Frontend would call `/api/sessions/:id/activate` when a user clicks a session. This matches the user's mental model, but it introduces a race: if the user sends before activation completes, the message can still hit the old session unless send also validates activation. It is useful as a UX enhancement after backend routing is correct, not as the primary correctness mechanism.
|
||||
|
||||
### Frontend guard only
|
||||
|
||||
Frontend could block sending if selected session differs from active Pi session. This avoids Pi core changes but makes UX worse and still requires a trustworthy active-session signal from the backend. It detects the problem instead of solving it.
|
||||
|
||||
## Tests
|
||||
|
||||
### Extension/unit tests
|
||||
|
||||
Add or update extension server tests to cover:
|
||||
|
||||
1. Sending with active `sessionId` calls `pi.sendUserMessage()` without switching.
|
||||
2. Sending with different `sessionId` resolves the matching JSONL file and calls `ctx.switchSession(sessionFile, { withSession })`.
|
||||
3. The `withSession` callback calls `replacedCtx.sendUserMessage(text)`.
|
||||
4. Unknown `sessionId` returns 404 and does not send to the active session.
|
||||
5. Cancelled switch returns 409 and does not send.
|
||||
6. `session_shutdown` with `reason: 'resume'` does not close the web server during web-initiated session switching.
|
||||
|
||||
### Frontend tests
|
||||
|
||||
Add or update API/hook tests to cover:
|
||||
|
||||
1. `sendMessageAsync()` includes `sessionId` in the payload.
|
||||
2. Send error responses surface through the existing error handling path.
|
||||
3. Existing route/session selection behavior remains unchanged.
|
||||
|
||||
### Manual verification
|
||||
|
||||
1. Start Pi in session A.
|
||||
2. Open PiAgentUI.
|
||||
3. Select existing session B in the sidebar.
|
||||
4. Send a message.
|
||||
5. Verify new user and assistant messages appear in session B.
|
||||
6. Verify session A JSONL is unchanged.
|
||||
7. Verify `/global/event` remains connected and streaming works without F5.
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- Switching sessions invalidates old extension contexts. Mitigation: send inside `withSession(replacedCtx)` and avoid captured stale `pi`/ctx after switch.
|
||||
- Current extension closes the HTTP server on `session_shutdown`. Mitigation: preserve server across `resume` switches and update `latestCtx` on `session_start`.
|
||||
- Pi core API change can be too broad. Mitigation: expose only the existing switch handler; do not create parallel session logic.
|
||||
- Session file lookup by suffix must be strict. Mitigation: only match `_${sessionId}.jsonl` inside the current session dir.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Sending from selected session B never writes to active-at-launch session A.
|
||||
- Frontend routing and message loading stay visually the same.
|
||||
- The web server port remains stable during a session switch initiated by PiAgentUI.
|
||||
- Streaming still updates live after switch.
|
||||
- Typecheck, extension typecheck, relevant tests, and build pass.
|
||||
@@ -0,0 +1,326 @@
|
||||
# Design — README atual do PiAgentUI e sistema de áudio/STT
|
||||
|
||||
**Data:** 2026-05-30
|
||||
**Status:** aprovado conceitualmente pelo usuário; aguardando revisão do arquivo antes da implementação
|
||||
**Escopo:** reescrever o `README.md` do projeto atual e preparar a arquitetura completa para envio de áudio e transcrição em tempo real no app.
|
||||
|
||||
## Contexto
|
||||
|
||||
O projeto começou como uma adaptação do OpenCodeUI, mas atualmente está evoluindo para o **PiAgentUI**: uma interface web/desktop para o Pi Agent, com ambição explícita de se tornar um app inteiro no estilo Codex ou Claude Desktop.
|
||||
|
||||
O estado atual já inclui:
|
||||
|
||||
- frontend React 19 + TypeScript + Vite;
|
||||
- app desktop via Tauri 2;
|
||||
- extensão Pi local em `extensions/piagentui-server.ts` como backend/runtime bridge;
|
||||
- chat com streaming/SSE;
|
||||
- sessões reais do Pi, histórico e roteamento de sessão;
|
||||
- tool calls e tool results renderizados em cards;
|
||||
- contexto real do runtime via endpoint `/api/sessions/:sessionId/context`;
|
||||
- enriquecimento de metadados de modelo via catálogo OpenRouter local em `src/assets/models.json`;
|
||||
- lista real de Skills agrupadas por origem;
|
||||
- metadados reais de MCP configurado;
|
||||
- slash commands reais do Pi expostos por `/api/commands/list`;
|
||||
- anexos de arquivos, incluindo áudio como arquivo quando o modelo selecionado declara suporte a áudio.
|
||||
|
||||
Ainda não existe:
|
||||
|
||||
- gravação de microfone no composer;
|
||||
- transcrição parcial em tempo real;
|
||||
- endpoint backend para STT;
|
||||
- configurações STT;
|
||||
- provedor STT customizável OpenAI-compatible;
|
||||
- pipeline de áudio integrado ao envio de mensagens.
|
||||
|
||||
## Objetivos
|
||||
|
||||
1. Reescrever `README.md` em português para refletir o produto atual, não o projeto original OpenCodeUI.
|
||||
2. Apresentar PiAgentUI como interface moderna para o Pi Agent e base futura de app completo tipo Codex/Claude.
|
||||
3. Documentar capacidades existentes de forma honesta, separando **implementado**, **em andamento** e **roadmap**.
|
||||
4. Projetar um sistema de áudio/STT completo, com opção padrão forte e configuração externa flexível.
|
||||
5. Implementar o sistema em fases testáveis, com TDD para lógica, API, stores e componentes.
|
||||
|
||||
## Não objetivos
|
||||
|
||||
- Não renomear pacote npm, bundle, app Tauri ou metadados de release nesta primeira etapa, salvo menção no README como débito de branding.
|
||||
- Não implementar agente de voz falante/TTS nesta fase; o foco é entrada por voz e transcrição para texto.
|
||||
- Não substituir o fluxo normal de texto; áudio será uma camada opcional sobre o composer.
|
||||
- Não acoplar PiAgentUI ao OpenRouter para STT. OpenRouter continua sendo catálogo/metadados quando útil; STT usa provedores próprios.
|
||||
|
||||
## README.md proposto
|
||||
|
||||
O novo README deve ser escrito em português e deve substituir integralmente o conteúdo herdado. Estrutura recomendada:
|
||||
|
||||
1. **Título e tagline**
|
||||
- `# PiAgentUI`
|
||||
- “Interface web/desktop para Pi Agent — caminhando para um app completo estilo Codex/Claude.”
|
||||
|
||||
2. **Visão geral**
|
||||
- Explicar que o PiAgentUI é uma UI local para operar o Pi Agent com sessões, ferramentas, MCP, Skills, comandos e contexto real.
|
||||
- Deixar claro que o runtime Pi é a fonte de verdade.
|
||||
|
||||
3. **Estado atual**
|
||||
- Listar capacidades implementadas:
|
||||
- chat streaming;
|
||||
- sessões reais;
|
||||
- tool cards;
|
||||
- context usage;
|
||||
- models metadata;
|
||||
- skills;
|
||||
- MCP metadata;
|
||||
- slash commands;
|
||||
- anexos multimodais;
|
||||
- terminal/file explorer/Tauri quando aplicável.
|
||||
|
||||
4. **Roadmap do produto**
|
||||
- Ações visuais para comandos Pi;
|
||||
- áudio/STT em tempo real;
|
||||
- experiência voice-first;
|
||||
- MCP actions/connect/auth;
|
||||
- app completo tipo Codex/Claude.
|
||||
|
||||
5. **Arquitetura**
|
||||
- Frontend React/Vite/Tauri;
|
||||
- extensão Pi como bridge local;
|
||||
- Pi runtime como estado autoritativo;
|
||||
- APIs locais `/api/*`;
|
||||
- SSE para eventos.
|
||||
|
||||
6. **Desenvolvimento local**
|
||||
- `npm install`;
|
||||
- `npm run dev`;
|
||||
- `npm run build`;
|
||||
- `npm run test:run`;
|
||||
- `npm run typecheck`;
|
||||
- `npm run typecheck:extensions`.
|
||||
|
||||
7. **Notas de runtime**
|
||||
- Algumas mudanças exigem reiniciar/recarregar o Pi/extension server.
|
||||
- O arquivo de descoberta local fica em `~/.pi/agent/piagentui-port.json`.
|
||||
|
||||
8. **Áudio e transcrição — planejado**
|
||||
- Resumir o plano STT sem prometer que já está implementado.
|
||||
|
||||
9. **Licença e origem**
|
||||
- Manter licença atual se aplicável.
|
||||
- Mencionar que o projeto deriva de uma base OpenCodeUI, mas agora segue direção própria para PiAgentUI.
|
||||
|
||||
## Sistema de áudio/STT — abordagem recomendada
|
||||
|
||||
### Opção padrão
|
||||
|
||||
Usar **OpenAI Realtime Transcription** com `gpt-realtime-whisper` como padrão para transcrição ao vivo.
|
||||
|
||||
Razões:
|
||||
|
||||
- foi projetado para live speech-to-text;
|
||||
- entrega deltas parciais;
|
||||
- tem latência baixa;
|
||||
- usa sessão dedicada de transcrição, sem resposta falada do modelo;
|
||||
- combina com um composer por voz onde o texto aparece enquanto o usuário fala.
|
||||
|
||||
### Fallback padrão
|
||||
|
||||
Usar **OpenAI Audio Transcriptions** para áudio fechado/arquivo:
|
||||
|
||||
- `gpt-4o-transcribe` como opção de maior qualidade;
|
||||
- `gpt-4o-mini-transcribe` como opção de menor custo;
|
||||
- `whisper-1` para compatibilidade com integrações antigas.
|
||||
|
||||
Esse fallback deve ser usado quando:
|
||||
|
||||
- o navegador/ambiente não suportar streaming confiável;
|
||||
- o usuário enviar um arquivo de áudio;
|
||||
- o provedor configurado só oferecer endpoint `/audio/transcriptions`;
|
||||
- a conexão realtime falhar e houver áudio gravado em buffer.
|
||||
|
||||
### Provedor OpenAI-compatible externo
|
||||
|
||||
O usuário deve poder configurar qualquer API compatível com o endpoint de transcrição da OpenAI.
|
||||
|
||||
Configuração proposta:
|
||||
|
||||
```ts
|
||||
interface SttProviderConfig {
|
||||
id: string
|
||||
label: string
|
||||
kind: 'openai' | 'openai-compatible'
|
||||
mode: 'realtime' | 'file' | 'both'
|
||||
baseUrl: string
|
||||
apiKey: string
|
||||
realtimeModel?: string
|
||||
transcriptionModel: string
|
||||
transcriptionEndpoint?: string
|
||||
language?: string
|
||||
prompt?: string
|
||||
responseFormat?: 'json' | 'text' | 'verbose_json'
|
||||
}
|
||||
```
|
||||
|
||||
Exemplo para Together AI:
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": "openai-compatible",
|
||||
"mode": "file",
|
||||
"baseUrl": "https://api.together.xyz/v1",
|
||||
"transcriptionEndpoint": "/audio/transcriptions",
|
||||
"transcriptionModel": "openai/whisper-large-v3"
|
||||
}
|
||||
```
|
||||
|
||||
Observação: APIs “OpenAI-compatible” costumam ser compatíveis no endpoint HTTP de arquivo, mas realtime/WebSocket varia por provedor. Por isso, realtime deve ser capability separada, não assumida automaticamente.
|
||||
|
||||
## Arquitetura proposta do STT
|
||||
|
||||
### Frontend
|
||||
|
||||
Criar uma feature dedicada:
|
||||
|
||||
- `src/features/voice/VoiceRecorderButton.tsx`
|
||||
- `src/features/voice/VoiceTranscriptOverlay.tsx`
|
||||
- `src/features/voice/useVoiceRecorder.ts`
|
||||
- `src/features/voice/useRealtimeTranscript.ts`
|
||||
- `src/features/voice/audioEncoding.ts`
|
||||
|
||||
Responsabilidades:
|
||||
|
||||
- pedir permissão de microfone;
|
||||
- mostrar estados: idle, requesting_permission, recording, connecting, transcribing, error;
|
||||
- capturar áudio;
|
||||
- enviar chunks para o backend;
|
||||
- renderizar transcrição parcial e final;
|
||||
- inserir texto final no composer;
|
||||
- opcionalmente enviar automaticamente após confirmação do usuário.
|
||||
|
||||
### Store/configuração
|
||||
|
||||
Criar configuração local de STT:
|
||||
|
||||
- `src/store/sttSettingsStore.ts`
|
||||
- persistência em localStorage inicialmente;
|
||||
- campos para provider padrão, base URL, modelo, idioma, prompt, modo e preferências de UX.
|
||||
|
||||
Preferências de UX:
|
||||
|
||||
```ts
|
||||
interface VoiceInputPreferences {
|
||||
insertMode: 'append' | 'replace-selection'
|
||||
autoSend: boolean
|
||||
showPartialTranscript: boolean
|
||||
pushToComposerWhileSpeaking: boolean
|
||||
silenceAutoStopMs: number
|
||||
}
|
||||
```
|
||||
|
||||
### API frontend
|
||||
|
||||
Criar cliente dedicado:
|
||||
|
||||
- `src/api/stt.ts`
|
||||
|
||||
Endpoints planejados:
|
||||
|
||||
- `GET /api/stt/config/schema`
|
||||
- `POST /api/stt/transcriptions`
|
||||
- `POST /api/stt/realtime/session`
|
||||
- `POST /api/stt/realtime/:sessionId/chunk`
|
||||
- `POST /api/stt/realtime/:sessionId/commit`
|
||||
- `DELETE /api/stt/realtime/:sessionId`
|
||||
|
||||
### Backend/extensão Pi
|
||||
|
||||
Adicionar ao `extensions/piagentui-server.ts` ou extrair para módulo dedicado se o arquivo ficar grande:
|
||||
|
||||
- parser seguro de multipart/audio para transcrição por arquivo;
|
||||
- proxy para `/v1/audio/transcriptions`;
|
||||
- gerenciador de sessão realtime;
|
||||
- normalização de eventos para o frontend.
|
||||
|
||||
Formato de eventos para o frontend:
|
||||
|
||||
```ts
|
||||
type SttEvent =
|
||||
| { type: 'stt.session.started'; sessionId: string }
|
||||
| { type: 'stt.transcript.delta'; sessionId: string; text: string; itemId?: string }
|
||||
| { type: 'stt.transcript.completed'; sessionId: string; text: string; itemId?: string }
|
||||
| { type: 'stt.error'; sessionId?: string; message: string; recoverable: boolean }
|
||||
| { type: 'stt.session.closed'; sessionId: string }
|
||||
```
|
||||
|
||||
### Segurança
|
||||
|
||||
- API keys não devem aparecer em logs.
|
||||
- Configurações secretas devem ser mascaradas na UI.
|
||||
- O backend deve limitar tamanho/duração de áudio.
|
||||
- O backend deve validar `baseUrl` e impedir esquemas não HTTP(S).
|
||||
- Erros do provedor devem ser normalizados, sem vazar headers.
|
||||
- O usuário deve confirmar antes de habilitar auto-send de transcrição.
|
||||
|
||||
### Testes
|
||||
|
||||
Seguir TDD:
|
||||
|
||||
1. `src/api/stt.test.ts`
|
||||
- monta payloads corretos;
|
||||
- trata deltas/finais;
|
||||
- mascara erros.
|
||||
|
||||
2. `src/store/sttSettingsStore.test.ts`
|
||||
- defaults;
|
||||
- persistência;
|
||||
- atualização parcial;
|
||||
- remoção de secret.
|
||||
|
||||
3. `src/features/voice/*.test.tsx`
|
||||
- estados do botão;
|
||||
- permissão negada;
|
||||
- parcial/final no composer;
|
||||
- auto-send desligado por padrão.
|
||||
|
||||
4. `extensions/piagentui-server.test.ts`
|
||||
- endpoints STT;
|
||||
- validação de config;
|
||||
- proxy OpenAI-compatible;
|
||||
- realtime session lifecycle com mocks.
|
||||
|
||||
## Plano de implementação recomendado
|
||||
|
||||
1. Atualizar `README.md` com foco PiAgentUI atual.
|
||||
2. Adicionar uma seção curta no README para STT planejado.
|
||||
3. Criar plano TDD específico para STT antes de escrever produção.
|
||||
4. Implementar configuração STT.
|
||||
5. Implementar transcrição por arquivo OpenAI-compatible.
|
||||
6. Implementar botão de gravação e inserção no composer.
|
||||
7. Implementar realtime OpenAI como provider padrão.
|
||||
8. Adicionar fallback de arquivo para provedores sem realtime.
|
||||
9. Fazer verificação manual no browser/Tauri.
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
### README
|
||||
|
||||
- Não deve mais vender o projeto como OpenCodeUI.
|
||||
- Deve explicar claramente que o projeto atual é PiAgentUI.
|
||||
- Deve listar funcionalidades reais já implementadas.
|
||||
- Deve separar roadmap de funcionalidades existentes.
|
||||
- Deve incluir comandos de desenvolvimento corretos.
|
||||
|
||||
### STT
|
||||
|
||||
- Usuário consegue configurar provider padrão OpenAI.
|
||||
- Usuário consegue configurar provider externo OpenAI-compatible para `/audio/transcriptions`.
|
||||
- Usuário consegue gravar áudio pelo microfone.
|
||||
- Usuário vê transcrição parcial quando o provider realtime suporta.
|
||||
- Usuário recebe texto final no composer.
|
||||
- Auto-send fica desligado por padrão.
|
||||
- Falhas de permissão, rede e provider são visíveis e recuperáveis.
|
||||
- Testes cobrem API, store e UI principal.
|
||||
|
||||
## Fontes consultadas
|
||||
|
||||
- OpenAI Realtime transcription: https://developers.openai.com/api/docs/guides/realtime-transcription
|
||||
- OpenAI GPT-4o Transcribe: https://developers.openai.com/api/docs/models/gpt-4o-transcribe
|
||||
- OpenAI speech transcription methods cookbook: https://developers.openai.com/cookbook/examples/speech_transcription_methods
|
||||
- Together AI transcription docs: https://docs.together.ai/docs/inference/transcription/overview
|
||||
- Together AI Whisper Large v3: https://www.together.ai/models/openai-whisper-large-v3
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
# Spec — STT, acesso LAN/QR, sessões reais e histórico por projeto
|
||||
|
||||
**Data:** 2026-05-30
|
||||
**Status:** proposta técnica pronta para revisão do usuário antes de implementação
|
||||
**Produto:** PiAgentUI
|
||||
**Escopo:** implementar, com mudanças mínimas e TDD, quatro melhorias conectadas ao uso real do PiAgentUI:
|
||||
|
||||
1. sistema de áudio/STT no composer;
|
||||
2. exposição controlada do PiAgentUI na rede local com IP e QR Code;
|
||||
3. criação de novas sessões que realmente criam novos chats no Pi Agent;
|
||||
4. histórico lateral agrupado por pastas/projetos, expansível e recolhível.
|
||||
|
||||
## Contexto observado no projeto
|
||||
|
||||
Arquitetura atual relevante:
|
||||
|
||||
- Backend local da UI fica em `extensions/piagentui-server.ts`.
|
||||
- Núcleo de helpers seguros fica em `extensions/piagentui-server-core.ts`.
|
||||
- O servidor HTTP atualmente faz bind em `127.0.0.1` e valida `Host`/`Origin` como loopback-only.
|
||||
- `serveIndexHtml()` injeta `window.PI_BOOT_DATA` com `baseUrl: http://127.0.0.1:<port>` e bearer token local.
|
||||
- O discovery local é salvo em `~/.pi/agent/piagentui-port.json` com `{ port, token }`.
|
||||
- `POST /api/sessions` atualmente **não cria sessão real**: ele apenas retorna `getSessionInfo(getCurrentSessionId())`.
|
||||
- `GET /api/sessions` lê arquivos `.jsonl` do diretório de sessões e deriva metadados via `readSessionMetadata()`.
|
||||
- `readSessionMetadata()` já extrai `header.cwd`, título por `session_info` ou primeira mensagem de usuário, timestamps e contagem básica.
|
||||
- `sendUserMessageToRequestedSession()` já consegue trocar rapidamente para uma sessão solicitada via `latestCtx.switchSession(sessionFile, { withSession })` e enviar mensagem nela.
|
||||
- A UI de sessões está em `src/features/sessions/SessionList.tsx`.
|
||||
- O estado de sessões vem de `src/contexts/SessionContext.tsx` e `src/api/session.ts`.
|
||||
- O input/composer está em `src/features/chat/InputBox.tsx` e `src/features/chat/input/InputToolbar.tsx`.
|
||||
- Já existem anexos multimodais, incluindo áudio como arquivo quando o modelo suporta.
|
||||
- A store de servidores fica em `src/store/serverStore.ts` e já suporta múltiplos backends, bearer token, Basic Auth e health checks.
|
||||
|
||||
## Objetivos
|
||||
|
||||
### Objetivo 1 — STT no composer
|
||||
|
||||
Adicionar entrada por voz sem substituir o fluxo de texto:
|
||||
|
||||
- botão de microfone no composer;
|
||||
- gravação de áudio pelo navegador/Tauri webview;
|
||||
- transcrição por provider configurável;
|
||||
- inserção do texto transcrito no input;
|
||||
- fallback por arquivo via OpenAI-compatible `/audio/transcriptions`;
|
||||
- arquitetura preparada para realtime OpenAI depois, sem bloquear o MVP.
|
||||
|
||||
### Objetivo 2 — acesso LAN com QR Code
|
||||
|
||||
Permitir que o usuário acesse o PiAgentUI por outro dispositivo na mesma rede local:
|
||||
|
||||
- manter acesso LAN **desligado por padrão**;
|
||||
- quando ligado, mostrar IP local do computador, URL de acesso e QR Code;
|
||||
- não remover autenticação existente;
|
||||
- não expor sem aviso que o controle do agente fica disponível na rede local;
|
||||
- manter loopback funcionando normalmente.
|
||||
|
||||
### Objetivo 3 — novas sessões reais no Pi Agent
|
||||
|
||||
Corrigir o comportamento de “novo chat”:
|
||||
|
||||
- `New chat` deve criar um novo arquivo de sessão Pi `.jsonl`;
|
||||
- deve alternar rapidamente para essa sessão real;
|
||||
- enviar a primeira mensagem nessa sessão deve persistir no novo arquivo, não apenas simular um chat novo na UI;
|
||||
- o retorno de `POST /api/sessions` deve conter o id real, diretório real e metadata real.
|
||||
|
||||
### Objetivo 4 — histórico por pastas/projetos
|
||||
|
||||
Agrupar sessões no histórico lateral por projeto:
|
||||
|
||||
- cada pasta representa um `cwd`/diretório de projeto vindo do header da sessão;
|
||||
- nome visual da pasta = basename do diretório, com fallback legível;
|
||||
- pastas podem expandir/recolher;
|
||||
- sessões ficam dentro da pasta do projeto;
|
||||
- busca continua funcionando;
|
||||
- seleção e drag/drop de sessão continuam funcionando;
|
||||
- sem reorganizar o backend inteiro de sessões.
|
||||
|
||||
## Não objetivos
|
||||
|
||||
- Não implementar TTS/voz falada nesta fase.
|
||||
- Não criar sistema multiusuário, login ou permissões finas.
|
||||
- Não abrir o servidor LAN por padrão.
|
||||
- Não publicar automaticamente a extensão.
|
||||
- Não renomear todo o legado `opencode`/OpenCodeUI agora.
|
||||
- Não refatorar `SessionList.tsx` inteiro além do necessário para agrupamento por projeto.
|
||||
- Não alterar arquitetura de pane layout, terminal, file explorer ou MCP além dos pontos necessários.
|
||||
|
||||
## Decisões recomendadas
|
||||
|
||||
### A. STT em duas fases, mas com base única
|
||||
|
||||
**Recomendado:** implementar primeiro `file transcription`, com tipos e UI já preparados para `realtime`.
|
||||
|
||||
Motivo:
|
||||
|
||||
- é mais rápido e testável;
|
||||
- funciona com OpenAI e provedores OpenAI-compatible;
|
||||
- evita acoplar a primeira entrega a WebSocket realtime;
|
||||
- mantém o caminho aberto para `gpt-realtime-whisper`.
|
||||
|
||||
Configuração inicial:
|
||||
|
||||
```ts
|
||||
export type SttMode = 'file' | 'realtime'
|
||||
export type SttProviderKind = 'openai' | 'openai-compatible'
|
||||
|
||||
export interface SttSettings {
|
||||
enabled: boolean
|
||||
providerKind: SttProviderKind
|
||||
mode: SttMode
|
||||
baseUrl: string
|
||||
apiKeyConfigured: boolean
|
||||
transcriptionEndpoint: string
|
||||
transcriptionModel: string
|
||||
language?: string
|
||||
insertMode: 'append' | 'replace'
|
||||
}
|
||||
```
|
||||
|
||||
O segredo real da API key não deve ser retornado ao frontend depois de salvo. A UI recebe apenas `apiKeyConfigured: true/false`.
|
||||
|
||||
### B. Settings STT server-side local
|
||||
|
||||
**Recomendado:** salvar configuração STT em arquivo local do usuário:
|
||||
|
||||
```text
|
||||
~/.pi/agent/piagentui-settings.json
|
||||
```
|
||||
|
||||
Estrutura mínima:
|
||||
|
||||
```json
|
||||
{
|
||||
"stt": {
|
||||
"enabled": true,
|
||||
"providerKind": "openai-compatible",
|
||||
"mode": "file",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKey": "...",
|
||||
"transcriptionEndpoint": "/audio/transcriptions",
|
||||
"transcriptionModel": "gpt-4o-mini-transcribe",
|
||||
"language": "pt",
|
||||
"insertMode": "append"
|
||||
},
|
||||
"network": {
|
||||
"lanAccessEnabled": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Motivo:
|
||||
|
||||
- evita persistir API key no localStorage do browser;
|
||||
- permite LAN sem expor secrets no `window.PI_BOOT_DATA`;
|
||||
- mantém tudo local e simples;
|
||||
- evita banco de dados ou integração de credenciais complexa.
|
||||
|
||||
### C. LAN por bind amplo + validação dinâmica
|
||||
|
||||
**Recomendado:** o servidor pode escutar em `0.0.0.0`, mas responder a clientes não-loopback apenas quando `network.lanAccessEnabled === true`.
|
||||
|
||||
Mudanças necessárias:
|
||||
|
||||
- `listenOnAvailablePort(server, DEFAULT_PORT, '0.0.0.0')`;
|
||||
- substituir validação loopback-only por função configurável:
|
||||
- loopback sempre permitido;
|
||||
- LAN permitido apenas se `lanAccessEnabled`;
|
||||
- `Origin` deve bater com host loopback ou LAN permitido;
|
||||
- `serveIndexHtml(req, res)` deve montar `baseUrl` a partir de `req.headers.host`, não sempre `127.0.0.1`;
|
||||
- endpoint `GET /api/network/access` retorna estado, IPs e URLs;
|
||||
- endpoint `POST /api/network/access` liga/desliga LAN.
|
||||
|
||||
Acesso LAN expõe controle do agente para quem estiver na rede e conseguir acessar a URL. A UI deve mostrar warning explícito.
|
||||
|
||||
### D. QR Code no frontend
|
||||
|
||||
**Recomendado:** gerar o QR Code no frontend a partir da URL LAN ativa.
|
||||
|
||||
Opções:
|
||||
|
||||
1. usar dependência pequena `qrcode`;
|
||||
2. implementar QR manualmente;
|
||||
3. usar serviço externo.
|
||||
|
||||
Decisão: usar `qrcode` se não houver lib existente. É a solução mais simples, testável e offline. Evitar serviço externo por privacidade.
|
||||
|
||||
### E. Criar sessão real sem mexer no core do Pi
|
||||
|
||||
O contexto regular da extensão tem `switchSession`, mas `newSession()` fica restrito ao command context do Pi. Para não mexer no core do Pi agora, o backend do PiAgentUI pode criar o arquivo `.jsonl` compatível com `SessionManager.newSession()` e depois chamar `latestCtx.switchSession(newSessionFile, { withSession })`.
|
||||
|
||||
Header da sessão compatível:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session",
|
||||
"version": 3,
|
||||
"id": "<novo-id>",
|
||||
"timestamp": "<iso-date>",
|
||||
"cwd": "<diretorio-do-projeto>",
|
||||
"parentSession": "<opcional>"
|
||||
}
|
||||
```
|
||||
|
||||
Fluxo:
|
||||
|
||||
1. receber `POST /api/sessions` com `{ directory?, title?, parentID? }`;
|
||||
2. resolver `cwd = directory || latestCtx.cwd || process.cwd()`;
|
||||
3. gerar id válido e filename com timestamp;
|
||||
4. escrever arquivo `.jsonl` com header;
|
||||
5. se `title` existir, opcionalmente adicionar entry `session_info` compatível com o parser atual;
|
||||
6. trocar rapidamente para o novo arquivo com `latestCtx.switchSession()`;
|
||||
7. atualizar `latestCtx` no `withSession`;
|
||||
8. retornar metadata real lida de `readSessionMetadata()`.
|
||||
|
||||
Se `switchSession` não estiver disponível, retornar `501` em vez de fingir sucesso.
|
||||
|
||||
### F. Histórico por projeto no frontend
|
||||
|
||||
**Recomendado:** agrupar no frontend usando `session.directory` já retornado pelo backend.
|
||||
|
||||
Criar helper puro:
|
||||
|
||||
```text
|
||||
src/features/sessions/sessionProjectGroups.ts
|
||||
```
|
||||
|
||||
Responsabilidades:
|
||||
|
||||
- normalizar paths Windows/Linux;
|
||||
- extrair nome do projeto pelo basename;
|
||||
- criar chave estável por diretório normalizado;
|
||||
- ordenar grupos por `max(session.time.updated)` desc;
|
||||
- ordenar sessões dentro do grupo por `time.updated` desc;
|
||||
- manter busca como modo flat ou grupos filtrados.
|
||||
|
||||
A UI pode adicionar modo `groupedByProject?: boolean` em `SessionList`, preservando o agrupamento temporal antigo se necessário.
|
||||
|
||||
Estado de expansão:
|
||||
|
||||
- store local leve em `localStorage`, por exemplo `piagentui-session-project-folders`;
|
||||
- padrão: pasta do projeto atual expandida; demais recolhidas;
|
||||
- se busca ativa, expandir todos os grupos com match.
|
||||
|
||||
## Endpoints propostos
|
||||
|
||||
### STT
|
||||
|
||||
#### `GET /api/settings/stt`
|
||||
|
||||
Retorna settings redigidos:
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"providerKind": "openai-compatible",
|
||||
"mode": "file",
|
||||
"baseUrl": "https://api.openai.com/v1",
|
||||
"apiKeyConfigured": true,
|
||||
"transcriptionEndpoint": "/audio/transcriptions",
|
||||
"transcriptionModel": "gpt-4o-mini-transcribe",
|
||||
"language": "pt",
|
||||
"insertMode": "append"
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/settings/stt`
|
||||
|
||||
Atualiza settings. Se `apiKey` vier vazia/ausente, mantém chave anterior; se vier `null`, remove.
|
||||
|
||||
#### `POST /api/stt/transcriptions`
|
||||
|
||||
Recebe áudio como `multipart/form-data`, chama provider configurado e retorna:
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "texto transcrito",
|
||||
"durationMs": 1234,
|
||||
"provider": "openai-compatible",
|
||||
"model": "gpt-4o-mini-transcribe"
|
||||
}
|
||||
```
|
||||
|
||||
### LAN/QR
|
||||
|
||||
#### `GET /api/network/access`
|
||||
|
||||
```json
|
||||
{
|
||||
"lanAccessEnabled": false,
|
||||
"port": 58785,
|
||||
"localUrl": "http://127.0.0.1:58785",
|
||||
"lanUrls": ["http://192.168.0.10:58785"],
|
||||
"primaryLanUrl": "http://192.168.0.10:58785"
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /api/network/access`
|
||||
|
||||
```json
|
||||
{ "lanAccessEnabled": true }
|
||||
```
|
||||
|
||||
Retorna o mesmo shape do `GET`.
|
||||
|
||||
### Sessões
|
||||
|
||||
#### `POST /api/sessions`
|
||||
|
||||
Novo comportamento: cria sessão real.
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"directory": "D:/Documents/Github/PiAgentUI/PiAgentUi",
|
||||
"title": "Nova conversa",
|
||||
"parentID": "opcional"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "019...",
|
||||
"title": "Nova conversa",
|
||||
"directory": "D:/Documents/Github/PiAgentUI/PiAgentUi",
|
||||
"time": { "created": 123, "updated": 123 },
|
||||
"status": { "type": "idle" },
|
||||
"summary": { "deletions": 0, "files": 0, "additions": 0 }
|
||||
}
|
||||
```
|
||||
|
||||
## Componentes/frontend propostos
|
||||
|
||||
### STT
|
||||
|
||||
Criar:
|
||||
|
||||
- `src/api/stt.ts` — chamadas de settings e transcrição;
|
||||
- `src/types/api/stt.ts` — tipos de settings/resposta;
|
||||
- `src/hooks/useVoiceTranscription.ts` — MediaRecorder, estados e envio para backend;
|
||||
- `src/features/chat/input/VoiceInputButton.tsx` — botão mic/stop/loading/error;
|
||||
- alterações pontuais em `InputBox.tsx` e `InputToolbar.tsx` para inserir texto transcrito.
|
||||
|
||||
Estados mínimos:
|
||||
|
||||
```ts
|
||||
type VoiceState =
|
||||
| 'idle'
|
||||
| 'requesting-permission'
|
||||
| 'recording'
|
||||
| 'transcribing'
|
||||
| 'error'
|
||||
```
|
||||
|
||||
### LAN/QR
|
||||
|
||||
Criar:
|
||||
|
||||
- `src/api/network.ts`;
|
||||
- `src/types/api/network.ts`;
|
||||
- `src/features/settings/components/NetworkAccessSettings.tsx` ou adicionar seção pequena em `ServersSettings.tsx`;
|
||||
- QR gerado a partir de `primaryLanUrl`.
|
||||
|
||||
Como o app já tem `ServersSettings.tsx`, a mudança mínima é adicionar uma seção “Local network access” dentro dele, sem criar novo painel de settings se não for necessário.
|
||||
|
||||
### Sessões por projeto
|
||||
|
||||
Criar:
|
||||
|
||||
- `src/features/sessions/sessionProjectGroups.ts`;
|
||||
- testes unitários para grouping;
|
||||
- alterar `SessionList.tsx` para renderizar project folders quando `groupedByProject` estiver ativo;
|
||||
- adicionar traduções apenas nos namespaces já usados (`chat`/`commands`/`common`) conforme necessário.
|
||||
|
||||
## Segurança
|
||||
|
||||
- LAN fica desligado por padrão.
|
||||
- Loopback continua sempre permitido.
|
||||
- API continua exigindo bearer token.
|
||||
- O HTML servido injeta o token apenas para o cliente que abriu a UI; isso significa que qualquer pessoa com acesso à URL LAN durante LAN enabled pode controlar o agente. A UI deve deixar isso claro.
|
||||
- STT API key não deve aparecer em resposta de settings.
|
||||
- Não logar API key.
|
||||
- Não aceitar `baseUrl` sem `http:` ou `https:`.
|
||||
- Limitar upload STT por tamanho. Sugestão inicial: 25 MB.
|
||||
- Reusar `isRequestBodySizeAllowed` ou criar limite específico para multipart.
|
||||
- Não permitir path traversal ou file reads novos.
|
||||
|
||||
## Plano de testes obrigatório
|
||||
|
||||
### Backend extension
|
||||
|
||||
Arquivo: `extensions/piagentui-server.test.ts`
|
||||
|
||||
Casos:
|
||||
|
||||
1. `POST /api/sessions` cria arquivo `.jsonl` com header `type=session`, `version=3`, `cwd` correto e id novo.
|
||||
2. `POST /api/sessions` chama `switchSession()` com `withSession` e retorna metadata da sessão nova.
|
||||
3. `POST /api/sessions` retorna `501` quando `switchSession` indisponível, sem fingir sucesso.
|
||||
4. `GET /api/network/access` retorna loopback URL e lista de IPs LAN mockável.
|
||||
5. LAN disabled rejeita host não-loopback.
|
||||
6. LAN enabled aceita host LAN com bearer token.
|
||||
7. `serveIndexHtml()` injeta `baseUrl` baseado no host da request.
|
||||
8. `GET /api/settings/stt` redige `apiKey`.
|
||||
9. `POST /api/settings/stt` salva settings e preserva/remover chave conforme contrato.
|
||||
10. `POST /api/stt/transcriptions` valida provider config e trata erro do provider sem vazar segredo.
|
||||
|
||||
Arquivo: `extensions/piagentui-server-core.test.ts`
|
||||
|
||||
Casos:
|
||||
|
||||
1. `isLoopbackHost()` preserva comportamento atual.
|
||||
2. nova validação aceita LAN apenas quando enabled.
|
||||
3. `getLocalNetworkUrls()` ignora loopback, IPv6 local problemático e interfaces internas inválidas.
|
||||
4. validação de URL STT recusa protocolos não HTTP(S).
|
||||
|
||||
### Frontend API/hooks
|
||||
|
||||
Arquivos novos:
|
||||
|
||||
- `src/api/stt.test.ts`;
|
||||
- `src/api/network.test.ts`;
|
||||
- `src/hooks/useVoiceTranscription.test.tsx`;
|
||||
- `src/features/sessions/sessionProjectGroups.test.ts`.
|
||||
|
||||
Casos:
|
||||
|
||||
1. API STT envia/recebe shape correto.
|
||||
2. API network chama endpoints corretos.
|
||||
3. hook de voz passa por `idle -> recording -> transcribing -> idle` e chama callback com texto final.
|
||||
4. hook trata permissão negada.
|
||||
5. grouping por projeto cria pastas corretas para paths Windows e POSIX.
|
||||
6. grouping ordena pastas por sessão mais recente.
|
||||
7. grouping mantém sessões ordenadas por update.
|
||||
|
||||
### Componentes
|
||||
|
||||
Arquivos prováveis:
|
||||
|
||||
- `src/features/chat/input/VoiceInputButton.test.tsx`;
|
||||
- `src/features/settings/components/ServersSettings.test.tsx` ou novo test de `NetworkAccessSettings`;
|
||||
- `src/features/sessions/SessionList.test.tsx`.
|
||||
|
||||
Casos:
|
||||
|
||||
1. botão de microfone mostra estados e dispara start/stop.
|
||||
2. seção LAN mostra URL e QR quando enabled.
|
||||
3. SessionList renderiza pastas por projeto, expande/recolhe e preserva seleção.
|
||||
|
||||
## Ordem de implementação recomendada
|
||||
|
||||
1. **Sessões reais primeiro** — corrige fundação crítica de chat.
|
||||
2. **Agrupamento por projeto** — depende de sessões reais/metadata confiável.
|
||||
3. **LAN/QR** — isolado, mas tem implicações de segurança.
|
||||
4. **STT file transcription** — maior superfície; deve entrar depois da base de rede/sessão estar sólida.
|
||||
5. **Realtime STT** — fase seguinte, usando o contrato já criado.
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
### Sessões reais
|
||||
|
||||
- Clicar em New chat cria um novo arquivo `.jsonl` de sessão Pi.
|
||||
- O id retornado pelo backend é diferente da sessão anterior.
|
||||
- Enviar mensagem após criar novo chat persiste nessa nova sessão.
|
||||
- Troca de sessão continua rápida e não perde SSE.
|
||||
|
||||
### Histórico por projeto
|
||||
|
||||
- Sidebar mostra pastas por projeto.
|
||||
- Cada pasta contém sessões daquele `cwd`.
|
||||
- Expand/recolher funciona.
|
||||
- Busca ainda localiza sessões.
|
||||
- Selecionar sessão dentro da pasta navega para o chat correto.
|
||||
|
||||
### LAN/QR
|
||||
|
||||
- Com LAN off, acesso por IP LAN recebe 403.
|
||||
- Com LAN on, URL LAN abre a UI em outro dispositivo da rede.
|
||||
- QR Code aponta para a URL LAN ativa.
|
||||
- API continua exigindo token.
|
||||
- UI mostra aviso de segurança.
|
||||
|
||||
### STT
|
||||
|
||||
- Usuário consegue gravar áudio e transcrever para texto.
|
||||
- Texto final entra no composer sem auto-send.
|
||||
- Provider OpenAI-compatible é configurável.
|
||||
- API key não aparece em respostas/logs.
|
||||
- Erros de permissão/rede/provider são visíveis e recuperáveis.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
| Risco | Mitigação |
|
||||
| --- | --- |
|
||||
| LAN expõe controle total do agente | default off, warning explícito, bearer obrigatório, permitir desligar rápido |
|
||||
| Criar sessão manualmente diverge do Pi core | usar header exatamente compatível com `SessionManager.newSession()`; testes contra parser atual |
|
||||
| `switchSession` indisponível em runtime antigo | retornar `501`, não simular sucesso |
|
||||
| QR exige dependência nova | usar `qrcode` pequena e isolada; se rejeitado, renderizar URL primeiro e QR depois |
|
||||
| STT com CORS se chamado direto do frontend | chamadas a providers sempre pelo backend local |
|
||||
| API key em LAN | armazenar server-side local, responder apenas `apiKeyConfigured` |
|
||||
| `SessionList.tsx` já é grande | extrair grouping puro para arquivo separado; modificar renderização o mínimo possível |
|
||||
|
||||
## Arquivos previstos
|
||||
|
||||
### Criar
|
||||
|
||||
- `src/api/stt.ts`
|
||||
- `src/api/stt.test.ts`
|
||||
- `src/types/api/stt.ts`
|
||||
- `src/api/network.ts`
|
||||
- `src/api/network.test.ts`
|
||||
- `src/types/api/network.ts`
|
||||
- `src/hooks/useVoiceTranscription.ts`
|
||||
- `src/hooks/useVoiceTranscription.test.tsx`
|
||||
- `src/features/chat/input/VoiceInputButton.tsx`
|
||||
- `src/features/chat/input/VoiceInputButton.test.tsx`
|
||||
- `src/features/sessions/sessionProjectGroups.ts`
|
||||
- `src/features/sessions/sessionProjectGroups.test.ts`
|
||||
|
||||
### Modificar
|
||||
|
||||
- `extensions/piagentui-server.ts`
|
||||
- `extensions/piagentui-server.test.ts`
|
||||
- `extensions/piagentui-server-core.ts`
|
||||
- `extensions/piagentui-server-core.test.ts`
|
||||
- `src/features/chat/InputBox.tsx`
|
||||
- `src/features/chat/input/InputToolbar.tsx`
|
||||
- `src/features/sessions/SessionList.tsx`
|
||||
- `src/features/sessions/SessionList.test.tsx`
|
||||
- `src/features/settings/components/ServersSettings.tsx`
|
||||
- `src/store/serverStore.ts` apenas se necessário para refletir LAN URL ativa
|
||||
- `src/locales/en/*.json` e `src/locales/zh-CN/*.json` para labels mínimos
|
||||
- `package.json` se a dependência `qrcode` for adotada
|
||||
|
||||
## Abordagens consideradas
|
||||
|
||||
### Abordagem 1 — implementar tudo no frontend
|
||||
|
||||
Rejeitada. STT direto do browser expõe API keys, sofre com CORS e não combina com LAN.
|
||||
|
||||
### Abordagem 2 — mexer no core do Pi para expor `newSession` ao extension context
|
||||
|
||||
Possível, mas maior. Já fizemos patch de `switchSession`; para este pacote o objetivo é mudança mínima no PiAgentUI. Só considerar se criação manual de sessão se mostrar incompatível.
|
||||
|
||||
### Abordagem 3 — backend PiAgentUI como orquestrador local
|
||||
|
||||
Recomendada. Mantém mudanças dentro do PiAgentUI, usa `switchSession` já disponível, preserva segurança local e permite testes focados.
|
||||
|
||||
## Gate de implementação
|
||||
|
||||
Esta spec cobre múltiplos subsistemas e altera segurança/rede/sessão. A implementação deve começar somente após aprovação explícita do usuário sobre este design.
|
||||
|
||||
Após aprovação, criar plano em:
|
||||
|
||||
```text
|
||||
docs/superpowers/plans/2026-05-30-piagentui-stt-lan-real-sessions-project-folders.md
|
||||
```
|
||||
|
||||
O plano deve usar TDD e commits pequenos por feature:
|
||||
|
||||
1. sessões reais;
|
||||
2. agrupamento por projeto;
|
||||
3. LAN/QR;
|
||||
4. STT settings + file transcription;
|
||||
5. voice UI;
|
||||
6. validação final e revisão de código.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Design — Controle Git no composer do PiAgentUI
|
||||
|
||||
## Objetivo
|
||||
|
||||
Adicionar à bolha de envio de mensagens um controle Git compacto, localizado no canto inferior esquerdo da toolbar do composer, mostrando a branch atual e abrindo um popover com branches e operações Git principais.
|
||||
|
||||
## Direção visual aprovada
|
||||
|
||||
- O controle aparece como um chip integrado à `InputToolbar`, antes dos seletores de agente e variante.
|
||||
- O chip usa iconografia GitHub/Octicons, texto monoespaçado, estado dirty e chevron.
|
||||
- Ao clicar, abre um popover acima da bolha de input, alinhado ao chip, seguindo o padrão visual de `DropdownMenu`: glass, borda sutil, radius alto e sombra do app.
|
||||
- O popover tem duas áreas:
|
||||
- lista de branches local/remota e ação de nova branch;
|
||||
- status, ações Git e área dedicada de commit.
|
||||
- As ações principais usam ícones: branch, commit, diff/status, pull, push, refresh, stage/unstage, arquivos e alertas.
|
||||
|
||||
## Escopo MVP seguro
|
||||
|
||||
Inclui:
|
||||
|
||||
- carregar branch atual, branches locais/remotas e status resumido;
|
||||
- trocar branch local/remota;
|
||||
- criar branch a partir da atual;
|
||||
- listar arquivos alterados;
|
||||
- stage/unstage arquivo;
|
||||
- stage all/unstage all;
|
||||
- commit com textarea dedicada;
|
||||
- pull e push explícitos;
|
||||
- refresh de status;
|
||||
- mensagens de erro e loading.
|
||||
|
||||
Não inclui no MVP:
|
||||
|
||||
- merge manual;
|
||||
- rebase;
|
||||
- reset hard;
|
||||
- revert;
|
||||
- stash;
|
||||
- force push;
|
||||
- edição interativa de hunks.
|
||||
|
||||
## Segurança e comportamento
|
||||
|
||||
- Backend executa Git via `spawn`/`execFile` com argv fixo, nunca shell arbitrário.
|
||||
- Diretório vem do projeto atual e precisa ser repositório Git válido.
|
||||
- Commit exige mensagem não vazia e pelo menos um arquivo staged.
|
||||
- Pull/push são ações explícitas no popover.
|
||||
- Erros do Git são retornados como mensagens sanitizadas.
|
||||
- Operações atualizam status e branch após sucesso.
|
||||
|
||||
## Componentes
|
||||
|
||||
- `GitComposerControl`: chip + popover Git do composer.
|
||||
- `src/api/vcs.ts`: funções de API para status, branches e ações.
|
||||
- `src/types/api/vcs.ts`: contratos frontend/backend.
|
||||
- `extensions/piagentui-git-manager.ts`: camada backend focada em Git.
|
||||
- `extensions/piagentui-server.ts`: endpoints `/api/vcs/*`.
|
||||
|
||||
## Endpoints planejados
|
||||
|
||||
- `GET /api/vcs/info`
|
||||
- `GET /api/vcs/branches`
|
||||
- `GET /api/vcs/status`
|
||||
- `POST /api/vcs/checkout`
|
||||
- `POST /api/vcs/branch`
|
||||
- `POST /api/vcs/stage`
|
||||
- `POST /api/vcs/unstage`
|
||||
- `POST /api/vcs/commit`
|
||||
- `POST /api/vcs/pull`
|
||||
- `POST /api/vcs/push`
|
||||
|
||||
## Testes
|
||||
|
||||
- Testes unitários do git manager com runner fake.
|
||||
- Testes de API frontend (`src/api/vcs.test.ts`).
|
||||
- Teste de componente para renderizar branch, abrir popover, preencher commit e disparar ações.
|
||||
- Testes de servidor para endpoints principais com mocks.
|
||||
|
||||
## Referência de ícones
|
||||
|
||||
Usar `@primer/octicons-react`, pacote oficial do Primer/GitHub Octicons. A documentação oficial mostra importação direta, por exemplo:
|
||||
|
||||
```ts
|
||||
import { GitCommitIcon } from '@primer/octicons-react'
|
||||
```
|
||||
|
||||
Fonte consultada via Context7: `https://primer.style/octicons/icon/git-commit-24` e `https://primer.style/octicons/code`.
|
||||
Reference in new issue
Block a user