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:
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.
|
||||
Reference in new issue
Block a user