/** * ariaView — Empfaenger der von ARIA komponierten RAEUMLICHEN Ansichten (M1). * * Fluss: ARIA ruft im Brain `present_view` → Brain-Event `aria_view` → Bridge → * RVS `aria_view` → hier gepuffert → WorkspaceCanvas rendert Orb + Karten, die * auf der Flaeche materialisieren. * * Der Service haelt pro Projekt die AKTUELLE View-Spec, damit eine spaet * gemountete Canvas-Kachel sofort den Ist-Stand bekommt. Muster wie * services/codeFile.ts (Singleton, rvs.onMessage). * * Die Karten-Typen sind bewusst offen (string), damit spaetere Renderer (vnc, * chart, file …) ohne Service-Aenderung dazukommen. Der jeweilige Client-Renderer * entscheidet, was er mit einem unbekannten Typ macht (i.d.R. ignorieren). */ import rvs, { RVSMessage } from './rvs'; export type OrbState = 'idle' | 'listening' | 'thinking' | 'speaking' | 'working'; export interface ViewMarker { lat: number; lon: number; label?: string; } export interface ViewCard { type: 'text' | 'image' | 'map' | 'code' | 'list' | string; title?: string; md?: string; // text/list src?: string; // image markers?: ViewMarker[]; // map path?: string; // code lang?: string; // code // Zukuenftige Kartenfelder ohne Service-Aenderung: [k: string]: any; } export interface ViewSpec { cards: ViewCard[]; orb?: OrbState; title?: string; } export interface AriaView { projectId: string; view: ViewSpec; clientMsgId?: string; ts: number; } type ViewSub = (v: AriaView) => void; class AriaViewService { private views = new Map(); private subs: ViewSub[] = []; constructor() { rvs.onMessage((m) => this.onMessage(m)); } private onMessage(m: RVSMessage): void { if (m.type !== 'aria_view') return; const p = (m.payload || {}) as any; const raw = (p.view || {}) as any; const cards: ViewCard[] = Array.isArray(raw.cards) ? raw.cards : []; if (cards.length === 0) return; // leere Ansicht ignorieren const view: ViewSpec = { cards, orb: raw.orb || 'speaking', title: raw.title || '', }; const projectId: string = p.projectId || ''; const entry: AriaView = { projectId, view, clientMsgId: p.clientMsgId || '', ts: Date.now(), }; this.views.set(projectId, entry); this.subs.forEach((cb) => { try { cb(entry); } catch {} }); } /** Aktuelle Ansicht eines Projekts (leer = Hauptchat). */ getView(projectId: string): AriaView | undefined { return this.views.get(projectId || ''); } /** Registriert einen Listener fuer neue Ansichten. */ subscribe(cb: ViewSub): () => void { this.subs.push(cb); return () => { this.subs = this.subs.filter((s) => s !== cb); }; } /** Ansicht eines Projekts verwerfen (z.B. wenn der User sie wegwischt). */ clear(projectId: string): void { this.views.delete(projectId || ''); } } const ariaView = new AriaViewService(); export default ariaView;