From 4006a72eec60c833379a50e84cfffdf09db531d9 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Thu, 20 Nov 2025 15:13:54 -0300 Subject: [PATCH] feat(ui): enhance chat UI with new components and styling refactor(chat): improve message display and actions feat(chat): add high contrast mode toggle style(chat): update spacing and sizing for better readability feat(chat): add new Section and Stack layout components docs: add UI style guide documentation fix(chat): improve browser connection handling in scraper feat(settings): expand default sources and improve UI refactor: consolidate chat page layout and components --- ...st (Browser Resurrection, Send e Retry).md | 114 +++++++++++++ app/chat/page.tsx | 151 ++++++++---------- app/globals.css | 32 ++++ app/settings/page.tsx | 111 ++++++++----- components/chat/chat-input.tsx | 83 ++++++++-- components/chat/chat-message.tsx | 22 +-- components/chat/message-actions.tsx | 30 +++- components/chat/message-background.tsx | 37 +++++ components/chat/prompt-generator.tsx | 2 +- components/chat/search-progress.tsx | 45 +++--- components/chat/thinking-indicator.tsx | 4 +- components/chat/web-search-status.tsx | 2 +- components/ui/button.tsx | 8 +- components/ui/section.tsx | 29 ++++ components/ui/stack.tsx | 65 ++++++++ docs/style-guide.md | 34 ++++ src-tauri/src/lib.rs | 7 +- src-tauri/src/sources_config.rs | 74 ++++++++- src-tauri/src/web_scraper.rs | 124 +++++++++----- store/settings-store.ts | 2 +- 20 files changed, 741 insertions(+), 235 deletions(-) create mode 100644 .trae/documents/Estabilizar Scraper Rust (Browser Resurrection, Send e Retry).md create mode 100644 components/chat/message-background.tsx create mode 100644 components/ui/section.tsx create mode 100644 components/ui/stack.tsx create mode 100644 docs/style-guide.md diff --git a/.trae/documents/Estabilizar Scraper Rust (Browser Resurrection, Send e Retry).md b/.trae/documents/Estabilizar Scraper Rust (Browser Resurrection, Send e Retry).md new file mode 100644 index 0000000..07d3d3f --- /dev/null +++ b/.trae/documents/Estabilizar Scraper Rust (Browser Resurrection, Send e Retry).md @@ -0,0 +1,114 @@ +## 📋 Análise +**Solicitação**: Corrigir falhas de conexão do browser headless e o erro de compilação `Send`, tornar o scraper resiliente com retry e ampliar resultados de busca. +**Objetivo real**: Garantir estabilidade contínua do backend de scraping, mesmo quando o Chrome headless cai, evitando violações de `Send` e melhorando a cobertura de fontes. + +## 🔍 Contexto Identificado +### Arquivos Examinados +- `src-tauri/src/lib.rs:1176–1193` get_or_create_browser: cria/reutiliza `Browser` singleton. +- `src-tauri/src/lib.rs:1247–1277` search_web_metadata: comando Tauri que exige `Future + Send`. +- `src-tauri/src/web_scraper.rs:136–240` search_duckduckgo_metadata: parsing HTML, previously segurava `Html` atravessando `await`. +- `src-tauri/src/web_scraper.rs:469–538` search_and_scrape_with_config: coordena scraping paralelo e coleta resultados. +- `src-tauri/src/web_scraper.rs:605–736` fetch_and_convert_sync: cria aba, navega, extrai HTML e usa Readability. +- `src-tauri/src/lib.rs:1306–1399` force_kill_browser: encerra processos Chrome headless. + +### Padrões Detectados +- **Singleton Browser** via `State` com `Arc`. +- **Concorrência controlada** com `Semaphore` e `tokio::spawn_blocking` para operações de Chrome. +- **Fallbacks**: limpeza de URL, filtros anti-ads, Readability + fallback por parágrafos. + +### Dependências Mapeadas +``` +web_scraper (modificado) + ├─ headless_chrome::Browser/Tab + ├─ reqwest (HTTP) + ├─ scraper (Html, Selector) [não Send] + └─ readability, html2text +lib (modificado) + └─ tauri commands (Future + Send obrigatório) +``` + +## 🎯 Abordagens Viáveis + +### Abordagem A: Verificação ativa de Browser + Retry cooperativo +**Conceito**: Validar liveness do `Browser` antes de uso; se cair durante scraping, recriar e repetir uma passada de URLs com limite de tentativas. +``` +request → get_or_create_browser (is_alive?) → spawn scraping + ↳ erro conn fechada → reset + recreate → retry restante (1x) +``` +**Vantagens**: ✓ Simples de integrar, ✓ Minimiza impacto, ✓ Boa resiliência +**Desvantagens**: ✗ Retry global pode repetir algumas URLs +**Complexidade**: O(n) | Tempo: 3–4h | Risco: Médio + +### Abordagem B: Pool/Auto-heal por task +**Conceito**: Cada task recupera o browser ao falhar e re-executa só sua URL. +``` +spawn_blocking(url) → new_tab → erro conn → sinaliza reset → reobtem browser → reprocessa url +``` +**Vantagens**: ✓ Isolamento por URL +**Desvantagens**: ✗ Coordenação de estado entre threads, ✗ Maior complexidade +**Complexidade**: O(n) | Tempo: 5–6h | Risco: Médio-Alto + +## 🏆 Recomendação +**Abordagem A** porque: +- Alinha com o padrão singleton já existente. +- Menor acoplamento entre tarefas e estado global. +- Implementação direta com menor risco de condições de corrida. + +## ⚠️ Riscos & Mitigações +| Risco | Prob | Impacto | Mitigação | +|-------|------|---------|-----------| +| Loop de retry infinito | Baixa | Médio | Limitar a 1–2 tentativas, backoff curto | +| Recriação concorrente do Browser | Média | Médio | Guardar lock no `BrowserState` e checar antes de recriar | +| Demasiadas abas simultâneas | Média | Médio | Reduzir `max_concurrent_tabs` ao detectar falha | +| DDG bloqueios 429 | Média | Baixo | Rotação User-Agent e backoff | + +## 📝 Plano de Execução +1. Ajustes `Send` (consolidação) + - Isolar parsing `scraper::Html` em escopo síncrono que retorna `Vec` (já aplicado em parte na função; revisar para todos os caminhos). + - Garantir que nenhum `Html`/`Selector` vive além do `await` nos métodos `search_*`. + - Verificar pontos: `src-tauri/src/web_scraper.rs:136–240` e demais que usem `Html`. + +2. Browser Resurrection + - Adicionar `fn is_browser_alive(browser: &Browser) -> bool` em `web_scraper.rs` ou `lib.rs` (ex.: tentar `new_tab()` e descartar; ou `get_tabs()`; se falhar → false). + - Alterar `get_or_create_browser` (`src-tauri/src/lib.rs:1176–1193`) para: se `Some(browser)` e `!is_alive(browser)` → `reset` e `create_browser()`. + - Em `force_kill_browser` (`src-tauri/src/lib.rs:1306–1399`): após kill, setar `BrowserState` para `None` via lock (ou orientar UI a chamar `reset_browser` antes do kill), garantindo recriação no próximo uso. + +3. Retry resiliente no Scraper + - Envolver `fetch_and_convert_sync` com camada de retry (máx. 2 tentativas) na orquestração async (`search_and_scrape_with_config` em `src-tauri/src/web_scraper.rs:469–538`). + - Se erro contiver "underlying connection is closed"/timeout global do browser: + - Executar `reset_browser` e `get_or_create_browser` para novo `Arc` até atingir `limit` (30–50). + - Extrair links por página, deduplicar, respeitar `excluded_domains`/ads. + +5. Telemetria e Logs + - Logar eventos de recreação de browser, tentativas de retry e motivos. + - Contadores: quedas de browser, tentativas, tempo médio por URL. + +6. Testes/Validação + - Caso 1: Forçar `force_kill_browser` durante scraping → validar que a próxima chamada recria o browser e conclui. + - Caso 2: Simular DDG com >= 40 resultados → paginar e coletar sem violar `Send`. + - Caso 3: Stress com `max_concurrent_tabs=5` → se cair, reduzir e recuperar. + +## Key Changes (Resumo de Implementação) +- `lib.rs:1176–1193`: `get_or_create_browser` verifica liveness; recria se morto. +- `lib.rs:1306–1399`: sincronia com `reset_browser` ao matar processos. +- `web_scraper.rs:469–538`: retry global (1x) e recriação de browser ao detectar conexão fechada; ajuste dinamicamente `Semaphore` na segunda passada. +- `web_scraper.rs:78–133`: paginação DDG com `s=` offset. +- `web_scraper.rs:136–240`: parsing isolado; garantir que `Html` é dropado antes do `await`. + +## Entregáveis +- Código robusto em `web_scraper.rs` e `lib.rs` com: + - Correção total do `Send`. + - Resurreição automática do browser. + - Retry controlado por erro de conexão. + - Paginação para 30–50 resultados. +- Logs melhorados e validação manual automática. + +## ❓ Aguardando Aprovação +- [ ] Posso prosseguir com as mudanças propostas? +- [ ] Preferências de `max_concurrent_tabs` e `limit` padrão (sugestão: 5 e 40)? +- [ ] Deseja manter `force_kill_browser` como utilitário avançado, com alerta na UI para reiniciar o scraper? \ No newline at end of file diff --git a/app/chat/page.tsx b/app/chat/page.tsx index d8f1483..fffa864 100644 --- a/app/chat/page.tsx +++ b/app/chat/page.tsx @@ -1,17 +1,14 @@ 'use client'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { MessageSquare, Settings, Server, Moon, Sun, PanelLeftClose, PanelLeftOpen, Loader2, ChevronDown } from "lucide-react"; import { ChatInput } from "@/components/chat/chat-input"; import { ChatMessage } from "@/components/chat/chat-message"; -import { SystemPanel } from "@/components/chat/system-panel"; -import { PromptGeneratorDialog } from "@/components/chat/prompt-generator"; + import { SidebarList } from "@/components/chat/sidebar-list"; import { useChat } from "@/hooks/use-chat"; import { useLocalModels } from "@/hooks/use-local-models"; @@ -36,7 +33,7 @@ import defaultFormatPrompt from "@/data/prompts/default-format.md"; export default function ChatPage() { const router = useRouter(); const { messages, setMessages, sendMessage, isLoading, stop, clearChat } = useChat(); - const { models } = useLocalModels(); + const { models, refresh } = useLocalModels(); const { theme, setTheme } = useTheme(); const { @@ -56,6 +53,8 @@ export default function ChatPage() { const settings = useSettingsStore(); const [selectedModel, setSelectedModel] = useState(""); + const [mounted, setMounted] = useState(false); + const initializedRef = useRef(false); // Initialize with default format prompt const [systemPrompt, setSystemPrompt] = useState(defaultFormatPrompt || "Você é um assistente útil e prestativo."); const [isChatsSidebarCollapsed, setIsChatsSidebarCollapsed] = useState(false); @@ -71,10 +70,22 @@ export default function ChatPage() { // Auto-select first model useEffect(() => { - if (models.length > 0 && !selectedModel) { - setSelectedModel(models[0].name); + setMounted(true); + }, []); + + useEffect(() => { + if (initializedRef.current) return; + const stored = settings.selectedModel; + const first = models.length > 0 ? models[0].name : ""; + const next = stored || first || ""; + if (next) { + setSelectedModel(next); + if (!stored) { + settings.setSelectedModel(next); + } } - }, [models, selectedModel]); + initializedRef.current = true; + }, [settings.selectedModel, models]); // Auto-scroll para o final durante streaming useEffect(() => { @@ -372,7 +383,7 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente onClick={() => setTheme(theme === "dark" ? "light" : "dark")} className="rounded-lg text-muted-foreground hover:text-foreground" > - {theme === "dark" ? : } + {mounted ? (theme === "dark" ? : ) : null} @@ -460,7 +481,7 @@ Ao responder sobre fatos atuais ou notícias, inicie mencionando explicitamente

