/** * SetupTab/EmbedderSetup.tsx — Embedder setup section (P0b). * * Shows current embedder configuration, warns about trigram recall quality, * and mirrors the /megasetup command with detection results. Extracted from * SetupTab.tsx to respect the extensions/ 500-line file limit. * * PREVENT-PI-004: all data comes from localhost dashboard API endpoints — * no external network calls. */ import type React from "react"; import { useState, useEffect, useCallback } from "react"; import type { SetupStatusResponse, SetupDetectResponse, SetupConfigureResponse } from "@contracts"; import { fetchSetupStatus, fetchSetupDetect, configureEmbedder } from "../../api/client"; import EmbedderHealthCard from "./EmbedderHealthCard"; import SettingsPanel from "./SettingsPanel"; import CustomEndpointSection from "./CustomEndpointSection"; import { styles } from "./EmbedderSetupStyles"; import { detectBadge, CurrentConfigSection } from "./EmbedderSetupHelpers"; export default function EmbedderSetup(): React.ReactElement { const [status, setStatus] = useState(null); const [detect, setDetect] = useState(null); const [statusError, setStatusError] = useState(null); const [detectError, setDetectError] = useState(null); const [runningDetect, setRunningDetect] = useState(false); const [configuring, setConfiguring] = useState(null); const [configResult, setConfigResult] = useState(null); const [configError, setConfigError] = useState(null); const [customUrl, setCustomUrl] = useState(""); // True once the URL field has been seeded from the persisted endpoint URL — // seeding runs only on the first status load that carries the field so the // user's in-flight edits are never clobbered by a poll refresh. const [customUrlSeeded, setCustomUrlSeeded] = useState(false); const loadStatus = useCallback(() => { fetchSetupStatus() .then(setStatus) .catch((e: unknown) => setStatusError(e instanceof Error ? e.message : String(e)), ); }, []); const applyEmbedder = useCallback( ( embedder: "ollama" | "llama" | "trigram" | "custom" | "onnx", url?: string, apiKey?: string, dim?: string, headers?: string, allowRemote?: boolean, ) => { setConfiguring(embedder); setConfigError(null); setConfigResult(null); // ENC-1a: optional custom-endpoint URL + bearer key ride on the setup-configure POST (additive; flag-off ignores them). // ENC-1b: the dim / headers / allow-remote fields are forwarded additively, gated on the same // `embeddingHeadersSet` presence marker the aggregator/server uses (no separate client env read). const enc1bOn = "embeddingHeadersSet" in (status ?? {}); configureEmbedder({ embedder, url, embeddingEndpointUrl: url, embeddingApiKey: apiKey, ...(enc1bOn ? { embeddingDim: dim, embeddingHeaders: headers, allowRemoteEmbedder: allowRemote } : {}), }) .then((r) => { setConfigResult(r); setConfiguring(null); loadStatus(); }) .catch((e: unknown) => { setConfigError(e instanceof Error ? e.message : String(e)); setConfiguring(null); }); }, [loadStatus, status], ); const runDetect = useCallback(() => { setRunningDetect(true); setDetectError(null); fetchSetupDetect() .then((d) => { setDetect(d); setRunningDetect(false); }) .catch((e: unknown) => { setDetectError(e instanceof Error ? e.message : String(e)); setRunningDetect(false); }); }, []); useEffect(() => { loadStatus(); }, [loadStatus]); // Seed the custom-endpoint URL field once from the persisted // `embeddingEndpointUrl` (ENC-1a). Later poll refreshes do NOT re-seed — // only the first load, so typed edits survive the 5s status poll. useEffect(() => { if (customUrlSeeded) return; if (status && typeof status.embeddingEndpointUrl === "string" && status.embeddingEndpointUrl.length > 0) { setCustomUrl(status.embeddingEndpointUrl); setCustomUrlSeeded(true); } }, [status, customUrlSeeded]); // Poll status every 5s — the same cadence the Setup Cortex sub-tab uses // (see useSetupCortexPoll) so the embedder + cortex sub-tabs share one poll // contract (VC9D consolidation). Detection is memoized server-side, so the // periodic detect refresh below hits the cache rather than re-spawning. useEffect(() => { const id = setInterval(loadStatus, 5000); return () => clearInterval(id); }, [loadStatus]); // Auto-refresh the (server-memoized) detection at the same 5s cadence so the // embedder sub-tab reflects cached detect changes without manual clicks; the // manual "Run Detection" button still works for an explicit refresh. useEffect(() => { runDetect(); const id = setInterval(runDetect, 5000); return () => clearInterval(id); }, [runDetect]); return (

Embedder Setup

{/* Current Config Section */} {/* Detection Section */}

Local Embedder Detection

Detect which local embedding backends are available on this machine. No software is installed — detection only.

{detect && ( <> {/* Ollama */}
Ollama {detect.ollama !== null ? ( {detectBadge(detect.ollama.installed)} {detect.ollama.installed && ( {detect.ollama.running ? `server running, ${detect.ollama.models.length} model(s)` : "server not running"} )} ) : ( not checked )}
{/* llama.cpp */}
llama.cpp {detect.llamaCpp !== null ? ( {detectBadge(detect.llamaCpp.installed)} {detect.llamaCpp.installed && detect.llamaCpp.detail && ( {detect.llamaCpp.detail} )} ) : ( not checked )}
{/* ONNX */}
ONNX Runtime {detect.onnx !== null ? ( {detectBadge(detect.onnx.installed)} {detect.onnx.installed && detect.onnx.detail && ( {detect.onnx.detail} )} ) : ( not checked )}
{detect.error && (

Detection error: {detect.error}

)} )} {detectError && (

Error: {detectError}

)}
Or run /megasetup in the chat for an interactive wizard.
{/* Configure buttons — appear after detection runs */} {detect && (
Configure
{configResult && configResult.embedder !== "custom" && (
{configResult.alreadyActive ? "Already active — no restart needed." : `Configured ${configResult.embedder}. Restart pi to activate (config written to ${configResult.envPath}).`}
)} {configError && (

Configure error: {configError}

)}
)}
{/* Custom (remote) embedder — third-party / hosted endpoint */} {/* Help Section */}

How to Upgrade

  • Ollama: Install from{" "} ollama.com , then ollama pull nomic-embed-text and restart pi. Set MEGACOMPACT_EMBEDDING_URL=http://localhost:11434/api/embeddings.
  • llama.cpp: Build or install llama.cpp, run{" "} llama-server with an embedding model, and set the URL accordingly.
  • ONNX Runtime: Run a local text-embeddings server (e.g. TEI) and click Use ONNX above. Default URL: http://localhost:8081/v1/embeddings.
{/* Settings Section — every adjustable MEGACOMPACT_* setting */}

Settings

); }