Merge branch 'main' into enhance/invokeai-logs

This commit is contained in:
Lincoln Stein 2023-04-29 11:00:18 -04:00 committed by GitHub
commit 0bc2edc044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 434 additions and 308 deletions

View File

@ -65,6 +65,7 @@
"@emotion/styled": "^11.10.6",
"@fontsource/inter": "^4.5.15",
"@reduxjs/toolkit": "^1.9.5",
"@roarr/browser-log-writer": "^1.1.5",
"chakra-ui-contextmenu": "^1.0.5",
"dateformat": "^5.0.3",
"formik": "^2.2.9",
@ -93,17 +94,19 @@
"redux-deep-persist": "^1.0.7",
"redux-dynamic-middlewares": "^2.2.0",
"redux-persist": "^6.0.0",
"roarr": "^7.15.0",
"socket.io-client": "^4.6.0",
"use-image": "^1.1.0",
"uuid": "^9.0.0"
},
"peerDependencies": {
"@chakra-ui/cli": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ts-toolbelt": "^9.6.0",
"@chakra-ui/cli": "^2.4.0"
"ts-toolbelt": "^9.6.0"
},
"devDependencies": {
"@chakra-ui/cli": "^2.4.0",
"@types/dateformat": "^5.0.0",
"@types/lodash-es": "^4.14.194",
"@types/node": "^18.16.2",

View File

@ -530,7 +530,11 @@
"resetWebUI": "Reset Web UI",
"resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.",
"resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.",
"resetComplete": "Web UI has been reset. Refresh the page to reload."
"resetComplete": "Web UI has been reset. Refresh the page to reload.",
"consoleLogLevel": "Log Level",
"shouldLogToConsole": "Console Logging",
"developer": "Developer",
"general": "General"
},
"toast": {
"serverError": "Server Error",

View File

@ -1,5 +1,4 @@
import ImageUploader from 'common/components/ImageUploader';
import Console from 'features/system/components/Console';
import ProgressBar from 'features/system/components/ProgressBar';
import SiteHeader from 'features/system/components/SiteHeader';
import InvokeTabs from 'features/ui/components/InvokeTabs';
@ -27,6 +26,7 @@ import { PartialAppConfig } from 'app/types/invokeai';
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
import { configChanged } from 'features/system/store/configSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useLogger } from 'app/logging/useLogger';
const DEFAULT_CONFIG = {};
@ -37,6 +37,7 @@ interface Props extends PropsWithChildren {
const App = ({ config = DEFAULT_CONFIG, children }: Props) => {
useToastWatcher();
useGlobalHotkeys();
const log = useLogger();
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
@ -50,9 +51,9 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => {
const dispatch = useAppDispatch();
useEffect(() => {
console.log('Received config: ', config);
log.info({ namespace: 'App', data: config }, 'Received config');
dispatch(configChanged(config));
}, [dispatch, config]);
}, [dispatch, config, log]);
useEffect(() => {
setColorMode(['light'].includes(currentTheme) ? 'light' : 'dark');
@ -119,9 +120,6 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => {
<Portal>
<FloatingGalleryButton />
</Portal>
<Portal>
<Console />
</Portal>
</Grid>
);
};

View File

@ -0,0 +1,94 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors';
import { isEqual } from 'lodash-es';
import { useEffect } from 'react';
import { LogLevelName, ROARR, Roarr } from 'roarr';
import { createLogWriter } from '@roarr/browser-log-writer';
// Base logging context includes only the package name
const baseContext = { package: '@invoke-ai/invoke-ai-ui' };
// Create browser log writer
ROARR.write = createLogWriter();
// Module-scoped logger - can be imported and used anywhere
export let log = Roarr.child(baseContext);
// Translate human-readable log levels to numbers, used for log filtering
export const LOG_LEVEL_MAP: Record<LogLevelName, number> = {
trace: 10,
debug: 20,
info: 30,
warn: 40,
error: 50,
fatal: 60,
};
export const VALID_LOG_LEVELS = [
'trace',
'debug',
'info',
'warn',
'error',
'fatal',
] as const;
export type InvokeLogLevel = (typeof VALID_LOG_LEVELS)[number];
const selector = createSelector(
systemSelector,
(system) => {
const { app_version, consoleLogLevel, shouldLogToConsole } = system;
return {
version: app_version,
consoleLogLevel,
shouldLogToConsole,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export const useLogger = () => {
const { version, consoleLogLevel, shouldLogToConsole } =
useAppSelector(selector);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {
if (shouldLogToConsole) {
// Enable console log output
localStorage.setItem('ROARR_LOG', 'true');
// Use a filter to show only logs of the given level
localStorage.setItem(
'ROARR_FILTER',
`context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`
);
} else {
// Disable console log output
localStorage.setItem('ROARR_LOG', 'false');
}
ROARR.write = createLogWriter();
}, [consoleLogLevel, shouldLogToConsole]);
// Update the module-scoped logger context as needed
useEffect(() => {
const newContext: Record<string, any> = {
...baseContext,
};
if (version) {
newContext.version = version;
}
log = Roarr.child(newContext);
}, [version]);
// Use the logger within components - no different than just importing it directly
return log;
};

View File

@ -12,7 +12,6 @@ import {
removeImage,
} from 'features/gallery/store/gallerySlice';
import {
addLogEntry,
generationRequested,
modelChangeRequested,
modelConvertRequested,

View File

@ -6,7 +6,6 @@ import { v4 as uuidv4 } from 'uuid';
import * as InvokeAI from 'app/types/invokeai';
import {
addLogEntry,
addToast,
errorOccurred,
processingCanceled,

View File

@ -28,7 +28,7 @@ import { lightboxDenylist } from 'features/lightbox/store/lightboxPersistDenylis
import { modelsDenylist } from 'features/system/store/modelsPersistDenylist';
import { nodesDenylist } from 'features/nodes/store/nodesPersistDenylist';
import { postprocessingDenylist } from 'features/parameters/store/postprocessingPersistDenylist';
import { systemDenylist } from 'features/system/store/systemPersistsDenylist';
import { systemDenylist } from 'features/system/store/systemPersistDenylist';
import { uiDenylist } from 'features/ui/store/uiPersistDenylist';
/**
@ -82,7 +82,6 @@ const rootPersistConfig = getPersistConfig({
'hotkeys',
'config',
],
debounce: 300,
});
const persistedReducer = persistReducer(rootPersistConfig, rootReducer);

View File

@ -16,13 +16,23 @@ type IAISelectProps = SelectProps & {
validValues:
| Array<number | string>
| Array<{ key: string; value: string | number }>;
horizontal?: boolean;
spaceEvenly?: boolean;
};
/**
* Customized Chakra FormControl + Select multi-part component.
*/
const IAISelect = (props: IAISelectProps) => {
const { label, isDisabled, validValues, tooltip, tooltipProps, ...rest } =
props;
const {
label,
isDisabled,
validValues,
tooltip,
tooltipProps,
horizontal,
spaceEvenly,
...rest
} = props;
return (
<FormControl
isDisabled={isDisabled}
@ -32,10 +42,28 @@ const IAISelect = (props: IAISelectProps) => {
e.nativeEvent.stopPropagation();
e.nativeEvent.cancelBubble = true;
}}
sx={
horizontal
? {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 4,
}
: {}
}
>
{label && <FormLabel>{label}</FormLabel>}
{label && (
<FormLabel sx={spaceEvenly ? { flexBasis: 0, flexGrow: 1 } : {}}>
{label}
</FormLabel>
)}
<Tooltip label={tooltip} {...tooltipProps}>
<Select {...rest}>
<Select
{...rest}
rootProps={{ sx: spaceEvenly ? { flexBasis: 0, flexGrow: 1 } : {} }}
>
{validValues.map((opt) => {
return typeof opt === 'string' || typeof opt === 'number' ? (
<IAIOption key={opt} value={opt}>

View File

@ -16,6 +16,8 @@ import { receivedOpenAPISchema } from 'services/thunks/schema';
import { isFulfilledAnyGraphBuilt } from 'services/thunks/session';
import { InvocationTemplate, InvocationValue } from '../types/types';
import { parseSchema } from '../util/parseSchema';
import { log } from 'app/logging/useLogger';
import { size } from 'lodash-es';
export type NodesState = {
nodes: Node<InvocationValue>[];
@ -85,7 +87,12 @@ const nodesSlice = createSlice({
parsedOpenAPISchema: (state, action: PayloadAction<OpenAPIV3.Document>) => {
try {
const parsedSchema = parseSchema(action.payload);
console.debug('Parsed schema: ', parsedSchema);
// TODO: Achtung! Side effect in a reducer!
log.info(
{ namespace: 'schema', nodes: parsedSchema },
`Parsed ${size(parsedSchema)} nodes`
);
state.invocationTemplates = parsedSchema;
} catch (err) {
console.error(err);

View File

@ -1,197 +0,0 @@
import { Flex, Text, Tooltip } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import {
errorSeen,
setShouldShowLogViewer,
SystemState,
} from 'features/system/store/systemSlice';
import { isEqual } from 'lodash-es';
import { Resizable } from 're-resizable';
import { memo, useLayoutEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { FaAngleDoubleDown, FaCode, FaMinus } from 'react-icons/fa';
import { systemSelector } from '../store/systemSelectors';
const logSelector = createSelector(
systemSelector,
(system: SystemState) => system.log,
{
memoizeOptions: {
// We don't need a deep equality check for this selector.
resultEqualityCheck: (a, b) => a.length === b.length,
},
}
);
const consoleSelector = createSelector(
systemSelector,
(system: SystemState) => {
return {
shouldShowLogViewer: system.shouldShowLogViewer,
hasError: system.hasError,
wasErrorSeen: system.wasErrorSeen,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
/**
* Basic log viewer, floats on bottom of page.
*/
const Console = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const log = useAppSelector(logSelector);
const { shouldShowLogViewer, hasError, wasErrorSeen } =
useAppSelector(consoleSelector);
// Rudimentary autoscroll
const [shouldAutoscroll, setShouldAutoscroll] = useState<boolean>(true);
const viewerRef = useRef<HTMLDivElement>(null);
/**
* If autoscroll is on, scroll to the bottom when:
* - log updates
* - viewer is toggled
*
* Also scroll to the bottom whenever autoscroll is turned on.
*/
useLayoutEffect(() => {
if (viewerRef.current !== null && shouldAutoscroll) {
viewerRef.current.scrollTop = viewerRef.current.scrollHeight;
}
}, [shouldAutoscroll, log, shouldShowLogViewer]);
const handleClickLogViewerToggle = () => {
dispatch(errorSeen());
dispatch(setShouldShowLogViewer(!shouldShowLogViewer));
};
useHotkeys(
'`',
() => {
dispatch(setShouldShowLogViewer(!shouldShowLogViewer));
},
[shouldShowLogViewer]
);
useHotkeys('esc', () => {
dispatch(setShouldShowLogViewer(false));
});
const handleOnScroll = () => {
if (!viewerRef.current) return;
if (
shouldAutoscroll &&
viewerRef.current.scrollTop <
viewerRef.current.scrollHeight - viewerRef.current.clientHeight
) {
setShouldAutoscroll(false);
}
};
return (
<>
{shouldShowLogViewer && (
<Resizable
defaultSize={{
width: '100%',
height: 200,
}}
style={{
display: 'flex',
position: 'fixed',
insetInlineStart: 0,
bottom: 0,
zIndex: 1,
}}
maxHeight="90vh"
>
<Flex
sx={{
flexDirection: 'column',
width: '100vw',
overflow: 'auto',
direction: 'column',
fontFamily: 'monospace',
pt: 0,
pr: 4,
pb: 4,
pl: 12,
borderTopWidth: 5,
bg: 'base.850',
borderColor: 'base.700',
zIndex: 2,
}}
ref={viewerRef}
onScroll={handleOnScroll}
>
{log.map((entry, i) => {
const { timestamp, message, level } = entry;
const colorScheme = level === 'info' ? 'base' : level;
return (
<Flex
key={i}
sx={{
gap: 2,
color: `${colorScheme}.300`,
}}
>
<Text fontWeight="600">{timestamp}:</Text>
<Text wordBreak="break-all">{message}</Text>
</Flex>
);
})}
</Flex>
</Resizable>
)}
{shouldShowLogViewer && (
<Tooltip
hasArrow
label={shouldAutoscroll ? 'Autoscroll On' : 'Autoscroll Off'}
>
<IAIIconButton
size="sm"
aria-label={t('accessibility.toggleAutoscroll')}
icon={<FaAngleDoubleDown />}
onClick={() => setShouldAutoscroll(!shouldAutoscroll)}
isChecked={shouldAutoscroll}
sx={{
position: 'fixed',
insetInlineStart: 2,
bottom: 12,
zIndex: 1,
}}
/>
</Tooltip>
)}
<Tooltip
hasArrow
label={shouldShowLogViewer ? 'Hide Console' : 'Show Console'}
>
<IAIIconButton
size="sm"
aria-label={t('accessibility.toggleLogViewer')}
icon={shouldShowLogViewer ? <FaMinus /> : <FaCode />}
onClick={handleClickLogViewerToggle}
sx={{
position: 'fixed',
insetInlineStart: 2,
bottom: 2,
zIndex: 1,
}}
colorScheme={hasError || !wasErrorSeen ? 'error' : 'base'}
/>
</Tooltip>
</>
);
};
export default memo(Console);

View File

@ -23,12 +23,14 @@ import IAISelect from 'common/components/IAISelect';
import IAISwitch from 'common/components/IAISwitch';
import { systemSelector } from 'features/system/store/systemSelectors';
import {
consoleLogLevelChanged,
InProgressImageType,
setEnableImageDebugging,
setSaveIntermediatesInterval,
setShouldConfirmOnDelete,
setShouldDisplayGuides,
setShouldDisplayInProgressType,
shouldLogToConsoleChanged,
SystemState,
} from 'features/system/store/systemSlice';
import { uiSelector } from 'features/ui/store/uiSelectors';
@ -39,8 +41,11 @@ import {
import { UIState } from 'features/ui/store/uiTypes';
import { isEqual, map } from 'lodash-es';
import { persistor } from 'app/store/persistor';
import { ChangeEvent, cloneElement, ReactElement } from 'react';
import { ChangeEvent, cloneElement, ReactElement, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { InvokeLogLevel, VALID_LOG_LEVELS } from 'app/logging/useLogger';
import { LogLevelName } from 'roarr';
import { F } from 'ts-toolbelt';
const selector = createSelector(
[systemSelector, uiSelector],
@ -52,6 +57,8 @@ const selector = createSelector(
model_list,
saveIntermediatesInterval,
enableImageDebugging,
consoleLogLevel,
shouldLogToConsole,
} = system;
const { shouldUseCanvasBetaLayout, shouldUseSliders } = ui;
@ -65,6 +72,8 @@ const selector = createSelector(
enableImageDebugging,
shouldUseCanvasBetaLayout,
shouldUseSliders,
consoleLogLevel,
shouldLogToConsole,
};
},
{
@ -77,6 +86,7 @@ const modalSectionStyles: ChakraProps['sx'] = {
gap: 2,
p: 4,
bg: 'base.900',
borderRadius: 'base',
};
type SettingsModalProps = {
@ -116,6 +126,8 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
enableImageDebugging,
shouldUseCanvasBetaLayout,
shouldUseSliders,
consoleLogLevel,
shouldLogToConsole,
} = useAppSelector(selector);
/**
@ -135,6 +147,20 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
dispatch(setSaveIntermediatesInterval(value));
};
const handleLogLevelChanged = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => {
dispatch(consoleLogLevelChanged(e.target.value as LogLevelName));
},
[dispatch]
);
const handleLogToConsoleChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldLogToConsoleChanged(e.target.checked));
},
[dispatch]
);
return (
<>
{cloneElement(children, {
@ -145,15 +171,20 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
isOpen={isSettingsModalOpen}
onClose={onSettingsModalClose}
size="xl"
isCentered
>
<ModalOverlay />
<ModalContent paddingInlineEnd={4}>
<ModalHeader>{t('common.settingsLabel')}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Grid gap={4}>
<Flex sx={{ gap: 4, flexDirection: 'column' }}>
<Flex sx={modalSectionStyles}>
<Heading size="sm">{t('settings.general')}</Heading>
<IAISelect
horizontal
spaceEvenly
label={t('settings.displayInProgress')}
validValues={IN_PROGRESS_IMAGE_TYPES}
value={shouldDisplayInProgressType}
@ -208,9 +239,21 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
</Flex>
<Flex sx={modalSectionStyles}>
<Heading size="sm" style={{ fontWeight: 'bold' }}>
Developer
</Heading>
<Heading size="sm">{t('settings.developer')}</Heading>
<IAISwitch
label={t('settings.shouldLogToConsole')}
isChecked={shouldLogToConsole}
onChange={handleLogToConsoleChanged}
/>
<IAISelect
horizontal
spaceEvenly
isDisabled={!shouldLogToConsole}
label={t('settings.consoleLogLevel')}
onChange={handleLogLevelChanged}
value={consoleLogLevel}
validValues={VALID_LOG_LEVELS.concat()}
/>
<IAISwitch
label={t('settings.enableImageDebugging')}
isChecked={enableImageDebugging}
@ -228,7 +271,7 @@ const SettingsModal = ({ children }: SettingsModalProps) => {
<Text>{t('settings.resetWebUIDesc1')}</Text>
<Text>{t('settings.resetWebUIDesc2')}</Text>
</Flex>
</Grid>
</Flex>
</ModalBody>
<ModalFooter>

View File

@ -17,7 +17,6 @@ const itemsToDenylist: (keyof SystemState)[] = [
'totalSteps',
'openModel',
'isCancelScheduled',
// 'sessionId',
'progressImage',
'wereModelsReceived',
'wasSchemaParsed',

View File

@ -14,13 +14,14 @@ import {
} from 'services/events/actions';
import i18n from 'i18n';
import { isImageOutput } from 'services/types/guards';
import { ProgressImage } from 'services/events/types';
import { initialImageSelected } from 'features/parameters/store/generationSlice';
import { makeToast } from '../hooks/useToastWatcher';
import { sessionCanceled, sessionInvoked } from 'services/thunks/session';
import { receivedModels } from 'services/thunks/model';
import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
import { LogLevelName } from 'roarr';
import { InvokeLogLevel } from 'app/logging/useLogger';
export type LogLevel = 'info' | 'warning' | 'error';
@ -42,7 +43,6 @@ export interface SystemState
extends InvokeAI.SystemStatus,
InvokeAI.SystemConfig {
shouldDisplayInProgressType: InProgressImageType;
log: Array<LogEntry>;
shouldShowLogViewer: boolean;
isGFPGANAvailable: boolean;
isESRGANAvailable: boolean;
@ -97,12 +97,16 @@ export interface SystemState
* Whether or not the OpenAPI schema was received and parsed
*/
wasSchemaParsed: boolean;
/**
* The console output logging level
*/
consoleLogLevel: InvokeLogLevel;
shouldLogToConsole: boolean;
}
const initialSystemState: SystemState = {
isConnected: false,
isProcessing: false,
log: [],
shouldShowLogViewer: false,
shouldDisplayInProgressType: 'latents',
shouldDisplayGuides: true,
@ -144,11 +148,10 @@ const initialSystemState: SystemState = {
cancelType: 'immediate',
isCancelScheduled: false,
subscribedNodeIds: [],
// shouldTransformUrls: false,
// disabledTabs: [],
// disabledFeatures: [],
wereModelsReceived: false,
wasSchemaParsed: false,
consoleLogLevel: 'error',
shouldLogToConsole: true,
};
export const systemSlice = createSlice({
@ -189,25 +192,6 @@ export const systemSlice = createSlice({
? i18n.t('common.statusConnected')
: i18n.t('common.statusDisconnected');
},
addLogEntry: (
state,
action: PayloadAction<{
timestamp: string;
message: string;
level?: LogLevel;
}>
) => {
const { timestamp, message, level } = action.payload;
const logLevel = level || 'info';
const entry: LogEntry = {
timestamp,
message,
level: logLevel,
};
state.log.push(entry);
},
setShouldShowLogViewer: (state, action: PayloadAction<boolean>) => {
state.shouldShowLogViewer = action.payload;
},
@ -346,6 +330,12 @@ export const systemSlice = createSlice({
subscribedNodeIdsSet: (state, action: PayloadAction<string[]>) => {
state.subscribedNodeIds = action.payload;
},
consoleLogLevelChanged: (state, action: PayloadAction<LogLevelName>) => {
state.consoleLogLevel = action.payload;
},
shouldLogToConsoleChanged: (state, action: PayloadAction<boolean>) => {
state.shouldLogToConsole = action.payload;
},
},
extraReducers(builder) {
/**
@ -353,7 +343,6 @@ export const systemSlice = createSlice({
*/
builder.addCase(socketSubscribed, (state, action) => {
state.sessionId = action.payload.sessionId;
console.log(`Subscribed to session ${action.payload.sessionId}`);
});
/**
@ -371,11 +360,6 @@ export const systemSlice = createSlice({
state.isConnected = true;
state.currentStatus = i18n.t('common.statusConnected');
state.log.push({
timestamp,
message: `Connected to server`,
level: 'info',
});
});
/**
@ -386,11 +370,6 @@ export const systemSlice = createSlice({
state.isConnected = false;
state.currentStatus = i18n.t('common.statusDisconnected');
state.log.push({
timestamp,
message: `Disconnected from server`,
level: 'error',
});
});
/**
@ -433,15 +412,6 @@ export const systemSlice = createSlice({
state.totalSteps = 0;
state.progressImage = null;
state.currentStatus = i18n.t('common.statusProcessingComplete');
// TODO: handle logging for other invocation types
if (isImageOutput(data.result)) {
state.log.push({
timestamp,
message: `Generated: ${data.result.image.image_name}`,
level: 'info',
});
}
});
/**
@ -450,12 +420,6 @@ export const systemSlice = createSlice({
builder.addCase(invocationError, (state, action) => {
const { data, timestamp } = action.payload;
state.log.push({
timestamp,
message: `Server error: ${data.error}`,
level: 'error',
});
state.wasErrorSeen = true;
state.progressImage = null;
state.isProcessing = false;
@ -463,12 +427,6 @@ export const systemSlice = createSlice({
state.toastQueue.push(
makeToast({ title: i18n.t('toast.serverError'), status: 'error' })
);
state.log.push({
timestamp,
message: `Server error: ${data.error}`,
level: 'error',
});
});
/**
@ -495,12 +453,6 @@ export const systemSlice = createSlice({
state.toastQueue.push(
makeToast({ title: i18n.t('toast.canceled'), status: 'warning' })
);
state.log.push({
timestamp,
message: `Processing canceled`,
level: 'warning',
});
});
/**
@ -529,7 +481,6 @@ export const systemSlice = createSlice({
export const {
setShouldDisplayInProgressType,
setIsProcessing,
addLogEntry,
setShouldShowLogViewer,
setIsConnected,
setSocketId,
@ -562,6 +513,8 @@ export const {
scheduledCancelAborted,
cancelTypeChanged,
subscribedNodeIdsSet,
consoleLogLevelChanged,
shouldLogToConsoleChanged,
} = systemSlice.actions;
export default systemSlice.reducer;

View File

@ -1,6 +1,7 @@
import { createAction } from '@reduxjs/toolkit';
import {
GeneratorProgressEvent,
GraphExecutionStateCompleteEvent,
InvocationCompleteEvent,
InvocationErrorEvent,
InvocationStartedEvent,
@ -45,6 +46,10 @@ export const invocationError = createAction<
BaseSocketPayload & { data: InvocationErrorEvent }
>('socket/invocationError');
export const graphExecutionStateComplete = createAction<
BaseSocketPayload & { data: GraphExecutionStateCompleteEvent }
>('socket/graphExecutionStateComplete');
export const generatorProgress = createAction<
BaseSocketPayload & { data: GeneratorProgressEvent }
>('socket/generatorProgress');

View File

@ -6,13 +6,9 @@ import {
ServerToClientEvents,
} from 'services/events/types';
import {
generatorProgress,
invocationComplete,
invocationError,
invocationStarted,
socketConnected,
socketDisconnected,
socketReset,
socketSubscribed,
socketUnsubscribed,
} from './actions';
@ -25,7 +21,6 @@ import { getTimestamp } from 'common/util/getTimestamp';
import {
sessionInvoked,
isFulfilledSessionCreatedAction,
sessionCanceled,
} from 'services/thunks/session';
import { OpenAPI } from 'services/api';
import { receivedModels } from 'services/thunks/model';
@ -33,6 +28,9 @@ import { receivedOpenAPISchema } from 'services/thunks/schema';
import { isImageOutput } from 'services/types/guards';
import { imageReceived, thumbnailReceived } from 'services/thunks/image';
import { setEventListeners } from 'services/events/util/setEventListeners';
import { log } from 'app/logging/useLogger';
const moduleLog = log.child({ namespace: 'socketio' });
export const socketMiddleware = () => {
let areListenersSet = false;
@ -91,6 +89,8 @@ export const socketMiddleware = () => {
// Must happen in middleware to get access to `dispatch`
if (!areListenersSet) {
socket.on('connect', () => {
moduleLog.debug('Connected');
dispatch(socketConnected({ timestamp: getTimestamp() }));
const { results, uploads, models, nodes, config, system } =
@ -116,7 +116,12 @@ export const socketMiddleware = () => {
}
if (system.sessionId) {
console.log(`Re-subscribing to session ${system.sessionId}`);
const sessionLog = moduleLog.child({ sessionId: system.sessionId });
sessionLog.debug(
`Subscribed to existing session (${system.sessionId})`
);
socket.emit('subscribe', { session: system.sessionId });
dispatch(
socketSubscribed({
@ -124,11 +129,12 @@ export const socketMiddleware = () => {
timestamp: getTimestamp(),
})
);
setEventListeners({ socket, store });
setEventListeners({ socket, store, sessionLog });
}
});
socket.on('disconnect', () => {
moduleLog.debug('Disconnected');
dispatch(socketDisconnected({ timestamp: getTimestamp() }));
});
@ -140,6 +146,8 @@ export const socketMiddleware = () => {
// Everything else only happens once we have created a session
if (isFulfilledSessionCreatedAction(action)) {
const sessionId = action.payload.id;
const sessionLog = moduleLog.child({ sessionId });
const oldSessionId = getState().system.sessionId;
// const subscribedNodeIds = getState().system.subscribedNodeIds;
@ -152,6 +160,10 @@ export const socketMiddleware = () => {
// };
if (oldSessionId) {
sessionLog.debug(
{ oldSessionId },
`Unsubscribed from old session (${oldSessionId})`
);
// Unsubscribe when invocations complete
socket.emit('unsubscribe', {
session: oldSessionId,
@ -176,8 +188,7 @@ export const socketMiddleware = () => {
});
}
const sessionId = action.payload.id;
sessionLog.debug(`Subscribe to new session (${sessionId})`);
socket.emit('subscribe', { session: sessionId });
dispatch(
socketSubscribed({
@ -185,7 +196,7 @@ export const socketMiddleware = () => {
timestamp: getTimestamp(),
})
);
setEventListeners({ socket, store });
setEventListeners({ socket, store, sessionLog });
// Finally we actually invoke the session, starting processing
dispatch(sessionInvoked({ sessionId }));

View File

@ -5,34 +5,42 @@ import { sessionCanceled } from 'services/thunks/session';
import { Socket } from 'socket.io-client';
import {
generatorProgress,
graphExecutionStateComplete,
invocationComplete,
invocationError,
invocationStarted,
} from '../actions';
import { ClientToServerEvents, ServerToClientEvents } from '../types';
import { Logger } from 'roarr';
import { JsonObject } from 'roarr/dist/types';
type SetEventListenersArg = {
socket: Socket<ServerToClientEvents, ClientToServerEvents>;
store: MiddlewareAPI<AppDispatch, RootState>;
sessionLog: Logger<JsonObject>;
};
export const setEventListeners = (arg: SetEventListenersArg) => {
const { socket, store } = arg;
const { socket, store, sessionLog } = arg;
const { dispatch, getState } = store;
// Set up listeners for the present subscription
socket.on('invocation_started', (data) => {
sessionLog.child({ data }).info(`Invocation started (${data.node.type})`);
dispatch(invocationStarted({ data, timestamp: getTimestamp() }));
});
socket.on('generator_progress', (data) => {
sessionLog.child({ data }).trace(`Generator progress (${data.node.type})`);
dispatch(generatorProgress({ data, timestamp: getTimestamp() }));
});
socket.on('invocation_error', (data) => {
sessionLog.child({ data }).error(`Invocation error (${data.node.type})`);
dispatch(invocationError({ data, timestamp: getTimestamp() }));
});
socket.on('invocation_complete', (data) => {
sessionLog.child({ data }).info(`Invocation complete (${data.node.type})`);
const sessionId = data.graph_execution_state_id;
const { cancelType, isCancelScheduled } = getState().system;
@ -51,4 +59,13 @@ export const setEventListeners = (arg: SetEventListenersArg) => {
})
);
});
socket.on('graph_execution_state_complete', (data) => {
sessionLog
.child({ data })
.info(
`Graph execution state complete (${data.graph_execution_state_id})`
);
dispatch(graphExecutionStateComplete({ data, timestamp: getTimestamp() }));
});
};

View File

@ -1,8 +1,11 @@
import { log } from 'app/logging/useLogger';
import { createAppAsyncThunk } from 'app/store/storeUtils';
import { ImagesService } from 'services/api';
export const IMAGES_PER_PAGE = 20;
const galleryLog = log.child({ namespace: 'gallery' });
export const receivedResultImagesPage = createAppAsyncThunk(
'results/receivedResultImagesPage',
async (_arg, { getState }) => {
@ -12,6 +15,8 @@ export const receivedResultImagesPage = createAppAsyncThunk(
perPage: IMAGES_PER_PAGE,
});
galleryLog.info({ response }, `Received ${response.items.length} results`);
return response;
}
);
@ -25,6 +30,8 @@ export const receivedUploadImagesPage = createAppAsyncThunk(
perPage: IMAGES_PER_PAGE,
});
galleryLog.info({ response }, `Received ${response.items.length} uploads`);
return response;
}
);

View File

@ -1,10 +1,13 @@
import { isFulfilled, isRejected } from '@reduxjs/toolkit';
import { log } from 'app/logging/useLogger';
import { createAppAsyncThunk } from 'app/store/storeUtils';
import { imageSelected } from 'features/gallery/store/gallerySlice';
import { clamp } from 'lodash-es';
import { ImagesService } from 'services/api';
import { getHeaders } from 'services/util/getHeaders';
const imagesLog = log.child({ namespace: 'image' });
type ImageReceivedArg = Parameters<(typeof ImagesService)['getImage']>[0];
/**
@ -14,6 +17,9 @@ export const imageReceived = createAppAsyncThunk(
'api/imageReceived',
async (arg: ImageReceivedArg, _thunkApi) => {
const response = await ImagesService.getImage(arg);
imagesLog.info({ arg, response }, 'Received image');
return response;
}
);
@ -29,6 +35,9 @@ export const thumbnailReceived = createAppAsyncThunk(
'api/thumbnailReceived',
async (arg: ThumbnailReceivedArg, _thunkApi) => {
const response = await ImagesService.getThumbnail(arg);
imagesLog.info({ arg, response }, 'Received thumbnail');
return response;
}
);
@ -43,6 +52,12 @@ export const imageUploaded = createAppAsyncThunk(
async (arg: ImageUploadedArg, _thunkApi) => {
const response = await ImagesService.uploadImage(arg);
const { location } = getHeaders(response);
imagesLog.info(
{ arg: '<Blob>', response, location },
`Image uploaded (${response.image_name})`
);
return { response, location };
}
);
@ -96,6 +111,11 @@ export const imageDeleted = createAppAsyncThunk(
const response = await ImagesService.deleteImage(arg);
imagesLog.info(
{ arg, response },
`Image deleted (${arg.imageType} - ${arg.imageName})`
);
return response;
}
);

View File

@ -1,14 +1,18 @@
import { log } from 'app/logging/useLogger';
import { createAppAsyncThunk } from 'app/store/storeUtils';
import { Model } from 'features/system/store/modelSlice';
import { reduce } from 'lodash-es';
import { reduce, size } from 'lodash-es';
import { ModelsService } from 'services/api';
const models = log.child({ namespace: 'model' });
export const IMAGES_PER_PAGE = 20;
export const receivedModels = createAppAsyncThunk(
'models/receivedModels',
async (_arg) => {
async (_) => {
const response = await ModelsService.listModels();
const deserializedModels = reduce(
response.models,
(modelsAccumulator, model, modelName) => {
@ -19,6 +23,8 @@ export const receivedModels = createAppAsyncThunk(
{} as Record<string, Model>
);
models.info({ response }, `Received ${size(response.models)} models`);
return deserializedModels;
}
);

View File

@ -1,16 +1,19 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { log } from 'app/logging/useLogger';
import { parsedOpenAPISchema } from 'features/nodes/store/nodesSlice';
import { OpenAPIV3 } from 'openapi-types';
const schemaLog = log.child({ namespace: 'schema' });
export const receivedOpenAPISchema = createAsyncThunk(
'nodes/receivedOpenAPISchema',
async (_, { dispatch }): Promise<OpenAPIV3.Document> => {
const response = await fetch(`openapi.json`);
const openAPISchema = (await response.json()) as OpenAPIV3.Document;
const openAPISchema = await response.json();
console.debug('OpenAPI schema: ', openAPISchema);
schemaLog.info({ openAPISchema }, 'Received OpenAPI schema');
dispatch(parsedOpenAPISchema(openAPISchema));
dispatch(parsedOpenAPISchema(openAPISchema as OpenAPIV3.Document));
return openAPISchema;
}

View File

@ -3,6 +3,9 @@ import { SessionsService } from 'services/api';
import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/linearGraphBuilder/buildLinearGraph';
import { isAnyOf, isFulfilled } from '@reduxjs/toolkit';
import { buildNodesGraph } from 'features/nodes/util/nodesGraphBuilder/buildNodesGraph';
import { log } from 'app/logging/useLogger';
const sessionLog = log.child({ namespace: 'session' });
export const generateGraphBuilt = createAppAsyncThunk(
'api/generateGraphBuilt',
@ -43,12 +46,12 @@ type SessionCreatedArg = {
export const sessionCreated = createAppAsyncThunk(
'api/sessionCreated',
async (arg: SessionCreatedArg, { dispatch, getState }) => {
console.log('Session created, graph: ', arg.graph);
const response = await SessionsService.createSession({
requestBody: arg.graph,
});
sessionLog.info({ arg, response }, `Session created (${response.id})`);
return response;
}
);
@ -74,6 +77,8 @@ export const nodeAdded = createAppAsyncThunk(
sessionId: arg.sessionId,
});
sessionLog.info({ arg, response }, `Node added (${response})`);
return response;
}
);
@ -91,6 +96,8 @@ export const sessionInvoked = createAppAsyncThunk(
all: true,
});
sessionLog.info({ arg, response }, `Session invoked (${sessionId})`);
return response;
}
);
@ -111,6 +118,8 @@ export const sessionCanceled = createAppAsyncThunk(
sessionId,
});
sessionLog.info({ arg, response }, `Session canceled (${sessionId})`);
return response;
}
);
@ -127,6 +136,11 @@ export const listedSessions = createAppAsyncThunk(
async (arg: SessionsListedArg, _thunkApi) => {
const response = await SessionsService.listSessions(arg);
sessionLog.info(
{ arg, response },
`Sessions listed (${response.items.length})`
);
return response;
}
);

View File

@ -1421,6 +1421,15 @@
redux-thunk "^2.4.2"
reselect "^4.1.8"
"@roarr/browser-log-writer@^1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@roarr/browser-log-writer/-/browser-log-writer-1.1.5.tgz#755ff62ddaa297bb3488067408a7085db382352b"
integrity sha512-yLn//DRjh1/rUgZpZkwmT/5RqHYfkdOwGXWXnKBR3l/HE04DIhSVeYin3sc8aWHBa7s7WglQpYX/uw/WI6POpw==
dependencies:
boolean "^3.1.4"
globalthis "^1.0.2"
liqe "^3.6.0"
"@rollup/pluginutils@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
@ -2077,7 +2086,7 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv@^6.10.0, ajv@^6.12.4, ajv@~6.12.6:
ajv@^6.10.0, ajv@^6.11.0, ajv@^6.12.4, ajv@~6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -2307,6 +2316,11 @@ bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
boolean@^3.1.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b"
integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==
boxen@^5.0.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50"
@ -2609,7 +2623,7 @@ commander@^10.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
commander@^2.16.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1:
commander@^2.16.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -2828,6 +2842,11 @@ deepmerge@^2.1.1:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
deepmerge@^4.2.2:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
defaults@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a"
@ -3031,6 +3050,11 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
discontinuous-range@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==
doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@ -3442,11 +3466,28 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-json-stringify@^2.7.10:
version "2.7.13"
resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz#277aa86c2acba4d9851bd6108ed657aa327ed8c0"
integrity sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==
dependencies:
ajv "^6.11.0"
deepmerge "^4.2.2"
rfdc "^1.2.0"
string-similarity "^4.0.1"
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-printf@^1.6.9:
version "1.6.9"
resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676"
integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==
dependencies:
boolean "^3.1.4"
fastq@^1.6.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"
@ -3768,7 +3809,7 @@ globals@^13.19.0:
dependencies:
type-fest "^0.20.2"
globalthis@^1.0.3:
globalthis@^1.0.2, globalthis@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
@ -4465,6 +4506,14 @@ lint-staged@^13.2.2:
string-argv "^0.3.1"
yaml "^2.2.2"
liqe@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/liqe/-/liqe-3.6.0.tgz#2d05376e93ff9f4bfdb3e76481f6456d165b499f"
integrity sha512-CYVQr0bk5CCTkX3wW2MdyEWdr9FHLpiE/1cQXQ36Sdjn5gv7JIpm9jnkovFwiVzumw7f6JDFXpljwUY+fAcFYQ==
dependencies:
nearley "^2.20.1"
ts-error "^1.0.6"
listr2@^5.0.7:
version "5.0.8"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-5.0.8.tgz#a9379ffeb4bd83a68931a65fb223a11510d6ba23"
@ -4714,6 +4763,11 @@ module-lookup-amd@^7.0.1:
requirejs "^2.3.5"
requirejs-config-file "^4.0.0"
moo@^0.5.0:
version "0.5.2"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@ -4734,6 +4788,16 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
nearley@^2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474"
integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==
dependencies:
commander "^2.19.0"
moo "^0.5.0"
railroad-diagrams "^1.0.0"
randexp "0.4.6"
neo-async@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
@ -5215,6 +5279,19 @@ quote-unquote@^1.0.0:
resolved "https://registry.yarnpkg.com/quote-unquote/-/quote-unquote-1.0.0.tgz#67a9a77148effeaf81a4d428404a710baaac8a0b"
integrity sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
integrity sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==
randexp@0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
dependencies:
discontinuous-range "1.0.0"
ret "~0.1.10"
rc@1.2.8, rc@^1.2.7, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@ -5554,12 +5631,17 @@ restore-cursor@^3.1.0:
onetime "^5.1.0"
signal-exit "^3.0.2"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfdc@^1.3.0:
rfdc@^1.2.0, rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
@ -5578,6 +5660,18 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
roarr@^7.15.0:
version "7.15.0"
resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.15.0.tgz#09b792f0cd31b4a7f91030bb1c47550ceec98ee4"
integrity sha512-CV9WefQfUXTX6wr8CrEMhfNef3sjIt9wNhE/5PNu4tNWsaoDNDXqq+OGn/RW9A1UPb0qc7FQlswXRaJJJsqn8A==
dependencies:
boolean "^3.1.4"
fast-json-stringify "^2.7.10"
fast-printf "^1.6.9"
globalthis "^1.0.2"
safe-stable-stringify "^2.4.1"
semver-compare "^1.0.0"
rollup-plugin-visualizer@^5.9.0:
version "5.9.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b"
@ -5630,6 +5724,11 @@ safe-regex-test@^1.0.0:
get-intrinsic "^1.1.3"
is-regex "^1.1.4"
safe-stable-stringify@^2.4.1:
version "2.4.3"
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886"
integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==
sass-lookup@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-3.0.0.tgz#3b395fa40569738ce857bc258e04df2617c48cac"
@ -5644,6 +5743,11 @@ scheduler@^0.23.0:
dependencies:
loose-envify "^1.1.0"
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
semver-diff@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b"
@ -5810,6 +5914,11 @@ string-argv@^0.3.1, string-argv@~0.3.1:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-similarity@^4.0.1:
version "4.0.4"
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
@ -6032,6 +6141,11 @@ tree-kill@^1.2.2:
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
ts-error@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/ts-error/-/ts-error-1.0.6.tgz#277496f2a28de6c184cfce8dfd5cdd03a4e6b0fc"
integrity sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==
ts-graphviz@^1.5.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/ts-graphviz/-/ts-graphviz-1.6.1.tgz#f44525c048cb8c8c188b7324d2a91015fd31ceaf"