forked from Rockachopa/Timmy-time-dashboard
Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
96 lines
2.8 KiB
JavaScript
96 lines
2.8 KiB
JavaScript
/**
|
|
* State reader — hardcoded JSON for Phase 2, WebSocket in Phase 3.
|
|
*
|
|
* Provides Timmy's current state to the scene. In Phase 2 this is a
|
|
* static default; the WebSocket path is stubbed for future use.
|
|
*/
|
|
|
|
const DEFAULTS = {
|
|
timmyState: {
|
|
mood: "focused",
|
|
activity: "Pondering the arcane arts",
|
|
energy: 0.6,
|
|
confidence: 0.7,
|
|
},
|
|
activeThreads: [],
|
|
recentEvents: [],
|
|
concerns: [],
|
|
visitorPresent: false,
|
|
updatedAt: new Date().toISOString(),
|
|
version: 1,
|
|
};
|
|
|
|
export class StateReader {
|
|
constructor() {
|
|
this.state = { ...DEFAULTS };
|
|
this.listeners = [];
|
|
this._ws = null;
|
|
}
|
|
|
|
/** Subscribe to state changes. */
|
|
onChange(fn) {
|
|
this.listeners.push(fn);
|
|
}
|
|
|
|
/** Notify all listeners. */
|
|
_notify() {
|
|
for (const fn of this.listeners) {
|
|
try {
|
|
fn(this.state);
|
|
} catch (e) {
|
|
console.warn("State listener error:", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Try to connect to the world WebSocket for live updates. */
|
|
connect() {
|
|
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
const url = `${proto}//${location.host}/api/world/ws`;
|
|
try {
|
|
this._ws = new WebSocket(url);
|
|
this._ws.onopen = () => {
|
|
const dot = document.getElementById("connection-dot");
|
|
if (dot) dot.classList.add("connected");
|
|
};
|
|
this._ws.onclose = () => {
|
|
const dot = document.getElementById("connection-dot");
|
|
if (dot) dot.classList.remove("connected");
|
|
};
|
|
this._ws.onmessage = (ev) => {
|
|
try {
|
|
const msg = JSON.parse(ev.data);
|
|
if (msg.type === "world_state" || msg.type === "timmy_state") {
|
|
if (msg.timmyState) this.state.timmyState = msg.timmyState;
|
|
if (msg.mood) {
|
|
this.state.timmyState.mood = msg.mood;
|
|
this.state.timmyState.activity = msg.activity || "";
|
|
this.state.timmyState.energy = msg.energy ?? 0.5;
|
|
}
|
|
this._notify();
|
|
}
|
|
} catch (e) {
|
|
/* ignore parse errors */
|
|
}
|
|
};
|
|
} catch (e) {
|
|
console.warn("WebSocket unavailable — using static state");
|
|
}
|
|
}
|
|
|
|
/** Current mood string. */
|
|
get mood() {
|
|
return this.state.timmyState.mood;
|
|
}
|
|
|
|
/** Current activity string. */
|
|
get activity() {
|
|
return this.state.timmyState.activity;
|
|
}
|
|
|
|
/** Energy level 0-1. */
|
|
get energy() {
|
|
return this.state.timmyState.energy;
|
|
}
|
|
}
|