import { useEffect, useRef, useState } from "react"; type LineKind = "user" | "system" | "error" | "muted"; interface Line { kind: LineKind; text: string; } interface PersonaSummary { name: string; title: string; temperature: number; tools: string[]; } interface Citation { source: string; index: number; text: string; score: number; } interface AskResponse { persona: string; query: string; answer: string; citations: Citation[]; model: string; } interface QueryResponse { query: string; results: Citation[]; } const HELP = `T.I.A. terminal — available commands: /personas List configured personas /persona Select active persona /health Hit /v1/system/health /search Plain RAG search via /v1/harvest/query /token Set/replace the Bearer token (kept in memory only) /clear Clear the screen /help Show this message Ask the active persona (defaults to Librarian)`; export function TerminalView() { const [lines, setLines] = useState([ { kind: "system", text: "T.I.A. RAG Engine — Terminal v0.1" }, { kind: "muted", text: "Type /help for available commands." }, ]); const [input, setInput] = useState(""); const [persona, setPersona] = useState("Librarian"); const [personas, setPersonas] = useState([]); const [token, setToken] = useState(""); const [busy, setBusy] = useState(false); const outputRef = useRef(null); const inputRef = useRef(null); useEffect(() => { outputRef.current?.scrollTo({ top: outputRef.current.scrollHeight }); }, [lines]); useEffect(() => { inputRef.current?.focus(); }, []); function append(kind: LineKind, text: string) { setLines((prev) => [...prev, { kind, text }]); } async function api(path: string, init?: RequestInit): Promise { const headers = new Headers(init?.headers || {}); headers.set("Accept", "application/json"); if (init?.body && !headers.has("Content-Type")) { headers.set("Content-Type", "application/json"); } if (token) headers.set("Authorization", `Bearer ${token}`); return fetch(path, { ...init, headers }); } async function loadPersonas() { try { const res = await api("/v1/personas"); if (!res.ok) { append("error", `GET /v1/personas → ${res.status}`); return; } const data = (await res.json()) as { personas: PersonaSummary[] }; setPersonas(data.personas); append( "system", `Personas: ${data.personas.map((p) => p.name).join(", ")}` ); } catch (err) { append("error", `Failed to load personas: ${(err as Error).message}`); } } async function runHealth() { try { const res = await fetch("/v1/system/health"); const body = await res.json(); append("system", JSON.stringify(body, null, 2)); } catch (err) { append("error", `health check failed: ${(err as Error).message}`); } } async function runSearch(query: string) { if (!query.trim()) { append("error", "Usage: /search "); return; } setBusy(true); try { const res = await api("/v1/harvest/query", { method: "POST", body: JSON.stringify({ query, top_k: 5 }), }); if (!res.ok) { append("error", `query failed: ${res.status} ${await res.text()}`); return; } const data = (await res.json()) as QueryResponse; if (!data.results.length) { append("muted", "(no results)"); } for (const hit of data.results) { append( "system", `[${hit.source}#${hit.index}] (score=${hit.score})\n${hit.text}` ); } } catch (err) { append("error", `search failed: ${(err as Error).message}`); } finally { setBusy(false); } } async function askPersona(query: string) { setBusy(true); try { const res = await api(`/v1/personas/${encodeURIComponent(persona)}/ask`, { method: "POST", body: JSON.stringify({ query, top_k: 5 }), }); if (!res.ok) { append( "error", `${persona} replied ${res.status}: ${await res.text()}` ); return; } const data = (await res.json()) as AskResponse; append("system", `${data.persona} (${data.model}):\n${data.answer}`); if (data.citations.length) { const cites = data.citations .map((c) => ` • [${c.source}#${c.index}] (score=${c.score})`) .join("\n"); append("muted", `citations:\n${cites}`); } } catch (err) { append("error", `persona call failed: ${(err as Error).message}`); } finally { setBusy(false); } } async function handleSubmit(raw: string) { const command = raw.trim(); if (!command) return; append("user", `❯ ${command}`); if (command === "/help") { append("muted", HELP); return; } if (command === "/clear") { setLines([]); return; } if (command === "/personas") { await loadPersonas(); return; } if (command === "/health") { await runHealth(); return; } if (command.startsWith("/persona ")) { const next = command.slice("/persona ".length).trim(); if (!next) { append("error", "Usage: /persona "); } else { setPersona(next); append("system", `Active persona set to: ${next}`); } return; } if (command.startsWith("/search ")) { await runSearch(command.slice("/search ".length).trim()); return; } if (command.startsWith("/token ")) { const next = command.slice("/token ".length).trim(); setToken(next); append( "system", next ? "Bearer token updated (in-memory)." : "Bearer token cleared." ); return; } if (command.startsWith("/")) { append("error", `Unknown command: ${command}. Type /help.`); return; } await askPersona(command); } function onKeyDown(event: React.KeyboardEvent) { if (event.key === "Enter" && !busy) { const value = input; setInput(""); void handleSubmit(value); } } return (
T.I.A. // TERMINAL
{lines.map((line, i) => (
{line.text}
))}
❯ setInput(e.target.value)} onKeyDown={onKeyDown} />
); }