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({