Files
ForgeLM/FRONTEND_API_DOCUMENTATION.md
Evandro Fonseca Junior 88f4319c3f fix: stop transcript leakage and repetition loops in Generic chat template
Backend (inference-server/compat.rs):
- PreparedPrompt now carries extra_stop_sequences injected by template
- Generic template injects User:/System:/Assistant:/Human: stop sequences
  to prevent the model from continuing the transcript after its reply
- Increased repeat_penalty 1.15→1.3 and repeat_last_n 256→512 to
  break repetition loops more aggressively

Backend (inference-server/routes.rs):
- Merge template extra_stop_sequences into sampler before inference

Frontend (chat.ts):
- Added 'System:' and 'Human:' to CHAT_TRANSCRIPT_STOP_SEQUENCES
- Extended TRANSCRIPT_LEAKAGE_PATTERN to include 'Human:'

Frontend (ChatView.tsx):
- buildApiMessages now strips leaked transcript from assistant messages
  before sending history back to the model
- Reduced history window to 20 messages to limit context pollution
2026-04-02 20:42:04 -03:00

35 KiB

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

// 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:

const headers: Record<string, string> = { 
  "Content-Type": "application/json" 
};
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;

Body da requisição:

{
  "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:

import { invoke } from "@tauri-apps/api/core";

// Exemplo: Iniciar runtime
export async function startRuntime(): Promise<RuntimeSnapshot> {
  return invoke<RuntimeSnapshot>("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):

// 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:

// ChatView.tsx - Linha 308-318
} else {
  const data = await res.json();
  fullContent = data.choices?.[0]?.message?.content || "";
  usage = data.usage;
}

Resposta blocking:

{
  "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

const res = await fetch(`${serverUrl}/v1/models`, {
  headers: apiKey ? { "Authorization": `Bearer ${apiKey}` } : {},
});
const data = await res.json();

Resposta:

{
  "object": "list",
  "data": [
    {
      "id": "local-model",
      "object": "model",
      "created": 1234567890,
      "owned_by": "local"
    }
  ]
}

2.3 GET /health

Arquivo: hooks/useModels.ts (status check)

const res = await fetch(`${serverUrl}/health`);

Resposta:

{
  "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:

// api.ts
export async function startRuntime(): Promise<RuntimeSnapshot> {
  try {
    return await invoke<RuntimeSnapshot>("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:

export async function scanRuntimeModels(dir: string): Promise<RuntimeModelFile[]> {
  return invoke<RuntimeModelFile[]>("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:

export async function searchHuggingFaceModels(
  query: string, 
  token = ""
): Promise<HuggingFaceModelResult[]> {
  return invoke<HuggingFaceModelResult[]>("search_hugging_face_models_command", {
    query,
    token: token.trim() || null,
  });
}

3.4 GPU Info API

export async function queryRuntimeGpu(): Promise<RuntimeGpuInfo | null> {
  return invoke<RuntimeGpuInfo>("query_gpu");
}
// Retorna: { name: "NVIDIA RTX 4090", vram_mib: 24576, recommended_layers: 999 }

3.5 ForgeAgent Session API

Arquivo: hooks/useForgeAgentSession.ts

const response = await invoke<StartForgeAgentResponse>(
  "start_forgeagent_session_command", 
  { prompt }
);
// Retorna: { plan_id: "forgeagent-1234567890", plan: AgentPlan }

Eventos escutados:

listen<StepStartedEvent>("forgeagent:step-started", handler);
listen<StepCompletedEvent>("forgeagent:step-completed", handler);
listen<ArtifactReadyEvent>("forgeagent:artifact-ready", handler);
listen<PlanCompletedEvent>("forgeagent:plan-completed", handler);
listen<SpecialistThinkingStartedEvent>("forgeagent:specialist-thinking-started", handler);
listen<SpecialistThinkingUpdateEvent>("forgeagent:specialist-thinking-update", handler);
listen<SpecialistResponseCompletedEvent>("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:

// 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 <think>...</think>
    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

// 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:

// ChatView.tsx - loadMessages()
const rows = db.query<Message>(
  "SELECT * FROM messages WHERE chat_id = ? ORDER BY created_at ASC",
  [targetChatId]
);

Inserir mensagem do usuário:

db.run(
  "INSERT INTO messages (chat_id, role, content) VALUES (?, 'user', ?)",
  [cid, text]
);

Inserir resposta do assistente:

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:

// 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:

export function useRuntimeManager() {
  // Estados
  const [draftState, setDraftState] = useState<RuntimeDraftState>(...);
  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

const id = window.setInterval(() => {
  refresh().catch(() => undefined);
}, 3000);

6.2 useModels

Arquivo: hooks/useModels.ts

Gerencia a lista de modelos disponíveis:

export function useModels(serverUrl: string, apiKey: string, enabled: boolean) {
  const [models, setModels] = useState<ModelInfo[]>([]);
  const [selectedModel, setSelectedModel] = useState("");
  const [status, setStatus] = useState<ServerStatus>("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

export function useForgeAgentSession() {
  const [state, setState] = useState<AgentPlanState | null>(null);

  const start = useCallback(async (prompt: string) => {
    const response = await invoke<StartForgeAgentResponse>(
      "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<StepStartedEvent>("forgeagent:step-started", handler),
      listen<StepCompletedEvent>("forgeagent:step-completed", handler),
      // ... mais eventos
    ]);
  }, []);

  return { state, start, reset };
}

6.4 useDB

Arquivo: hooks/useDB.ts

export function useDB() {
  const [db, setDb] = useState<DB | null>(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

// lib/types.ts
interface Message {
  id: number;
  chat_id: number;
  role: "user" | "assistant" | "system";
  content: string;
  reasoning_content?: string | null;    // <think>...</think>
  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

// 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

// 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)

// App.tsx
const runtime = useRuntimeManager();

// Acesso em componentes filhos via props
<ChatView
  server={runtime.connection.serverUrl}      // "http://127.0.0.1:8080"
  apiKey={runtime.connection.apiKey}         // ""
  model={selectedModel}                      // "qwen2.5-7b-instruct"
  runtimeStatus={runtime.snapshot.status}    // "ready"
/>

Persistência do Runtime

// 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.