Spaces:
Sleeping
Sleeping
Download frontend/src/TerminalView.tsx from DJ-Goanna-Coding/Oppo-TIA: direct link, hf CLI and curl.
- Browser
- Download file 8.25 kB
-
https://huggingface.co/spaces/DJ-Goanna-Coding/Oppo-TIA/resolve/main/frontend/src/TerminalView.tsx
- Command line
-
hf download hf://spaces/DJ-Goanna-Coding/Oppo-TIA/frontend/src/TerminalView.tsx
-
curl -L -o TerminalView.tsx https://huggingface.co/spaces/DJ-Goanna-Coding/Oppo-TIA/resolve/main/frontend/src/TerminalView.tsx
8.25 kB
| 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 <name> Select active persona | |
| /health Hit /v1/system/health | |
| /search <query> Plain RAG search via /v1/harvest/query | |
| /token <jwt> Set/replace the Bearer token (kept in memory only) | |
| /clear Clear the screen | |
| /help Show this message | |
| <free text> Ask the active persona (defaults to Librarian)`; | |
| export function TerminalView() { | |
| const [lines, setLines] = useState<Line[]>([ | |
| { 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<PersonaSummary[]>([]); | |
| const [token, setToken] = useState<string>(""); | |
| const [busy, setBusy] = useState(false); | |
| const outputRef = useRef<HTMLDivElement>(null); | |
| const inputRef = useRef<HTMLInputElement>(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<Response> { | |
| 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 <query>"); | |
| 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 <name>"); | |
| } 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<HTMLInputElement>) { | |
| if (event.key === "Enter" && !busy) { | |
| const value = input; | |
| setInput(""); | |
| void handleSubmit(value); | |
| } | |
| } | |
| return ( | |
| <div className="terminal"> | |
| <div className="terminal-header"> | |
| <div className="terminal-title">T.I.A. // TERMINAL</div> | |
| <div className="terminal-controls"> | |
| <label htmlFor="persona-select">persona</label> | |
| <select | |
| id="persona-select" | |
| value={persona} | |
| onChange={(e) => setPersona(e.target.value)} | |
| > | |
| {(personas.length | |
| ? personas.map((p) => p.name) | |
| : ["Librarian", "Vanguard", "Oracle"] | |
| ).map((name) => ( | |
| <option key={name} value={name}> | |
| {name} | |
| </option> | |
| ))} | |
| </select> | |
| <button onClick={() => void loadPersonas()} type="button"> | |
| refresh | |
| </button> | |
| <button onClick={() => void runHealth()} type="button"> | |
| health | |
| </button> | |
| </div> | |
| </div> | |
| <div className="terminal-output" ref={outputRef}> | |
| {lines.map((line, i) => ( | |
| <div key={i} className={`terminal-line ${line.kind}`}> | |
| {line.text} | |
| </div> | |
| ))} | |
| </div> | |
| <div className="terminal-input"> | |
| <span>❯</span> | |
| <input | |
| ref={inputRef} | |
| type="text" | |
| autoComplete="off" | |
| spellCheck={false} | |
| disabled={busy} | |
| value={input} | |
| placeholder={busy ? "…" : `ask ${persona} or /help`} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={onKeyDown} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| } | |