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 2cbe9235d71..cfa679c8050 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageMetadataViewer/ImageMetadataActions.tsx @@ -1,5 +1,8 @@ +import { MetadataControlNets } from 'features/metadata/components/MetadataControlNets'; +import { MetadataIPAdapters } from 'features/metadata/components/MetadataIPAdapters'; import { MetadataItem } from 'features/metadata/components/MetadataItem'; import { MetadataLoRAs } from 'features/metadata/components/MetadataLoRAs'; +import { MetadataT2IAdapters } from 'features/metadata/components/MetadataT2IAdapters'; import { handlers } from 'features/metadata/util/handlers'; import { memo } from 'react'; @@ -16,193 +19,36 @@ const ImageMetadataActions = (props: Props) => { return ( <> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx index b508c9424d1..fd71ce3e19a 100644 --- a/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx +++ b/invokeai/frontend/web/src/features/lora/components/LoRACard.tsx @@ -23,29 +23,29 @@ type LoRACardProps = { export const LoRACard = memo((props: LoRACardProps) => { const { lora } = props; const dispatch = useAppDispatch(); - const { data: loraConfig } = useGetModelConfigQuery(lora.key); + const { data: loraConfig } = useGetModelConfigQuery(lora.model.key); const handleChange = useCallback( (v: number) => { - dispatch(loraWeightChanged({ key: lora.key, weight: v })); + dispatch(loraWeightChanged({ key: lora.model.key, weight: v })); }, - [dispatch, lora.key] + [dispatch, lora.model.key] ); const handleSetLoraToggle = useCallback(() => { - dispatch(loraIsEnabledChanged({ key: lora.key, isEnabled: !lora.isEnabled })); - }, [dispatch, lora.key, lora.isEnabled]); + dispatch(loraIsEnabledChanged({ key: lora.model.key, isEnabled: !lora.isEnabled })); + }, [dispatch, lora.model.key, lora.isEnabled]); const handleRemoveLora = useCallback(() => { - dispatch(loraRemoved(lora.key)); - }, [dispatch, lora.key]); + dispatch(loraRemoved(lora.model.key)); + }, [dispatch, lora.model.key]); return ( - {loraConfig?.name ?? lora.key.substring(0, 8)} + {loraConfig?.name ?? lora.model.key.substring(0, 8)} diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataControlNets.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataControlNets.tsx index 2d66fce0d30..2e8e3b6f9ab 100644 --- a/invokeai/frontend/web/src/features/metadata/components/MetadataControlNets.tsx +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataControlNets.tsx @@ -1,7 +1,8 @@ -import { Text } from '@invoke-ai/ui-library'; import type { ControlNetConfig } from 'features/controlAdapters/store/types'; +import { MetadataItemView } from 'features/metadata/components/MetadataItemView'; +import type { MetadataHandlers } from 'features/metadata/types'; import { handlers } from 'features/metadata/util/handlers'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; type Props = { metadata: unknown; @@ -15,18 +16,51 @@ export const MetadataControlNets = ({ metadata }: Props) => { try { const parsed = await handlers.controlNets.parse(metadata); setControlNets(parsed); - } catch { + } catch (e) { setControlNets([]); } }; parse(); }, [metadata]); + const label = useMemo(() => handlers.controlNets.getLabel(), []); + return ( <> {controlNets.map((controlNet) => ( - {controlNet.model?.key} + ))} ); }; + +const MetadataViewControlNet = ({ + label, + controlNet, + handlers, +}: { + label: string; + controlNet: ControlNetConfig; + handlers: MetadataHandlers; +}) => { + const onRecall = useCallback(() => { + if (!handlers.recallItem) { + return; + } + handlers.recallItem(controlNet, true); + }, [handlers, controlNet]); + + const renderedValue = useMemo(() => { + if (!handlers.renderItemValue) { + return null; + } + return handlers.renderItemValue(controlNet); + }, [handlers, controlNet]); + + return ; +}; diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataIPAdapters.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataIPAdapters.tsx index b003b15279a..fef281cd092 100644 --- a/invokeai/frontend/web/src/features/metadata/components/MetadataIPAdapters.tsx +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataIPAdapters.tsx @@ -1,7 +1,8 @@ -import { Text } from '@invoke-ai/ui-library'; import type { IPAdapterConfig } from 'features/controlAdapters/store/types'; +import { MetadataItemView } from 'features/metadata/components/MetadataItemView'; +import type { MetadataHandlers } from 'features/metadata/types'; import { handlers } from 'features/metadata/util/handlers'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; type Props = { metadata: unknown; @@ -15,18 +16,51 @@ export const MetadataIPAdapters = ({ metadata }: Props) => { try { const parsed = await handlers.ipAdapters.parse(metadata); setIPAdapters(parsed); - } catch { - setIPAdapters([]) + } catch (e) { + setIPAdapters([]); } }; parse(); }, [metadata]); + const label = useMemo(() => handlers.ipAdapters.getLabel(), []); + return ( <> {ipAdapters.map((ipAdapter) => ( - {ipAdapter.model?.key} + ))} ); }; + +const MetadataViewIPAdapter = ({ + label, + ipAdapter, + handlers, +}: { + label: string; + ipAdapter: IPAdapterConfig; + handlers: MetadataHandlers; +}) => { + const onRecall = useCallback(() => { + if (!handlers.recallItem) { + return; + } + handlers.recallItem(ipAdapter, true); + }, [handlers, ipAdapter]); + + const renderedValue = useMemo(() => { + if (!handlers.renderItemValue) { + return null; + } + return handlers.renderItemValue(ipAdapter); + }, [handlers, ipAdapter]); + + return ; +}; diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataItem.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataItem.tsx index bcc7415a897..66d101f458b 100644 --- a/invokeai/frontend/web/src/features/metadata/components/MetadataItem.tsx +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataItem.tsx @@ -1,78 +1,32 @@ -import { Flex, Text, typedMemo } from '@invoke-ai/ui-library'; -import { RecallButton } from 'features/metadata/components/RecallButton'; +import { typedMemo } from '@invoke-ai/ui-library'; +import { MetadataItemView } from 'features/metadata/components/MetadataItemView'; +import { useMetadataItem } from 'features/metadata/hooks/useMetadataItem'; import type { MetadataHandlers } from 'features/metadata/types'; -import { MetadataParseFailedToken, MetadataParsePendingToken } from 'features/metadata/util/parsers'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { MetadataParseFailedToken } from 'features/metadata/util/parsers'; type MetadataItemProps = { metadata: unknown; - getLabel: MetadataHandlers['getLabel']; - parse: MetadataHandlers['parse']; - recall: MetadataHandlers['recall']; - renderValue: MetadataHandlers['renderValue']; + handlers: MetadataHandlers; direction?: 'row' | 'column'; }; -const _MetadataItem = typedMemo( - ({ metadata, getLabel, parse, recall, renderValue, direction = 'row' }: MetadataItemProps) => { - const [value, setValue] = useState( - MetadataParsePendingToken - ); +const _MetadataItem = typedMemo(({ metadata, handlers, direction = 'row' }: MetadataItemProps) => { + const { label, isDisabled, value, renderedValue, onRecall } = useMetadataItem(metadata, handlers); - useEffect(() => { - const _parse = async () => { - try { - const parsed = await parse(metadata); - setValue(parsed); - } catch (e) { - setValue(MetadataParseFailedToken); - } - }; - _parse(); - }, [metadata, parse]); - - const isReady = useMemo(() => value !== MetadataParsePendingToken && value !== MetadataParseFailedToken, [value]); - - const label = useMemo(() => getLabel(), [getLabel]); - - const renderedValue = useMemo(() => { - if (value === MetadataParsePendingToken) { - return Loading; - } - if (value === MetadataParseFailedToken) { - return Parsing Failed; - } - const rendered = renderValue(value); - if (typeof rendered === 'string') { - return {rendered}; - } - return rendered; - }, [renderValue, value]); - - const onRecall = useCallback(() => { - if (!recall || value === MetadataParsePendingToken || value === MetadataParseFailedToken) { - return null; - } - recall(value, true); - }, [recall, value]); - - if (value === MetadataParseFailedToken) { - return null; - } - - return ( - - {recall && } - - - {label}: - - {renderedValue} - - - ); + if (value === MetadataParseFailedToken) { + return null; } -); + + return ( + + ); +}); export const MetadataItem = typedMemo(_MetadataItem); diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataItemView.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataItemView.tsx new file mode 100644 index 00000000000..14ccc4ae2b6 --- /dev/null +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataItemView.tsx @@ -0,0 +1,29 @@ +import { Flex, Text } from '@invoke-ai/ui-library'; +import { RecallButton } from 'features/metadata/components/RecallButton'; +import { memo } from 'react'; + +type MetadataItemViewProps = { + onRecall: () => void; + label: string; + renderedValue: React.ReactNode; + isDisabled: boolean; + direction?: 'row' | 'column'; +}; + +export const MetadataItemView = memo( + ({ label, onRecall, isDisabled, renderedValue, direction = 'row' }: MetadataItemViewProps) => { + return ( + + {onRecall && } + + + {label}: + + {renderedValue} + + + ); + } +); + +MetadataItemView.displayName = 'MetadataItemView'; diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataLoRAs.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataLoRAs.tsx index 7c2d128d261..3225a048d4e 100644 --- a/invokeai/frontend/web/src/features/metadata/components/MetadataLoRAs.tsx +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataLoRAs.tsx @@ -1,7 +1,8 @@ import type { LoRA } from 'features/lora/store/loraSlice'; -import { MetadataItem } from 'features/metadata/components/MetadataItem'; +import { MetadataItemView } from 'features/metadata/components/MetadataItemView'; +import type { MetadataHandlers } from 'features/metadata/types'; import { handlers } from 'features/metadata/util/handlers'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; type Props = { metadata: unknown; @@ -22,18 +23,39 @@ export const MetadataLoRAs = ({ metadata }: Props) => { parse(); }, [metadata]); + const label = useMemo(() => handlers.loras.getLabel(), []); + return ( <> {loras.map((lora) => ( - + ))} ); }; + +const MetadataViewLoRA = ({ + label, + lora, + handlers, +}: { + label: string; + lora: LoRA; + handlers: MetadataHandlers; +}) => { + const onRecall = useCallback(() => { + if (!handlers.recallItem) { + return; + } + handlers.recallItem(lora, true); + }, [handlers, lora]); + + const renderedValue = useMemo(() => { + if (!handlers.renderItemValue) { + return null; + } + return handlers.renderItemValue(lora); + }, [handlers, lora]); + + return ; +}; diff --git a/invokeai/frontend/web/src/features/metadata/components/MetadataT2IAdapters.tsx b/invokeai/frontend/web/src/features/metadata/components/MetadataT2IAdapters.tsx index 79cb4864d89..4a73fb3ccbd 100644 --- a/invokeai/frontend/web/src/features/metadata/components/MetadataT2IAdapters.tsx +++ b/invokeai/frontend/web/src/features/metadata/components/MetadataT2IAdapters.tsx @@ -1,7 +1,8 @@ -import { Text } from '@invoke-ai/ui-library'; import type { T2IAdapterConfig } from 'features/controlAdapters/store/types'; +import { MetadataItemView } from 'features/metadata/components/MetadataItemView'; +import type { MetadataHandlers } from 'features/metadata/types'; import { handlers } from 'features/metadata/util/handlers'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; type Props = { metadata: unknown; @@ -15,18 +16,51 @@ export const MetadataT2IAdapters = ({ metadata }: Props) => { try { const parsed = await handlers.t2iAdapters.parse(metadata); setT2IAdapters(parsed); - } catch { - // no-op + } catch (e) { + setT2IAdapters([]); } }; parse(); }, [metadata]); + const label = useMemo(() => handlers.t2iAdapters.getLabel(), []); + return ( <> {t2iAdapters.map((t2iAdapter) => ( - {t2iAdapter.model?.key} + ))} ); }; + +const MetadataViewT2IAdapter = ({ + label, + t2iAdapter, + handlers, +}: { + label: string; + t2iAdapter: T2IAdapterConfig; + handlers: MetadataHandlers; +}) => { + const onRecall = useCallback(() => { + if (!handlers.recallItem) { + return; + } + handlers.recallItem(t2iAdapter, true); + }, [handlers, t2iAdapter]); + + const renderedValue = useMemo(() => { + if (!handlers.renderItemValue) { + return null; + } + return handlers.renderItemValue(t2iAdapter); + }, [handlers, t2iAdapter]); + + return ; +}; diff --git a/invokeai/frontend/web/src/features/metadata/hooks/useMetadataItem.tsx b/invokeai/frontend/web/src/features/metadata/hooks/useMetadataItem.tsx new file mode 100644 index 00000000000..178ac5155a7 --- /dev/null +++ b/invokeai/frontend/web/src/features/metadata/hooks/useMetadataItem.tsx @@ -0,0 +1,51 @@ +import { Text } from '@invoke-ai/ui-library'; +import type { MetadataHandlers } from 'features/metadata/types'; +import { MetadataParseFailedToken, MetadataParsePendingToken } from 'features/metadata/util/parsers'; +import { useCallback, useEffect, useMemo, useState } from 'react'; + +export const useMetadataItem = (metadata: unknown, handlers: MetadataHandlers) => { + const [value, setValue] = useState( + MetadataParsePendingToken + ); + + useEffect(() => { + const _parse = async () => { + try { + const parsed = await handlers.parse(metadata); + setValue(parsed); + } catch (e) { + setValue(MetadataParseFailedToken); + } + }; + _parse(); + }, [handlers, metadata]); + + const isDisabled = useMemo(() => value === MetadataParsePendingToken || value === MetadataParseFailedToken, [value]); + + const label = useMemo(() => handlers.getLabel(), [handlers]); + + const renderedValue = useMemo(() => { + if (value === MetadataParsePendingToken) { + return Loading; + } + if (value === MetadataParseFailedToken) { + return Parsing Failed; + } + + const rendered = handlers.renderValue(value); + + if (typeof rendered === 'string') { + return {rendered}; + } + return rendered; + }, [handlers, value]); + + const onRecall = useCallback(() => { + if (!handlers.recall || value === MetadataParsePendingToken || value === MetadataParseFailedToken) { + return null; + } + handlers.recall(value, true); + }, [handlers, value]); + + return { label, isDisabled, value, renderedValue, onRecall }; +}; diff --git a/invokeai/frontend/web/src/features/metadata/util/handlers.ts b/invokeai/frontend/web/src/features/metadata/util/handlers.tsx similarity index 98% rename from invokeai/frontend/web/src/features/metadata/util/handlers.ts rename to invokeai/frontend/web/src/features/metadata/util/handlers.tsx index 9b2699bf079..1f86257d422 100644 --- a/invokeai/frontend/web/src/features/metadata/util/handlers.ts +++ b/invokeai/frontend/web/src/features/metadata/util/handlers.tsx @@ -1,3 +1,4 @@ +import { Text } from '@invoke-ai/ui-library'; import { toast } from 'common/util/toast'; import type { ControlNetConfig, IPAdapterConfig, T2IAdapterConfig } from 'features/controlAdapters/store/types'; import type { LoRA } from 'features/lora/store/loraSlice'; @@ -19,10 +20,10 @@ import { recallers } from './recallers'; const renderModelConfigValue: MetadataRenderValueFunc = (value) => `${value.name} (${value.base.toUpperCase()}, ${value.key})`; -const renderLoRAValue: MetadataRenderValueFunc = (value) => `${value.model.key} (${value.weight})`; +const renderLoRAValue: MetadataRenderValueFunc = (value) => {`${value.model.key} (${value.weight})`}; const renderControlAdapterValue: MetadataRenderValueFunc = ( value -) => `${value.model?.key} (${value.weight})`; +) => {`${value.model?.key} (${value.weight})`}; const parameterSetToast = (parameter: string, description?: string) => { toast({