AI Inspector: memstored collapse state, and disable auto-snap

This commit is contained in:
Enrico Ros
2026-04-22 14:35:18 -07:00
parent 76183fd840
commit 27d1f081ab
2 changed files with 35 additions and 12 deletions
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useShallow } from 'zustand/react/shallow';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, Card, Chip, Divider, Sheet, Typography } from '@mui/joy';
@@ -10,6 +11,7 @@ import TimelapseIcon from '@mui/icons-material/Timelapse';
import type { AixClientDebugger } from './memstore-aix-client-debugger';
import { AixDebuggerMeasurementsTable } from './AixDebuggerMeasurementsTable';
import { useAixClientDebuggerStore } from './memstore-aix-client-debugger';
const _styles = {
@@ -79,14 +81,17 @@ export function AixDebuggerFrame(props: {
frame: AixClientDebugger.Frame;
}) {
// state
const [showHeaders, setShowHeaders] = React.useState(true);
const [showBody, setShowBody] = React.useState(true);
const [showParticles, setShowParticles] = React.useState(false); // hide by default (heavy)
// state: section open/close is kept in the debugger store so it persists across frame switches
const { showHeaders, showBody, showParticles, toggleOpenState } = useAixClientDebuggerStore(useShallow(state => ({
showHeaders: !!state.openStates.headers,
showBody: !!state.openStates.body,
showParticles: !!state.openStates.particles,
toggleOpenState: state.toggleOpenState,
})));
const handleToggleShowHeaders = React.useCallback(() => setShowHeaders(on => !on), []);
const handleToggleShowBody = React.useCallback(() => setShowBody(on => !on), []);
const handleToggleShowParticles = React.useCallback(() => setShowParticles(on => !on), []);
const handleToggleShowHeaders = React.useCallback(() => toggleOpenState('headers'), [toggleOpenState]);
const handleToggleShowBody = React.useCallback(() => toggleOpenState('body'), [toggleOpenState]);
const handleToggleShowParticles = React.useCallback(() => toggleOpenState('particles'), [toggleOpenState]);
const { frame } = props;
@@ -89,10 +89,14 @@ function _createAixClientDebuggerFrame(transport: AixClientDebugger.Transport, f
/// Store ///
export type AixDebuggerOpenKey = 'headers' | 'body' | 'particles';
interface AixClientDebuggerState {
frames: AixClientDebugger.Frame[];
activeFrameId: AixFrameId | null;
maxFrames: number;
// per-section open state for the current frame view
openStates: Partial<Record<AixDebuggerOpenKey, true>>;
// AIX force disable streaming for all requests (separate from per-model llmForceNoStream)
aixNoStreaming: boolean;
// AIX next payload override - JSON string injected into requests after validation
@@ -111,6 +115,7 @@ interface AixClientDebuggerActions {
setActiveFrame: (activeFrameId: AixFrameId | null) => void;
setMaxFrames: (count: number) => void;
clearHistory: () => void;
toggleOpenState: (key: AixDebuggerOpenKey) => void;
}
type AixClientDebuggerStore = AixClientDebuggerState & AixClientDebuggerActions;
@@ -122,6 +127,7 @@ export const useAixClientDebuggerStore = create<AixClientDebuggerStore>((_set) =
frames: [],
activeFrameId: null,
maxFrames: DEFAULT_FRAMES_COUNT,
openStates: { headers: true, body: true }, // headers + body open by default; particles closed (heavy)
aixNoStreaming: false,
requestBodyOverrideJson: '',
@@ -135,11 +141,16 @@ export const useAixClientDebuggerStore = create<AixClientDebuggerStore>((_set) =
// stealing focus from the main conversation request
const isBackgroundOperation = (BACKGROUND_CONTEXT_NAMES as readonly string[]).includes(initialContext.contextName);
_set((state) => ({
frames: [newFrame, ...state.frames].slice(0, state.maxFrames),
// Auto-select if: no active frame yet, OR this is not a background operation
activeFrameId: (!state.activeFrameId || !isBackgroundOperation) ? newFrame.id : state.activeFrameId,
}));
_set(({ activeFrameId, frames, maxFrames }) => {
// Sticky selection: only snap to the new frame if the user was following latest (nothing selected, or the current selection is the previous top frame).
const previousLatestId = frames[0]?.id ?? null;
const isFollowingLatest = activeFrameId === null || activeFrameId === previousLatestId;
const shouldAutoSelect = isFollowingLatest && !isBackgroundOperation;
return {
frames: [newFrame, ...(frames)].slice(0, maxFrames),
activeFrameId: shouldAutoSelect ? newFrame.id : activeFrameId,
};
});
return newFrame.id;
},
@@ -193,6 +204,13 @@ export const useAixClientDebuggerStore = create<AixClientDebuggerStore>((_set) =
activeFrameId: null,
}),
toggleOpenState: (key) => _set(state => {
const next = { ...state.openStates };
if (next[key]) delete next[key];
else next[key] = true;
return { openStates: next };
}),
}));