GPS: near()/entered_near()/left_near()-Watcher schalten das Tracking automatisch an bzw. beim Loeschen des letzten wieder aus; gpsTracking.start() sichert jetzt die Background-Permission. Cockpit-Fundament: neues Brain-Tool present_view emittiert aria_view (Orb + Karten), Bridge/RVS leiten weiter, ariaView.ts haelt die Spec App-seitig. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
105 lines
2.9 KiB
TypeScript
105 lines
2.9 KiB
TypeScript
/**
|
|
* 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<string, AriaView>();
|
|
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;
|