mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
AIX: Debugger: first version
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
import * as React from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
|
||||
import { Box, Button, Divider, FormControl, FormLabel, Option, Select, Typography } from '@mui/joy';
|
||||
import ClearAllIcon from '@mui/icons-material/ClearAll';
|
||||
|
||||
import { GoodModal } from '~/common/components/modals/GoodModal';
|
||||
|
||||
import { AixDebuggerFrame } from './AixDebuggerFrame';
|
||||
import { aixClientDebuggerActions, useAixClientDebuggerStore } from './memstore-aix-client-debugger';
|
||||
|
||||
|
||||
export function AixDebuggerDialog(props: {
|
||||
onClose: () => void;
|
||||
}) {
|
||||
|
||||
// external state - we subscribe to Any update - it's a temp debugger anyway
|
||||
const { frames, activeFrameId, maxFrames } = useAixClientDebuggerStore(useShallow((state) => ({
|
||||
frames: state.frames,
|
||||
activeFrameId: state.activeFrameId,
|
||||
maxFrames: state.maxFrames,
|
||||
})));
|
||||
|
||||
// derived state
|
||||
const activeFrame = frames.find(f => f.id === activeFrameId) ?? null;
|
||||
|
||||
|
||||
// handlers
|
||||
|
||||
const handleSetMaxFrames = React.useCallback((value: number) => {
|
||||
aixClientDebuggerActions().setMaxFrames(value);
|
||||
}, []);
|
||||
|
||||
const handleSetActiveFrame = React.useCallback((value: number | null) => {
|
||||
aixClientDebuggerActions().setActiveFrame(value);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<GoodModal
|
||||
open
|
||||
onClose={props.onClose}
|
||||
title='AIX API Debugger'
|
||||
sx={{ maxWidth: undefined }}
|
||||
>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 2 }}>
|
||||
|
||||
{/* Request Switcher */}
|
||||
<FormControl sx={{ flex: 1, minWidth: 0 }}>
|
||||
<FormLabel>Select Request</FormLabel>
|
||||
<Select
|
||||
size='sm'
|
||||
variant='outlined'
|
||||
value={activeFrameId}
|
||||
onChange={(_, value) => handleSetActiveFrame(value)}
|
||||
placeholder='No requests available'
|
||||
disabled={!frames.length}
|
||||
sx={{ backgroundColor: 'background.popup' }}
|
||||
>
|
||||
{frames.map((frame) => {
|
||||
const label = `Request #${frame.id} - ${frame.context.contextName}`;
|
||||
return (
|
||||
<Option key={frame.id} value={frame.id} label={label + (frame.isComplete ? ` (${frame.particles.length})` : ' (Running)')}>
|
||||
<div>{label} - {frame.particles.length} pts.</div>
|
||||
<Box component='span' sx={{ marginLeft: 'auto', fontSize: 'xs' }}>{new Date(frame.timestamp).toLocaleTimeString()}</Box>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* History Size Preferenes */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-end', gap: 2 }}>
|
||||
<FormControl>
|
||||
<FormLabel>History Size</FormLabel>
|
||||
<Select
|
||||
size='sm'
|
||||
value={maxFrames}
|
||||
onChange={(_, value) => value !== null && handleSetMaxFrames(value)}
|
||||
sx={{ backgroundColor: 'background.popup' }}
|
||||
>
|
||||
<Option value={5}>Keep 5 requests</Option>
|
||||
<Option value={10}>Keep 10 requests</Option>
|
||||
<Option value={20}>Keep 20 requests</Option>
|
||||
<Option value={50}>Keep 50 requests</Option>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{/* Clear History */}
|
||||
<Button
|
||||
size='sm'
|
||||
color='danger'
|
||||
onClick={aixClientDebuggerActions().clearHistory}
|
||||
startDecorator={<ClearAllIcon />}
|
||||
disabled={frames.length === 0}
|
||||
>
|
||||
Clear History
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Zero State */}
|
||||
{(!frames.length || !activeFrame) && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', minHeight: '200px' }}>
|
||||
{!frames.length && <>
|
||||
<Typography level='title-lg'>
|
||||
No API requests recorded yet
|
||||
</Typography>
|
||||
<Typography sx={{ mt: 1 }}>
|
||||
Make a request with the AI to see it here
|
||||
</Typography>
|
||||
</>}
|
||||
{!activeFrame && (
|
||||
<Typography>
|
||||
Select a request to view details
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Frame viewer */}
|
||||
{!!activeFrame && (
|
||||
<Box sx={{ overflow: 'hidden' }}>
|
||||
<AixDebuggerFrame frame={activeFrame} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
</GoodModal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { Box, Card, Chip, Typography } from '@mui/joy';
|
||||
|
||||
import type { AixClientDebugger } from './memstore-aix-client-debugger';
|
||||
|
||||
|
||||
const _styles = {
|
||||
|
||||
requestCard: {
|
||||
overflow: 'auto',
|
||||
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
|
||||
fontFamily: 'code',
|
||||
fontSize: 'xs',
|
||||
gap: 1,
|
||||
} as const,
|
||||
|
||||
requestCardText: {
|
||||
whiteSpace: 'pre',
|
||||
} as const,
|
||||
|
||||
particleNorminal: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
} as const,
|
||||
|
||||
particleAborted: {
|
||||
// ..._styles.particleNorminal,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
// change look
|
||||
backgroundColor: '#f9f9f9',
|
||||
borderLeft: '3px solid orange',
|
||||
} as const,
|
||||
|
||||
pTime: {
|
||||
pl: 2,
|
||||
fontSize: 'xs',
|
||||
whiteSpace: 'nowrap',
|
||||
} as const,
|
||||
|
||||
} as const;
|
||||
|
||||
|
||||
export function AixDebuggerFrame(props: {
|
||||
frame: AixClientDebugger.Frame;
|
||||
}) {
|
||||
|
||||
const { frame } = props;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
|
||||
{/* Frame Header */}
|
||||
<Box sx={{ fontSize: 'sm', display: 'grid', gridTemplateColumns: { xs: 'auto 1fr', md: 'auto 1fr auto 1fr' }, gap: 1, alignItems: 'center' }}>
|
||||
<Typography fontWeight='bold'>Request </Typography>
|
||||
<Typography fontWeight='bold'>{frame.id}</Typography>
|
||||
<div>status</div>
|
||||
<Chip variant='soft' color={frame.isComplete ? 'success' : 'warning'}>{frame.isComplete ? 'Complete' : 'In Progress'}</Chip>
|
||||
<div>Date</div>
|
||||
<div>{new Date(frame.timestamp).toLocaleString()}</div>
|
||||
<div>URL:</div>
|
||||
<Chip>{frame.url || 'No URL data available'}</Chip>
|
||||
<div>Context:</div>
|
||||
<Chip>{frame.context.contextName}</Chip>
|
||||
<div>Reference:</div>
|
||||
<Chip>{frame.context.contextRef}</Chip>
|
||||
</Box>
|
||||
|
||||
{/* Headers */}
|
||||
<Typography color='warning' level='title-md' mb={-2}>
|
||||
Headers:
|
||||
</Typography>
|
||||
<Card variant='soft' color='warning' sx={_styles.requestCard}>
|
||||
<Box sx={_styles.requestCardText}>
|
||||
{frame.headers || 'No headers data available'}
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* Body */}
|
||||
<Typography color='primary' level='title-md' mb={-2}>
|
||||
Body:
|
||||
</Typography>
|
||||
<Card variant='soft' color='primary' sx={_styles.requestCard}>
|
||||
<Box sx={_styles.requestCardText}>
|
||||
{frame.body || 'No headers data available'}
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* Particles List */}
|
||||
<Typography level='title-md' mb={-2}>
|
||||
Particles {frame.particles.length > 0 && `(${frame.particles.length})`}
|
||||
{!frame.isComplete && ' • Streaming...'}
|
||||
</Typography>
|
||||
<Card variant='soft' sx={_styles.requestCard}>
|
||||
|
||||
{/* Zero state */}
|
||||
{!frame.particles.length && (
|
||||
<Typography>
|
||||
No particles received yet
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* List of particles */}
|
||||
{frame.particles.map((particle, idx) => {
|
||||
|
||||
// truncated preview of particle content
|
||||
let jsonPreview = '';
|
||||
try {
|
||||
const content = particle.content;
|
||||
jsonPreview = JSON.stringify(content).substring(0, 1024);
|
||||
if (jsonPreview.length >= 1024) jsonPreview += '...';
|
||||
} catch (e) {
|
||||
jsonPreview = 'Error parsing content';
|
||||
}
|
||||
|
||||
return (
|
||||
<Box key={idx} sx={particle.isAborted ? _styles.particleAborted : _styles.particleNorminal}>
|
||||
<Box className='agi-ellipsize'>
|
||||
<span style={{ opacity: 0.5 }}>{idx + 1}:</span> {particle.isAborted ? ' (Aborted)' : ''} {jsonPreview}
|
||||
</Box>
|
||||
<Box sx={_styles.pTime}>
|
||||
{new Date(particle.timestamp).toLocaleTimeString()}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -149,3 +149,8 @@ export const useAixClientDebuggerStore = create<AixClientDebuggerStore>((_set) =
|
||||
}),
|
||||
|
||||
}));
|
||||
|
||||
|
||||
export function aixClientDebuggerActions() {
|
||||
return useAixClientDebuggerStore.getState() as AixClientDebuggerActions;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user