mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
overseer v2 1
This commit is contained in:
@@ -5,6 +5,7 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSession } from '../../context/SessionContext.jsx';
|
||||
import RoverRoster from '../RoverRoster/index.jsx';
|
||||
import LlmCommentaryPanel from './LlmCommentaryPanel.jsx';
|
||||
import OverseerControlPanel from './OverseerControlPanel.jsx';
|
||||
import ReplaySnapshotHealth from './ReplaySnapshotHealth.jsx';
|
||||
import AdminIpLogPanel from './AdminIpLogPanel.jsx';
|
||||
|
||||
@@ -28,14 +29,17 @@ export default function AdminPanelContent() {
|
||||
setAudioLevels,
|
||||
setPrivateSafety,
|
||||
llmControl,
|
||||
overseerControl,
|
||||
adminLogs,
|
||||
llmCommentaryState,
|
||||
overseerControlState,
|
||||
} = useSession();
|
||||
const roster = useMemo(() => session?.roster ?? [], [session?.roster]);
|
||||
const [lockStates, setLockStates] = useState({});
|
||||
const [rebootStates, setRebootStates] = useState({});
|
||||
const [serverRebooting, setServerRebooting] = useState(false);
|
||||
const [clearingLlmHistory, setClearingLlmHistory] = useState(false);
|
||||
const [clearingOverseerHistory, setClearingOverseerHistory] = useState(false);
|
||||
const health = session?.health || null;
|
||||
const currentGoal = session?.globalObjective?.text || '';
|
||||
const goalUpdatedAt = session?.globalObjective?.updatedAt || null;
|
||||
@@ -126,6 +130,19 @@ export default function AdminPanelContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearOverseerHistory = async () => {
|
||||
const ok = window.confirm('Clear Overseer Control history now?');
|
||||
if (!ok) return;
|
||||
setClearingOverseerHistory(true);
|
||||
try {
|
||||
await overseerControl('clearHistory');
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
} finally {
|
||||
setClearingOverseerHistory(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoalSave = async () => {
|
||||
try {
|
||||
await setGlobalObjective(goalDraft);
|
||||
@@ -491,6 +508,11 @@ export default function AdminPanelContent() {
|
||||
onClearHistory={handleClearLlmHistory}
|
||||
clearingHistory={clearingLlmHistory}
|
||||
/>
|
||||
<OverseerControlPanel
|
||||
state={overseerControlState}
|
||||
onClearHistory={handleClearOverseerHistory}
|
||||
clearingHistory={clearingOverseerHistory}
|
||||
/>
|
||||
<AdminIpLogPanel entries={adminLogs} />
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function OverseerControlPanel({ state, onClearHistory, clearingHistory }) {
|
||||
const [showPayload, setShowPayload] = useState(false);
|
||||
if (!state) {
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<div className="panel-muted text-xs uppercase">Overseer Control</div>
|
||||
<div className="surface text-xs text-slate-300">No status received yet.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const runtime = state.runtime || {};
|
||||
const cfg = state.config || {};
|
||||
const output = state.output || {};
|
||||
const timings = state.timings || {};
|
||||
const input = state.input || {};
|
||||
const errors = state.errors || {};
|
||||
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
<div className="panel-muted text-xs uppercase">Overseer Control</div>
|
||||
<div className="flex gap-0.5 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClearHistory}
|
||||
disabled={Boolean(clearingHistory)}
|
||||
className="button-danger disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
{clearingHistory ? 'Clearing...' : 'Clear Overseer History'}
|
||||
</button>
|
||||
</div>
|
||||
<div className="surface flex flex-wrap gap-0.5 text-xs">
|
||||
<span className="surface-muted">running: {runtime.running ? 'yes' : 'no'}</span>
|
||||
<span className="surface-muted">phase: {runtime.phase || '--'}</span>
|
||||
<span className="surface-muted">tick: {runtime.tickCount ?? 0}</span>
|
||||
<span className="surface-muted">trigger: {runtime.lastTriggerReason || '--'}</span>
|
||||
<span className="surface-muted">name: {cfg.name || '--'}</span>
|
||||
<span className="surface-muted">model: {cfg.model || '--'}</span>
|
||||
<span className="surface-muted">observeOnly: {cfg.observeOnly ? 'yes' : 'no'}</span>
|
||||
<span className="surface-muted">decision: {output.normalized || '--'}</span>
|
||||
<span className="surface-muted">outcome: {output.outcome || '--'}</span>
|
||||
<span className="surface-muted">reason: {output.reason || '--'}</span>
|
||||
<span className="surface-muted">last gen: {timings.lastGenerationMs != null ? `${timings.lastGenerationMs}ms` : '--'}</span>
|
||||
</div>
|
||||
|
||||
<details className="surface text-xs text-slate-200" open>
|
||||
<summary className="cursor-pointer select-none text-slate-300">Latest Context</summary>
|
||||
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
|
||||
{input.stateUpdate || '<none>'}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<details className="surface text-xs text-slate-200">
|
||||
<summary className="cursor-pointer select-none text-slate-300">Tool Availability</summary>
|
||||
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
|
||||
{JSON.stringify({ available: input.availableTools, blocked: input.blockedTools }, null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
{errors.message ? <div className="surface text-xs text-red-300">Error: {errors.message}</div> : null}
|
||||
|
||||
<details className="surface text-xs text-slate-200">
|
||||
<summary className="cursor-pointer select-none text-slate-300">Recent Runs</summary>
|
||||
<pre className="mt-0.5 whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">
|
||||
{JSON.stringify(state.history || [], null, 2)}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<button type="button" className="button-dark text-xs" onClick={() => setShowPayload((v) => !v)}>
|
||||
{showPayload ? 'Hide Full Payload' : 'Show Full Payload'}
|
||||
</button>
|
||||
{showPayload ? (
|
||||
<pre className="surface whitespace-pre-wrap break-words text-[0.72rem] text-slate-200">{JSON.stringify(state, null, 2)}</pre>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ const INITIAL_STATE = {
|
||||
adminLogs: [],
|
||||
llmCommentaryState: null,
|
||||
llmCommentaryStatus: null,
|
||||
overseerControlState: null,
|
||||
alerts: [],
|
||||
};
|
||||
|
||||
@@ -128,6 +129,10 @@ export function SessionProvider({ children }) {
|
||||
],
|
||||
}));
|
||||
}
|
||||
function handleOverseerState(payload = null) {
|
||||
const state = payload && typeof payload === 'object' ? payload : null;
|
||||
setState((prev) => ({ ...prev, overseerControlState: state }));
|
||||
}
|
||||
function handleNeatoLidar(payload = null) {
|
||||
const next = payload && typeof payload === 'object' ? payload : null;
|
||||
setState((prev) => ({ ...prev, neatoLidar: next }));
|
||||
@@ -139,6 +144,7 @@ export function SessionProvider({ children }) {
|
||||
socket.on('adminlog:init', handleAdminLogInit);
|
||||
socket.on('adminlog:entry', handleAdminLogEntry);
|
||||
socket.on('llm:state', handleLlmState);
|
||||
socket.on('overseer:state', handleOverseerState);
|
||||
socket.on('alert:new', handleAlertNew);
|
||||
return () => {
|
||||
socket.off('session:sync', handleSession);
|
||||
@@ -148,6 +154,7 @@ export function SessionProvider({ children }) {
|
||||
socket.off('adminlog:init', handleAdminLogInit);
|
||||
socket.off('adminlog:entry', handleAdminLogEntry);
|
||||
socket.off('llm:state', handleLlmState);
|
||||
socket.off('overseer:state', handleOverseerState);
|
||||
socket.off('alert:new', handleAlertNew);
|
||||
};
|
||||
}, [setState, socket]);
|
||||
@@ -204,6 +211,8 @@ export function SessionProvider({ children }) {
|
||||
emitWithAck('session:privateSafety:set', { roverId, safety }),
|
||||
llmControl: (action, controls = {}) =>
|
||||
emitWithAck('llm:control', { controls: { action, ...controls } }),
|
||||
overseerControl: (action, controls = {}) =>
|
||||
emitWithAck('overseer:control', { controls: { action, ...controls } }),
|
||||
pushAlert: (alert) =>
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
|
||||
Reference in New Issue
Block a user