diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx index cb00e1557c..6bf936a55c 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -1,7 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Box, Flex, IconButton } from '@invoke-ai/ui-library'; +import { useAppStore } from 'app/store/storeHooks'; import { typedMemo } from 'common/util/typedMemo'; import type { CollectionMetadataHandler, + ParsedSuccessData, SingleMetadataHandler, UnrecallableMetadataHandler, } from 'features/metadata/parsing'; @@ -11,7 +14,7 @@ import { useSingleMetadataDatum, useUnrecallableMetadataDatum, } from 'features/metadata/parsing'; -import { memo } from 'react'; +import { memo, useCallback } from 'react'; import { PiArrowBendUpLeftBold } from 'react-icons/pi'; type Props = { @@ -69,76 +72,83 @@ const UnrecallableMetadataDatum = typedMemo( } if (data.isSuccess) { - const { LabelComponent, ValueComponent } = handler; - - return ( - - - - - ); + return ; } } ); UnrecallableMetadataDatum.displayName = 'UnrecallableMetadataDatum'; +const UnrecallableMetadataParsed = typedMemo( + ({ data, handler }: { data: ParsedSuccessData; handler: UnrecallableMetadataHandler }) => { + const { LabelComponent, ValueComponent } = handler; + + return ( + + + + + ); + } +); +UnrecallableMetadataParsed.displayName = 'UnrecallableMetadataParsed'; + const SingleMetadataDatum = typedMemo( ({ metadata, handler }: { metadata: unknown; handler: SingleMetadataHandler }) => { - const { data, recall } = useSingleMetadataDatum(metadata, handler); + const { data } = useSingleMetadataDatum(metadata, handler); if (!data.isParsed) { return null; } if (data.isSuccess) { - const { LabelComponent, ValueComponent } = handler; - return ( - - } - size="xs" - variant="ghost" - onClick={recall} - /> - - - - - - ); + return ; } } ); SingleMetadataDatum.displayName = 'SingleMetadataDatum'; +const SingleMetadataParsed = typedMemo( + ({ data, handler }: { data: ParsedSuccessData; handler: SingleMetadataHandler }) => { + const store = useAppStore(); + + const { LabelComponent, ValueComponent } = handler; + + const onClick = useCallback(() => { + handler.recall(data.value, store); + }, [data.value, handler, store]); + + return ( + + } + size="xs" + variant="ghost" + onClick={onClick} + /> + + + + + + ); + } +); +SingleMetadataParsed.displayName = 'SingleMetadataParsed'; + const CollectionMetadataDatum = typedMemo( ({ metadata, handler }: { metadata: unknown; handler: CollectionMetadataHandler }) => { - const { data, recallAll, recallItem } = useCollectionMetadataDatum(metadata, handler); + const { data } = useCollectionMetadataDatum(metadata, handler); if (!data.isParsed) { return null; } if (data.isSuccess) { - const { LabelComponent, ValueComponent } = handler; - return ( <> {data.value.map((value, i) => ( - - } - size="xs" - variant="ghost" - onClick={() => recallItem(value)} - /> - - - - - + ))} ); @@ -146,3 +156,42 @@ const CollectionMetadataDatum = typedMemo( } ); CollectionMetadataDatum.displayName = 'CollectionMetadataDatum'; + +const CollectionMetadataParsed = typedMemo( + ({ + value, + i, + data, + handler, + }: { + value: T[number]; + i: number; + data: ParsedSuccessData; + handler: CollectionMetadataHandler; + }) => { + const store = useAppStore(); + + const { LabelComponent, ValueComponent } = handler; + + const onClick = useCallback(() => { + handler.recallOne(value, store); + }, [handler, store, value]); + + return ( + + } + size="xs" + variant="ghost" + onClick={onClick} + /> + + + + + + ); + } +); +CollectionMetadataParsed.displayName = 'CollectionMetadataParsed'; diff --git a/invokeai/frontend/web/src/features/metadata/parsing.tsx b/invokeai/frontend/web/src/features/metadata/parsing.tsx index 77cb583cd7..e3b45fd7f8 100644 --- a/invokeai/frontend/web/src/features/metadata/parsing.tsx +++ b/invokeai/frontend/web/src/features/metadata/parsing.tsx @@ -2,7 +2,7 @@ import { Text } from '@invoke-ai/ui-library'; import type { AppStore } from 'app/store/store'; import { useAppStore } from 'app/store/storeHooks'; -import { withResultAsync } from 'common/util/result'; +import { WrappedError } from 'common/util/result'; import { get, isArray, isString } from 'es-toolkit/compat'; import { getPrefixedId } from 'features/controlLayers/konva/util'; import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice'; @@ -111,7 +111,7 @@ const getProperty = (obj: unknown, path: string): unknown => { return get(obj, path) as unknown; }; -type UnparsedData = { +export type UnparsedData = { isParsed: false; isSuccess: false; isError: false; @@ -126,7 +126,7 @@ const buildUnparsedData = (): UnparsedData => ({ error: null, }); -type ParsedSuccessData = { +export type ParsedSuccessData = { isParsed: true; isSuccess: true; isError: false; @@ -141,7 +141,7 @@ const buildParsedSuccessData = (value: T): ParsedSuccessData => ({ error: null, }); -type ParsedErrorData = { +export type ParsedErrorData = { isParsed: true; isSuccess: false; isError: true; @@ -167,7 +167,7 @@ type SingleMetadataValueProps = { }; export type SingleMetadataHandler = { type: string; - parse: (metadata: unknown, store: AppStore) => Promise | T; + parse: (metadata: unknown, store: AppStore) => Promise; recall: (value: T, store: AppStore) => void; LabelComponent: ComponentType>; ValueComponent: ComponentType>; @@ -182,9 +182,9 @@ type CollectionMetadataValueProps = { }; export type CollectionMetadataHandler = { type: string; - parse: (metadata: unknown, store: AppStore) => Promise | T; + parse: (metadata: unknown, store: AppStore) => Promise; recallAll: (values: T, store: AppStore) => void; - recallItem: (value: T[number], store: AppStore) => void; + recallOne: (value: T[number], store: AppStore) => void; LabelComponent: ComponentType>; ValueComponent: ComponentType>; }; @@ -198,7 +198,7 @@ type UnrecallableMetadataValueProps = { }; export type UnrecallableMetadataHandler = { type: string; - parse: (metadata: unknown, store: AppStore) => Promise | T; + parse: (metadata: unknown, store: AppStore) => Promise; LabelComponent: ComponentType>; ValueComponent: ComponentType>; }; @@ -209,7 +209,7 @@ const CreatedBy: UnrecallableMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'created_by'); const parsed = z.string().parse(raw); - return parsed; + return Promise.resolve(parsed); }, LabelComponent: () => , ValueComponent: ({ value }: UnrecallableMetadataLabelProps) => , @@ -222,7 +222,7 @@ const GenerationMode: UnrecallableMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'generation_mode'); const parsed = z.string().parse(raw); - return parsed; + return Promise.resolve(parsed); }, LabelComponent: () => , ValueComponent: ({ value }: UnrecallableMetadataLabelProps) => , @@ -235,7 +235,7 @@ const PositivePrompt: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'positive_prompt'); const parsed = zParameterPositivePrompt.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(positivePromptChanged(value)); @@ -253,7 +253,7 @@ const NegativePrompt: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'negative_prompt'); const parsed = zParameterNegativePrompt.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(negativePromptChanged(value)); @@ -271,7 +271,7 @@ const PositiveStylePrompt: SingleMetadataHandler { const raw = getProperty(metadata, 'positive_style_prompt'); const parsed = zParameterPositiveStylePromptSDXL.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(positivePrompt2Changed(value)); @@ -289,7 +289,7 @@ const NegativeStylePrompt: SingleMetadataHandler { const raw = getProperty(metadata, 'negative_style_prompt'); const parsed = zParameterNegativeStylePromptSDXL.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(negativePrompt2Changed(value)); @@ -307,7 +307,7 @@ const CFGScale: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'cfg_scale'); const parsed = zParameterCFGScale.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setCfgScale(value)); @@ -323,7 +323,7 @@ const CFGRescaleMultiplier: SingleMetadataHandler parse: (metadata, _store) => { const raw = getProperty(metadata, 'cfg_rescale_multiplier'); const parsed = zParameterCFGRescaleMultiplier.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setCfgRescaleMultiplier(value)); @@ -341,7 +341,7 @@ const Guidance: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'guidance'); const parsed = zParameterGuidance.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setGuidance(value)); @@ -357,7 +357,7 @@ const Scheduler: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'scheduler'); const parsed = zParameterScheduler.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setScheduler(value)); @@ -373,7 +373,7 @@ const Width: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'width'); const parsed = zParameterImageDimension.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(bboxWidthChanged({ width: value, updateAspectRatio: true, clamp: true })); @@ -389,7 +389,7 @@ const Height: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'height'); const parsed = zParameterImageDimension.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(bboxHeightChanged({ height: value, updateAspectRatio: true, clamp: true })); @@ -405,7 +405,7 @@ const Seed: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'seed'); const parsed = zParameterSeed.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setSeed(value)); @@ -421,7 +421,7 @@ const Steps: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'steps'); const parsed = zParameterSteps.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setSteps(value)); @@ -437,7 +437,7 @@ const DenoisingStrength: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'strength'); const parsed = zParameterStrength.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setImg2imgStrength(value)); @@ -453,7 +453,7 @@ const SeamlessX: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'seamless_x'); const parsed = zParameterSeamlessX.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setSeamlessXAxis(value)); @@ -469,7 +469,7 @@ const SeamlessY: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'seamless_y'); const parsed = zParameterSeamlessY.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setSeamlessYAxis(value)); @@ -487,7 +487,7 @@ const RefinerModel: SingleMetadataHandler = { const parsed = await parseModelIdentifier(raw, store, 'main'); assert(parsed.type === 'main'); assert(parsed.base === 'sdxl-refiner'); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(refinerModelChanged(value)); @@ -505,7 +505,7 @@ const RefinerSteps: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'refiner_steps'); const parsed = zParameterSteps.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerSteps(value)); @@ -521,7 +521,7 @@ const RefinerCFGScale: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'refiner_cfg_scale'); const parsed = zParameterCFGScale.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerCFGScale(value)); @@ -537,7 +537,7 @@ const RefinerScheduler: SingleMetadataHandler = { parse: (metadata, _store) => { const raw = getProperty(metadata, 'refiner_scheduler'); const parsed = zParameterScheduler.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerScheduler(value)); @@ -553,7 +553,7 @@ const RefinerPositiveAestheticScore: SingleMetadataHandler { const raw = getProperty(metadata, 'refiner_positive_aesthetic_score'); const parsed = zParameterSDXLRefinerPositiveAestheticScore.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerPositiveAestheticScore(value)); @@ -571,7 +571,7 @@ const RefinerNegativeAestheticScore: SingleMetadataHandler { const raw = getProperty(metadata, 'refiner_negative_aesthetic_score'); const parsed = zParameterSDXLRefinerNegativeAestheticScore.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerNegativeAestheticScore(value)); @@ -589,7 +589,7 @@ const RefinerDenoisingStart: SingleMetadataHandler = parse: (metadata, _store) => { const raw = getProperty(metadata, 'refiner_start'); const parsed = zParameterSDXLRefinerStart.parse(raw); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(setRefinerStart(value)); @@ -608,7 +608,7 @@ const MainModel: SingleMetadataHandler = { const raw = getProperty(metadata, 'model'); const parsed = await parseModelIdentifier(raw, store, 'main'); assert(parsed.type === 'main'); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(modelSelected(value)); @@ -627,7 +627,7 @@ const VAEModel: SingleMetadataHandler = { const raw = getProperty(metadata, 'vae'); const parsed = await parseModelIdentifier(raw, store, 'vae'); assert(parsed.type === 'vae'); - return parsed; + return Promise.resolve(parsed); }, recall: (value, store) => { store.dispatch(vaeSelected(value)); @@ -656,11 +656,11 @@ const LoRAs: CollectionMetadataHandler = { const rawIdentifier = getProperty(rawItem, 'model'); identifier = await parseModelIdentifier(rawIdentifier, store, 'lora'); } catch { - // Old format - { lora : { key } } + // Old format - { lora : { key: string } } const key = getProperty(rawItem, 'lora.key'); assert(isString(key)); - const modelConfig = await getModelConfig(key, store); - identifier = zModelIdentifierField.parse(modelConfig); + // No need to catch here - if this throws, we move on to the next item + identifier = await getModelIdentiferFromKey(key, store); } assert(identifier.type === 'lora'); const weight = getProperty(rawItem, 'weight'); @@ -681,7 +681,7 @@ const LoRAs: CollectionMetadataHandler = { throw new Error('No valid LoRAs found in metadata'); }, - recallItem: (value, store) => { + recallOne: (value, store) => { store.dispatch(loraRecalled({ lora: value })); }, recallAll: (values, store) => { @@ -744,11 +744,11 @@ export function useSingleMetadataDatum(metadata: unknown, handler: SingleMeta const parse = useCallback( async (metadata: unknown) => { - const result = await withResultAsync(async () => await Promise.resolve(handler.parse(metadata, store))); - if (result.isOk()) { - setData(buildParsedSuccessData(result.value)); - } else { - setData(buildParsedErrorData(result.error)); + try { + const value = await handler.parse(metadata, store); + setData(buildParsedSuccessData(value)); + } catch (error) { + setData(buildParsedErrorData(WrappedError.wrap(error))); } }, [handler, store] @@ -758,12 +758,12 @@ export function useSingleMetadataDatum(metadata: unknown, handler: SingleMeta parse(metadata); }, [metadata, parse]); - const recall = useCallback(() => { - if (!data.isSuccess) { - return; - } - handler.recall?.(data.value, store); - }, [data.isSuccess, data.value, handler, store]); + const recall = useCallback( + (value: T) => { + handler.recall(value, store); + }, + [handler, store] + ); return { data, recall }; } @@ -775,11 +775,11 @@ export function useCollectionMetadataDatum(metadata: unknown, h const parse = useCallback( async (metadata: unknown) => { - const result = await withResultAsync(async () => await Promise.resolve(handler.parse(metadata, store))); - if (result.isOk()) { - setData(buildParsedSuccessData(result.value)); - } else { - setData(buildParsedErrorData(result.error)); + try { + const value = await handler.parse(metadata, store); + setData(buildParsedSuccessData(value)); + } catch (error) { + setData(buildParsedErrorData(WrappedError.wrap(error))); } }, [handler, store] @@ -789,21 +789,21 @@ export function useCollectionMetadataDatum(metadata: unknown, h parse(metadata); }, [metadata, parse]); - const recallAll = useCallback(() => { - if (!data.isSuccess) { - return; - } - handler.recallAll(data.value, store); - }, [data.isSuccess, data.value, handler, store]); - - const recallItem = useCallback( - (item: T) => { - handler.recallItem(item, store); + const recallAll = useCallback( + (values: T) => { + handler.recallAll(values, store); }, [handler, store] ); - return { data, recallAll, recallItem }; + const recallOne = useCallback( + (value: T[number]) => { + handler.recallOne(value, store); + }, + [handler, store] + ); + + return { data, recallAll, recallOne }; } export function useUnrecallableMetadataDatum(metadata: unknown, handler: UnrecallableMetadataHandler) { @@ -812,11 +812,11 @@ export function useUnrecallableMetadataDatum(metadata: unknown, handler: Unre const parse = useCallback( async (metadata: unknown) => { - const result = await withResultAsync(async () => await Promise.resolve(handler.parse(metadata, store))); - if (result.isOk()) { - setData(buildParsedSuccessData(result.value)); - } else { - setData(buildParsedErrorData(result.error)); + try { + const value = await handler.parse(metadata, store); + setData(buildParsedSuccessData(value)); + } catch (error) { + setData(buildParsedErrorData(WrappedError.wrap(error))); } }, [handler, store] @@ -829,10 +829,11 @@ export function useUnrecallableMetadataDatum(metadata: unknown, handler: Unre return { data }; } -const getModelConfig = async (key: string, store: AppStore): Promise => { - const modelConfig = await store - .dispatch(modelsApi.endpoints.getModelConfig.initiate(key, { subscribe: false })) - .unwrap(); +const OPTIONS = { subscribe: false }; + +const getModelIdentiferFromKey = async (key: string, store: AppStore): Promise => { + const req = store.dispatch(modelsApi.endpoints.getModelConfig.initiate(key, OPTIONS)); + const modelConfig = await req.unwrap(); return modelConfig; }; @@ -840,7 +841,7 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy // First try the current format identifier: key, name, base, type, hash try { const { key } = zModelIdentifierField.parse(raw); - const req = store.dispatch(modelsApi.endpoints.getModelConfig.initiate(key, { subscribe: false })); + const req = store.dispatch(modelsApi.endpoints.getModelConfig.initiate(key, OPTIONS)); const modelConfig = await req.unwrap(); return zModelIdentifierField.parse(modelConfig); } catch { @@ -850,9 +851,8 @@ const parseModelIdentifier = async (raw: unknown, store: AppStore, type: ModelTy // Fall back to old format identifier: model_name, base_model try { const { model_name: name, base_model: base } = zModelIdentifier.parse(raw); - const req = store.dispatch( - modelsApi.endpoints.getModelConfigByAttrs.initiate({ name, base, type }, { subscribe: false }) - ); + const arg = { name, base, type }; + const req = store.dispatch(modelsApi.endpoints.getModelConfigByAttrs.initiate(arg, OPTIONS)); const modelConfig = await req.unwrap(); return zModelIdentifierField.parse(modelConfig); } catch {