feat(ui): clean up logging namespaces, allow skipping namespaces

This commit is contained in:
psychedelicious 2024-08-20 16:11:49 +10:00
parent e83513882a
commit 4da4b3bd50
42 changed files with 337 additions and 198 deletions

View File

@ -1106,7 +1106,6 @@
"confirmOnDelete": "Confirm On Delete",
"developer": "Developer",
"displayInProgress": "Display Progress Images",
"enableImageDebugging": "Enable Image Debugging",
"enableInformationalPopovers": "Enable Informational Popovers",
"informationalPopoversDisabled": "Informational Popovers Disabled",
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
@ -1817,5 +1816,30 @@
"upscaling": "Upscaling",
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
}
},
"system": {
"enableLogging": "Enable Logging",
"logLevel": {
"logLevel": "Log Level",
"trace": "Trace",
"debug": "Debug",
"info": "Info",
"warn": "Warn",
"error": "Error",
"fatal": "Fatal"
},
"logNamespaces": {
"logNamespaces": "Log Namespaces",
"gallery": "Gallery",
"models": "Models",
"config": "Config",
"canvas": "Canvas",
"generation": "Generation",
"workflows": "Workflows",
"system": "System",
"events": "Events",
"queue": "Queue",
"metadata": "Metadata"
}
}
}

View File

@ -15,24 +15,23 @@ export const BASE_CONTEXT = {};
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
export type LoggerNamespace =
| 'images'
| 'models'
| 'config'
| 'canvas'
| 'generation'
| 'nodes'
| 'system'
| 'socketio'
| 'session'
| 'queue'
| 'dnd'
| 'controlLayers'
| 'metadata'
| 'konva'
| 'worker';
export const zLogNamespace = z.enum([
'gallery',
'models',
'config',
'canvas',
'generation',
'workflows',
'system',
'events',
'queue',
'metadata',
]);
const zLogNamespacesArray = z.array(zLogNamespace);
export type LogNamespace = z.infer<typeof zLogNamespace>;
export const isLogNamespaceArray = (v: unknown): v is LogNamespace[] => zLogNamespacesArray.safeParse(v).success;
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });
export const logger = (namespace: LogNamespace) => $logger.get().child({ namespace });
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
export type LogLevel = z.infer<typeof zLogLevel>;

View File

