# Documentação: Frontend Consumindo o Backend - ForgeLM ## Sumário Esta documentação detalha **exatamente como** o frontend React consome os dados do backend, quais rotas ele bate, como envia e recebe dados, e a arquitetura de comunicação entre as camadas. --- ## Arquitetura de Comunicação ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ Frontend React + TypeScript │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────────┐ │ │ │ App.tsx │ │ ChatView.tsx │ │ useRuntimeManager.ts │ │ │ │ (Main App) │ │ (Chat UI) │ │ (Runtime State) │ │ │ └────────┬────────┘ └────────┬────────┘ └─────────────┬───────────────┘ │ │ │ │ │ │ │ │ │ fetch() │ invoke() │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ Camada de API │ │ │ │ ┌─────────────────────┐ ┌─────────────────────────────────────────┐ │ │ │ │ │ lib/db.ts │ │ features/runtime/api.ts │ │ │ │ │ │ (sql.js + localStorage)│ │ (Tauri Commands) │ │ │ │ │ └─────────────────────┘ └─────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ │ │ HTTP / Tauri Bridge ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ Backend │ ├─────────────────────────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────┐ ┌─────────────────────────────────────┐ │ │ │ Tauri Backend (Rust) │ │ HTTP Server (Axum) │ │ │ │ - Commands │ │ - Port: 8080 │ │ │ │ - Events │ │ - /v1/chat/completions │ │ │ │ - Process spawning │ │ - /v1/models │ │ │ └─────────────────────────────┘ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## 1. Camadas de Comunicação ### 1.1 HTTP API (Chat Completions) **Local:** `ChatView.tsx` → HTTP Server Axum ```typescript // ChatView.tsx - Linha 251-263 const res = await fetch(`${server}/v1/chat/completions`, { method: "POST", headers, signal: abortRef.current.signal, body: JSON.stringify({ model, messages: apiMessages, // Array de {role, content} stream: useStream, // true/false stream_options: useStream ? { include_usage: true } : undefined, max_tokens: 2048, stop: CHAT_TRANSCRIPT_STOP_SEQUENCES, // ["\nSystem:", "\nUser:", ...] }), }); ``` **Headers enviados:** ```typescript const headers: Record = { "Content-Type": "application/json" }; if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`; ``` **Body da requisição:** ```json { "model": "qwen2.5-7b-instruct", "messages": [ { "role": "user", "content": "Olá, como vai?" } ], "stream": true, "stream_options": { "include_usage": true }, "max_tokens": 2048, "stop": ["\nSystem:", "\nUser:", "\nAssistant:", "\n\nSystem:", "\n\nUser:", "\n\nAssistant:"] } ``` ### 1.2 Tauri Commands (Gerenciamento do Runtime) **Local:** `features/runtime/api.ts` → Tauri Backend (Rust) O frontend usa `invoke` do `@tauri-apps/api/core` para chamar comandos Rust: ```typescript import { invoke } from "@tauri-apps/api/core"; // Exemplo: Iniciar runtime export async function startRuntime(): Promise { return invoke("start_runtime_command"); } ``` --- ## 2. Rotas HTTP Consumidas ### 2.1 POST /v1/chat/completions **Arquivo:** `ChatView.tsx` (linha 251) **Fluxo completo:** ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Usuário │────►│ ChatView │────►│ fetch() │ │ digita │ │ handleSend │ │ POST /v1/ │ └──────────────┘ └──────────────┘ └──────┬───────┘ │ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ DB (local) │◄────│ parse() │◄────│ Server │ │ sql.js │ │ response │ │ Axum │ └──────────────┘ └──────────────┘ └──────────────┘ ``` **Streaming (SSE):** ```typescript // ChatView.tsx - Linha 274-307 if (useStream) { const reader = res.body!.getReader(); const decoder = new TextDecoder('utf-8'); let buf = ""; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const lines = buf.split("\n"); buf = lines.pop()!; for (const line of lines) { if (!line.startsWith("data: ")) continue; const data = line.slice(6).trim(); if (data === "[DONE]") continue; const chunk = JSON.parse(data); const delta = chunk.choices?.[0]?.delta?.content; if (delta) { fullContent += delta; setStreamContent(parsed.content); setStreamReasoning(parsed.reasoning); } if (chunk.usage) usage = chunk.usage; } } } ``` **Resposta em streaming:** ``` data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"}}]} data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"Olá"}}]} data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"!"}}]} ... data: {"id":"...","object":"chat.completion.chunk","choices":[{"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":20,"total_tokens":30,"tokens_per_second":45.5}} data: [DONE] ``` **Modo Blocking:** ```typescript // ChatView.tsx - Linha 308-318 } else { const data = await res.json(); fullContent = data.choices?.[0]?.message?.content || ""; usage = data.usage; } ``` **Resposta blocking:** ```json { "id": "uuid-123", "object": "chat.completion", "created": 1234567890, "model": "qwen2.5-7b-instruct", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Olá! Como posso ajudar?" }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30, "tokens_per_second": 45.5 } } ``` ### 2.2 GET /v1/models **Arquivo:** `hooks/useModels.ts` ```typescript const res = await fetch(`${serverUrl}/v1/models`, { headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {}, }); const data = await res.json(); ``` **Resposta:** ```json { "object": "list", "data": [ { "id": "local-model", "object": "model", "created": 1234567890, "owned_by": "local" } ] } ``` ### 2.3 GET /health **Arquivo:** `hooks/useModels.ts` (status check) ```typescript const res = await fetch(`${serverUrl}/health`); ``` **Resposta:** ```json { "status": "ok", "model_loaded": true, "model_id": "qwen2.5-7b-instruct", "uptime_secs": 3600 } ``` --- ## 3. Comandos Tauri (Invoke) ### 3.1 Runtime Management API **Arquivo:** `features/runtime/api.ts` | Função | Comando Tauri | Descrição | |--------|---------------|-----------| | `bootstrapRuntime()` | `bootstrap_runtime_command` | Inicializa o runtime | | `getRuntimeSnapshot()` | `get_runtime_snapshot_command` | Obtém estado atual | | `saveRuntimeConfig(config)` | `save_runtime_config_command_handler` | Salva configurações | | `startRuntime()` | `start_runtime_command` | Inicia o servidor local | | `stopRuntime()` | `stop_runtime_command` | Para o servidor | **Exemplo - Iniciar Runtime:** ```typescript // api.ts export async function startRuntime(): Promise { try { return await invoke("start_runtime_command"); } catch { // Fallback para modo web (sem Tauri) const snapshot = readFallbackSnapshot(); return writeFallbackSnapshot({ ...snapshot, status: "error", last_error: { code: "tauri_unavailable", message: "Local runtime control requires the Tauri desktop app." }, }); } } ``` ### 3.2 Model Management API | Função | Comando Tauri | Descrição | |--------|---------------|-----------| | `scanRuntimeModels(dir)` | `scan_models` | Escaneia diretório de modelos | | `pickRuntimeModelsDir()` | `pick_models_dir` | Abre dialog de seleção | | `pickRuntimeModelFiles()` | `pick_model_files` | Seleciona arquivos | | `swapModel(path)` | `swap_model_command` | Troca modelo ativo | | `deleteModelFile(path)` | `delete_model_file` | Deleta arquivo .gguf | | `openModelFolder(path)` | `open_model_folder_command` | Abre pasta no Explorer | **Exemplo - Escaneamento:** ```typescript export async function scanRuntimeModels(dir: string): Promise { return invoke("scan_models", { dir }); } // Retorna: [{ name: "model.gguf", path: "/path/to/model.gguf", size: 4200000000 }] ``` ### 3.3 HuggingFace Integration API | Função | Comando Tauri | Descrição | |--------|---------------|-----------| | `searchHuggingFaceModels(query, token)` | `search_hugging_face_models_command` | Busca modelos | | `startHuggingFaceDownload(repo, file, token)` | `start_hugging_face_download_command` | Inicia download | | `cancelHuggingFaceDownload(id)` | `cancel_hugging_face_download_command` | Cancela download | **Exemplo - Busca HuggingFace:** ```typescript export async function searchHuggingFaceModels( query: string, token = "" ): Promise { return invoke("search_hugging_face_models_command", { query, token: token.trim() || null, }); } ``` ### 3.4 GPU Info API ```typescript export async function queryRuntimeGpu(): Promise { return invoke("query_gpu"); } // Retorna: { name: "NVIDIA RTX 4090", vram_mib: 24576, recommended_layers: 999 } ``` ### 3.5 ForgeAgent Session API **Arquivo:** `hooks/useForgeAgentSession.ts` ```typescript const response = await invoke( "start_forgeagent_session_command", { prompt } ); // Retorna: { plan_id: "forgeagent-1234567890", plan: AgentPlan } ``` **Eventos escutados:** ```typescript listen("forgeagent:step-started", handler); listen("forgeagent:step-completed", handler); listen("forgeagent:artifact-ready", handler); listen("forgeagent:plan-completed", handler); listen("forgeagent:specialist-thinking-started", handler); listen("forgeagent:specialist-thinking-update", handler); listen("forgeagent:specialist-response-completed", handler); ``` --- ## 4. Persistência Local (sql.js) ### 4.1 Estrutura do Banco **Arquivo:** `lib/db.ts` O frontend usa **sql.js** (SQLite compilado para WebAssembly) com persistência em `localStorage`: ```typescript // initDB() - Criação das tabelas db.run(` CREATE TABLE IF NOT EXISTS chats ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL DEFAULT 'New Chat', created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) ) `); db.run(` CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id INTEGER NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, reasoning_content TEXT, -- Conteúdo do ... prompt_tokens INTEGER DEFAULT 0, completion_tokens INTEGER DEFAULT 0, total_tokens INTEGER DEFAULT 0, tokens_per_second REAL, created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) ) `); db.run(` CREATE TABLE IF NOT EXISTS user_memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL UNIQUE, value TEXT NOT NULL, confidence REAL DEFAULT 1.0, created_at INTEGER, updated_at INTEGER ) `); ``` ### 4.2 Persistência ```typescript // db.ts - persist() persist() { const data = this.inner.export(); // Uint8Array // Converte para base64 em chunks let b64 = ""; const CHUNK = 8192; for (let i = 0; i < data.length; i += CHUNK) { b64 += String.fromCharCode(...data.subarray(i, i + CHUNK)); } const encoded = btoa(b64); localStorage.setItem("forgelm_db_v2", encoded); } ``` ### 4.3 Queries Principais **Carregar mensagens:** ```typescript // ChatView.tsx - loadMessages() const rows = db.query( "SELECT * FROM messages WHERE chat_id = ? ORDER BY created_at ASC", [targetChatId] ); ``` **Inserir mensagem do usuário:** ```typescript db.run( "INSERT INTO messages (chat_id, role, content) VALUES (?, 'user', ?)", [cid, text] ); ``` **Inserir resposta do assistente:** ```typescript db.run( `INSERT INTO messages ( chat_id, role, content, reasoning_content, prompt_tokens, completion_tokens, total_tokens, tokens_per_second ) VALUES (?, 'assistant', ?, ?, ?, ?, ?, ?)`, [cid, content, reasoning, usage?.prompt_tokens, usage?.completion_tokens, usage?.total_tokens, usage?.tokens_per_second] ); ``` **Histórico para API:** ```typescript // ChatView.tsx - buildApiMessages() const history = db.query<{ role: string; content: string }>( `SELECT role, content FROM messages WHERE chat_id = ? AND content != '' AND content NOT LIKE '⚠️ Error:%' ORDER BY created_at DESC LIMIT ?`, [cid, 100] ).reverse(); ``` --- ## 5. Fluxos de Dados Detalhados ### 5.1 Fluxo: Enviar Mensagem ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ FLUXO: ENVIAR MENSAGEM │ └─────────────────────────────────────────────────────────────────────────────┘ Usuário clica "Enviar" │ ▼ ┌───────────────┐ │ handleSend() │──► 1. Valida input e estado └───────┬───────┘ │ ▼ ┌───────────────┐ │ getOrCreateChat│──► 2. Cria novo chat se necessário (INSERT INTO chats) └───────┬───────┘ │ ▼ ┌───────────────┐ │ db.run(INSERT) │──► 3. Salva mensagem do usuário na DB local └───────┬───────┘ │ ▼ ┌───────────────┐ │ buildApiMessages│──► 4. Busca histórico das últimas 100 mensagens └───────┬───────┘ │ ▼ ┌───────────────────────────┐ │ fetch(POST /v1/chat/completions) │──► 5. Envia para servidor HTTP │ │ Headers: Content-Type, Authorization │ │ Body: {model, messages, stream, ...} └───────────┬───────────────┘ │ ┌────┴────┐ ▼ ▼ ┌─────────┐ ┌─────────┐ │ Stream │ │ Block │ │ Mode │ │ Mode │ └────┬────┘ └────┬────┘ │ │ ▼ ▼ ┌───────────────────────────┐ │ Processa chunks SSE │ │ - setStreamContent() │ │ - setStreamReasoning() │ │ - parseAssistantResponse()│ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ db.run(INSERT assistant) │──► 6. Salva resposta completa na DB └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ loadMessages() │──► 7. Recarrega mensagens do chat └───────────────────────────┘ ``` ### 5.2 Fluxo: Trocar Modelo ``` Usuário seleciona novo modelo no dropdown │ ▼ ┌───────────────────────────┐ │ onModelChange() │ │ onModelSwap() │ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ swapModel(newModelPath) │──► invoke("swap_model_command") └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ Tauri Backend (Rust) │ │ 1. stop_runtime() │ │ 2. Atualiza model_path │ │ 3. save_runtime_config() │ │ 4. start_runtime() │ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ RuntimeSnapshot retornado │ │ { status: "ready", ... } │ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────┐ │ setSelectedModel(path) │──► Atualiza estado React └───────────────────────────┘ ``` ### 5.3 Fluxo: Download HuggingFace ``` Usuário busca e seleciona modelo │ ▼ ┌───────────────────────────────┐ │ searchHuggingFaceModels() │──► invoke("search_hugging_face_models_command") │ │ Args: { query, token } └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Lista de modelos retornada │ │ [{ repo_id, author, │ │ verified_files: [...] }] │ └───────────────┬───────────────┘ │ ▼ Usuário clica "Download" │ ▼ ┌───────────────────────────────┐ │ startHuggingFaceDownload() │──► invoke("start_hugging_face_download_command") │ │ Args: { repo, fileName, token } └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Escuta eventos de progresso │ │ listen("hugging-face-download- │ │ progress", handler) │ └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Eventos recebidos: │ │ { phase: "progress", │ │ progressPercentage: 45, │ │ downloadedMB: 2048, │ │ totalMB: 4096 } │ └───────────────────────────────┘ ``` ### 5.4 Fluxo: ForgeAgent ``` Usuário ativa modo Agentic e envia mensagem │ ▼ ┌───────────────────────────────┐ │ start_forgeagent_session │──► invoke("start_forgeagent_session_command") │ _command({ prompt }) │ └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Resposta inicial: │ │ { plan_id, plan: { │ │ title, overview, steps[] }} │ └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Escuta eventos Tauri: │ ├───────────────────────────────┤ │ forgeagent:step-started │──► Atualiza UI com passo atual │ forgeagent:step-completed │──► Marca passo como concluído │ forgeagent:specialist- │──► Mostra especialista │ thinking-started │ │ forgeagent:specialist- │──► Atualiza pensamento │ thinking-update │ │ forgeagent:specialist- │──► Mostra resposta do especialista │ response-completed │ │ forgeagent:plan-completed │──► Mostra resumo final └───────────────────────────────┘ ``` --- ## 6. Hooks Principais ### 6.1 useRuntimeManager **Arquivo:** `features/runtime/useRuntimeManager.ts` Gerencia todo o estado do runtime local: ```typescript export function useRuntimeManager() { // Estados const [draftState, setDraftState] = useState(...); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); // Métodos expostos return { snapshot: draftState.liveSnapshot, // Estado atual do runtime connection: { mode, serverUrl, apiKey }, // Dados de conexão startLocal, // Inicia servidor stopLocal, // Para servidor saveConfig, // Salva configurações scanModels, // Escaneia modelos // ... e mais }; } ``` **Polling:** Atualiza a cada 3 segundos ```typescript const id = window.setInterval(() => { refresh().catch(() => undefined); }, 3000); ``` ### 6.2 useModels **Arquivo:** `hooks/useModels.ts` Gerencia a lista de modelos disponíveis: ```typescript export function useModels(serverUrl: string, apiKey: string, enabled: boolean) { const [models, setModels] = useState([]); const [selectedModel, setSelectedModel] = useState(""); const [status, setStatus] = useState("loading"); const refresh = useCallback(async () => { // 1. Health check const healthRes = await fetch(`${serverUrl}/health`); // 2. Lista modelos const modelsRes = await fetch(`${serverUrl}/v1/models`, { headers }); const data = await modelsRes.json(); setModels(data.data); }, [serverUrl, apiKey]); return { models, selectedModel, setSelectedModel, status, refresh }; } ``` ### 6.3 useForgeAgentSession **Arquivo:** `hooks/useForgeAgentSession.ts` ```typescript export function useForgeAgentSession() { const [state, setState] = useState(null); const start = useCallback(async (prompt: string) => { const response = await invoke( "start_forgeagent_session_command", { prompt } ); // Inicializa estado com plano recebido const initial = createForgeAgentState(response.plan_id, response.plan); updateState(initial); }, []); // Escuta eventos em tempo real useEffect(() => { const unlisten = await Promise.all([ listen("forgeagent:step-started", handler), listen("forgeagent:step-completed", handler), // ... mais eventos ]); }, []); return { state, start, reset }; } ``` ### 6.4 useDB **Arquivo:** `hooks/useDB.ts` ```typescript export function useDB() { const [db, setDb] = useState(null); const [ready, setReady] = useState(false); useEffect(() => { initDB().then((database) => { setDb(database); setReady(true); }); }, []); return { db, ready }; } ``` --- ## 7. Tipos de Dados (TypeScript) ### 7.1 Mensagens ```typescript // lib/types.ts interface Message { id: number; chat_id: number; role: "user" | "assistant" | "system"; content: string; reasoning_content?: string | null; // ... display_mode?: "default" | "agentic_final"; forgeagent_state?: AgentPlanState; prompt_tokens?: number; completion_tokens?: number; total_tokens?: number; tokens_per_second?: number; created_at: number; } ``` ### 7.2 Runtime ```typescript // features/runtime/types.ts interface RuntimeSnapshot { status: "stopped" | "starting" | "loading" | "ready" | "degraded" | "stopping" | "error"; ready: boolean; process_running: boolean; resolved_url: string; binary_path: string | null; last_error: { code: string; message: string } | null; active_command: "idle" | "save" | "start" | "stop" | "save_and_start"; last_command_result: { command: string; outcome: "succeeded" | "blocked" | "failed"; message: string; }; config: RuntimeConfig; } interface RuntimeConfig { connection_mode: "local" | "remote"; server_url: string; api_key: string; model_path: string | null; models_dir: string | null; addr: string; // "127.0.0.1:8080" gpu_layers: number; ctx_size: number; // ... } ``` ### 7.3 ForgeAgent ```typescript // types/forgeagent.ts interface AgentPlan { title: string; overview: string; steps: AgentStep[]; } interface AgentStep { id: number; title: string; description: string; status: "pending" | "running" | "completed" | "error"; sub_steps: AgentSubStep[]; } interface SpecialistResponse { specialist_id: string; specialist_name: string; thinking: string; response: string; } ``` --- ## 8. Resumo das Comunicações ### Tabela de Endpoints | Origem | Destino | Método | Rota/Command | Payload | Response | |--------|---------|--------|--------------|---------|----------| | ChatView | HTTP Server | POST | `/v1/chat/completions` | `{model, messages, stream, max_tokens}` | SSE ou JSON | | useModels | HTTP Server | GET | `/v1/models` | - | `{data: ModelInfo[]}` | | useModels | HTTP Server | GET | `/health` | - | `{status, model_loaded}` | | useRuntimeManager | Tauri | invoke | `start_runtime_command` | - | `RuntimeSnapshot` | | useRuntimeManager | Tauri | invoke | `stop_runtime_command` | - | `RuntimeSnapshot` | | useRuntimeManager | Tauri | invoke | `save_runtime_config_command_handler` | `{config}` | `RuntimeSnapshot` | | useRuntimeManager | Tauri | invoke | `scan_models` | `{dir}` | `RuntimeModelFile[]` | | useRuntimeManager | Tauri | invoke | `swap_model_command` | `{newModelPath}` | `RuntimeSnapshot` | | API HuggingFace | Tauri | invoke | `search_hugging_face_models_command` | `{query, token}` | `HuggingFaceModelResult[]` | | API HuggingFace | Tauri | invoke | `start_hugging_face_download_command` | `{repo, fileName, token}` | `downloadId` | | useForgeAgentSession | Tauri | invoke | `start_forgeagent_session_command` | `{prompt}` | `{plan_id, plan}` | | useForgeAgentSession | Tauri | listen | `forgeagent:step-started` | - | `StepStartedEvent` | | useForgeAgentSession | Tauri | listen | `forgeagent:step-completed` | - | `StepCompletedEvent` | | useForgeAgentSession | Tauri | listen | `forgeagent:plan-completed` | - | `PlanCompletedEvent` | --- ## 9. Configurações e Estado Global ### Estado do Runtime (Contexto Global) ```typescript // App.tsx const runtime = useRuntimeManager(); // Acesso em componentes filhos via props ``` ### Persistência do Runtime ```typescript // localStorage: forgelm_runtime_snapshot_v1 { "status": "ready", "ready": true, "process_running": true, "resolved_url": "http://127.0.0.1:8080", "config": { "connection_mode": "local", "model_path": "D:/models/model.gguf", "addr": "127.0.0.1:8080" } } ``` --- ## 10. Diagrama de Sequência - Chat Completo ``` Usuário ChatView DB(sql.js) HTTP Server Tauri Modelo │ │ │ │ │ │ │──mensagem►│ │ │ │ │ │ │──INSERT───►│ │ │ │ │ │◄───────────│ │ │ │ │ │ │ │ │ │ │ │──SELECT───►│ (histórico) │ │ │ │ │◄───────────│ │ │ │ │ │ │ │ │ │ │ │────────────┼──────────────┼──────────┼──► │ │ │ fetch() │ │ │ inference │ │ POST /v1/ │ │ │ │ │ │chat/completions │ │ │ │ │ │ │ │ │ │ │◄───────────┼──────────────┼──────────┼─────────│ │ │ SSE │ │ │ │ │ │ chunks │ │ │ │ │ │ │ │ │ │ │◄──stream─│ │ │ │ │ │ tokens │ │ │ │ │ │ │ │ │ │ │ │ │──INSERT───►│ │ │ │ │ │ assistant │ │ │ │ │ │◄───────────│ │ │ │ │ │ │ │ │ │ │◄─display │ │ │ │ │ │ completo│ │ │ │ │ ``` --- ## Conclusão O frontend ForgeLM consome o backend através de **duas camadas** distintas: 1. **HTTP API** - Para chat completions (streaming e blocking) via `fetch()` 2. **Tauri Commands** - Para gerenciamento do runtime, modelos e downloads via `invoke()` A persistência local usa **sql.js** (SQLite WASM) com backup em `localStorage`, garantindo que conversas e memórias do usuário sejam mantidas entre sessões. O sistema suporta **dois modos**: - **Local**: Tauri gerencia o processo do servidor HTTP internamente - **Remote**: Conecta a um servidor OpenAI-compatible externo Todos os comandos Tauri possuem **fallbacks para modo web**, permitindo que o app funcione (com funcionalidade reduzida) mesmo sem o backend Tauri.