diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 74443fbb7f4..fac5150cb70 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -854,6 +854,7 @@
"defaultVAE": "Default VAE"
},
"nodes": {
+ "noBatchGroup": "no group",
"addNode": "Add Node",
"addNodeToolTip": "Add Node (Shift+A, Space)",
"addLinearView": "Add to Linear View",
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/BatchGroupId.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/BatchGroupId.tsx
new file mode 100644
index 00000000000..3f4231230ff
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/BatchGroupId.tsx
@@ -0,0 +1,24 @@
+import type { TextProps } from '@invoke-ai/ui-library';
+import { Text } from '@invoke-ai/ui-library';
+import { useBatchGroupColorToken } from 'features/nodes/hooks/useBatchGroupColorToken';
+import { memo } from 'react';
+
+type Props = TextProps & {
+ batchGroupId?: string;
+};
+
+export const BatchGroupId = memo(({ batchGroupId, ...rest }: Props) => {
+ const batchGroupColorToken = useBatchGroupColorToken(batchGroupId);
+
+ if (!batchGroupColorToken || !batchGroupId) {
+ return null;
+ }
+
+ return (
+
+ {batchGroupId}
+
+ );
+});
+
+BatchGroupId.displayName = 'BatchGroupId';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
index 58e9bd99159..71261155067 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
@@ -1,12 +1,14 @@
-import type { SystemStyleObject } from '@invoke-ai/ui-library';
-import { Box, Editable, EditableInput, EditablePreview, Flex, useEditableControls } from '@invoke-ai/ui-library';
+import type { SystemStyleObject, TextProps } from '@invoke-ai/ui-library';
+import { Box, Editable, EditableInput, Flex, Text, useEditableControls } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
+import { useBatchGroupColorToken } from 'features/nodes/hooks/useBatchGroupColorToken';
+import { useBatchGroupId } from 'features/nodes/hooks/useBatchGroupId';
import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle';
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import type { MouseEvent } from 'react';
-import { memo, useCallback, useEffect, useState } from 'react';
+import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
@@ -17,6 +19,8 @@ type Props = {
const NodeTitle = ({ nodeId, title }: Props) => {
const dispatch = useAppDispatch();
const label = useNodeLabel(nodeId);
+ const batchGroupId = useBatchGroupId(nodeId);
+ const batchGroupColorToken = useBatchGroupColorToken(batchGroupId);
const templateTitle = useNodeTemplateTitle(nodeId);
const { t } = useTranslation();
@@ -29,6 +33,16 @@ const NodeTitle = ({ nodeId, title }: Props) => {
[dispatch, nodeId, title, templateTitle, label, t]
);
+ const localTitleWithBatchGroupId = useMemo(() => {
+ if (!batchGroupId) {
+ return localTitle;
+ }
+ if (batchGroupId === 'None') {
+ return `${localTitle} (${t('nodes.noBatchGroup')})`;
+ }
+ return `${localTitle} (${batchGroupId})`;
+ }, [batchGroupId, localTitle, t]);
+
const handleChange = useCallback((newTitle: string) => {
setLocalTitle(newTitle);
}, []);
@@ -50,7 +64,16 @@ const NodeTitle = ({ nodeId, title }: Props) => {
w="full"
h="full"
>
-
+
+ {localTitleWithBatchGroupId}
+
@@ -60,6 +83,16 @@ const NodeTitle = ({ nodeId, title }: Props) => {
export default memo(NodeTitle);
+const Preview = (props: TextProps) => {
+ const { isEditing } = useEditableControls();
+
+ if (isEditing) {
+ return null;
+ }
+
+ return ;
+};
+
function EditableControls() {
const { isEditing, getEditButtonProps } = useEditableControls();
const handleDoubleClick = useCallback(
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupColorToken.ts b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupColorToken.ts
new file mode 100644
index 00000000000..0426258e0e0
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupColorToken.ts
@@ -0,0 +1,22 @@
+import { useMemo } from 'react';
+
+export const useBatchGroupColorToken = (batchGroupId?: string) => {
+ const batchGroupColorToken = useMemo(() => {
+ switch (batchGroupId) {
+ case 'Group 1':
+ return 'invokeGreen.300';
+ case 'Group 2':
+ return 'invokeBlue.300';
+ case 'Group 3':
+ return 'invokePurple.200';
+ case 'Group 4':
+ return 'invokeRed.300';
+ case 'Group 5':
+ return 'invokeYellow.300';
+ default:
+ return undefined;
+ }
+ }, [batchGroupId]);
+
+ return batchGroupColorToken;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts
new file mode 100644
index 00000000000..9ae03fa39ad
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts
@@ -0,0 +1,19 @@
+import { useNode } from 'features/nodes/hooks/useNode';
+import { isBatchNode, isInvocationNode } from 'features/nodes/types/invocation';
+import { useMemo } from 'react';
+
+export const useBatchGroupId = (nodeId: string) => {
+ const node = useNode(nodeId);
+
+ const batchGroupId = useMemo(() => {
+ if (!isInvocationNode(node)) {
+ return;
+ }
+ if (!isBatchNode(node)) {
+ return;
+ }
+ return node.data.inputs['batch_group_id']?.value as string;
+ }, [node]);
+
+ return batchGroupId;
+};