@ -3,27 +3,35 @@ import { useAppSelector } from 'app/store/storeHooks';
import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr';
import type { LoggerNamespace } from './logger';
import type { LogNamespace } from './logger';
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
export const useLogger = (namespace: LoggerNamespace) => {
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
export const useLogger = (namespace: LogNamespace) => {
const logLevel = useAppSelector((s) => s.system.logLevel);
const logNamespaces = useAppSelector((s) => s.system.logNamespaces);
const logIsEnabled = useAppSelector((s) => s.system.logIsEnabled);
// The provided Roarr browser log writer uses localStorage to config logging to console
useEffect(() => {
if (shouldLogToConsole) {
if (logIsEnabled) {
// 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]}`);
let filter = `context.logLevel:>=${LOG_LEVEL_MAP[logLevel]}`;
if (logNamespaces.length > 0) {
filter += ' AND ';
filter = `(${logNamespaces.map((ns) => `context.namespace:${ns}`).join(' OR ')})`;
} else {
filter += ' AND context.namespace:undefined';
}
localStorage.setItem('ROARR_FILTER', filter);
} else {
// Disable console log output
localStorage.setItem('ROARR_LOG', 'false');
}
ROARR.write = createLogWriter();
}, [consoleLogLevel, shouldLogToConsole]);
}, [logLevel, logIsEnabled, logNamespaces]);
// Update the module-scoped logger context as needed
useEffect(() => {

View File

@ -8,14 +8,14 @@ import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig, ImageDTO } from 'services/api/types';
const log = logger('queue');
export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`);
export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: adHocPostProcessingRequested,
effect: async (action, { dispatch, getState }) => {
const log = logger('session');
const { imageDTO } = action.payload;
const state = getState();

View File

@ -13,12 +13,12 @@ import { queueApi } from 'services/api/endpoints/queue';
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
import { assert } from 'tsafe';
const log = logger('canvas');
export const addStagingListeners = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: sessionStagingAreaReset,
effect: async (_, { dispatch }) => {
const log = logger('canvas');
try {
const req = dispatch(
queueApi.endpoints.cancelByBatchOrigin.initiate(

View File

@ -7,6 +7,8 @@ import { t } from 'i18next';
import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue';
const log = logger('queue');
export const addBatchEnqueuedListener = (startAppListening: AppStartListening) => {
// success
startAppListening({
@ -14,7 +16,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
effect: async (action) => {
const response = action.payload;
const arg = action.meta.arg.originalArgs;
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
log.debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
toast({
id: 'QUEUE_BATCH_SUCCEEDED',
@ -42,7 +44,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
status: 'error',
description: t('common.unknownError'),
});
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
log.error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
return;
}
@ -68,7 +70,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
description: t('common.unknownError'),
});
}
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
log.error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
},
});
};

View File

@ -4,7 +4,7 @@ import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
const log = logger('images');
const log = logger('gallery');
export const addBulkDownloadListeners = (startAppListening: AppStartListening) => {
startAppListening({

View File

@ -7,11 +7,12 @@ import { toast } from 'features/toast/toast';
import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
const log = logger('canvas');
export const addCanvasImageToControlNetListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: canvasImageToControlAdapter,
effect: async (action, { dispatch, getState }) => {
const log = logger('canvas');
const state = getState();
const { id } = action.payload;

View File

@ -25,7 +25,7 @@ import { assert } from 'tsafe';
const matcher = isAnyOf(caImageChanged, caProcessedImageChanged, caProcessorConfigChanged, caModelChanged, caRecalled);
const DEBOUNCE_MS = 300;
const log = logger('session');
const log = logger('queue');
/**
* Simple helper to cancel a batch and reset the pending batch ID

View File

@ -6,7 +6,7 @@ import { parseify } from 'common/util/serialize';
import { canvasBatchIdAdded, stagingAreaInitialized } from 'features/canvas/store/canvasSlice';
import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
import { blobToDataURL } from "features/controlLayers/konva/util";
import { blobToDataURL } from 'features/controlLayers/konva/util';
import { canvasGraphBuilt } from 'features/nodes/store/actions';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { buildCanvasGraph } from 'features/nodes/util/graph/canvas/buildCanvasGraph';
@ -14,6 +14,8 @@ import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue';
import type { ImageDTO } from 'services/api/types';
const log = logger('queue');
/**
* This listener is responsible invoking the canvas. This involves a number of steps:
*
@ -32,7 +34,6 @@ export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartLis
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
enqueueRequested.match(action) && action.payload.tabName === 'canvas',
effect: async (action, { getState, dispatch }) => {
const log = logger('queue');
const { prepend } = action.payload;
const state = getState();

View File

@ -6,11 +6,12 @@ import { parseSchema } from 'features/nodes/util/schema/parseSchema';
import { size } from 'lodash-es';
import { appInfoApi } from 'services/api/endpoints/appInfo';
const log = logger('system');
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
startAppListening({
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
effect: (action, { getState }) => {
const log = logger('system');
const schemaJSON = action.payload;
log.debug({ schemaJSON: parseify(schemaJSON) }, 'Received OpenAPI schema');
@ -30,7 +31,6 @@ export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening
// If action.meta.condition === true, the request was canceled/skipped because another request was in flight or
// the value was already in the cache. We don't want to log these errors.
if (!action.meta.condition) {
const log = logger('system');
log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema');
}
},

View File

@ -2,15 +2,13 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { imagesApi } from 'services/api/endpoints/images';
const log = logger('gallery');
export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStartListening) => {
startAppListening({
matcher: imagesApi.endpoints.addImageToBoard.matchFulfilled,
effect: (action) => {
const log = logger('images');
const { board_id, imageDTO } = action.meta.arg.originalArgs;
// TODO: update listImages cache for this board
log.debug({ board_id, imageDTO }, 'Image added to board');
},
});
@ -18,9 +16,7 @@ export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStar
startAppListening({
matcher: imagesApi.endpoints.addImageToBoard.matchRejected,
effect: (action) => {
const log = logger('images');
const { board_id, imageDTO } = action.meta.arg.originalArgs;
log.debug({ board_id, imageDTO }, 'Problem adding image to board');
},
});

View File

@ -13,6 +13,8 @@ import { forEach, intersectionBy } from 'lodash-es';
import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
const log = logger('gallery');
// Some utils to delete images from different parts of the app
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
state.nodes.present.nodes.forEach((node) => {
@ -185,7 +187,6 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
startAppListening({
matcher: imagesApi.endpoints.deleteImage.matchFulfilled,
effect: (action) => {
const log = logger('images');
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Image deleted');
},
});
@ -193,7 +194,6 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
startAppListening({
matcher: imagesApi.endpoints.deleteImage.matchRejected,
effect: (action) => {
const log = logger('images');
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
},
});

View File

@ -2,11 +2,7 @@ import { createAction } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { parseify } from 'common/util/serialize';
import {
ipaImageChanged,
rasterLayerAdded,
rgIPAdapterImageChanged,
} from 'features/controlLayers/store/canvasV2Slice';
import { ipaImageChanged, rasterLayerAdded, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasV2Slice';
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@ -26,11 +22,12 @@ export const dndDropped = createAction<{
activeData: TypesafeDraggableData;
}>('dnd/dndDropped');
const log = logger('system');
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: dndDropped,
effect: (action, { dispatch, getState }) => {
const log = logger('dnd');
const { activeData, overData } = action.payload;
if (!isValidDrop(overData, activeData)) {
return;

View File

@ -2,13 +2,13 @@ import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { imagesApi } from 'services/api/endpoints/images';
const log = logger('gallery');
export const addImageRemovedFromBoardFulfilledListener = (startAppListening: AppStartListening) => {
startAppListening({
matcher: imagesApi.endpoints.removeImageFromBoard.matchFulfilled,
effect: (action) => {
const log = logger('images');
const imageDTO = action.meta.arg.originalArgs;
log.debug({ imageDTO }, 'Image removed from board');
},
});
@ -16,9 +16,7 @@ export const addImageRemovedFromBoardFulfilledListener = (startAppListening: App
startAppListening({
matcher: imagesApi.endpoints.removeImageFromBoard.matchRejected,
effect: (action) => {
const log = logger('images');
const imageDTO = action.meta.arg.originalArgs;
log.debug({ imageDTO }, 'Problem removing image from board');
},
});

View File

@ -10,11 +10,12 @@ import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards';
import { imagesApi } from 'services/api/endpoints/images';
const log = logger('gallery');
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
effect: (action, { dispatch, getState }) => {
const log = logger('images');
const imageDTO = action.payload;
const state = getState();
const { autoAddBoardId } = state.gallery;
@ -112,7 +113,6 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchRejected,
effect: (action) => {
const log = logger('images');
const sanitizedData = {
arg: {
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),

View File

@ -1,21 +1,17 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import {
loraDeleted,
modelChanged,
vaeSelected,
} from 'features/controlLayers/store/canvasV2Slice';
import { loraDeleted, modelChanged, vaeSelected } from 'features/controlLayers/store/canvasV2Slice';
import { modelSelected } from 'features/parameters/store/actions';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
const log = logger('models');
export const addModelSelectedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: modelSelected,
effect: (action, { getState, dispatch }) => {
const log = logger('models');
const state = getState();
const result = zParameterModel.safeParse(action.payload);

View File

@ -30,12 +30,13 @@ import {
isVAEModelConfig,
} from 'services/api/types';
const log = logger('models');
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
startAppListening({
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
effect: (action, { getState, dispatch }) => {
// models loaded, we need to ensure the selected model is available and if not, select the first one
const log = logger('models');
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
const state = getState();

View File

@ -8,7 +8,7 @@ import { modelsApi } from 'services/api/endpoints/models';
import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
import { socketConnected } from 'services/events/setEventListeners';
const log = logger('socketio');
const log = logger('events');
const $isFirstConnection = atom(true);

View File

@ -8,11 +8,12 @@ import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate'
import { toast } from 'features/toast/toast';
import { t } from 'i18next';
const log = logger('workflows');
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: updateAllNodesRequested,
effect: (action, { dispatch, getState }) => {
const log = logger('nodes');
const { nodes } = getState().nodes.present;
const templates = $templates.get();

View File

@ -16,6 +16,8 @@ import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/ty
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';
const log = logger('workflows');
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
if (data.workflow) {
// Prefer to load the workflow if it's available - it has more information
@ -35,7 +37,6 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
startAppListening({
actionCreator: workflowLoadRequested,
effect: async (action, { dispatch }) => {
const log = logger('nodes');
const { data, asCopy } = action.payload;
const nodeTemplates = $templates.get();

View File

@ -36,6 +36,8 @@ import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware';
const log = logger('system');
const allReducers = {
[api.reducerPath]: api.reducer,
[gallerySlice.name]: gallerySlice.reducer,
@ -98,7 +100,6 @@ const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
};
const unserialize: UnserializeFunction = (data, key) => {
const log = logger('system');
const persistConfig = persistConfigs[key as keyof typeof persistConfigs];
if (!persistConfig) {
throw new Error(`No persist config for slice "${key}"`);

View File

@ -54,7 +54,6 @@ export class CanvasManager {
background: CanvasBackground;
log: Logger;
workerLog: Logger;
socket: AppSocket;
_store: AppStore;
@ -85,7 +84,6 @@ export class CanvasManager {
},
};
});
this.workerLog = logger('worker');
this.preview = new CanvasPreview(this);
this.stage.add(this.preview.getLayer());
@ -97,9 +95,9 @@ export class CanvasManager {
const { type, data } = event.data;
if (type === 'log') {
if (data.ctx) {
this.workerLog[data.level](data.ctx, data.message);
this.log[data.level](data.ctx, data.message);
} else {
this.workerLog[data.level](data.message);
this.log[data.level](data.message);
}
} else if (type === 'extents') {
const task = this._tasks.get(data.id);

View File

@ -1,7 +1,7 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import {
alignCoordForTool,
getObjectId,
getPrefixedId,
getScaledCursorPosition,
offsetCoord,
} from 'features/controlLayers/konva/util';
@ -216,7 +216,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
}
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('brush_line', true),
id: getPrefixedId('brush_line'),
type: 'brush_line',
points: [
// The last point of the last line is already normalized to the entity's coordinates
@ -234,7 +234,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
selectedEntity.adapter.renderer.commitBuffer();
}
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('brush_line', true),
id: getPrefixedId('brush_line'),
type: 'brush_line',
points: [alignedPoint.x, alignedPoint.y],
strokeWidth: toolState.brush.width,
@ -254,7 +254,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
selectedEntity.adapter.renderer.commitBuffer();
}
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('eraser_line', true),
id: getPrefixedId('eraser_line'),
type: 'eraser_line',
points: [
// The last point of the last line is already normalized to the entity's coordinates
@ -271,7 +271,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
selectedEntity.adapter.renderer.commitBuffer();
}
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('eraser_line', true),
id: getPrefixedId('eraser_line'),
type: 'eraser_line',
points: [alignedPoint.x, alignedPoint.y],
strokeWidth: toolState.eraser.width,
@ -286,7 +286,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
selectedEntity.adapter.renderer.commitBuffer();
}
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('rect', true),
id: getPrefixedId('rect'),
type: 'rect',
rect: { x: Math.round(normalizedPoint.x), y: Math.round(normalizedPoint.y), width: 0, height: 0 },
color: getCurrentFill(),
@ -375,7 +375,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
const normalizedPoint = offsetCoord(pos, selectedEntity.state.position);
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.brush.width);
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('brush_line', true),
id: getPrefixedId('brush_line'),
type: 'brush_line',
points: [alignedPoint.x, alignedPoint.y],
strokeWidth: toolState.brush.width,
@ -412,7 +412,7 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
const normalizedPoint = offsetCoord(pos, selectedEntity.state.position);
const alignedPoint = alignCoordForTool(normalizedPoint, toolState.eraser.width);
await selectedEntity.adapter.renderer.setBuffer({
id: getObjectId('eraser_line', true),
id: getPrefixedId('eraser_line'),
type: 'eraser_line',
points: [alignedPoint.x, alignedPoint.y],
strokeWidth: toolState.eraser.width,

View File

@ -1,4 +1,4 @@
import type { CanvasObjectState, Coordinate, Rect, RgbaColor } from 'features/controlLayers/store/types';
import type { Coordinate, Rect, RgbaColor } from 'features/controlLayers/store/types';
import Konva from 'konva';
import type { KonvaEventObject } from 'konva/lib/Node';
import type { Vector2d } from 'konva/lib/types';
@ -363,14 +363,6 @@ export function getPrefixedId(prefix: string): string {
return `${prefix}:${nanoid()}`;
}
export function getObjectId(type: CanvasObjectState['type'], isBuffer?: boolean): string {
if (isBuffer) {
return getPrefixedId(`buffer_${type}`);
} else {
return getPrefixedId(type);
}
}
export const getEmptyRect = (): Rect => {
return { x: 0, y: 0, width: 0, height: 0 };
};

View File

@ -1,7 +1,7 @@
import type { CanvasControlAdapter } from 'features/controlLayers/konva/CanvasControlAdapter';
import { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import { getObjectId } from 'features/controlLayers/konva/util';
import { getPrefixedId } from 'features/controlLayers/konva/util';
import { zModelIdentifierField } from 'features/nodes/types/common';
import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
import type {
@ -803,7 +803,7 @@ export const imageDTOToImageWithDims = ({ image_name, width, height }: ImageDTO)
export const imageDTOToImageObject = (imageDTO: ImageDTO, overrides?: Partial<CanvasImageState>): CanvasImageState => {
const { width, height, image_name } = imageDTO;
return {
id: getObjectId('image'),
id: getPrefixedId('image'),
type: 'image',
image: {
image_name,

View File

@ -11,23 +11,21 @@ import { memo, useCallback, useState } from 'react';
import { DndContextTypesafe } from './DndContextTypesafe';
const log = logger('system');
const AppDndContext = (props: PropsWithChildren) => {
const [activeDragData, setActiveDragData] = useState<TypesafeDraggableData | null>(null);
const log = logger('images');
const dispatch = useAppDispatch();
const handleDragStart = useCallback(
(event: DragStartEvent) => {
log.trace({ dragData: parseify(event.active.data.current) }, 'Drag started');
const activeData = event.active.data.current;
if (!activeData) {
return;
}
setActiveDragData(activeData);
},
[log]
);
const handleDragStart = useCallback((event: DragStartEvent) => {
log.trace({ dragData: parseify(event.active.data.current) }, 'Drag started');
const activeData = event.active.data.current;
if (!activeData) {
return;
}
setActiveDragData(activeData);
}, []);
const handleDragEnd = useCallback(
(event: DragEndEvent) => {
@ -39,7 +37,7 @@ const AppDndContext = (props: PropsWithChildren) => {
dispatch(dndDropped({ overData, activeData: activeDragData }));
setActiveDragData(null);
},
[activeDragData, dispatch, log]
[activeDragData, dispatch]
);
const mouseSensor = useSensor(MouseSensor, {

View File

@ -15,14 +15,14 @@ import {
bboxWidthChanged,
// caRecalled,
ipaRecalled,
rasterLayerAllDeleted,
rasterLayerRecalled,
loraAllDeleted,
loraRecalled,
negativePrompt2Changed,
negativePromptChanged,
positivePrompt2Changed,
positivePromptChanged,
rasterLayerAllDeleted,
rasterLayerRecalled,
refinerModelChanged,
rgRecalled,
setCfgRescaleMultiplier,
@ -80,6 +80,8 @@ import type {
import { getImageDTO } from 'services/api/endpoints/images';
import { v4 as uuidv4 } from 'uuid';
const log = logger('metadata');
const recallPositivePrompt: MetadataRecallFunc<ParameterPositivePrompt> = (positivePrompt) => {
getStore().dispatch(positivePromptChanged(positivePrompt));
};
@ -351,7 +353,7 @@ const recallLayer: MetadataRecallFunc<CanvasRasterLayerState> = async (layer) =>
} else if (obj.type === 'rect') {
obj.id = getRectShapeId(clone.id, uuidv4());
} else {
logger('metadata').error(`Unknown object type ${obj.type}`);
log.error(`Unknown object type ${obj.type}`);
}
}
clone.id = getRGId(uuidv4());

View File

@ -34,6 +34,7 @@ import { isNonRefinerMainModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import { addRegions } from './addRegions';
const log = logger('system');
export const buildSD1Graph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {

View File

@ -33,6 +33,7 @@ import { isNonRefinerMainModelConfig } from 'services/api/types';
import { assert } from 'tsafe';
import { addRegions } from './addRegions';
const log = logger('system');
export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): Promise<Graph> => {

View File

@ -25,6 +25,8 @@ import { buildFieldInputTemplate } from './buildFieldInputTemplate';
import { buildFieldOutputTemplate } from './buildFieldOutputTemplate';
import { isCollectionFieldType, parseFieldType } from './parseFieldType';
const log = logger('system');
const RESERVED_INPUT_FIELD_NAMES = ['id', 'type', 'use_cache'];
const RESERVED_OUTPUT_FIELD_NAMES = ['type'];
const RESERVED_FIELD_TYPES = ['IsIntermediate'];
@ -85,18 +87,12 @@ export const parseSchema = (
schema.properties,
(inputsAccumulator: Record<string, FieldInputTemplate>, property, propertyName) => {
if (isReservedInputField(type, propertyName)) {
logger('nodes').trace(
{ node: type, field: propertyName, schema: parseify(property) },
'Skipped reserved input field'
);
log.trace({ node: type, field: propertyName, schema: parseify(property) }, 'Skipped reserved input field');
return inputsAccumulator;
}
if (!isInvocationFieldSchema(property)) {
logger('nodes').warn(
{ node: type, field: propertyName, schema: parseify(property) },
'Unhandled input property'
);
log.warn({ node: type, field: propertyName, schema: parseify(property) }, 'Unhandled input property');
return inputsAccumulator;
}
@ -111,18 +107,12 @@ export const parseSchema = (
const fieldType = fieldTypeOverride ?? originalFieldType;
if (!fieldType) {
logger('nodes').trace(
{ node: type, field: propertyName, schema: parseify(property) },
'Unable to parse field type'
);
log.trace({ node: type, field: propertyName, schema: parseify(property) }, 'Unable to parse field type');
return inputsAccumulator;
}
if (isReservedFieldType(fieldType.name)) {
logger('nodes').trace(
{ node: type, field: propertyName, schema: parseify(property) },
'Skipped reserved input field'
);
log.trace({ node: type, field: propertyName, schema: parseify(property) }, 'Skipped reserved input field');
return inputsAccumulator;
}
@ -141,18 +131,18 @@ export const parseSchema = (
const outputSchemaName = schema.output.$ref.split('/').pop();
if (!outputSchemaName) {
logger('nodes').warn({ outputRefObject: parseify(schema.output) }, 'No output schema name found in ref object');
log.warn({ outputRefObject: parseify(schema.output) }, 'No output schema name found in ref object');
return invocationsAccumulator;
}
const outputSchema = openAPI.components?.schemas?.[outputSchemaName];
if (!outputSchema) {
logger('nodes').warn({ outputSchemaName }, 'Output schema not found');
log.warn({ outputSchemaName }, 'Output schema not found');
return invocationsAccumulator;
}
if (!isInvocationOutputSchemaObject(outputSchema)) {
logger('nodes').error({ outputSchema: parseify(outputSchema) }, 'Invalid output schema');
log.error({ outputSchema: parseify(outputSchema) }, 'Invalid output schema');
return invocationsAccumulator;
}
@ -162,18 +152,12 @@ export const parseSchema = (
outputSchema.properties,
(outputsAccumulator, property, propertyName) => {
if (!isAllowedOutputField(type, propertyName)) {
logger('nodes').trace(
{ node: type, field: propertyName, schema: parseify(property) },
'Skipped reserved output field'
);
log.trace({ node: type, field: propertyName, schema: parseify(property) }, 'Skipped reserved output field');
return outputsAccumulator;
}
if (!isInvocationFieldSchema(property)) {
logger('nodes').warn(
{ node: type, field: propertyName, schema: parseify(property) },
'Unhandled output property'
);
log.warn({ node: type, field: propertyName, schema: parseify(property) }, 'Unhandled output property');
return outputsAccumulator;
}
@ -188,10 +172,7 @@ export const parseSchema = (
const fieldType = fieldTypeOverride ?? originalFieldType;
if (!fieldType) {
logger('nodes').trace(
{ node: type, field: propertyName, schema: parseify(property) },
'Unable to parse field type'
);
log.trace({ node: type, field: propertyName, schema: parseify(property) }, 'Unable to parse field type');
return outputsAccumulator;
}
@ -242,7 +223,7 @@ const getFieldType = (
} catch (e) {
const tKey = kind === 'input' ? 'nodes.inputFieldTypeParseError' : 'nodes.outputFieldTypeParseError';
if (e instanceof FieldParseError) {
logger('nodes').warn(
log.warn(
{
node: type,
field: propertyName,
@ -255,7 +236,7 @@ const getFieldType = (
})
);
} else {
logger('nodes').warn(
log.warn(
{
node: type,
field: propertyName,

View File

@ -9,6 +9,8 @@ import i18n from 'i18n';
import { pick } from 'lodash-es';
import { fromZodError } from 'zod-validation-error';
const log = logger('workflows');
export type BuildWorkflowArg = {
nodes: NodesState['nodes'];
edges: NodesState['edges'];
@ -93,7 +95,7 @@ export const buildWorkflowWithValidation = ({ nodes, edges, workflow }: BuildWor
prefix: i18n.t('nodes.unableToValidateWorkflow'),
});
logger('nodes').warn({ workflow: parseify(workflowToValidate) }, message);
log.warn({ workflow: parseify(workflowToValidate) }, message);
return null;
}

View File

@ -9,6 +9,8 @@ import { forEach } from 'lodash-es';
import type { NonNullableGraph } from 'services/api/types';
import { v4 as uuidv4 } from 'uuid';
const log = logger('workflows');
/**
* Converts a graph to a workflow. This is a best-effort conversion and may not be perfect.
* For example, if a graph references an unknown node type, that node will be skipped.
@ -43,7 +45,7 @@ export const graphToWorkflow = (graph: NonNullableGraph, autoLayout = true): Wor
// Skip missing node templates - this is a best-effort
if (!template) {
logger('nodes').warn(`Node type ${node.type} not found in templates`);
log.warn(`Node type ${node.type} not found in templates`);
return;
}
@ -60,7 +62,7 @@ export const graphToWorkflow = (graph: NonNullableGraph, autoLayout = true): Wor
// Skip missing input templates
if (!inputTemplate) {
logger('nodes').warn(`Input ${key} not found in template for node type ${node.type}`);
log.warn(`Input ${key} not found in template for node type ${node.type}`);
return;
}

View File

@ -0,0 +1,17 @@
import { Flex } from '@invoke-ai/ui-library';
import { SettingsDeveloperLogIsEnabled } from 'features/system/components/SettingsModal/SettingsDeveloperLogIsEnabled';
import { SettingsDeveloperLogLevel } from 'features/system/components/SettingsModal/SettingsDeveloperLogLevel';
import { SettingsDeveloperLogNamespaces } from 'features/system/components/SettingsModal/SettingsDeveloperLogNamespaces';
import { memo } from 'react';
export const SettingsDeveloperContent = memo(() => {
return (
<Flex flexDir="column" gap={4}>
<SettingsDeveloperLogIsEnabled />
<SettingsDeveloperLogLevel />
<SettingsDeveloperLogNamespaces />
</Flex>
);
});
SettingsDeveloperContent.displayName = 'SettingsDeveloperContent';

View File

@ -0,0 +1,29 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { logIsEnabledChanged } from 'features/system/store/systemSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
export const SettingsDeveloperLogIsEnabled = memo(() => {
const { t } = useTranslation();
const dispatch = useDispatch();
const logIsEnabled = useAppSelector((s) => s.system.logIsEnabled);
const onChangeLogIsEnabled = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(logIsEnabledChanged(e.target.checked));
},
[dispatch]
);
return (
<FormControl>
<FormLabel>{t('system.enableLogging')}</FormLabel>
<Switch isChecked={logIsEnabled} onChange={onChangeLogIsEnabled} />
</FormControl>
);
});
SettingsDeveloperLogIsEnabled.displayName = 'SettingsDeveloperLogIsEnabled';

View File

@ -0,0 +1,34 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { isLogLevel, zLogLevel } from 'app/logging/logger';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { logLevelChanged } from 'features/system/store/systemSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const SettingsDeveloperLogLevel = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const logLevel = useAppSelector((s) => s.system.logLevel);
const options = useMemo(() => zLogLevel.options.map((o) => ({ label: t(`system.logLevel.${o}`), value: o })), [t]);
const value = useMemo(() => options.find((o) => o.value === logLevel), [logLevel, options]);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isLogLevel(v?.value)) {
return;
}
dispatch(logLevelChanged(v.value));
},
[dispatch]
);
return (
<FormControl>
<FormLabel>{t('system.logLevel.logLevel')}</FormLabel>
<Combobox value={value} options={options} onChange={onChange} isSearchable={false} />
</FormControl>
);
});
SettingsDeveloperLogLevel.displayName = 'SettingsDeveloperLogLevel';

View File

@ -0,0 +1,70 @@
import { Flex, FormControl, FormLabel, Tag, TagCloseButton, Text } from '@invoke-ai/ui-library';
import type { LogNamespace } from 'app/logging/logger';
import { zLogNamespace } from 'app/logging/logger';
import { EMPTY_ARRAY } from 'app/store/constants';
import { useAppSelector } from 'app/store/storeHooks';
import { logNamespaceToggled } from 'features/system/store/systemSlice';
import { difference } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
export const SettingsDeveloperLogNamespaces = memo(() => {
const { t } = useTranslation();
const enabledLogNamespaces = useAppSelector((s) => {
if (s.system.logNamespaces.length === 0) {
return EMPTY_ARRAY;
} else {
return s.system.logNamespaces.toSorted();
}
});
const disabledLogNamespaces = useMemo(
() => difference(zLogNamespace.options, enabledLogNamespaces).toSorted(),
[enabledLogNamespaces]
);
return (
<FormControl orientation="vertical">
<FormLabel>{t('system.logNamespaces.logNamespaces')}</FormLabel>
<Flex w="full" gap={2} flexWrap="wrap" minH="32px" borderRadius="base" borderWidth={1} p={2} alignItems="center">
{enabledLogNamespaces.map((namespace) => (
<LogLevelTag key={`enabled-${namespace}`} namespace={namespace} isEnabled={true} />
))}
</Flex>
<Flex gap={2} flexWrap="wrap">
{disabledLogNamespaces.map((namespace) => (
<LogLevelTag key={`disabled-${namespace}`} namespace={namespace} isEnabled={false} />
))}
</Flex>
</FormControl>
);
});
SettingsDeveloperLogNamespaces.displayName = 'SettingsDeveloperLogNamespaces';
const LogLevelTag = ({ namespace, isEnabled }: { namespace: LogNamespace; isEnabled: boolean }) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const onClick = useCallback(() => {
dispatch(logNamespaceToggled(namespace));
}, [dispatch, namespace]);
return (
<Tag
h="min-content"
borderRadius="base"
onClick={onClick}
colorScheme={isEnabled ? 'invokeBlue' : 'base'}
userSelect="none"
role="button"
size="md"
color="base.900"
bg={isEnabled ? 'invokeBlue.300' : 'base.300'}
>
<Text fontSize="sm" fontWeight="semibold">
{t(`system.logNamespaces.${namespace}`)}
</Text>
{isEnabled && <TagCloseButton />}
</Tag>
);
};

View File

@ -2,30 +2,30 @@ import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { isLogLevel, zLogLevel } from 'app/logging/logger';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { consoleLogLevelChanged } from 'features/system/store/systemSlice';
import { logLevelChanged } from 'features/system/store/systemSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const SettingsLogLevelSelect = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
const logLevel = useAppSelector((s) => s.system.logLevel);
const logIsEnabled = useAppSelector((s) => s.system.logIsEnabled);
const options = useMemo(() => zLogLevel.options.map((o) => ({ label: o, value: o })), []);
const value = useMemo(() => options.find((o) => o.value === consoleLogLevel), [consoleLogLevel, options]);
const value = useMemo(() => options.find((o) => o.value === logLevel), [logLevel, options]);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isLogLevel(v?.value)) {
return;
}
dispatch(consoleLogLevelChanged(v.value));
dispatch(logLevelChanged(v.value));
},
[dispatch]
);
return (
<FormControl isDisabled={!shouldLogToConsole}>
<FormControl isDisabled={!logIsEnabled}>
<FormLabel>{t('common.loglevel')}</FormLabel>
<Combobox value={value} options={options} onChange={onChange} />
</FormControl>

View File

@ -20,14 +20,16 @@ import { InformationalPopover } from 'common/components/InformationalPopover/Inf
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { useClearStorage } from 'common/hooks/useClearStorage';
import { shouldUseCpuNoiseChanged } from 'features/controlLayers/store/canvasV2Slice';
import { SettingsDeveloperLogIsEnabled } from 'features/system/components/SettingsModal/SettingsDeveloperLogIsEnabled';
import { SettingsDeveloperLogLevel } from 'features/system/components/SettingsModal/SettingsDeveloperLogLevel';
import { SettingsDeveloperLogNamespaces } from 'features/system/components/SettingsModal/SettingsDeveloperLogNamespaces';
import { useClearIntermediates } from 'features/system/components/SettingsModal/useClearIntermediates';
import { StickyScrollable } from 'features/system/components/StickyScrollable';
import {
setEnableImageDebugging,
logIsEnabledChanged,
setShouldConfirmOnDelete,
setShouldEnableInformationalPopovers,
shouldAntialiasProgressImageChanged,
shouldLogToConsoleChanged,
shouldUseNSFWCheckerChanged,
shouldUseWatermarkerChanged,
} from 'features/system/store/systemSlice';
@ -38,7 +40,6 @@ import { useTranslation } from 'react-i18next';
import { useGetAppConfigQuery } from 'services/api/endpoints/appInfo';
import { SettingsLanguageSelect } from './SettingsLanguageSelect';
import { SettingsLogLevelSelect } from './SettingsLogLevelSelect';
type ConfigOptions = {
shouldShowDeveloperSettings?: boolean;
@ -65,7 +66,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
useEffect(() => {
if (!shouldShowDeveloperSettings) {
dispatch(shouldLogToConsoleChanged(false));
dispatch(logIsEnabledChanged(false));
}
}, [shouldShowDeveloperSettings, dispatch]);
@ -90,9 +91,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
const shouldUseCpuNoise = useAppSelector((s) => s.canvasV2.params.shouldUseCpuNoise);
const shouldConfirmOnDelete = useAppSelector((s) => s.system.shouldConfirmOnDelete);
const enableImageDebugging = useAppSelector((s) => s.system.enableImageDebugging);
const shouldShowProgressInViewer = useAppSelector((s) => s.ui.shouldShowProgressInViewer);
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
const shouldAntialiasProgressImage = useAppSelector((s) => s.system.shouldAntialiasProgressImage);
const shouldUseNSFWChecker = useAppSelector((s) => s.system.shouldUseNSFWChecker);
const shouldUseWatermarker = useAppSelector((s) => s.system.shouldUseWatermarker);
@ -120,13 +119,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
}
}, [countdown]);
const handleLogToConsoleChanged = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldLogToConsoleChanged(e.target.checked));
},
[dispatch]
);
const handleChangeShouldConfirmOnDelete = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(setShouldConfirmOnDelete(e.target.checked));
@ -163,12 +155,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
},
[dispatch]
);
const handleChangeEnableImageDebugging = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(setEnableImageDebugging(e.target.checked));
},
[dispatch]
);
const handleChangeShouldUseCpuNoise = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(shouldUseCpuNoiseChanged(e.target.checked));
@ -242,15 +228,9 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
{shouldShowDeveloperSettings && (
<StickyScrollable title={t('settings.developer')}>
<FormControl>
<FormLabel>{t('settings.shouldLogToConsole')}</FormLabel>
<Switch isChecked={shouldLogToConsole} onChange={handleLogToConsoleChanged} />
</FormControl>
<SettingsLogLevelSelect />
<FormControl>
<FormLabel>{t('settings.enableImageDebugging')}</FormLabel>
<Switch isChecked={enableImageDebugging} onChange={handleChangeEnableImageDebugging} />
</FormControl>
<SettingsDeveloperLogIsEnabled />
<SettingsDeveloperLogLevel />
<SettingsDeveloperLogNamespaces />
</StickyScrollable>
)}

View File

@ -1,21 +1,23 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import type { LogNamespace } from 'app/logging/logger';
import { zLogNamespace } from 'app/logging/logger';
import type { PersistConfig, RootState } from 'app/store/store';
import type { LogLevelName } from 'roarr';
import { uniq } from 'lodash-es';
import type { Language, SystemState } from './types';
const initialSystemState: SystemState = {
_version: 1,
shouldConfirmOnDelete: true,
enableImageDebugging: false,
shouldAntialiasProgressImage: false,
consoleLogLevel: 'debug',
shouldLogToConsole: true,
language: 'en',
shouldUseNSFWChecker: false,
shouldUseWatermarker: false,
shouldEnableInformationalPopovers: true,
logIsEnabled: true,
logLevel: 'debug',
logNamespaces: [...zLogNamespace.options],
};
export const systemSlice = createSlice({
@ -25,14 +27,18 @@ export const systemSlice = createSlice({
setShouldConfirmOnDelete: (state, action: PayloadAction<boolean>) => {
state.shouldConfirmOnDelete = action.payload;
},
setEnableImageDebugging: (state, action: PayloadAction<boolean>) => {
state.enableImageDebugging = action.payload;
logIsEnabledChanged: (state, action: PayloadAction<SystemState['logIsEnabled']>) => {
state.logIsEnabled = action.payload;
},
consoleLogLevelChanged: (state, action: PayloadAction<LogLevelName>) => {
state.consoleLogLevel = action.payload;
logLevelChanged: (state, action: PayloadAction<SystemState['logLevel']>) => {
state.logLevel = action.payload;
},
shouldLogToConsoleChanged: (state, action: PayloadAction<boolean>) => {
state.shouldLogToConsole = action.payload;
logNamespaceToggled: (state, action: PayloadAction<LogNamespace>) => {
if (state.logNamespaces.includes(action.payload)) {
state.logNamespaces = uniq(state.logNamespaces.filter((n) => n !== action.payload));
} else {
state.logNamespaces = uniq([...state.logNamespaces, action.payload]);
}
},
shouldAntialiasProgressImageChanged: (state, action: PayloadAction<boolean>) => {
state.shouldAntialiasProgressImage = action.payload;
@ -54,9 +60,9 @@ export const systemSlice = createSlice({
export const {
setShouldConfirmOnDelete,
setEnableImageDebugging,
consoleLogLevelChanged,
shouldLogToConsoleChanged,
logIsEnabledChanged,
logLevelChanged,
logNamespaceToggled,
shouldAntialiasProgressImageChanged,
languageChanged,
shouldUseNSFWCheckerChanged,

View File

@ -1,4 +1,4 @@
import type { LogLevel } from 'app/logging/logger';
import type { LogLevel, LogNamespace } from 'app/logging/logger';
import { z } from 'zod';
const zLanguage = z.enum([
@ -31,12 +31,12 @@ export const isLanguage = (v: unknown): v is Language => zLanguage.safeParse(v).
export interface SystemState {
_version: 1;
shouldConfirmOnDelete: boolean;
enableImageDebugging: boolean;
consoleLogLevel: LogLevel;
shouldLogToConsole: boolean;
shouldAntialiasProgressImage: boolean;
language: Language;
shouldUseNSFWChecker: boolean;
shouldUseWatermarker: boolean;
shouldEnableInformationalPopovers: boolean;
logIsEnabled: boolean;
logLevel: LogLevel;
logNamespaces: LogNamespace[];
}

View File

@ -27,7 +27,7 @@ import type { Socket } from 'socket.io-client';
export const socketConnected = createAction('socket/connected');
const log = logger('socketio');
const log = logger('events');
type SetEventListenersArg = {
socket: Socket<ServerToClientEvents, ClientToServerEvents>;