Skip to content

Commit a6b35ea

Browse files
committed
feat(workbench): render diagnostics entries in diagnostics widget
1 parent 25a3716 commit a6b35ea

2 files changed

Lines changed: 119 additions & 29 deletions

File tree

invokeai/frontend/webv2/src/workbench/widgets/diagnostics/DiagnosticsHeaderActions.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
import { Badge } from '@chakra-ui/react';
22
import { Button } from '@workbench/components/ui';
3-
import { useWorkbenchDispatch, useWorkbenchSelector } from '@workbench/WorkbenchContext';
3+
import { clearProjectDiagnostics, useProjectDiagnostics } from '@workbench/diagnostics/logger';
4+
import { useActiveProjectSelector } from '@workbench/WorkbenchContext';
45
import { BugIcon } from 'lucide-react';
56
import { useCallback } from 'react';
67

78
export const DiagnosticsHeaderActions = () => {
8-
const errorCount = useWorkbenchSelector((snapshot) => snapshot.state.errorLog.length);
9-
const dispatch = useWorkbenchDispatch();
10-
const clearErrorLog = useCallback(() => dispatch({ type: 'clearErrorLog' }), [dispatch]);
9+
const projectId = useActiveProjectSelector((project) => project.id);
10+
const entries = useProjectDiagnostics(projectId);
11+
const clearEntries = useCallback(() => clearProjectDiagnostics(projectId), [projectId]);
1112

1213
return (
1314
<>
14-
{errorCount ? (
15+
{entries.length ? (
1516
<Badge colorPalette="red" size="xs">
1617
<BugIcon />
17-
{errorCount}
18+
{entries.length}
1819
</Badge>
1920
) : null}
20-
<Button disabled={errorCount === 0} size="2xs" variant="outline" onClick={clearErrorLog}>
21+
<Button disabled={entries.length === 0} size="2xs" variant="outline" onClick={clearEntries}>
2122
Clear
2223
</Button>
2324
</>
Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,147 @@
1+
import type { DiagnosticEntry } from '@workbench/diagnostics/logger';
12
import type { WidgetViewProps } from '@workbench/types';
23

3-
import { HStack, Stack, Text } from '@chakra-ui/react';
4+
import { Badge, Box, HStack, Stack, Text } from '@chakra-ui/react';
45
import { Button, Panel } from '@workbench/components/ui';
6+
import { clearProjectDiagnostics, useProjectDiagnostics } from '@workbench/diagnostics/logger';
57
import { StatusWidgetChip } from '@workbench/widget-frame';
6-
import { useWorkbenchSelector } from '@workbench/WorkbenchContext';
8+
import { useActiveProjectSelector } from '@workbench/WorkbenchContext';
79
import { BugIcon, ClipboardListIcon } from 'lucide-react';
8-
import { useCallback } from 'react';
10+
import { memo, useCallback, useState } from 'react';
911

1012
export const DiagnosticsWidgetView = ({ presentation, region }: WidgetViewProps) => {
11-
const errorCount = useWorkbenchSelector((snapshot) => snapshot.state.errorLog.length);
12-
const label = errorCount === 0 ? 'Clean' : `${errorCount} issue${errorCount === 1 ? '' : 's'}`;
13+
const projectId = useActiveProjectSelector((project) => project.id);
14+
const entries = useProjectDiagnostics(projectId);
15+
const label = entries.length === 0 ? 'Clean' : `${entries.length} event${entries.length === 1 ? '' : 's'}`;
1316

1417
if (region === 'bottom' && presentation !== 'expanded') {
1518
return (
16-
<StatusWidgetChip icon={errorCount > 0 ? BugIcon : ClipboardListIcon}>Diagnostics: {label}</StatusWidgetChip>
19+
<StatusWidgetChip icon={entries.length > 0 ? BugIcon : ClipboardListIcon}>Diagnostics: {label}</StatusWidgetChip>
1720
);
1821
}
1922

20-
return <DiagnosticsPanel />;
23+
return <DiagnosticsPanel entries={entries} projectId={projectId} />;
2124
};
2225

23-
const DiagnosticsPanel = () => {
24-
const errorLog = useWorkbenchSelector((snapshot) => snapshot.state.errorLog);
26+
const DiagnosticsPanel = ({ entries, projectId }: { entries: DiagnosticEntry[]; projectId: string }) => {
27+
const copyAll = useCallback(() => void navigator.clipboard?.writeText(JSON.stringify(entries, null, 2)), [entries]);
28+
const clearEntries = useCallback(() => clearProjectDiagnostics(projectId), [projectId]);
2529

2630
return (
2731
<Stack gap="3" p="2">
28-
{errorLog.length === 0 ? (
32+
<HStack justify="space-between">
2933
<Text color="fg.subtle" fontSize="2xs">
30-
Shell errors and debugging details will appear here without covering the workbench.
34+
Project-scoped diagnostic events from workbench and widget loggers.
35+
</Text>
36+
<HStack gap="2">
37+
<Button disabled={entries.length === 0} size="2xs" variant="outline" onClick={copyAll}>
38+
Copy JSON
39+
</Button>
40+
<Button disabled={entries.length === 0} size="2xs" variant="outline" onClick={clearEntries}>
41+
Clear
42+
</Button>
43+
</HStack>
44+
</HStack>
45+
{entries.length === 0 ? (
46+
<Text color="fg.subtle" fontSize="2xs">
47+
Logger events and performance timings will appear here without covering the workbench.
3148
</Text>
3249
) : (
3350
<Stack gap="2">
34-
{errorLog.map((message, index) => (
35-
<DiagnosticsErrorRow key={`${message}-${index}`} message={message} />
51+
{entries.map((entry) => (
52+
<DiagnosticsEntryRow key={entry.id} entry={entry} />
3653
))}
3754
</Stack>
3855
)}
3956
</Stack>
4057
);
4158
};
4259

43-
const DiagnosticsErrorRow = ({ message }: { message: string }) => {
44-
const copyMessage = useCallback(() => void navigator.clipboard?.writeText(message), [message]);
60+
const DiagnosticsEntryRow = memo(({ entry }: { entry: DiagnosticEntry }) => {
61+
const [isRawVisible, setIsRawVisible] = useState(false);
62+
const copyEntry = useCallback(() => void navigator.clipboard?.writeText(JSON.stringify(entry, null, 2)), [entry]);
63+
const toggleRaw = useCallback(() => setIsRawVisible((current) => !current), []);
4564

4665
return (
4766
<Panel gap="2" p="2">
48-
<Text color="fg.muted" fontFamily="mono" fontSize="2xs" whiteSpace="pre-wrap">
49-
{message}
50-
</Text>
51-
<HStack justify="end">
52-
<Button size="2xs" variant="outline" onClick={copyMessage}>
53-
Copy
67+
<Stack gap="2">
68+
<HStack align="start" gap="2" justify="space-between">
69+
<Stack gap="1" minW="0">
70+
<HStack gap="1.5" wrap="wrap">
71+
<Badge colorPalette={getLevelColorPalette(entry.level)} size="xs">
72+
{entry.level}
73+
</Badge>
74+
<Badge colorPalette="gray" size="xs">
75+
{entry.namespace}
76+
</Badge>
77+
{entry.durationMs !== undefined ? (
78+
<Badge colorPalette="purple" size="xs">
79+
{entry.durationMs.toFixed(1)}ms
80+
</Badge>
81+
) : null}
82+
<Text color="fg.muted" fontFamily="mono" fontSize="2xs">
83+
{formatSource(entry)}
84+
</Text>
85+
</HStack>
86+
<Text color="fg" fontSize="xs" fontWeight="600">
87+
{entry.message || '(no message)'}
88+
</Text>
89+
<Text color="fg.subtle" fontSize="2xs">
90+
{new Date(entry.createdAt).toLocaleTimeString()}
91+
</Text>
92+
</Stack>
93+
<Button size="2xs" variant="outline" onClick={copyEntry}>
94+
Copy
95+
</Button>
96+
</HStack>
97+
<Button alignSelf="start" size="2xs" variant="ghost" onClick={toggleRaw}>
98+
{isRawVisible ? 'Hide raw entry' : 'Raw entry'}
5499
</Button>
55-
</HStack>
100+
{isRawVisible ? (
101+
<Box
102+
as="pre"
103+
bg="bg.inset"
104+
borderColor="border.subtle"
105+
borderWidth="1px"
106+
color="fg.muted"
107+
fontFamily="mono"
108+
fontSize="2xs"
109+
maxH="16rem"
110+
overflow="auto"
111+
p="2"
112+
rounded="md"
113+
whiteSpace="pre-wrap"
114+
>
115+
{JSON.stringify(entry, null, 2)}
116+
</Box>
117+
) : null}
118+
</Stack>
56119
</Panel>
57120
);
121+
});
122+
123+
DiagnosticsEntryRow.displayName = 'DiagnosticsEntryRow';
124+
125+
const formatSource = (entry: DiagnosticEntry): string => {
126+
if (entry.source.kind === 'widget') {
127+
return `${entry.source.typeId}:${entry.source.instanceId}:${entry.source.region}`;
128+
}
129+
130+
return `workbench:${entry.source.area}`;
131+
};
132+
133+
const getLevelColorPalette = (level: DiagnosticEntry['level']): string => {
134+
switch (level) {
135+
case 'fatal':
136+
case 'error':
137+
return 'red';
138+
case 'warn':
139+
return 'orange';
140+
case 'info':
141+
return 'blue';
142+
case 'debug':
143+
return 'purple';
144+
case 'trace':
145+
return 'gray';
146+
}
58147
};

0 commit comments

Comments
 (0)