umbau(boxwart): neue Oberflaeche im Cockpit-Stil (Richtung A), Werkzeuge angehoben
Drei Seiten statt zehn: Start, Updates, Modelle — am PC mit Kopfnavigation, am Handy mit Leiste unten. Umsetzung von Mockup A („Cockpit“), vom User am 23.09. gewaehlt. - Start: Hauptwarnleuchte + 8 Warnlampen, Rundinstrumente mit Live-Werten aus dem Strom (Speicher, Temperatur, Platte) und Laufzeit-Zaehlwerk, Checkliste der Waechter-Hinweise mit ihren Knoepfen, Flugplan (heute gelaufen / geplant), Radar-Kasten. - Updates: Bausteine mit „Laeuft → Neu“ und Zusammenfassung, laufende Auftraege, Verlauf der Sonntagslaeufe (neu: GET /api/updates/verlauf), Sicherungen samt Zurueckspielen mit Rueckfrage. - Modelle: Speicherbalken, Rollen Hirn/Coder/Dritte Rolle, wer die Modelle nutzt (7 Tage + 24 h je Stunde), Modell-Radar, weitere Eintraege, Modelle selbst suchen. - Schubladen: Dienste mit Protokoll und Neustart, Einstellungen (HF-Zugang), Hermes-Link. - Werkzeuge: Vite 8, React 19.3 mit React Compiler 1.0 (Babel), vitest 5, Tailwind 4.3, shadcn 4 (Radix) fuer Dialog/Schublade/Knopf, Schriften Barlow/Barlow Condensed/ JetBrains Mono. Entfernt: recharts, cmdk, Kraftgraph, zustand, Inter, Space Grotesk. - Startbuendel 115 KB gzip (Budget 140); Updates/Modelle/Schubladen laden bei Bedarf. - 16 Oberflaechen-Tests (Instrument-Bogen, Hauptleuchte, Zeitformate, Versionen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3d2c9549fb
commit
907289d7dc
+40
-635
@@ -1,644 +1,49 @@
|
||||
// Schmaler Fetch-Helfer gegen das MC-2-Backend (/api/*).
|
||||
//
|
||||
// v3-Umbau P1 (28.08.2026): Hier wurden frueher zwei Geheimnisse aus dem localStorage
|
||||
// gelesen und an jeden mutierenden Request gehaengt — das Box-Sudo-Passwort (als Header
|
||||
// UND im JSON-Rumpf) und der HuggingFace-Token. Da der Dienst-Nutzer auf der Box mit
|
||||
// `NOPASSWD: ALL` laeuft, war ein XSS in dieser SPA gleichbedeutend mit Root.
|
||||
// Das Sudo-Passwort ist ersatzlos weg (auf der Box gemessen: `sudo -n true` laeuft durch,
|
||||
// es wurde nie gebraucht), der HF-Token liegt jetzt auf der Box (services/geheimnisse.py).
|
||||
// Diese Datei kennt kein Geheimnis mehr — und soll auch nie wieder eines kennen.
|
||||
// Dünner Zugriff auf das MC2-Backend. Bewusst ohne Anmelde- oder Geheimnis-Felder:
|
||||
// Nichts, was der Browser speichert, reist hier mit (Lehre aus v3-Umbau P1).
|
||||
|
||||
/** Fehler mit dem, was das Backend WIRKLICH gesagt hat.
|
||||
*
|
||||
* Vorher warf der Helfer `new Error("500 Internal Server Error")` und verwarf den Rumpf.
|
||||
* FastAPI legt den eigentlichen Grund aber in `detail` — der erreichte die Oberflaeche nie,
|
||||
* und der Nutzer las "500" statt "Dienst 'llama-swap' nicht erlaubt". */
|
||||
export class ApiError extends Error {
|
||||
readonly status: number
|
||||
readonly detail: string | null
|
||||
constructor(status: number, statusText: string, detail: string | null) {
|
||||
super(detail || `${status} ${statusText}`)
|
||||
this.name = "ApiError"
|
||||
this.status = status
|
||||
this.detail = detail
|
||||
export class ApiFehler extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
public detail: string,
|
||||
) {
|
||||
super(detail)
|
||||
this.name = "ApiFehler"
|
||||
}
|
||||
}
|
||||
|
||||
/** Liest den Fehler-Rumpf, ohne selbst zu scheitern: Ein kaputter oder leerer Rumpf darf
|
||||
* die Fehlermeldung nicht in einen zweiten Fehler verwandeln. */
|
||||
async function fehlerText(res: Response): Promise<string | null> {
|
||||
/** FastAPI legt Fehlertexte in `detail` ab — manchmal als Text, manchmal als Liste. */
|
||||
function detailAus(body: unknown, status: number): string {
|
||||
if (body && typeof body === "object" && "detail" in body) {
|
||||
const d = (body as { detail: unknown }).detail
|
||||
if (typeof d === "string") return d
|
||||
if (Array.isArray(d)) return d.map((x) => (x as { msg?: string }).msg ?? String(x)).join("; ")
|
||||
}
|
||||
return `Die Box antwortet mit Fehler ${status}.`
|
||||
}
|
||||
|
||||
export async function api<T>(pfad: string, init: RequestInit = {}): Promise<T> {
|
||||
let antwort: Response
|
||||
try {
|
||||
const roh = await res.text()
|
||||
if (!roh) return null
|
||||
try {
|
||||
const d = JSON.parse(roh)
|
||||
const detail = d?.detail ?? d?.err ?? d?.message
|
||||
if (typeof detail === "string" && detail.trim()) return detail.trim()
|
||||
if (Array.isArray(detail) && detail.length) {
|
||||
// FastAPI-Validierungsfehler: Liste aus {loc, msg, type}
|
||||
return detail.map((e: any) => e?.msg).filter(Boolean).join("; ") || null
|
||||
}
|
||||
return null
|
||||
} catch {
|
||||
// Kein JSON (HTML-Fehlerseite eines Proxys o. AE.) — erste Zeile reicht als Hinweis.
|
||||
return roh.slice(0, 200).split("\n")[0].trim() || null
|
||||
}
|
||||
antwort = await fetch(pfad, {
|
||||
...init,
|
||||
headers: { "Content-Type": "application/json", Accept: "application/json", ...init.headers },
|
||||
})
|
||||
} catch {
|
||||
return null
|
||||
throw new ApiFehler(0, "MC2 ist nicht erreichbar. Läuft die Box?")
|
||||
}
|
||||
const text = await antwort.text()
|
||||
const body = text ? safeJson(text) : null
|
||||
if (!antwort.ok) throw new ApiFehler(antwort.status, detailAus(body, antwort.status))
|
||||
return body as T
|
||||
}
|
||||
|
||||
function safeJson(text: string): unknown {
|
||||
try {
|
||||
return JSON.parse(text)
|
||||
} catch {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
export async function api<T = unknown>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(path, {
|
||||
...init,
|
||||
headers: { "Content-Type": "application/json", ...init?.headers },
|
||||
})
|
||||
if (!res.ok) throw new ApiError(res.status, res.statusText, await fehlerText(res))
|
||||
return res.json() as Promise<T>
|
||||
}
|
||||
|
||||
// llama-swap-`groups`: Mitglieder mit swap=false dürfen GLEICHZEITIG resident sein (Ko-Residenz).
|
||||
// Die `brains`-Gruppe hält Hirn (fast) + Augen (vision) gemeinsam warm, statt sich zu verdrängen.
|
||||
export interface GroupSpec {
|
||||
swap: boolean
|
||||
persist: boolean
|
||||
members: string[]
|
||||
}
|
||||
export interface GroupsResp {
|
||||
groups: Record<string, GroupSpec>
|
||||
}
|
||||
export const getGroups = () => api<GroupsResp>("/api/groups")
|
||||
export const setGroup = (group: string, members: string[], swap = false, persist = true) =>
|
||||
api("/api/groups", { method: "PUT", body: JSON.stringify({ group, members, swap, persist }) })
|
||||
|
||||
export interface Capabilities {
|
||||
moe: boolean
|
||||
active_b: number | null
|
||||
tools: "yes" | "likely" | "no"
|
||||
vision: boolean
|
||||
coder: boolean
|
||||
reasoning: boolean
|
||||
embedding: boolean
|
||||
ctx: number | null
|
||||
params_b: number | null
|
||||
arch: string | null
|
||||
}
|
||||
|
||||
export interface Fit {
|
||||
level: "perfect" | "marginal" | "too_tight"
|
||||
text: string
|
||||
req_gb: number
|
||||
tps: number
|
||||
}
|
||||
|
||||
export interface RoleRecModel {
|
||||
name: string
|
||||
current_role: string | null
|
||||
params_b: number
|
||||
quant: string
|
||||
fit: Fit
|
||||
suitable: boolean
|
||||
incomplete: boolean
|
||||
score: number
|
||||
reason: string
|
||||
recommended: boolean
|
||||
}
|
||||
|
||||
export interface RoleRecResp {
|
||||
role: string
|
||||
recommended: string | null
|
||||
models: RoleRecModel[]
|
||||
}
|
||||
|
||||
export interface FitResp {
|
||||
params_b: number
|
||||
fit: Fit
|
||||
optimal_ctx: number
|
||||
assigned_ctx: number
|
||||
budget: { gtt_gb: number; reserved_gb: number; budget_gb: number; mode: string }
|
||||
sys_ram_gb: number
|
||||
}
|
||||
|
||||
export interface ModelInfo {
|
||||
name: string
|
||||
role: string | null
|
||||
aliases: string[]
|
||||
api_ids: string[]
|
||||
ctx: number | null
|
||||
ttl: number | null
|
||||
cmd: string
|
||||
gguf_path: string
|
||||
filename: string
|
||||
quant: string
|
||||
size_bytes: number | null
|
||||
incomplete: boolean
|
||||
prompt_cache: boolean
|
||||
spec_draft_model: string | null
|
||||
spec_type: string | null
|
||||
spec_active: boolean
|
||||
parallel_slots: number
|
||||
capabilities: Capabilities
|
||||
}
|
||||
|
||||
export interface VocabFingerprint {
|
||||
model: string | null
|
||||
pre: string | null
|
||||
n_vocab: number | null
|
||||
arch: string | null
|
||||
}
|
||||
|
||||
export interface DraftInfo {
|
||||
path: string
|
||||
filename: string
|
||||
size_bytes: number | null
|
||||
vocab: VocabFingerprint | null
|
||||
compatible: boolean | null // null = nicht prüfbar (Ziel-GGUF fehlt)
|
||||
mtp?: boolean // MTP-Kopf (Multi-Token-Prediction) statt klassischem Draft
|
||||
}
|
||||
|
||||
export interface DraftsResp {
|
||||
target_path: string
|
||||
target_exists: boolean
|
||||
target_vocab: VocabFingerprint | null
|
||||
drafts: DraftInfo[]
|
||||
}
|
||||
|
||||
export interface DiscoverModel {
|
||||
name: string
|
||||
author: string
|
||||
repo: string
|
||||
role: string
|
||||
params_b: number
|
||||
quant: string
|
||||
tags: string[]
|
||||
downloads: number
|
||||
fit: Fit
|
||||
optimal_ctx: number
|
||||
caps: Capabilities
|
||||
}
|
||||
|
||||
export interface DiscoverCategory {
|
||||
role: string
|
||||
title: string
|
||||
icon: string
|
||||
models: DiscoverModel[]
|
||||
recommended: string | null
|
||||
}
|
||||
|
||||
export interface DiscoverResp {
|
||||
updated: number
|
||||
categories: DiscoverCategory[]
|
||||
sys_ram_gb: number
|
||||
}
|
||||
|
||||
export interface RoutingResp {
|
||||
mode?: string
|
||||
endpoint?: string
|
||||
heavy_threshold_chars?: number
|
||||
routes: { name: string; target: string }[]
|
||||
lanes?: { name: string; target: string; threshold_chars?: number; escalate_chars?: number; aka?: string }[]
|
||||
fallbacks: Record<string, string[]>[]
|
||||
context_window_fallbacks: Record<string, string[]>[]
|
||||
gateway_reachable: boolean
|
||||
}
|
||||
|
||||
// UI-editierbare Routing-Policy (GET/PUT /api/routing/policy). Aliase + Zeichen-Schwellen
|
||||
// hinter den Lanes; hot-reload im Backend (kein Restart).
|
||||
export interface RoutingPolicy {
|
||||
fast: string
|
||||
heavy: string
|
||||
coder: string
|
||||
coder_lite: string
|
||||
heavy_chars: number
|
||||
coding_escalate_chars: number
|
||||
fast_no_think: boolean
|
||||
}
|
||||
export interface RoutingPolicyField {
|
||||
key: keyof RoutingPolicy
|
||||
label: string
|
||||
type: "str" | "int" | "bool"
|
||||
min?: number
|
||||
max?: number
|
||||
}
|
||||
export interface RoutingPolicyMeta {
|
||||
policy: RoutingPolicy
|
||||
defaults: RoutingPolicy
|
||||
fields: RoutingPolicyField[]
|
||||
}
|
||||
export const getRoutingPolicy = () => api<RoutingPolicyMeta>("/api/routing/policy")
|
||||
export const updateRoutingPolicy = (patch: Partial<RoutingPolicy>) =>
|
||||
api<{ policy: RoutingPolicy }>("/api/routing/policy", { method: "PUT", body: JSON.stringify(patch) })
|
||||
|
||||
export interface GitInfo {
|
||||
hash: string
|
||||
date: string
|
||||
subject: string
|
||||
branch: string
|
||||
dirty: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
// Engine kann git-, binary- oder unbekannte Version sein — Felder je nach `type`.
|
||||
export interface ComponentVersion {
|
||||
type: "git" | "binary" | "unknown"
|
||||
hash?: string
|
||||
date?: string
|
||||
subject?: string
|
||||
branch?: string
|
||||
dirty?: boolean
|
||||
path?: string
|
||||
version_text?: string
|
||||
}
|
||||
|
||||
export interface Versions {
|
||||
mc2: GitInfo | null
|
||||
engine: ComponentVersion
|
||||
hermes_agent: GitInfo | null
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
cpu: { percent: number; cores: number | null }
|
||||
ram: { total: number; used: number; percent: number }
|
||||
gpu: {
|
||||
busy_percent: number | null
|
||||
vram_used: number | null
|
||||
vram_total: number | null
|
||||
gtt_used?: number | null
|
||||
gtt_total?: number | null
|
||||
} | null
|
||||
temp: { cpu?: number; gpu?: number } | null
|
||||
disk: { total: number; used: number; percent: number } | null
|
||||
/** Sekunden seit dem Systemstart (v3-Umbau P3). null, wenn die Box es nicht liefert —
|
||||
* die Statusleiste blendet das Feld dann aus, statt eine Zahl zu erfinden. */
|
||||
uptime_s?: number | null
|
||||
versions?: Versions
|
||||
}
|
||||
|
||||
export interface ServiceInfo {
|
||||
name: string
|
||||
unit: string // echter systemd-Name — Basis für Restart/Logs/Matching (kein Namens-Raten)
|
||||
url: string
|
||||
ok: boolean
|
||||
scope: "user" | "system"
|
||||
}
|
||||
export interface ServicesResp {
|
||||
services: ServiceInfo[]
|
||||
links: { engine_ui: string; gateway: string; hermes_ui: string }
|
||||
}
|
||||
|
||||
export interface ComponentUpdate {
|
||||
key: string // z.B. "hermes_agent"
|
||||
name: string
|
||||
current: string | null
|
||||
latest: string | null
|
||||
update: boolean | null // null = unbestimmbar (installierte Version remote nicht abfragbar)
|
||||
reachable: boolean | null
|
||||
}
|
||||
|
||||
export interface UpdatesResp {
|
||||
os: number
|
||||
engine: number
|
||||
swap: number
|
||||
models: number
|
||||
model_list: { role: string; title: string; repo: string }[]
|
||||
last_check?: number | null
|
||||
components?: ComponentUpdate[]
|
||||
}
|
||||
|
||||
export interface UpdateDetails {
|
||||
kind: "os" | "engine" | "swap" | "hermes"
|
||||
error?: string
|
||||
// LLM-Zusammenfassung in Lucys Stimme (hermes/engine/swap; Breaking Changes zuerst)
|
||||
summary?: string
|
||||
// Aktions-Verdikt: muss der Besitzer selbst etwas tun? (null = kein Verdikt/keine Summary)
|
||||
action_needed?: boolean | null
|
||||
action_text?: string
|
||||
// os
|
||||
count?: number
|
||||
packages?: { name: string; current: string; candidate: string }[]
|
||||
// os: vom System zurückgestellte Pakete (Phasen-Rollout / kept back) — ehrlich statt "hängt"
|
||||
held_back?: { name: string; reason: "phasing" | "kept_back" }[]
|
||||
// engine
|
||||
installed_build?: number | null
|
||||
latest_build?: number | null
|
||||
latest_tag?: string | null
|
||||
name?: string | null
|
||||
url?: string | null
|
||||
body?: string | null
|
||||
// hermes
|
||||
branch?: string | null
|
||||
behind?: number
|
||||
commits?: { hash: string; subject: string; when: string }[]
|
||||
}
|
||||
|
||||
export interface ConnectTool {
|
||||
label: string
|
||||
lang: string
|
||||
snippet: string
|
||||
note: string
|
||||
}
|
||||
export interface ConnectResp {
|
||||
host: string
|
||||
gateway_url: string
|
||||
mc_url: string
|
||||
tools: Record<string, ConnectTool> // Leitung 1 — Modell (IDEs/Agenten → Gateway)
|
||||
}
|
||||
|
||||
export interface ConnectLine {
|
||||
ok: boolean
|
||||
detail: string
|
||||
}
|
||||
export interface ConnectHealth {
|
||||
gateway: ConnectLine
|
||||
memory: ConnectLine // Leitung 2 — Hermes-natives Gedächtnis (nur Status, nichts zu konfigurieren)
|
||||
desktop_gateway: ConnectLine
|
||||
}
|
||||
|
||||
// Graph-Ansicht des Wissens-Vaults (GET /api/wissen/graph) — trägt aus historischen Gründen
|
||||
// noch „Memory" im Namen; die Daten kommen aus dem Vault, nicht aus einem Gedächtnis-Dienst.
|
||||
export interface MemoryGraphNode {
|
||||
id: string
|
||||
content: string
|
||||
category: string
|
||||
source: string
|
||||
}
|
||||
export interface MemoryGraph {
|
||||
nodes: MemoryGraphNode[]
|
||||
edges: { source: string; target: string; weight: number }[]
|
||||
}
|
||||
|
||||
export interface AgentStatus {
|
||||
gateway_url: string
|
||||
box_console_url?: string
|
||||
hermes_ui_url?: string
|
||||
gateway_reachable: boolean
|
||||
box_console_reachable?: boolean
|
||||
hermes_ui_reachable?: boolean
|
||||
home_exists: boolean
|
||||
brain_model?: string
|
||||
has_config: boolean
|
||||
has_skills: boolean
|
||||
has_memories: boolean
|
||||
telegram_enabled?: boolean
|
||||
mcp_server_count?: number
|
||||
pc_executor_reachable?: boolean
|
||||
pc_executor_url?: string
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: string
|
||||
label: string
|
||||
group?: string | null // "maintenance" = system-veränderndes Update (Wartungs-Riegel)
|
||||
state: "queued" | "running" | "done" | "failed" | "canceled"
|
||||
progress?: number
|
||||
total_bytes?: number
|
||||
done_bytes?: number
|
||||
rate_bps?: number
|
||||
eta_s?: number
|
||||
}
|
||||
|
||||
// Werkzeug-Verlauf des Agenten (GET /api/agent/aktivitaet, v3-Umbau P6).
|
||||
// Gelesen aus Hermes' Log — Denkstrom und Werkzeug-Argumente stehen dort NICHT und
|
||||
// tauchen darum auch hier nicht auf.
|
||||
export interface WerkzeugRuf {
|
||||
zeit: string
|
||||
lauf: string | null
|
||||
werkzeug: string
|
||||
dauer_s: number
|
||||
zeichen: number | null
|
||||
ok: boolean
|
||||
fehler: string | null
|
||||
}
|
||||
export interface WerkzeugBilanz {
|
||||
werkzeug: string
|
||||
rufe: number
|
||||
fehler: number
|
||||
dauer_schnitt_s: number
|
||||
letzter_fehler: string | null
|
||||
}
|
||||
export interface AgentLauf {
|
||||
lauf: string
|
||||
von: string
|
||||
bis: string
|
||||
rufe: number
|
||||
fehler: number
|
||||
}
|
||||
export interface AktivitaetResp {
|
||||
verfuegbar: boolean
|
||||
pfad: string
|
||||
rufe: WerkzeugRuf[]
|
||||
werkzeuge: WerkzeugBilanz[]
|
||||
laeufe: AgentLauf[]
|
||||
}
|
||||
|
||||
export interface Health {
|
||||
status: string
|
||||
version: string
|
||||
engine_reachable: boolean
|
||||
gateway_reachable: boolean
|
||||
brain?: { role: string; model: string | null; ready: boolean }
|
||||
}
|
||||
|
||||
// 24-h-Metrik-Verlauf (GET /api/system/history?minutes=N) — Basis der Cockpit-Zeitachse.
|
||||
// tp/tc sind Token-GESAMTZÄHLER; Raten rechnet das Frontend aus den Deltas.
|
||||
export interface HistoryPoint {
|
||||
t: number // Epoch-Sekunden
|
||||
cpu: number | null
|
||||
ram: number | null
|
||||
gpu: number | null
|
||||
disk: number | null
|
||||
tp: number | null
|
||||
tc: number | null
|
||||
}
|
||||
export interface HistoryResp {
|
||||
sample_s: number
|
||||
points: HistoryPoint[]
|
||||
}
|
||||
|
||||
export interface TokenStats {
|
||||
prompt_tokens: number
|
||||
completion_tokens: number
|
||||
total_tokens: number
|
||||
saved_usd: number
|
||||
saved_eur: number
|
||||
pricing?: Record<string, { in: number; out: number }>
|
||||
}
|
||||
|
||||
// Per-Turn-Latenz-Trace (GET /api/voice/trace). Balken-Stufen (zeitlich disjunkt):
|
||||
// stt · vision · hirn · gen. Der Gedächtnis-Abruf läuft seit der Mem0-Ablösung Hermes-intern
|
||||
// (kein Rückruf an MC2) und steckt daher unsichtbar in hirn_ms.
|
||||
export interface VoiceTurn {
|
||||
id: string
|
||||
ts: number // Epoch-Sekunden
|
||||
session: string
|
||||
kind: string
|
||||
had_images: boolean
|
||||
stt_ms: number | null
|
||||
vision_ms: number | null
|
||||
hirn_ms: number | null // Zeit bis erstes Inhalts-Token (Agent + Gedächtnis + LLM-TTFT)
|
||||
gen_ms: number | null // Generierung nach dem ersten Token bis Stream-Ende
|
||||
total_ms: number
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export interface VoiceTraceResp {
|
||||
turns: VoiceTurn[]
|
||||
}
|
||||
|
||||
export interface ModelsResp {
|
||||
models: ModelInfo[]
|
||||
running?: string[]
|
||||
}
|
||||
|
||||
// ── Ideen-Queue (natives Hermes-Kanban) ──────────────────────────────────────
|
||||
export interface IdeenItem {
|
||||
id: string
|
||||
titel: string
|
||||
body: string
|
||||
status: string // triage | todo | ready | running | blocked | review | done
|
||||
assignee: string | null
|
||||
welt?: string // "box" = Werkstatt baut · "ide" = für Zed vorbereitet
|
||||
art?: string // "box" | "idee" (nur durchdacht) | "projekt" (Repo vorbereitet)
|
||||
erstellt: number | null
|
||||
fertig: number | null
|
||||
von: string | null
|
||||
frage?: string | null // bei status=blocked: die Frage/der Grund des Workers
|
||||
frage_kind?: string | null // needs_input | capability | transient | ""
|
||||
eltern?: string[] // offene Vorgänger-Karten (task_links) — leer = startbereit
|
||||
wartet_auf?: string[] // Titel der offenen Vorgänger (max. 3, für die Anzeige)
|
||||
notiz?: string | null // bei running: jüngste Heartbeat-Notiz des Workers
|
||||
projekt?: string | null // Familien-Schlüssel (verkettete Karten) — null = Einzelkarte
|
||||
prio?: number // Dispatcher-Reihenfolge: priority DESC, created ASC
|
||||
puls_alter?: number | null // bei running: Sekunden seit letztem Lebenszeichen (Hänger-Warnung)
|
||||
gestartet?: number | null // bei running: started_at (Epoch)
|
||||
}
|
||||
// Karten-Familie (verkettete Karten = ein Projekt) für die gruppierte Queue-Sicht
|
||||
export interface IdeenProjekt {
|
||||
key: string
|
||||
titel: string
|
||||
gesamt: number
|
||||
fertig: number
|
||||
laeuft: number
|
||||
haengt: number
|
||||
}
|
||||
export interface IdeenLogResp {
|
||||
available: boolean
|
||||
lines: string[]
|
||||
}
|
||||
export interface IdeenResp {
|
||||
available: boolean
|
||||
items: IdeenItem[]
|
||||
offen: number
|
||||
fehler?: string
|
||||
projekte?: IdeenProjekt[]
|
||||
}
|
||||
|
||||
// Ergebnis einer fertigen Queue-Aufgabe (Abschluss-Zusammenfassung des Workers)
|
||||
export interface IdeenErgebnis {
|
||||
available: boolean
|
||||
summary: string
|
||||
result: string
|
||||
}
|
||||
|
||||
// Konzept einer IDE-Karte — aus dem Gitea-Repo oder aus ~/konzepte auf der Box
|
||||
export interface IdeenKonzept {
|
||||
available: boolean
|
||||
repo?: string | null
|
||||
clone_url?: string
|
||||
quelle?: "repo" | "datei"
|
||||
pfad?: string // nur quelle=datei: Pfad auf der Box
|
||||
datei?: string
|
||||
konzept?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
// ── Chronik (Timeline aus dem Melde-Briefkasten) ─────────────────────────────
|
||||
export interface ChronikItem {
|
||||
id: number
|
||||
ts: number
|
||||
subject: string
|
||||
text: string
|
||||
source: string
|
||||
priority: "normal" | "silent"
|
||||
}
|
||||
export interface ChronikResp {
|
||||
items: ChronikItem[]
|
||||
}
|
||||
|
||||
// ── Wissens-Vault (Traum-Notizen, read-only) ─────────────────────────────────
|
||||
export interface WissenFile {
|
||||
path: string
|
||||
name: string
|
||||
dir: string
|
||||
title: string
|
||||
mtime: number
|
||||
neu: boolean
|
||||
}
|
||||
export interface WissenResp {
|
||||
available: boolean
|
||||
files: WissenFile[]
|
||||
}
|
||||
export interface WissenDatei {
|
||||
path: string
|
||||
content: string
|
||||
mtime: number
|
||||
}
|
||||
|
||||
// ── Zeitmaschine (Snapshots + Restore) ───────────────────────────────────────
|
||||
export interface Snapshot {
|
||||
snapshot: string
|
||||
file: string
|
||||
size_mb: number
|
||||
}
|
||||
export interface ZeitmaschineResp {
|
||||
available: boolean
|
||||
backups: Snapshot[]
|
||||
}
|
||||
|
||||
// ── Erinnerungen (Wecker/Routinen) ───────────────────────────────────────────
|
||||
export interface Reminder {
|
||||
id: number
|
||||
text: string
|
||||
next_ts: number
|
||||
repeat: string // '' einmalig | daily | weekdays | weekly
|
||||
created_ts: number
|
||||
}
|
||||
|
||||
export interface HermesBrainModel {
|
||||
name: string
|
||||
filename?: string
|
||||
params_b: number | null
|
||||
quant?: string
|
||||
size_bytes?: number | null
|
||||
version?: number | null
|
||||
gguf_path?: string
|
||||
incomplete?: boolean
|
||||
}
|
||||
|
||||
export interface HermesBrainCandidate {
|
||||
repo: string
|
||||
name: string
|
||||
version: number
|
||||
params_b: number
|
||||
downloads: number
|
||||
fit: Fit
|
||||
}
|
||||
|
||||
export interface HermesBrainBudget {
|
||||
gtt_gb: number
|
||||
brain_gb: number // Footprint des (empfohlenen) Brains, das immer resident bleibt
|
||||
warm_projected_gb: number // alle persist-Modelle warm (Info)
|
||||
free_after_gb: number // frei nach Brain + größtem on-demand
|
||||
largest_ondemand_gb: number // größtes on-demand-Modell (z.B. heavy/coder)
|
||||
fits: boolean // passt Brain + größtes on-demand zusammen ins Budget?
|
||||
}
|
||||
|
||||
export interface HermesBrainResp {
|
||||
current: HermesBrainModel | null
|
||||
recommended: HermesBrainCandidate | null
|
||||
update_available: boolean
|
||||
budget?: HermesBrainBudget | null
|
||||
}
|
||||
|
||||
// ── Ideen-Log (Live Worker Log) ──────────────────────────────────────────────
|
||||
export const getIdeenLog = (id: string) => api<IdeenLogResp>(`/api/ideen/${id}/log`)
|
||||
export const post = <T>(pfad: string, daten?: unknown) =>
|
||||
api<T>(pfad, { method: "POST", body: daten === undefined ? undefined : JSON.stringify(daten) })
|
||||
|
||||
Reference in New Issue
Block a user