wip: snapshot das alterações não commitadas (2026-10-20)

Origem: D:/Documents/Github/PiAgentUI/PiAgentUi
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 4
This commit is contained in:
Evandro committed 2026-10-20 00:48:28 -03:00
1 parent b3489e1ede
commit 98b5db029f
70 files changed
+9466 -9191

No files matched your search

@@ -0,0 +1,225 @@
# Design — STT realtime no composer do PiAgentUI
## Objetivo
Implementar transcrição de fala em tempo real no PiAgentUI: enquanto o usuário fala, o texto aparece diretamente no campo de mensagem; ao parar, o texto permanece editável e pode ser enviado manualmente.
## Escopo aprovado
A abordagem aprovada é **Backend realtime**.
Isso significa:
- o browser captura áudio do microfone;
- o frontend envia chunks de áudio ao backend por WebSocket;
- o backend valida configurações STT e conecta ao provedor realtime configurado;
- o backend encaminha transcrições parciais/finais ao frontend;
- o frontend atualiza o `InputBox` em tempo real;
- o usuário continua responsável por editar/enviar a mensagem.
Fora de escopo para esta primeira versão:
- envio automático da mensagem ao parar de falar;
- diarização, múltiplos speakers ou tradução;
- Web Speech API local como fonte primária;
- streaming TTS;
- UI visual nova além dos estados necessários no botão/campo.
## Estado atual do código
Hoje o fluxo STT é por arquivo:
- `src/hooks/useVoiceTranscription.ts` usa `MediaRecorder` e só transcreve no `onstop`.
- `src/api/stt.ts` envia `POST /api/stt/transcriptions` com `FormData`.
- `extensions/piagentui-server.ts` proxy a requisição para `baseUrl + transcriptionEndpoint`.
- `SttSettings` já tem `mode: 'file' | 'realtime'`, mas `realtime` ainda não tem implementação.
- `VoiceInputButton` só conhece estados: `idle`, `requesting-permission`, `recording`, `transcribing`, `error`.
- `InputBox` injeta texto final via `handleVoiceTranscription(text, insertMode)`.
## Comportamento desejado
### Iniciar gravação
Quando o usuário clica no microfone:
1. o hook recarrega settings com `getSttSettings()`;
2. se `settings.mode === 'realtime'`, abre WebSocket para `/api/stt/realtime`;
3. ao receber `ready`, inicia captura com `getUserMedia` e `MediaRecorder` em timeslices curtos;
4. o botão muda para estado de gravação/stop.
### Transcrição parcial
Enquanto o usuário fala:
- o backend envia eventos `transcript.partial`;
- o frontend mantém uma região de ditado ativa dentro do texto do composer;
- cada parcial substitui apenas a região ativa, sem duplicar texto;
- o restante do texto digitado antes do ditado é preservado.
### Transcrição final
Quando o provedor envia `transcript.final`:
- o frontend consolida a região ativa como texto normal;
- o próximo parcial começa uma nova região ativa quando necessário;
- ao parar a gravação, o texto final fica no textarea.
### Parar gravação
Quando o usuário clica em stop:
1. o hook para o `MediaRecorder`;
2. envia `audio.commit` para o backend;
3. fecha tracks do microfone;
4. aguarda finalizações curtas do backend;
5. fecha WebSocket;
6. retorna para `idle` mantendo o texto no campo.
## Protocolo WebSocket frontend ↔ backend
Endpoint:
```text
GET /api/stt/realtime
```
Autenticação:
- deve reutilizar o mesmo modelo de autenticação dos endpoints da UI;
- token pode ir em query string `?token=...` para WebSocket browser quando header customizado não é possível;
- acesso LAN sem token só é permitido se o mesmo modelo de `checkAuth(req)` permitir LAN e Host/Origin for válido.
Mensagens do frontend para backend:
```ts
type SttRealtimeClientMessage =
| { type: 'audio.append'; audio: string }
| { type: 'audio.commit' }
| { type: 'session.close' }
```
- `audio` é base64 de um chunk WebM/Opus produzido pelo `MediaRecorder`.
- `audio.commit` indica que o usuário parou de falar.
- `session.close` força cleanup.
Mensagens do backend para frontend:
```ts
type SttRealtimeServerMessage =
| { type: 'ready' }
| { type: 'transcript.partial'; text: string }
| { type: 'transcript.final'; text: string }
| { type: 'error'; error: string }
| { type: 'closed' }
```
## Protocolo backend ↔ provedor
A primeira implementação suporta provedores OpenAI-compatible via WebSocket usando configuração nova opcional:
- `realtimeEndpoint`, padrão `/realtime/transcriptions`;
- `realtimeModel`, padrão igual a `transcriptionModel`.
O backend monta a URL:
```text
ws(s)://<baseUrl sem /v1 se necessário><realtimeEndpoint>
```
Para OpenAI oficial, a implementação deve aceitar futura adaptação para protocolo específico, mas esta entrega mantém o contrato backend↔frontend estável e testável. Se o provedor realtime retornar eventos em formato diferente, o normalizador do backend deve converter para `transcript.partial` e `transcript.final`.
## Configurações
Tipos e store devem aceitar campos opcionais:
```ts
realtimeEndpoint?: string
realtimeModel?: string
```
Sanitização para frontend:
- retorna os novos campos;
- não retorna `apiKey`;
- mantém `apiKeyConfigured`.
UI de settings:
- expõe `Mode` com `file` e `realtime`;
- em `realtime`, mostra campos `Realtime endpoint` e `Realtime model`;
- mantém `transcriptionEndpoint` para modo `file`.
## Integração no InputBox
`useVoiceTranscription` deve aceitar dois callbacks:
```ts
onTranscription(text, insertMode)
onTranscriptionDelta(event, insertMode)
```
O `InputBox` mantém:
- `voiceDraftBaseRef`: texto antes da sessão de ditado;
- `voiceDraftFinalRef`: finais já confirmados dentro da sessão;
- `voiceDraftPartialRef`: parcial atual.
Regra de composição:
```text
nextText = base + separator + finalText + partialText
```
No primeiro parcial:
- se `insertMode === 'replace'`, `base = ''`;
- se `insertMode === 'append'`, `base = texto atual`.
Ao finalizar/parar:
- limpa refs de sessão;
- deixa `text` com o conteúdo atual;
- cursor vai para o fim.
## Estados de erro
- STT desabilitado → erro no botão, sem abrir microfone.
- API key obrigatória ausente → erro no botão.
- Realtime não configurado → erro no botão.
- WebSocket backend falhou → erro no botão, microfone fechado.
- Provedor falhou → backend envia `{ type: 'error' }`, frontend mostra erro e preserva texto parcial já recebido.
## Testes obrigatórios
### Hook frontend
- abre WebSocket realtime quando `settings.mode === 'realtime'`;
- envia chunks `audio.append` enquanto grava;
- chama callback parcial ao receber `transcript.partial`;
- chama callback final ao receber `transcript.final`;
- envia `audio.commit` e fecha recursos ao parar.
### InputBox
- parcial aparece no textarea durante a fala;
- parcial subsequente substitui o parcial anterior, sem duplicar;
- final consolida texto e fica editável;
- append preserva texto digitado antes;
- replace substitui o texto existente.
### Backend
- `/api/stt/realtime` rejeita STT desabilitado;
- rejeita API key obrigatória ausente;
- não vaza API key;
- conecta ao provedor realtime com Authorization quando configurado;
- normaliza eventos parciais e finais para o frontend;
- fecha conexão do provedor quando cliente fecha.
## Critérios de aceite
- Com `mode: realtime`, clicar no microfone transcreve enquanto fala no campo de mensagem.
- Ao parar, o texto continua no campo e pode ser editado/enviado.
- O fluxo antigo por arquivo continua funcionando quando `mode: file`.
- Nenhuma chave de API é exposta ao frontend.
- Testes focados, typecheck e build passam.
@@ -0,0 +1,97 @@
# Spec: Tauri Pi Agent Integrated App
## Objective
Build PiAgentUI into a faster desktop-first Tauri app where creating a new agent session is near-instant from the user's perspective while Pi remains the authoritative runtime. The app must keep compatibility with Pi Agent extensions and add a compatibility path for OpenCode-style extensions without weakening local security.
Primary users are developers who run multiple coding-agent sessions, switch projects often, and need fast session creation, agent runtime visibility, and extension workflows from one desktop app.
## Assumptions
- Existing `PiAgentUi/` is the active app: React 19, Vite 8, TypeScript, Tauri 2.
- Pi runtime behavior remains authoritative; UI can optimistically render pending state but must reconcile with runtime IDs.
- Pi extension compatibility means preserving package `pi.extensions` discovery and current `extensions/piagentui-server.ts` behavior.
- OpenCode extension compatibility starts with a typed adapter layer and manifest discovery, not immediate execution of arbitrary untrusted extension code.
- No dependency or lockfile changes without explicit approval.
## Tech Stack
- Frontend: React 19, TypeScript 5.9, Tailwind CSS v4, Vite 8.
- Desktop: Tauri 2.
- Local backend: Pi extension server in `extensions/piagentui-server.ts`.
- Tests: Vitest and Testing Library.
- Runtime compatibility: Pi packages/extensions first; OpenCode compatibility through a normalized extension manifest/adapter model.
## Commands
- Dev: `npm run dev`
- Desktop dev: `npm run tauri dev`
- Targeted test: `npx vitest run <path>`
- Full app check: `npm run check`
- Typecheck: `npm run typecheck && npm run typecheck:extensions`
## Project Structure
- `src/features/sessions/` — fast session launch UX, optimistic session draft helpers, session route behavior.
- `src/store/` — session list/message state reconciliation.
- `src/api/` — typed client methods to call local PiAgentUI endpoints.
- `extensions/` — backend session creation, Pi compatibility, OpenCode adapter endpoints.
- `src-tauri/` — desktop shell, permissions, Tauri config.
- `docs/superpowers/specs/` — specs.
- `docs/superpowers/plans/` — implementation plans.
## Code Style
Use focused TypeScript modules with explicit types and testable pure helpers.
```ts
export interface OptimisticSessionDraftInput {
directory?: string
title?: string
parentID?: string
id?: string
now?: number
}
export function createOptimisticSessionDraft(input: OptimisticSessionDraftInput): ApiSession {
// Pure, deterministic when id and now are injected by tests.
}
```
## Testing Strategy
- TDD for every new behavior.
- Pure session/extension helpers get small Vitest unit tests.
- React integration changes get Testing Library tests.
- Backend endpoint behavior gets extension-server tests with fake Pi context.
- Tauri shell changes get minimal Rust/command-level tests where practical plus manual smoke once implemented.
## Boundaries
- Always: keep Pi runtime authoritative; add tests before behavior; avoid lockfile changes; keep secrets backend-only.
- Ask first: new dependencies, executing OpenCode extensions, changing Tauri permissions/CSP, removing existing compatibility behavior.
- Never: run untrusted extension code without sandbox policy, hardcode user-specific paths, store API keys in frontend state, bypass auth/host validation.
## Success Criteria
- New session action gives immediate visual feedback and navigates without a blank/grey wait state.
- Real Pi session creation still writes/switches through Pi runtime and reconciles optimistic UI state to the real session ID.
- Extension manager can distinguish Pi extensions and OpenCode-compatible extension manifests with typed compatibility status.
- Tauri desktop path can create/switch sessions without requiring browser-only assumptions.
- Existing tests pass for touched modules; new behavior has tests.
## Risks and Mitigations
- Session ID mismatch between optimistic UI and Pi runtime: store pending sessions under a prefixed temporary ID and atomically replace on success.
- Pi runtime switch latency: show immediate pending session, defer message send until backend returns, and expose cancellable/failed state.
- Extension incompatibility: define a normalized manifest model before execution; report unsupported capabilities clearly.
- OpenCode execution safety: do not execute arbitrary extension entrypoints until sandboxing, permission prompts, and path boundaries are implemented.
- Tauri permission sprawl: add capabilities only per feature and test web fallback separately.
- Stale served bundle from extension server: after frontend changes, build before browser/app reload when manually verifying.
- Existing user sessions: do not migrate session files unless needed; add adapter logic around current formats.
- Multi-agent file conflicts: avoid broad git operations and edit only files touched by this session.
## Open Questions
- Exact OpenCode extension manifest sources and execution semantics need confirmation before execution support.
- Visual direction is not finalized because `.impeccable.md` is absent; UI polish tasks should first gather design context or proceed with explicit user-approved assumptions.