mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
overser memory viewer!
This commit is contained in:
@@ -1,13 +1,53 @@
|
||||
import { useState } from 'react';
|
||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
import { useSettingsNamespace } from '../../settings/index.js';
|
||||
import CardFrame from '../CardFrame/index.jsx';
|
||||
|
||||
function formatUpdatedAt(value) {
|
||||
const timestamp = Number(value);
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) return null;
|
||||
const date = new Date(timestamp);
|
||||
if (Number.isNaN(date.getTime())) return null;
|
||||
return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', second: '2-digit' });
|
||||
}
|
||||
|
||||
function OverseerMemoryPopup({ memory, onClose }) {
|
||||
const summary = typeof memory?.summary === 'string' && memory.summary.trim() ? memory.summary : 'Memory unavailable.';
|
||||
const updatedAt = formatUpdatedAt(memory?.updatedAt);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-40 flex items-center justify-center bg-black/75 p-1"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="w-full max-w-2xl" onClick={(event) => event.stopPropagation()}>
|
||||
<CardFrame
|
||||
title="Overseer memory"
|
||||
meta={updatedAt ? `Updated ${updatedAt}` : null}
|
||||
actions={
|
||||
<button type="button" onClick={onClose} className="button-dark px-1 py-0.25 text-[0.75rem]">
|
||||
Close
|
||||
</button>
|
||||
}
|
||||
bodyClassName="p-0.5 text-xs"
|
||||
>
|
||||
{/* The memory is rendered as the server's existing plain-text summary so
|
||||
this popup stays consistent with the overseer/admin debug display. */}
|
||||
<pre className="surface max-h-[70vh] overflow-y-auto whitespace-pre-wrap break-words font-mono text-[0.72rem] text-slate-200">{summary}</pre>
|
||||
</CardFrame>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function OverseerPreferencePanel() {
|
||||
const { identifySession } = useSessionActions();
|
||||
const vote = useSessionSelector((state) => state.session?.overseerVote || null);
|
||||
const overseerMemory = useSessionSelector((state) => state.overseerMemory || null);
|
||||
const { value: profile } = useSettingsNamespace('profile', { nickname: '' });
|
||||
const { value: identity } = useSettingsNamespace('identity', { cookieUserId: '' });
|
||||
const { value, save } = useSettingsNamespace('overseerPreference', { enabled: false });
|
||||
const [memoryOpen, setMemoryOpen] = useState(false);
|
||||
const enabled = Boolean(value?.enabled);
|
||||
const running = Boolean(vote?.running);
|
||||
|
||||
@@ -21,30 +61,44 @@ export default function OverseerPreferencePanel() {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CardFrame title="Overseer vote" bodyClassName="space-y-0.5 text-center text-xs text-slate-200">
|
||||
<label
|
||||
className={`flex items-center justify-center gap-1.5 rounded px-1 py-0.5 ${
|
||||
enabled ? 'bg-emerald-600/80 text-emerald-50' : 'bg-slate-600/70 text-slate-100'
|
||||
}`}
|
||||
>
|
||||
<input type="checkbox" checked={enabled} onChange={onToggle} />
|
||||
<span>Your vote: {enabled ? 'Yes' : 'No'}</span>
|
||||
</label>
|
||||
<span className="text-xs">Enable a local LLM in chat</span>
|
||||
const actions = (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMemoryOpen(true)}
|
||||
className="button-dark px-1 py-0.25 text-[0.75rem]"
|
||||
title="View overseer memory"
|
||||
>
|
||||
Memory
|
||||
</button>
|
||||
);
|
||||
|
||||
<div className="mt-0.5 text-slate-400">
|
||||
Yes!: {Number(vote?.yesCount || 0)}, No...: {Number(vote?.noCount || 0)}
|
||||
</div>
|
||||
<div className="mt-0.5 flex justify-center">
|
||||
<span
|
||||
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[0.7rem] font-medium ${
|
||||
running ? 'bg-emerald-600/80 text-emerald-50' : 'bg-slate-600/70 text-slate-100'
|
||||
return (
|
||||
<>
|
||||
<CardFrame title="Overseer vote" actions={actions} bodyClassName="space-y-0.5 text-center text-xs text-slate-200">
|
||||
<label
|
||||
className={`flex items-center justify-center gap-1.5 rounded px-1 py-0.5 ${
|
||||
enabled ? 'bg-emerald-600/80 text-emerald-50' : 'bg-slate-600/70 text-slate-100'
|
||||
}`}
|
||||
>
|
||||
{running ? 'Running!' : 'Stopped by vote.'}
|
||||
</span>
|
||||
</div>
|
||||
</CardFrame>
|
||||
<input type="checkbox" checked={enabled} onChange={onToggle} />
|
||||
<span>Your vote: {enabled ? 'Yes' : 'No'}</span>
|
||||
</label>
|
||||
<span className="text-xs">Enable a local LLM in chat</span>
|
||||
|
||||
<div className="mt-0.5 text-slate-400">
|
||||
Yes!: {Number(vote?.yesCount || 0)}, No...: {Number(vote?.noCount || 0)}
|
||||
</div>
|
||||
<div className="mt-0.5 flex justify-center">
|
||||
<span
|
||||
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[0.7rem] font-medium ${
|
||||
running ? 'bg-emerald-600/80 text-emerald-50' : 'bg-slate-600/70 text-slate-100'
|
||||
}`}
|
||||
>
|
||||
{running ? 'Running!' : 'Stopped by vote.'}
|
||||
</span>
|
||||
</div>
|
||||
</CardFrame>
|
||||
{memoryOpen ? <OverseerMemoryPopup memory={overseerMemory} onClose={() => setMemoryOpen(false)} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const INITIAL_STATE = {
|
||||
llmCommentaryState: null,
|
||||
llmCommentaryStatus: null,
|
||||
overseerControlState: null,
|
||||
overseerMemory: null,
|
||||
alerts: [],
|
||||
};
|
||||
|
||||
@@ -132,6 +133,12 @@ export function SessionProvider({ children }) {
|
||||
const state = payload && typeof payload === 'object' ? payload : null;
|
||||
setState((prev) => ({ ...prev, overseerControlState: state }));
|
||||
}
|
||||
function handleOverseerMemory(payload = null) {
|
||||
// Overseer memory is intentionally stored separately from session sync so
|
||||
// the vote panel can refresh as soon as the overseer writes memory.
|
||||
const memory = payload && typeof payload === 'object' ? payload : null;
|
||||
setState((prev) => ({ ...prev, overseerMemory: memory }));
|
||||
}
|
||||
socket.on('session:sync', handleSession);
|
||||
socket.on('log:init', handleLogInit);
|
||||
socket.on('log:entry', handleLogEntry);
|
||||
@@ -139,6 +146,7 @@ export function SessionProvider({ children }) {
|
||||
socket.on('adminlog:entry', handleAdminLogEntry);
|
||||
socket.on('llm:state', handleLlmState);
|
||||
socket.on('overseer:state', handleOverseerState);
|
||||
socket.on('overseer:memory', handleOverseerMemory);
|
||||
socket.on('alert:new', handleAlertNew);
|
||||
return () => {
|
||||
socket.off('session:sync', handleSession);
|
||||
@@ -148,6 +156,7 @@ export function SessionProvider({ children }) {
|
||||
socket.off('adminlog:entry', handleAdminLogEntry);
|
||||
socket.off('llm:state', handleLlmState);
|
||||
socket.off('overseer:state', handleOverseerState);
|
||||
socket.off('overseer:memory', handleOverseerMemory);
|
||||
socket.off('alert:new', handleAlertNew);
|
||||
};
|
||||
}, [setState, socket]);
|
||||
|
||||
Reference in New Issue
Block a user