Files
2026-06-14 15:18:56 -04:00

135 lines
5.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
"use strict";
const PLUGIN = "digital-pet";
const apiBase = "/api/plugins/digital-pet";
function getSdk() { return window.__HERMES_PLUGIN_SDK__ || {}; }
function fetchJson(path, options) {
const sdk = getSdk();
const url = apiBase + path;
const opts = Object.assign({ headers: { "Content-Type": "application/json" } }, options || {});
const doFetch = sdk.authedFetch || fetch;
return doFetch(url, opts).then(function (res) {
if (!res.ok) throw new Error("digital pet request failed: " + res.status);
return res.json();
});
}
function DigitalPetOverlay() {
const sdk = getSdk();
const React = sdk.React || window.React;
const hooks = sdk.hooks || React;
const useEffect = hooks.useEffect;
const useRef = hooks.useRef;
const useState = hooks.useState;
const h = React.createElement;
const [state, setState] = useState(null);
const [error, setError] = useState(null);
const mounted = useRef(false);
const hoverTimer = useRef(null);
const transitionTimer = useRef(null);
function clearHoverTimer() {
if (hoverTimer.current) window.clearTimeout(hoverTimer.current);
hoverTimer.current = null;
}
function clearTransitionTimer() {
if (transitionTimer.current) window.clearTimeout(transitionTimer.current);
transitionTimer.current = null;
}
function applyRemoteState(data) {
if (!mounted.current) return null;
setError(null);
setState(data.state);
return data.state;
}
function applyRemoteError(err) {
if (!mounted.current) return undefined;
setError(err.message || String(err));
return undefined;
}
function refresh() {
return fetchJson("/state")
.then(applyRemoteState)
.catch(applyRemoteError);
}
function send(type, metadata) {
return fetchJson("/event", { method: "POST", body: JSON.stringify({ type: type, metadata: metadata || {} }) })
.then(applyRemoteState)
.catch(applyRemoteError);
}
useEffect(function () {
mounted.current = true;
return function () {
mounted.current = false;
clearHoverTimer();
clearTransitionTimer();
};
}, []);
useEffect(function () {
refresh();
const id = window.setInterval(refresh, 30000);
return function () { window.clearInterval(id); };
}, []);
useEffect(function () {
if (!state) return undefined;
clearTransitionTimer();
const completion = state.mode === "reacting" ? "reaction_complete"
: state.mode === "prompting" ? "prompt_complete"
: state.mode === "waking" ? "wake_complete"
: null;
if (!completion) return undefined;
transitionTimer.current = window.setTimeout(function () {
transitionTimer.current = null;
send(completion);
}, 2400);
return clearTransitionTimer;
}, [state && state.mode, state && state.revision]);
function onClick() {
if (!state || state.mode === "hidden") send("summon");
else if (state.mode === "sleeping") send("wake");
else send("click");
}
function startHover() {
clearHoverTimer();
if (!state || state.mode === "hidden" || state.mode === "sleeping") return;
hoverTimer.current = window.setTimeout(function () {
hoverTimer.current = null;
send("hover_hold");
}, 1000);
}
function stopHover() {
clearHoverTimer();
}
if (!state) return h("div", { className: "digital-pet-root digital-pet-loading", "aria-live": "polite" }, error || "");
if (state.mode === "hidden" || state.enabled === false) {
return h("button", { type: "button", className: "digital-pet-summon", onClick: onClick, title: "Summon station companion", "aria-label": "Summon station companion" }, "◆");
}
const mood = state.mood || "calm";
const mode = state.mode || "idle";
const face = mode === "sleeping" ? " " : mood === "pleased" ? "•‿•" : mood === "curious" ? "•?•" : mood === "tired" ? "-_-" : "•_•";
return h("div", { className: "digital-pet-root", "data-mode": mode, "data-mood": mood },
state.message ? h("div", { className: "digital-pet-bubble", role: "status", "aria-live": "polite" }, state.message) : null,
h("button", { type: "button", className: "digital-pet-avatar", onClick: onClick, onMouseEnter: startHover, onMouseLeave: stopHover, onFocus: stopHover, title: mode === "sleeping" ? "Click to wake Signal" : "Click Signal; hover to sleep", "aria-label": mode === "sleeping" ? "Wake digital pet" : "Interact with digital pet" },
h("span", { className: "digital-pet-antenna", "aria-hidden": "true" }, "⌁"),
h("span", { className: "digital-pet-face", "aria-hidden": "true" }, face),
h("span", { className: "digital-pet-name" }, state.name || "Signal")
)
);
}
function registerWhenReady() {
if (!window.__HERMES_PLUGINS__ || !window.__HERMES_PLUGIN_SDK__ || !window.__HERMES_PLUGIN_SDK__.React) {
window.setTimeout(registerWhenReady, 50);
return;
}
window.__HERMES_PLUGINS__.registerSlot(PLUGIN, "overlay", DigitalPetOverlay);
}
registerWhenReady();
})();