Inicie uma conversa com {selectedModel}

) : ( -
+
{messages.map((msg, i) => { // Verificar se a mensagem anterior teve busca web const hasWebSources = i > 0 && @@ -471,6 +492,19 @@ Ao responder sobre fatos atuais ou notĂ­cias, inicie mencionando explicitamente return (
+ {(msg.role === 'user') && ( + (webSearch.status === 'searching' || + webSearch.status === 'scraping' || + webSearch.status === 'completed' || + webSearch.status === 'error') && ( + + ) + )} {/* Mostrar fontes apĂłs resposta do assistente que usou web search */} {msg.role === 'assistant' && hasWebSources && (
@@ -507,18 +541,7 @@ Ao responder sobre fatos atuais ou notĂ­cias, inicie mencionando explicitamente
); })} - {/* Web Search Progress - mostrar antes da resposta da IA */} - {(webSearch.status === 'searching' || - webSearch.status === 'scraping' || - webSearch.status === 'completed' || - webSearch.status === 'error') && ( - - )} + {isLoading && thinkingStep === 'complete' && (
Gerando resposta... @@ -530,8 +553,8 @@ Ao responder sobre fatos atuais ou notĂ­cias, inicie mencionando explicitamente
- {/* Thinking Indicator */} - {thinkingStep && thinkingStep !== 'complete' && ( + {/* Thinking Indicator - aparece apenas quando não há web search ativo */} + {thinkingStep && thinkingStep !== 'complete' && (webSearch.status === 'idle') && ( { + const cat = settings.webSearch.categories.find(c => c.id === id); + if (!cat) return; + settings.updateCategory({ ...cat, enabled }); + }} />
- - - {/* Right Sidebar (Settings & Monitor) */} - - -
- - - Parâmetros - - - Sistema - - -
- - -
- {/* System Prompt */} -
-
- - -
-