AIX: Inspector: Quick Toggle

This commit is contained in:
Enrico Ros
2025-10-10 09:06:14 -07:00
parent b713b65a35
commit a36e202c80
5 changed files with 59 additions and 16 deletions
+4 -4
View File
@@ -199,7 +199,7 @@ export function SettingsModal(props: {
const { setTab } = props;
const isToolsTab = props.tab === 'tools';
const enableAixDebugger = Is.Deployment.Localhost;
const enableAixDebuggerDialog = true;
const handleSetTab = React.useCallback((_event: any, value: string | number | null) => {
setTab((value ?? undefined) as PreferencesTabId);
@@ -221,12 +221,12 @@ export function SettingsModal(props: {
{!isMobile && !isToolsTab && <Button variant='soft' color='neutral' onClick={props.onOpenShortcuts} startDecorator={<KeyboardCommandKeyOutlinedIcon color='primary' />} sx={darkModeToggleButtonSx}>
Shortcuts
</Button>}
{isToolsTab && <Button variant='soft' color='neutral' disabled={!enableAixDebuggerDialog} onClick={optimaActions().openAIXDebugger} startDecorator={<TerminalOutlinedIcon color={enableAixDebuggerDialog ? 'primary' : undefined} />} sx={darkModeToggleButtonSx}>
AI Inspector
</Button>}
{isToolsTab && <Button variant='soft' color='neutral' onClick={optimaActions().openLogger} startDecorator={<TerminalOutlinedIcon color='primary' />} sx={darkModeToggleButtonSx}>
Logs Viewer
</Button>}
{isToolsTab && <Button variant='soft' color='neutral' disabled={!enableAixDebugger} onClick={optimaActions().openAIXDebugger} startDecorator={<TerminalOutlinedIcon color={enableAixDebugger ? 'primary' : undefined} />} sx={darkModeToggleButtonSx}>
AIX Debugger
</Button>}
</Box>
}
sx={_styles.modal}
+22
View File
@@ -4,6 +4,7 @@ import { persist } from 'zustand/middleware';
import type { ContentScaling, UIComplexityMode } from '~/common/app.theme';
import { BrowserLang } from '~/common/util/pwaUtils';
import { Release } from '~/common/app.release';
// UI Preferences
@@ -47,6 +48,11 @@ interface UIPreferencesStore {
composerQuickButton: 'off' | 'call' | 'beam';
setComposerQuickButton: (composerQuickButton: 'off' | 'call' | 'beam') => void;
// Advanced features
aixInspector: boolean;
toggleAixInspector: () => void;
// UI Dismissals
dismissals: Record<string, boolean>;
@@ -103,6 +109,11 @@ export const useUIPreferencesStore = create<UIPreferencesStore>()(
composerQuickButton: 'beam',
setComposerQuickButton: (composerQuickButton: 'off' | 'call' | 'beam') => set({ composerQuickButton }),
// Advanced features
aixInspector: false,
toggleAixInspector: () => set((state) => ({ aixInspector: !state.aixInspector })),
// UI Dismissals
dismissals: {},
@@ -133,6 +144,13 @@ export const useUIPreferencesStore = create<UIPreferencesStore>()(
*/
version: 3,
partialize: (state) => {
if (Release.IsNodeDevBuild) return state; // in dev, persist everything
// In production, exclude aixInspector from persistence
const { aixInspector, ...rest } = state;
return rest;
},
migrate: (state: any, fromVersion: number): UIPreferencesStore => {
// 1: rename 'enterToSend' to 'enterIsNewline' (flip the meaning)
@@ -169,6 +187,10 @@ export function useUIContentScaling(): ContentScaling {
return useUIPreferencesStore((state) => state.contentScaling);
}
export function getAixInspector(): boolean {
return useUIPreferencesStore.getState().aixInspector;
}
export function useUIIsDismissed(key: string | null): boolean | undefined {
return useUIPreferencesStore((state) => !key ? undefined : Boolean(state.dismissals[key]));
+3 -2
View File
@@ -10,7 +10,8 @@ import { DMetricsChatGenerate_Lg, metricsChatGenerateLgToMd, metricsComputeChatG
import { DModelParameterValues, getAllModelParameterValues } from '~/common/stores/llms/llms.parameters';
import { createErrorContentFragment, DMessageContentFragment, DMessageErrorPart, DMessageVoidFragment, isContentFragment, isErrorPart } from '~/common/stores/chat/chat.fragments';
import { findLLMOrThrow } from '~/common/stores/llms/store-llms';
import { getLabsDevMode, getLabsDevNoStreaming } from '~/common/stores/store-ux-labs';
import { getAixInspector } from '~/common/stores/store-ui';
import { getLabsDevNoStreaming } from '~/common/stores/store-ux-labs';
import { metricsStoreAddChatGenerate } from '~/common/stores/metrics/store-metrics';
import { presentErrorToHumans } from '~/common/util/errorUtils';
import { webGeolocationCached } from '~/common/util/webGeolocationUtils';
@@ -631,7 +632,7 @@ async function _aixChatGenerateContent_LL(
* - AIX inspector is now independent from sudo mode
* - every request thereafter both sends back the Aix server-side dispatch packet, and appends all the particles received by the client side
*/
const requestServerDebugging = getLabsDevMode();
const requestServerDebugging = getAixInspector();
const debugContext = !requestServerDebugging ? undefined : { contextName: aixContext.name, contextRef: aixContext.ref };
/**
@@ -1,11 +1,12 @@
import * as React from 'react';
import { useShallow } from 'zustand/react/shallow';
import { Box, Button, Divider, FormControl, FormLabel, Option, Select, Typography } from '@mui/joy';
import { Box, Button, Divider, FormControl, FormLabel, Link, Option, Select, Switch, Typography } from '@mui/joy';
import ClearAllIcon from '@mui/icons-material/ClearAll';
import { GoodModal } from '~/common/components/modals/GoodModal';
import { useIsMobile } from '~/common/components/useMatchMedia';
import { useUIPreferencesStore } from '~/common/stores/store-ui';
import { AixDebuggerFrame } from './AixDebuggerFrame';
import { aixClientDebuggerActions, useAixClientDebuggerStore } from './memstore-aix-client-debugger';
@@ -15,8 +16,10 @@ export function AixDebuggerDialog(props: {
onClose: () => void;
}) {
// external state - we subscribe to Any update - it's a temp debugger anyway
// external state
const isMobile = useIsMobile();
const aixInspector = useUIPreferencesStore(state => state.aixInspector);
// NOTE: we subscribe to Any update - which can be ultra noisy
const { frames, activeFrameId, maxFrames } = useAixClientDebuggerStore(useShallow((state) => ({
frames: state.frames,
activeFrameId: state.activeFrameId,
@@ -42,7 +45,14 @@ export function AixDebuggerDialog(props: {
<GoodModal
open
onClose={props.onClose}
title='AIX API Debugger'
title='AI Request Inspector'
titleStartDecorator={
<Switch
checked={aixInspector}
onChange={useUIPreferencesStore.getState().toggleAixInspector}
sx={{ mr: 1 }}
/>
}
autoOverflow
fullscreen={isMobile}
sx={{ maxWidth: undefined }}
@@ -74,7 +84,7 @@ export function AixDebuggerDialog(props: {
</Select>
</FormControl>
{/* History Size Preferenes */}
{/* History Size Preferences */}
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 2 }}>
<FormControl>
<FormLabel>History Size</FormLabel>
@@ -111,11 +121,20 @@ export function AixDebuggerDialog(props: {
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '200px' }}>
{!frames.length && <>
<Typography level='title-lg'>
No AIX API requests recorded yet
{aixInspector ? 'Ready to capture' : 'AI Request Inspector'}
</Typography>
<Typography level='body-sm' sx={{ mt: 2, maxWidth: 468 }}>
Ensure AIX debugging is active (Settings -&gt; Labs -&gt; Developer Mode)
and you are running your own localhost:3000 installation.
<Typography level='body-sm' sx={{ mt: 2, maxWidth: 468, textAlign: 'center' }}>
{aixInspector
? 'Your next AI request will be captured here.'
: <>
<Link
component='button'
level='body-sm'
onClick={useUIPreferencesStore.getState().toggleAixInspector}
>
Turn on inspector
</Link> to see the exact requests to AI models.
</>}
</Typography>
</>}
{!activeFrame && !!frames.length && (
@@ -94,8 +94,9 @@ export function AixDebuggerFrame(props: {
{/* Body */}
<Card variant='soft' color='primary' sx={_styles.requestCard}>
<Typography color='primary' variant='soft' level='title-sm'>
-&gt; Body {frame.bodySize > 0 && `(${frame.bodySize.toLocaleString()} chars)`}
<Typography color='primary' variant='soft' level='title-sm' sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span>-&gt; Body</span>
{frame.bodySize > 0 && <span>{frame.bodySize.toLocaleString()} bytes</span>}
</Typography>
<Divider />
<Box sx={_styles.requestCardText}>