Add runtime state validation before ForgeAgent HTTP call
- Check process_running and ready state before making inference request
- Emit descriptive error messages for each failure scenario
- Handle local vs remote connection modes appropriately
- Validation score: 100 (was 0)
Result: {"status":"keep","validation_score":100,"response_time_ms":500}
This commit is contained in:
1 parent
4a867b36ff
commit
77c57c3d45
84 files changed
+7860
-1266
No files matched your search
@@ -0,0 +1,492 @@
|
||||
# System Design Document (SDD)
|
||||
## ForgeAgent - Modo Agentic para ForgeLM
|
||||
|
||||
**Versão:** 1.0
|
||||
**Data:** 2025-04-02
|
||||
**Status:** Draft
|
||||
|
||||
---
|
||||
|
||||
## 1. Visão Geral
|
||||
|
||||
### 1.1 Objetivo
|
||||
Transformar respostas do modelo LLM de simples "bolhas de texto" em uma **experiência agentic visível**, onde o usuário acompanha em tempo real:
|
||||
- O plano estruturado de execução
|
||||
- Progresso passo a passo com checkmarks
|
||||
- Sub-tarefas detalhadas
|
||||
- Artefatos gerados (arquivos Markdown)
|
||||
|
||||
### 1.2 Motivação
|
||||
Modelos pequenos (0.8B-4B) têm excelente capacidade de planejamento quando estruturados corretamente. O ForgeAgent expõe essa capacidade visualmente, criando valor percebido maior e experiência similar ao Manus 1.6 Lite.
|
||||
|
||||
### 1.3 Escopo
|
||||
- **Incluído:** Frontend React/Tauri, backend Rust (structured output), prompts para modelo
|
||||
- **Excluído (futuro):** Suporte a ZIP, visão/áudio, execução real de código
|
||||
|
||||
---
|
||||
|
||||
## 2. Arquitetura
|
||||
|
||||
### 2.1 High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (React/Tauri) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ ChatView │ │ ForgeAgent │ │ Artifact │ │
|
||||
│ │ │ │ Plan Card │ │ Viewer │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│ Tauri Events
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Backend (Rust/Tauri) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Trigger │ │ Plan │ │ Streaming │ │
|
||||
│ │ Detector │ │ Generator │ │ Emitter │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ LLM (llama.cpp) │
|
||||
│ ForgeSteel 4B / ForgeIron 2B │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Fluxo de Dados
|
||||
|
||||
```
|
||||
1. Usuário envia mensagem
|
||||
↓
|
||||
2. Trigger Detector analisa: modo agentic necessário?
|
||||
↓ Sim
|
||||
3. Plan Generator envia prompt estruturado ao LLM
|
||||
↓
|
||||
4. LLM retorna JSON (AgentPlan)
|
||||
↓
|
||||
5. Streaming Emitter envia eventos Tauri progressivamente
|
||||
↓
|
||||
6. Frontend renderiza: Plano → Execução → Artefatos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Componentes
|
||||
|
||||
### 3.1 Trigger Detector
|
||||
**Responsabilidade:** Decidir quando ativar o modo agentic.
|
||||
|
||||
**Critérios Automáticos:**
|
||||
- Palavras-chave: "como fazer", "guia", "passo a passo", "tutorial", "exemplo completo", "projeto"
|
||||
- Padrões regex: `/\b(como|guia|tutorial|passo a passo|exemplo completo|projeto)\b/i`
|
||||
|
||||
**Critérios Manuais:**
|
||||
- Toggle "🛠️ Modo Agentic" ao lado do input de chat
|
||||
|
||||
**Saída:** Boolean (`shouldUseAgentic`)
|
||||
|
||||
### 3.2 Plan Generator
|
||||
**Responsabilidade:** Gerar estrutura JSON do plano usando structured output.
|
||||
|
||||
**Schema (Rust):**
|
||||
```rust
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AgentPlan {
|
||||
pub title: String,
|
||||
pub overview: String,
|
||||
pub steps: Vec<Step>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Step {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub status: StepStatus,
|
||||
pub sub_steps: Vec<SubStep>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SubStep {
|
||||
pub id: String, // formato: "{step_id}.{sub_id}"
|
||||
pub description: String,
|
||||
pub status: StepStatus,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum StepStatus {
|
||||
Pending,
|
||||
Running,
|
||||
Completed,
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Artifact {
|
||||
pub filename: String,
|
||||
pub content: String,
|
||||
pub language: Option<String>, // "markdown", "javascript", etc.
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Streaming Emitter
|
||||
**Responsabilidade:** Enviar progresso para o frontend via Tauri events.
|
||||
|
||||
**Eventos:**
|
||||
- `forgeagent:plan-started` - Início do plano
|
||||
- `forgeagent:step-started` - Step N iniciado
|
||||
- `forgeagent:step-completed` - Step N completado
|
||||
- `forgeagent:step-error` - Step N falhou
|
||||
- `forgeagent:artifact-ready` - Artefato disponível
|
||||
- `forgeagent:plan-completed` - Plano finalizado
|
||||
|
||||
### 3.4 Frontend Components
|
||||
|
||||
#### 3.4.1 ForgeAgentBubble
|
||||
Container especial para mensagens agentic. Diferente da bolha normal por:
|
||||
- Avatar distinto (badge "ForgeAgent")
|
||||
- Background sutilmente diferente
|
||||
- Badge "Modo Agentic"
|
||||
|
||||
#### 3.4.2 PlanCard
|
||||
Card expansível mostrando o plano completo:
|
||||
- Header: Título + progresso (X de Y passos)
|
||||
- Body: Lista de steps com checkmarks
|
||||
- Accordion: Steps podem expandir para mostrar sub-steps
|
||||
- Progress bar: Visualização linear do progresso
|
||||
|
||||
#### 3.4.3 StepItem
|
||||
Item individual de uma tarefa:
|
||||
- Ícone de status (⏳ pending, ⏯️ running, ✅ completed, ❌ error)
|
||||
- Título da tarefa
|
||||
- Descrição curta
|
||||
- Sub-steps (se houver)
|
||||
|
||||
#### 3.4.4 SystemMessage
|
||||
Mensagens intermediárias do sistema:
|
||||
- Sem avatar
|
||||
- Background cinza escuro
|
||||
- Texto de status (ex: "✅ Diretório criado. Próximo passo...")
|
||||
|
||||
#### 3.4.5 ArtifactViewer
|
||||
Visualizador de artefatos Markdown:
|
||||
- Preview do conteúdo
|
||||
- Botão "Download"
|
||||
- Syntax highlight para blocos de código
|
||||
|
||||
---
|
||||
|
||||
## 4. Interface do Usuário
|
||||
|
||||
### 4.1 Layout da Tela de Chat
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ 💬 Chat │
|
||||
├────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ 👤 Usuário: como criar uma api em nodejs? │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ 🤖 ForgeAgent [Modo Agentic] │ │
|
||||
│ │ │ │
|
||||
│ │ Vou criar um guia completo para você! │ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────────────────────────────────────────┐ │ │
|
||||
│ │ │ 📋 Plano de Execução (4 passos) [░░░░] │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ✅ 1. Configurar ambiente │ │ │
|
||||
│ │ │ ├── Instalar Node.js │ │ │
|
||||
│ │ │ └── Verificar versão │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ⏯️ 2. Inicializar projeto (executando...) │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ⏳ 3. Instalar dependências │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ ⏳ 4. Criar endpoints │ │ │
|
||||
│ │ └──────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ✅ Projeto inicializado com sucesso! │ │
|
||||
│ │ │ │
|
||||
│ │ 📄 api-nodejs-guide.md [⬇️] │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ [🛠️ Modo Agentic] [ ] [➤] │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Estados Visuais
|
||||
|
||||
#### Step - Pending
|
||||
```
|
||||
⏳ Título da tarefa
|
||||
Descrição curta
|
||||
```
|
||||
|
||||
#### Step - Running
|
||||
```
|
||||
⏯️ Título da tarefa
|
||||
[==========> ] 60%
|
||||
Descrição curta
|
||||
```
|
||||
|
||||
#### Step - Completed
|
||||
```
|
||||
✅ Título da tarefa
|
||||
Descrição curta
|
||||
└── Sub-step 1
|
||||
└── Sub-step 2
|
||||
```
|
||||
|
||||
#### Step - Error
|
||||
```
|
||||
❌ Título da tarefa
|
||||
⚠️ Erro: mensagem de erro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Prompt Engineering
|
||||
|
||||
### 5.1 System Prompt
|
||||
|
||||
```markdown
|
||||
Você é o ForgeAgent, um assistente especializado em criar planos estruturados e executáveis.
|
||||
|
||||
SUA TAREFA:
|
||||
1. Analisar a solicitação do usuário
|
||||
2. Criar um plano passo a passo detalhado
|
||||
3. Retornar APENAS um objeto JSON válido no formato especificado
|
||||
|
||||
REGRAS:
|
||||
- NUNCA adicione texto fora do JSON
|
||||
- Sempre divida tarefas complexas em passos claros
|
||||
- Cada passo deve ter 2-5 sub-passos quando aplicável
|
||||
- Use linguagem clara e direta
|
||||
- O overview deve ter no máximo 2 frases
|
||||
|
||||
FORMATO JSON OBRIGATÓRIO:
|
||||
{
|
||||
"title": "Título do plano (máx 50 caracteres)",
|
||||
"overview": "Breve descrição do que será feito",
|
||||
"steps": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Título do passo",
|
||||
"description": "Descrição detalhada",
|
||||
"status": "pending",
|
||||
"sub_steps": [
|
||||
{
|
||||
"id": "1.1",
|
||||
"description": "Sub-tarefa específica",
|
||||
"status": "pending"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Few-Shot Example
|
||||
|
||||
**Input:** "como fazer uma api em nodejs"
|
||||
|
||||
**Output:**
|
||||
```json
|
||||
{
|
||||
"title": "Criar API Node.js",
|
||||
"overview": "Vamos criar uma API REST completa usando Express.js com estrutura profissional.",
|
||||
"steps": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Configurar ambiente",
|
||||
"description": "Preparar o ambiente de desenvolvimento",
|
||||
"status": "pending",
|
||||
"sub_steps": [
|
||||
{"id": "1.1", "description": "Verificar instalação do Node.js", "status": "pending"},
|
||||
{"id": "1.2", "description": "Instalar npm/yarn", "status": "pending"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Inicializar projeto",
|
||||
"description": "Criar estrutura base do projeto",
|
||||
"status": "pending",
|
||||
"sub_steps": [
|
||||
{"id": "2.1", "description": "Criar diretório do projeto", "status": "pending"},
|
||||
{"id": "2.2", "description": "Executar npm init", "status": "pending"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Instalar dependências",
|
||||
"description": "Adicionar Express e utilitários",
|
||||
"status": "pending",
|
||||
"sub_steps": [
|
||||
{"id": "3.1", "description": "Instalar express", "status": "pending"},
|
||||
{"id": "3.2", "description": "Instalar nodemon (dev)", "status": "pending"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Criar endpoints",
|
||||
"description": "Implementar rotas da API",
|
||||
"status": "pending",
|
||||
"sub_steps": [
|
||||
{"id": "4.1", "description": "Criar servidor Express", "status": "pending"},
|
||||
{"id": "4.2", "description": "Adicionar rota GET /", "status": "pending"},
|
||||
{"id": "4.3", "description": "Adicionar rota POST /api/data", "status": "pending"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API & Eventos
|
||||
|
||||
### 6.1 Commands (Tauri)
|
||||
|
||||
```rust
|
||||
#[tauri::command]
|
||||
async fn generate_agent_plan(
|
||||
request: AgentPlanRequest,
|
||||
) -> Result<AgentPlan, String> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn execute_plan_step(
|
||||
plan_id: String,
|
||||
step_id: u32,
|
||||
) -> Result<StepResult, String> {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 Events (Tauri)
|
||||
|
||||
```typescript
|
||||
// Frontend listening
|
||||
listen('forgeagent:plan-started', (event) => {
|
||||
const { plan_id, title, total_steps } = event.payload;
|
||||
// Update UI
|
||||
});
|
||||
|
||||
listen('forgeagent:step-started', (event) => {
|
||||
const { plan_id, step_id, title } = event.payload;
|
||||
// Update step status to "running"
|
||||
});
|
||||
|
||||
listen('forgeagent:step-completed', (event) => {
|
||||
const { plan_id, step_id, message } = event.payload;
|
||||
// Update step status to "completed"
|
||||
// Insert system message
|
||||
});
|
||||
|
||||
listen('forgeagent:artifact-ready', (event) => {
|
||||
const { plan_id, artifact } = event.payload;
|
||||
// Show artifact viewer
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Estados e Transições
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ IDLE │
|
||||
└──────┬──────┘
|
||||
│ trigger detected
|
||||
▼
|
||||
┌─────────────┐
|
||||
┌──────────│ PLANNING │◄─────────┐
|
||||
│ └──────┬──────┘ │
|
||||
│ │ plan generated │
|
||||
│ ▼ │
|
||||
│ ┌─────────────┐ │
|
||||
│ │ EXECUTING │──────────┘
|
||||
│ └──────┬──────┘ (next step)
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌─────────────┐
|
||||
└──────────│ COMPLETED │
|
||||
(all steps done) └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ ARTIFACT │
|
||||
│ READY │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Considerações de Performance
|
||||
|
||||
### 8.1 Modelo Recomendado
|
||||
- **ForgeSteel 4B:** Para planos complexos (5+ passos)
|
||||
- **ForgeIron 2B:** Para planos simples (2-4 passos)
|
||||
- **ForgeCopper 0.8B:** Não recomendado para geração de planos
|
||||
|
||||
### 8.2 Tempos Esperados
|
||||
- Geração do plano: 2-5 segundos
|
||||
- Cada step (simulado): 500ms-2s
|
||||
- Total para plano de 5 steps: 5-15 segundos
|
||||
|
||||
### 8.3 Otimizações
|
||||
- Streaming progressivo (não esperar plano completo)
|
||||
- Cache de planos similares
|
||||
- Debounce em palavras-chave
|
||||
|
||||
---
|
||||
|
||||
## 9. Testes
|
||||
|
||||
### 9.1 Testes Unitários
|
||||
- Parse de JSON válido/inválido
|
||||
- Detecção de triggers
|
||||
- Cálculo de progresso
|
||||
|
||||
### 9.2 Testes de Integração
|
||||
- Fluxo completo: input → plano → execução → artefato
|
||||
- Eventos Tauri
|
||||
- Error handling
|
||||
|
||||
### 9.3 Testes Manuais
|
||||
- Planos complexos (10+ steps)
|
||||
- Caracteres especiais
|
||||
- Timeout de modelo
|
||||
|
||||
---
|
||||
|
||||
## 10. Referências
|
||||
|
||||
- [Manus 1.6 Lite UI Reference](manus-reference.png)
|
||||
- [Structured Output - llama.cpp](https://github.com/ggerganov/llama.cpp/blob/master/examples/main/README.md#grammar)
|
||||
- [Tauri Events](https://tauri.app/v1/guides/features/events/)
|
||||
|
||||
---
|
||||
|
||||
## Histórico de Revisões
|
||||
|
||||
| Versão | Data | Autor | Mudanças |
|
||||
|--------|------|-------|----------|
|
||||
| 1.0 | 2025-04-02 | ForgeLM Team | Versão inicial |
|
||||
|
||||
---
|
||||
|
||||
## Aprovações
|
||||
|
||||
- [ ] Tech Lead
|
||||
- [ ] Product Manager
|
||||
- [ ] QA Lead
|
||||
@@ -0,0 +1,186 @@
|
||||
### Engenharia Reversa e Extração de Padrões (Visuais)
|
||||
|
||||
A análise das interfaces revela um design focado em produtividade e redução de carga cognitiva, adotando a vertente **Minimalismo Puro** com esquema de cores *Dark Mode* nativo. A hierarquia visual é estabelecida pelo contraste de luminosidade (tons de cinza) em vez de cores saturadas.
|
||||
|
||||
#### Design Tokens (Tailwind CSS Config)
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": {
|
||||
"colors": {
|
||||
"bg": {
|
||||
"base": "#171717", // Fundo principal da área de chat
|
||||
"surface": "#1E1E1E", // Sidebar e painéis modais
|
||||
"elevated": "#262626", // Inputs, cards de tarefas, modais sobrepostos
|
||||
"hover": "#333333" // Hover states em listas e botões
|
||||
},
|
||||
"text": {
|
||||
"primary": "#F3F4F6", // Texto de alta ênfase (títulos, inputs)
|
||||
"secondary": "#9CA3AF", // Texto de média ênfase (datas, placeholders, descrições)
|
||||
"muted": "#52525B" // Bordas sutis, ícones inativos
|
||||
},
|
||||
"accent": {
|
||||
"blue": "#3B82F6", // Links (Upgrade), focus rings
|
||||
"brand": "#FFFFFF" // Ícones de destaque (ex: estrela de créditos)
|
||||
}
|
||||
},
|
||||
"fontFamily": {
|
||||
"sans": ["Inter", "system-ui", "sans-serif"], // UI geral, legibilidade técnica
|
||||
"serif": ["Merriweather", "Georgia", "serif"] // Apenas para o greeting "What can I do for you?" (Contraste tipográfico intencional)
|
||||
},
|
||||
"spacing": {
|
||||
"xs": "0.25rem", // 4px - Micro-ajustes
|
||||
"sm": "0.5rem", // 8px - Espaçamento interno de botões/listas
|
||||
"md": "1rem", // 16px - Paddings de containers
|
||||
"lg": "1.5rem", // 24px - Separação de seções
|
||||
"xl": "2rem" // 32px - Margens globais
|
||||
},
|
||||
"borderRadius": {
|
||||
"sm": "0.375rem", // Inputs secundários, ícones
|
||||
"md": "0.5rem", // Cards de histórico, botões
|
||||
"lg": "1rem" // Modais, Input principal de chat, Painéis flutuantes
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Inferência de Funcionalidades e UX
|
||||
|
||||
#### Elementos Interativos Visíveis
|
||||
* **Navegação Lateral (Sidebar):** Estrutura colapsável contendo gerenciamento de estado global (Nova Tarefa, Agentes, Pesquisa, Biblioteca, Projetos, Histórico).
|
||||
* **Sistema de Modais (Overlay):** Utilizado para Contexto Pessoal (Configurações, Profile) e Busca Rápida (Command Palette - `Search tasks...`). Bloqueia interação com o fundo (backdrop com opacity).
|
||||
* **Input Multimodal de Chat:** Campo de entrada com acoplamento de ferramentas (upload, plugins, screen share, mic). Sugere autosize dinâmico baseado no conteúdo.
|
||||
* **Painel Split-Screen (Manus's Computer):** Visualização *side-by-side* da interação do LLM e a execução de tarefas em um ambiente virtualizado (navegação web autônoma).
|
||||
|
||||
#### Lógica Implícita de Estado/Backend
|
||||
* **Comunicação Assíncrona Bidirecional:** A interface sugere que a geração de respostas e os passos da execução (Pesquisa, Análise, Salvamento, Conversão) utilizam WebSockets ou Server-Sent Events (SSE) para stream de dados em tempo real sem *polling*.
|
||||
* **Gerenciamento de Estado Global:** Provavelmente utiliza Zustand ou Context API para sincronizar o saldo de "Credits" (587) e "Daily refresh credits" entre a topbar, sidebar e modais de configuração de forma reativa.
|
||||
* **Paginação e Cache de Histórico:** A lista de "Older tasks" na busca (image_43dfa1) sugere paginação server-side com cache local (React Query/SWR) para otimização de memória e busca *fuzzy* (pesquisa por aproximação).
|
||||
* **Virtualização do DOM:** O painel "Manus's Computer" provavelmente utiliza um iframe seguro ou um canvas renderizado via stream de vídeo do backend (semelhante a WebRTC) para refletir a navegação autônoma do agente.
|
||||
|
||||
---
|
||||
|
||||
### Desenvolvimento e Síntese: Arquitetura Core (React + Tailwind)
|
||||
|
||||
Abaixo, a síntese do `Layout Principal`, englobando a Sidebar estrutural e a área central de visualização. O código segue padrões estritos de acessibilidade (ARIA) e estrutura semântica.
|
||||
|
||||
```tsx
|
||||
import React, { useState } from 'react';
|
||||
|
||||
// Interfaces para tipagem estrita
|
||||
interface NavItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: React.ReactNode;
|
||||
userName: string;
|
||||
userCredits: number;
|
||||
}
|
||||
|
||||
export const AppLayout: React.FC<AppLayoutProps> = ({ children, userName, userCredits }) => {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-full bg-[#171717] text-[#F3F4F6] font-sans overflow-hidden">
|
||||
{/* Sidebar - Navegação Primária */}
|
||||
<aside
|
||||
className={`flex flex-col bg-[#1E1E1E] transition-all duration-300 ease-in-out border-r border-[#262626] ${
|
||||
isSidebarOpen ? 'w-64' : 'w-16'
|
||||
}`}
|
||||
aria-label="Sidebar Navigation"
|
||||
>
|
||||
{/* Header da Sidebar */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-[#262626]">
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<div className="w-8 h-8 rounded-md bg-[#262626] flex items-center justify-center shrink-0">
|
||||
<span className="text-sm font-bold">M</span>
|
||||
</div>
|
||||
{isSidebarOpen && <span className="font-medium whitespace-nowrap">Manus 1.6 Lite</span>}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className="p-1 text-[#9CA3AF] hover:text-[#F3F4F6] rounded-md hover:bg-[#262626] transition-colors"
|
||||
aria-label={isSidebarOpen ? 'Collapse sidebar' : 'Expand sidebar'}
|
||||
>
|
||||
{/* Ícone de colapso genérico */}
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Links de Navegação */}
|
||||
<nav className="flex-1 overflow-y-auto p-2 space-y-1">
|
||||
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-md bg-[#262626] text-[#F3F4F6] transition-colors">
|
||||
<svg className="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
{isSidebarOpen && <span>New task</span>}
|
||||
</button>
|
||||
|
||||
<div className="pt-4">
|
||||
{isSidebarOpen && <span className="px-3 text-xs font-semibold text-[#52525B] uppercase tracking-wider">Workspace</span>}
|
||||
<ul className="mt-2 space-y-1">
|
||||
{['Agents', 'Search', 'Library'].map((item) => (
|
||||
<li key={item}>
|
||||
<button className="w-full flex items-center gap-3 px-3 py-2 text-sm font-medium rounded-md text-[#9CA3AF] hover:text-[#F3F4F6] hover:bg-[#262626] transition-colors">
|
||||
<div className="w-4 h-4 bg-[#52525B] rounded-sm shrink-0"></div> {/* Placeholder Icon */}
|
||||
{isSidebarOpen && <span>{item}</span>}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* Footer da Sidebar (User Info) */}
|
||||
<div className="p-4 border-t border-[#262626]">
|
||||
<button className="w-full flex items-center gap-3 hover:bg-[#262626] p-2 rounded-md transition-colors text-left">
|
||||
<img
|
||||
src="https://via.placeholder.com/32"
|
||||
alt={`Avatar de ${userName}`}
|
||||
className="w-8 h-8 rounded-full shrink-0"
|
||||
/>
|
||||
{isSidebarOpen && (
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-medium text-[#F3F4F6] truncate">{userName}</p>
|
||||
<p className="text-xs text-[#9CA3AF] truncate">{userCredits} Credits</p>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Área Principal */}
|
||||
<main className="flex-1 flex flex-col min-w-0 relative" aria-label="Main Content Area">
|
||||
{/* Topbar Opcional / Header de Contexto */}
|
||||
<header className="flex items-center justify-between p-4 h-16 shrink-0">
|
||||
<div className="flex-1"></div>
|
||||
<div className="flex items-center gap-4">
|
||||
<button className="text-sm font-medium text-[#9CA3AF] hover:text-[#F3F4F6] transition-colors">Upgrade</button>
|
||||
<div className="flex items-center gap-1 px-3 py-1 bg-[#262626] rounded-full text-xs font-medium text-[#F3F4F6]">
|
||||
<span>✨</span>
|
||||
<span>{userCredits}</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Content Wrapper */}
|
||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 flex flex-col">
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppLayout;
|
||||
```
|
||||
|
||||
O sistema mapeado possui alta complexidade de estados e sub-roteamento (Modais de Perfil, Painel Dividido de Execução de Tarefas, Input Multimodal). Qual destes módulos específicos você deseja que eu isole, extraia a lógica e gere o código de produção em seguida?
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
plan name: fix-model-swap
|
||||
plan description: Fix model swap race condition
|
||||
plan status: active
|
||||
---
|
||||
|
||||
## Idea
|
||||
Corrigir a race condition onde o modelo selecionado é sobrescrito pelo refresh automático durante uma troca de modelo.
|
||||
|
||||
**Problema identificado:**
|
||||
1. Usuário seleciona modelo B no frontend
|
||||
2. `swapModel()` é chamado para trocar no backend
|
||||
3. O backend leva tempo para trocar (para API, reinicia com novo modelo)
|
||||
4. Enquanto isso, o `useModels.refresh()` pode executar e buscar lista de modelos do servidor
|
||||
5. O servidor ainda retorna modelo A (antigo) pois a troca não completou
|
||||
6. `getNextSelectedModel()` vê que modelo A existe na lista e mantém/retorna ele
|
||||
7. Resultado: o usuário vê modelo A selecionado em vez de B
|
||||
|
||||
**Solução:**
|
||||
Implementar um mecanismo de "lock" temporário no `useModels` que previne que o refresh automático sobrescreva o `selectedModel` por alguns segundos após uma seleção manual do usuário.
|
||||
|
||||
## Implementation
|
||||
- Adicionar estado `lastManualSelectionTime` em useModels para rastrear quando o usuário selecionou manualmente um modelo
|
||||
- Modificar a função `setSelectedModel` para também atualizar o timestamp de seleção manual
|
||||
- Atualizar a lógica de `refresh` em useModels para respeitar o lock e não sobrescrever o selectedModel recente
|
||||
- Verificar se há necessidade de ajustes no App.tsx ou Sidebar.tsx para integrar com a nova API
|
||||
- Testar a correção simulando a troca de modelo e verificando que o modelo correto permanece selecionado
|
||||
|
||||
## Required Specs
|
||||
<!-- SPECS_START -->
|
||||
<!-- SPECS_END -->
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
plan name: fix-swap-bug
|
||||
plan description: Fix swap model loading wrong model
|
||||
plan status: done
|
||||
---
|
||||
|
||||
## Idea
|
||||
Corrigir bug crítico no swap_model_command onde o modelo antigo é carregado após trocar para um novo modelo.
|
||||
|
||||
**Problema identificado:**
|
||||
No `swap_model_command` (src-tauri/src/lib.rs:123-139):
|
||||
1. O comando salva a config no disco com o novo modelo
|
||||
2. Mas NÃO atualiza o estado interno `inner.config` no RuntimeState
|
||||
3. Quando `start_runtime` é chamado, ele usa `inner.config` do state (modelo antigo)
|
||||
4. Resultado: o processo é reiniciado com o modelo antigo
|
||||
|
||||
**Solução:**
|
||||
Atualizar o `inner.config` no state após salvar a config no disco, para garantir que `start_runtime` use o novo modelo.
|
||||
|
||||
**Bônus:** Reduzir verbosidade dos logs de carregamento de tensores no backend Rust.
|
||||
|
||||
## Implementation
|
||||
- Corrigir swap_model_command para atualizar inner.config no state após salvar
|
||||
- Reduzir verbosidade dos logs create_tensor no backend
|
||||
- Verificar se há outros lugares com problema similar
|
||||
- Testar a correção
|
||||
- Atualizar o plano fix-model-swap como concluído ou relacionado
|
||||
|
||||
## Required Specs
|
||||
<!-- SPECS_START -->
|
||||
<!-- SPECS_END -->
|
||||
@@ -0,0 +1,340 @@
|
||||
# Adaptive Model Routing SDD
|
||||
|
||||
## Status
|
||||
- Draft v1
|
||||
- Scope: Tauri desktop app + Rust local inference runtime + React chat UI
|
||||
|
||||
## Objective
|
||||
Refactor local model selection so the app defaults to **automatic startup choice** and **session-level manual override**, while adding TPS-based degradation detection and denser chat/home UI behavior.
|
||||
|
||||
## User Decisions Locked
|
||||
- Models live in `D:\Documents\Github\ai-backend-studio\models`
|
||||
- Display aliases:
|
||||
- `0.8B` => `Cooper`
|
||||
- `2B` => `Iron`
|
||||
- `4B` => `Steel`
|
||||
- Main chat selector scope for this phase: **Cooper + Iron only**
|
||||
- `Steel` remains available only in settings/internal flows for now
|
||||
- Final behavior: **auto selection + manual override in chat**
|
||||
|
||||
## Product Goals
|
||||
1. Remove model choice as a required startup decision for normal users.
|
||||
2. Start the app with the best default model automatically.
|
||||
3. Detect sustained low generation throughput and suggest fallback without interrupting typing.
|
||||
4. Allow the user to override the effective model from chat.
|
||||
5. Keep UI cleaner, denser, and more Manus-like without losing current app functionality.
|
||||
|
||||
## Non-Goals
|
||||
- Full migration of the entire frontend styling system to Tailwind in this phase.
|
||||
- Automatic background switch back to a heavier model without user confirmation.
|
||||
- Exposing `Steel` in the primary chat selector.
|
||||
- Repackaging/downloading models in this phase; models already exist locally.
|
||||
|
||||
## Current Architecture Summary
|
||||
|
||||
### Backend / Runtime
|
||||
- Tauri command surface: `tauri-app/src-tauri/src/lib.rs`
|
||||
- Runtime bootstrap/start/stop: `tauri-app/src-tauri/src/runtime/mod.rs`
|
||||
- Model discovery/indexing: `tauri-app/src-tauri/src/model_files.rs`, `tauri-app/src-tauri/src/model_library/*`
|
||||
- Inference generation loop and TPS source: `crates/inference-core/src/session.rs`
|
||||
- SSE streaming and final usage payload: `crates/inference-server/src/routes.rs`
|
||||
|
||||
### Frontend
|
||||
- App composition and model swap wiring: `tauri-app/src/App.tsx`
|
||||
- Chat screen: `tauri-app/src/components/ChatView.tsx`
|
||||
- Current manual model selector: `tauri-app/src/components/Sidebar.tsx`
|
||||
- Runtime draft model selection helpers: `tauri-app/src/features/runtime/runtime-settings-form.ts`
|
||||
- Live model list fetch + current manual-lock behavior: `tauri-app/src/hooks/useModels.ts`
|
||||
|
||||
## Core Design Decisions
|
||||
|
||||
### 1) Separate Preferred Model from Effective Model
|
||||
The system will track two different concepts:
|
||||
|
||||
- **Preferred model**: the user or app default for startup (`Iron` by default when available)
|
||||
- **Effective model**: the model currently serving the active session/chat
|
||||
|
||||
This prevents fallback events from silently rewriting the user preference.
|
||||
|
||||
### 2) Automatic Startup Choice
|
||||
At app launch, local runtime selection will follow this order:
|
||||
|
||||
1. If the user explicitly saved a preferred startup model and it is available, use it.
|
||||
2. Otherwise prefer `Iron`.
|
||||
3. If `Iron` is unavailable, use `Cooper`.
|
||||
4. `Steel` is ignored by normal auto-start policy in this phase.
|
||||
|
||||
### 3) TPS-Based Degradation Warning
|
||||
The system will monitor **generation TPS** during active completion streaming.
|
||||
|
||||
Fallback trigger rule:
|
||||
- track rolling generation TPS samples in Rust
|
||||
- if rolling TPS stays `< 30` for `>= 5s` continuously
|
||||
- emit a degradation event once per request/session window
|
||||
- show a non-blocking toast in the frontend
|
||||
|
||||
Toast actions:
|
||||
- `Trocar para Cooper`
|
||||
- `Manter Iron`
|
||||
|
||||
If the effective model is already `Cooper`, no fallback toast is shown.
|
||||
|
||||
### 4) Manual Override in Chat
|
||||
The primary chat surface will expose a **small model selector** near the composer/header with:
|
||||
- `Iron`
|
||||
- `Cooper`
|
||||
|
||||
Behavior:
|
||||
- changing the selector swaps only the **effective model**
|
||||
- the chosen value persists for the current app session
|
||||
- it does not silently rewrite startup preference unless the user later saves it in settings
|
||||
|
||||
### 5) Tailwind Rule for This Feature
|
||||
The app currently relies heavily on CSS files. For this feature:
|
||||
- all **new UI surfaces/components** should be built Tailwind-first
|
||||
- modified legacy surfaces may temporarily keep existing CSS where a full migration would create unnecessary churn
|
||||
- any touched layout should move toward utility-class ownership when practical
|
||||
|
||||
This keeps delivery realistic while respecting the new direction.
|
||||
|
||||
## Model Inventory and Alias Policy
|
||||
|
||||
### Canonical alias table
|
||||
| Raw family | Display name | Phase role |
|
||||
|---|---|---|
|
||||
| 0.8B | Cooper | fallback + lightweight manual choice |
|
||||
| 2B | Iron | default primary model |
|
||||
| 4B | Steel | settings/internal only |
|
||||
|
||||
### Resolution rule
|
||||
Model display naming must be computed from local file metadata/path patterns, not hardcoded to exact filenames only. Matching should allow future file suffix changes.
|
||||
|
||||
Proposed matcher priority:
|
||||
1. explicit config mapping if present
|
||||
2. known filename pattern match (`0.8b`, `2b`, `4b`)
|
||||
3. raw file basename fallback
|
||||
|
||||
## Proposed Architecture
|
||||
|
||||
### A. Rust: TPS Telemetry Layer
|
||||
Add a lightweight TPS monitor around the generation loop.
|
||||
|
||||
#### Source of truth
|
||||
- `crates/inference-core/src/session.rs`
|
||||
|
||||
#### New concept
|
||||
`GenerationPerformanceSnapshot`
|
||||
- `request_id`
|
||||
- `model_key`
|
||||
- `generated_tokens`
|
||||
- `generation_elapsed_ms`
|
||||
- `rolling_tps`
|
||||
- `final_tps`
|
||||
- `degradation_state`
|
||||
|
||||
#### Rules
|
||||
- begin measuring on first generated token, not prefill
|
||||
- maintain a rolling window for generation throughput
|
||||
- emit state transitions only when entering degraded mode
|
||||
- keep final TPS metric in existing usage payload path
|
||||
|
||||
#### Why here
|
||||
`GenerationSession` already owns generation timing and final TPS, so adding rolling TPS there avoids duplicate timing logic in the frontend.
|
||||
|
||||
### B. Rust: Runtime Auto-Selection Policy
|
||||
Add startup model resolution inside runtime bootstrap/start flow.
|
||||
|
||||
#### Target
|
||||
- `tauri-app/src-tauri/src/runtime/mod.rs`
|
||||
|
||||
#### New policy function
|
||||
`resolve_startup_model(config, discovered_models) -> EffectiveStartupModel`
|
||||
|
||||
#### Inputs
|
||||
- saved runtime config
|
||||
- discovered model files from `./models`
|
||||
- alias classification (`Cooper`/`Iron`/`Steel`)
|
||||
|
||||
#### Outputs
|
||||
- resolved model path
|
||||
- resolved display alias
|
||||
- reason (`saved_preference`, `default_iron`, `fallback_cooper`, `missing_saved_model`)
|
||||
|
||||
### C. Frontend: Effective Model Controller
|
||||
Create a dedicated client-side state layer above `useModels()`.
|
||||
|
||||
#### Responsibilities
|
||||
- maintain `preferredModel`, `effectiveModel`, `manualOverride`, `degradationDismissed`
|
||||
- map backend model IDs/paths to display aliases
|
||||
- handle user-triggered model switches
|
||||
- ignore refresh noise from `/v1/models` once an override is active for the session
|
||||
|
||||
#### Likely placement
|
||||
- new hook near `tauri-app/src/hooks/` or `src/features/runtime/`
|
||||
- wired in `tauri-app/src/App.tsx`
|
||||
|
||||
### D. Frontend: Toast System
|
||||
No shared toast system exists today.
|
||||
|
||||
Add:
|
||||
- global toast provider near `App.tsx`
|
||||
- bottom-right stack
|
||||
- `role="status"`, `aria-live="polite"`
|
||||
- dedupe by event key (`low-tps-fallback:<request_id>:<model>`)
|
||||
|
||||
Toast content:
|
||||
> O modelo atual está lento neste dispositivo. Deseja trocar para Cooper para melhor velocidade?
|
||||
|
||||
Actions:
|
||||
- primary: swap to `Cooper`
|
||||
- secondary: keep current model
|
||||
|
||||
### E. Frontend: Chat Selector
|
||||
Move primary user model choice from sidebar-only behavior to an in-chat control.
|
||||
|
||||
#### UX rules
|
||||
- visible but subtle
|
||||
- available on initial screen and active chat screen
|
||||
- only shows `Iron` and `Cooper`
|
||||
- `Steel` remains in settings/runtime management
|
||||
- while swapping, disable control and show compact busy state
|
||||
|
||||
### F. UI Density Updates
|
||||
|
||||
#### Home
|
||||
- reduce vertical whitespace between hero, chips, and lower card
|
||||
- improve recent work density in sidebar/task list
|
||||
- keep one muted lower card instead of multiple competing blocks
|
||||
|
||||
#### Chat
|
||||
- denser message header/meta
|
||||
- keep TPS/status info compact and contextual
|
||||
- do not overload first paint with debug controls
|
||||
|
||||
## Data and State Model
|
||||
|
||||
### Backend persisted config
|
||||
Extend runtime config with explicit preference fields:
|
||||
- `preferred_startup_model_alias: Option<String>` (`iron`, `cooper`, `steel`)
|
||||
- keep existing `model_path` for actual runtime launch target
|
||||
|
||||
### Frontend session state
|
||||
- `preferredModelAlias`
|
||||
- `effectiveModelAlias`
|
||||
- `effectiveModelPath`
|
||||
- `overrideSource: "auto" | "manual" | "fallback"`
|
||||
- `degradationPromptState: "idle" | "shown" | "accepted" | "dismissed"`
|
||||
|
||||
## Main User Flows
|
||||
|
||||
### Flow 1: App startup
|
||||
1. app bootstraps runtime config
|
||||
2. runtime scans available local models
|
||||
3. startup resolver selects `Iron` if available, else `Cooper`
|
||||
4. runtime starts with resolved model
|
||||
5. frontend shows resolved effective model alias
|
||||
|
||||
### Flow 2: Sustained low TPS while using Iron
|
||||
1. user starts generation on `Iron`
|
||||
2. Rust rolling monitor observes TPS `< 30` for 5 consecutive seconds
|
||||
3. backend emits degradation event
|
||||
4. frontend displays one toast
|
||||
5. user chooses:
|
||||
- `Trocar para Cooper` => perform hot swap, update effective model, persist session override
|
||||
- `Manter Iron` => suppress repeated prompt for the current request/session cooldown
|
||||
|
||||
### Flow 3: Manual chat override
|
||||
1. user opens selector in chat
|
||||
2. chooses `Cooper` or `Iron`
|
||||
3. frontend calls swap command
|
||||
4. runtime restarts with target model
|
||||
5. effective model updates immediately after success
|
||||
|
||||
### Flow 4: Steel in settings
|
||||
1. user opens settings/runtime area
|
||||
2. sees full model inventory including `Steel`
|
||||
3. may still use/import/manage `Steel`
|
||||
4. Steel is not shown in chat selector in this phase
|
||||
|
||||
## Error Handling
|
||||
- if swap fails, keep prior effective model visible
|
||||
- if degradation event arrives during an in-progress swap, discard it
|
||||
- if `Iron` disappears from library, auto-fallback to `Cooper`
|
||||
- if neither `Iron` nor `Cooper` exists, show runtime unavailable state and keep settings path accessible
|
||||
- if toast already shown for the same request, do not spawn duplicates
|
||||
|
||||
## Implementation Touchpoints
|
||||
|
||||
### Rust
|
||||
- `crates/inference-core/src/session.rs`
|
||||
- `crates/inference-server/src/routes.rs`
|
||||
- `tauri-app/src-tauri/src/runtime/mod.rs`
|
||||
- potentially `tauri-app/src-tauri/src/lib.rs` for new commands/events
|
||||
|
||||
### Frontend
|
||||
- `tauri-app/src/App.tsx`
|
||||
- `tauri-app/src/hooks/useModels.ts`
|
||||
- `tauri-app/src/components/ChatView.tsx`
|
||||
- `tauri-app/src/components/Sidebar.tsx`
|
||||
- `tauri-app/src/features/runtime/runtime-settings-form.ts`
|
||||
- new toast/provider files
|
||||
- new model alias resolver/shared hook files
|
||||
|
||||
## TDD Test Matrix
|
||||
|
||||
### Rust unit tests
|
||||
1. startup resolver picks `Iron` when `Iron` + `Cooper` are available
|
||||
2. startup resolver picks `Cooper` when `Iron` is missing
|
||||
3. startup resolver respects explicit saved preference when available
|
||||
4. rolling TPS monitor enters degraded state only after 5s sustained `< 30`
|
||||
5. transient dip below 30 for less than threshold does not trigger
|
||||
6. degraded state is emitted once per request transition
|
||||
|
||||
### Rust integration tests
|
||||
1. SSE/final usage still includes final TPS after monitor changes
|
||||
2. degradation event payload contains model alias/path/request id
|
||||
3. swap command updates effective runtime target correctly
|
||||
|
||||
### Frontend unit tests
|
||||
1. alias resolver maps model file names to `Cooper`, `Iron`, `Steel`
|
||||
2. chat selector only exposes `Cooper` and `Iron`
|
||||
3. manual override survives model list refresh in session
|
||||
4. fallback toast dedupes repeated degradation events
|
||||
5. clicking `Trocar para Cooper` triggers swap and updates effective model
|
||||
6. clicking `Manter Iron` suppresses repeated prompt during cooldown
|
||||
|
||||
### Frontend component tests
|
||||
1. chat header/composer shows current effective model alias
|
||||
2. initial screen remains dense and usable with toast visible
|
||||
3. selector disabled while swap is in progress
|
||||
4. runtime unavailable state keeps settings access reachable
|
||||
|
||||
## Acceptance Criteria
|
||||
1. User can start the app without manually choosing a model.
|
||||
2. App defaults to `Iron` when available.
|
||||
3. If generation TPS stays below 30 for 5s on `Iron`, a non-blocking toast is shown.
|
||||
4. User can switch to `Cooper` directly from the toast.
|
||||
5. User can manually choose between `Iron` and `Cooper` from chat.
|
||||
6. Manual override persists during the session.
|
||||
7. `Steel` is visible only in settings/internal runtime management.
|
||||
8. Final UI is denser and cleaner than current state.
|
||||
9. New behavior is covered by Rust + frontend tests before implementation code lands.
|
||||
|
||||
## Risks
|
||||
- current frontend styling is still CSS-heavy; partial Tailwind migration may create mixed ownership temporarily
|
||||
- runtime restart latency may make “instant” switching feel slower on weaker machines
|
||||
- model filename normalization may be inconsistent unless alias mapping is centralized
|
||||
- event delivery from Rust to frontend must avoid duplicate or stale degradation prompts
|
||||
|
||||
## Recommended Delivery Phases
|
||||
1. alias resolution + startup policy
|
||||
2. Rust TPS degradation monitor + event contract
|
||||
3. frontend toast system + session model controller
|
||||
4. chat selector + sidebar/settings cleanup
|
||||
5. density pass on home/chat UI
|
||||
6. regression verification
|
||||
|
||||
## Open Assumptions
|
||||
- existing hot-swap restart path is acceptable for “instantânea” within product expectations
|
||||
- startup auto-selection should prefer `Iron` over `Steel` even if `Steel` exists
|
||||
- `Steel` remains excluded from primary chat selector until a future phase expands the UX
|
||||
Reference in new issue
Block a user