mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into feat/ip-adapter
This commit is contained in:
@ -12,9 +12,11 @@ import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel';
|
||||
import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel';
|
||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NodeEditor = () => {
|
||||
const isReady = useAppSelector((state) => state.nodes.isReady);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
@ -82,7 +84,7 @@ const NodeEditor = () => {
|
||||
}}
|
||||
>
|
||||
<IAINoContentFallback
|
||||
label="Loading Nodes..."
|
||||
label={t('nodes.loadingNodes')}
|
||||
icon={MdDeviceHub}
|
||||
/>
|
||||
</Flex>
|
||||
|
@ -24,6 +24,7 @@ import { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { AnyInvocationType } from 'services/events/types';
|
||||
import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type NodeTemplate = {
|
||||
label: string;
|
||||
@ -48,43 +49,45 @@ const filter = (value: string, item: NodeTemplate) => {
|
||||
);
|
||||
};
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
({ nodes }) => {
|
||||
const data: NodeTemplate[] = map(nodes.nodeTemplates, (template) => {
|
||||
return {
|
||||
label: template.title,
|
||||
value: template.type,
|
||||
description: template.description,
|
||||
tags: template.tags,
|
||||
};
|
||||
});
|
||||
|
||||
data.push({
|
||||
label: 'Progress Image',
|
||||
value: 'current_image',
|
||||
description: 'Displays the current image in the Node Editor',
|
||||
tags: ['progress'],
|
||||
});
|
||||
|
||||
data.push({
|
||||
label: 'Notes',
|
||||
value: 'notes',
|
||||
description: 'Add notes about your workflow',
|
||||
tags: ['notes'],
|
||||
});
|
||||
|
||||
data.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return { data };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const AddNodePopover = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const buildInvocation = useBuildNodeData();
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector],
|
||||
({ nodes }) => {
|
||||
const data: NodeTemplate[] = map(nodes.nodeTemplates, (template) => {
|
||||
return {
|
||||
label: template.title,
|
||||
value: template.type,
|
||||
description: template.description,
|
||||
tags: template.tags,
|
||||
};
|
||||
});
|
||||
|
||||
data.push({
|
||||
label: t('nodes.currentImage'),
|
||||
value: 'current_image',
|
||||
description: t('nodes.currentImageDescription'),
|
||||
tags: ['progress'],
|
||||
});
|
||||
|
||||
data.push({
|
||||
label: t('nodes.notes'),
|
||||
value: 'notes',
|
||||
description: t('nodes.notesDescription'),
|
||||
tags: ['notes'],
|
||||
});
|
||||
|
||||
data.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return { data, t };
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const { data } = useAppSelector(selector);
|
||||
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@ -92,18 +95,20 @@ const AddNodePopover = () => {
|
||||
const addNode = useCallback(
|
||||
(nodeType: AnyInvocationType) => {
|
||||
const invocation = buildInvocation(nodeType);
|
||||
|
||||
if (!invocation) {
|
||||
const errorMessage = t('nodes.unknownInvocation', {
|
||||
nodeType: nodeType,
|
||||
});
|
||||
toaster({
|
||||
status: 'error',
|
||||
title: `Unknown Invocation type ${nodeType}`,
|
||||
title: errorMessage,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(nodeAdded(invocation));
|
||||
},
|
||||
[dispatch, buildInvocation, toaster]
|
||||
[dispatch, buildInvocation, toaster, t]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
@ -179,11 +184,11 @@ const AddNodePopover = () => {
|
||||
<IAIMantineSearchableSelect
|
||||
inputRef={inputRef}
|
||||
selectOnBlur={false}
|
||||
placeholder="Search for nodes"
|
||||
placeholder={t('nodes.nodeSearch')}
|
||||
value={null}
|
||||
data={data}
|
||||
maxDropdownHeight={400}
|
||||
nothingFound="No matching nodes"
|
||||
nothingFound={t('nodes.noMatchingNodes')}
|
||||
itemComponent={AddNodePopoverSelectItem}
|
||||
filter={filter}
|
||||
onChange={handleChange}
|
||||
|
@ -22,6 +22,7 @@ import { memo, useMemo } from 'react';
|
||||
import { FaInfoCircle } from 'react-icons/fa';
|
||||
import NotesTextarea from './NotesTextarea';
|
||||
import { useDoNodeVersionsMatch } from 'features/nodes/hooks/useDoNodeVersionsMatch';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@ -32,6 +33,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
|
||||
const label = useNodeLabel(nodeId);
|
||||
const title = useNodeTemplateTitle(nodeId);
|
||||
const doVersionsMatch = useDoNodeVersionsMatch(nodeId);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -65,7 +67,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
|
||||
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>{label || title || 'Unknown Node'}</ModalHeader>
|
||||
<ModalHeader>{label || title || t('nodes.unknownNode')}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<NotesTextarea nodeId={nodeId} />
|
||||
@ -82,6 +84,7 @@ export default memo(InvocationNodeNotes);
|
||||
const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
const data = useNodeData(nodeId);
|
||||
const nodeTemplate = useNodeTemplate(nodeId);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (data?.label && nodeTemplate?.title) {
|
||||
@ -96,8 +99,8 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
return nodeTemplate.title;
|
||||
}
|
||||
|
||||
return 'Unknown Node';
|
||||
}, [data, nodeTemplate]);
|
||||
return t('nodes.unknownNode');
|
||||
}, [data, nodeTemplate, t]);
|
||||
|
||||
const versionComponent = useMemo(() => {
|
||||
if (!isInvocationNodeData(data) || !nodeTemplate) {
|
||||
@ -107,7 +110,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
if (!data.version) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
Version unknown
|
||||
{t('nodes.versionUnknown')}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@ -115,7 +118,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
if (!nodeTemplate.version) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
Version {data.version} (unknown template)
|
||||
{t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')})
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@ -123,7 +126,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
if (compare(data.version, nodeTemplate.version, '<')) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
Version {data.version} (update node)
|
||||
{t('nodes.version')} {data.version} ({t('nodes.updateNode')})
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@ -131,16 +134,20 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
if (compare(data.version, nodeTemplate.version, '>')) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
Version {data.version} (update app)
|
||||
{t('nodes.version')} {data.version} ({t('nodes.updateApp')})
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <Text as="span">Version {data.version}</Text>;
|
||||
}, [data, nodeTemplate]);
|
||||
return (
|
||||
<Text as="span">
|
||||
{t('nodes.version')} {data.version}
|
||||
</Text>
|
||||
);
|
||||
}, [data, nodeTemplate, t]);
|
||||
|
||||
if (!isInvocationNodeData(data)) {
|
||||
return <Text sx={{ fontWeight: 600 }}>Unknown Node</Text>;
|
||||
return <Text sx={{ fontWeight: 600 }}>{t('nodes.unknownNode')}</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -14,6 +14,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -72,10 +73,10 @@ type TooltipLabelProps = {
|
||||
|
||||
const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||
const { status, progress, progressImage } = nodeExecutionState;
|
||||
const { t } = useTranslation();
|
||||
if (status === NodeStatus.PENDING) {
|
||||
return <Text>Pending</Text>;
|
||||
}
|
||||
|
||||
if (status === NodeStatus.IN_PROGRESS) {
|
||||
if (progressImage) {
|
||||
return (
|
||||
@ -97,18 +98,22 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||
}
|
||||
|
||||
if (progress !== null) {
|
||||
return <Text>In Progress ({Math.round(progress * 100)}%)</Text>;
|
||||
return (
|
||||
<Text>
|
||||
{t('nodes.executionStateInProgress')} ({Math.round(progress * 100)}%)
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <Text>In Progress</Text>;
|
||||
return <Text>{t('nodes.executionStateInProgress')}</Text>;
|
||||
}
|
||||
|
||||
if (status === NodeStatus.COMPLETED) {
|
||||
return <Text>Completed</Text>;
|
||||
return <Text>{t('nodes.executionStateCompleted')}</Text>;
|
||||
}
|
||||
|
||||
if (status === NodeStatus.FAILED) {
|
||||
return <Text>nodeExecutionState.error</Text>;
|
||||
return <Text>{t('nodes.executionStateError')}</Text>;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -5,10 +5,12 @@ import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { isInvocationNodeData } from 'features/nodes/types/types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const data = useNodeData(nodeId);
|
||||
const { t } = useTranslation();
|
||||
const handleNotesChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
dispatch(nodeNotesChanged({ nodeId, notes: e.target.value }));
|
||||
@ -20,7 +22,7 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
}
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>Notes</FormLabel>
|
||||
<FormLabel>{t('nodes.notes')}</FormLabel>
|
||||
<IAITextarea
|
||||
value={data?.notes}
|
||||
onChange={handleNotesChanged}
|
||||
|
@ -14,6 +14,7 @@ import { fieldLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@ -33,10 +34,11 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
} = props;
|
||||
const label = useFieldLabel(nodeId, fieldName);
|
||||
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const [localTitle, setLocalTitle] = useState(
|
||||
label || fieldTemplateTitle || 'Unknown Field'
|
||||
label || fieldTemplateTitle || t('nodes.unknownFeild')
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
@ -44,10 +46,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) {
|
||||
return;
|
||||
}
|
||||
setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field');
|
||||
setLocalTitle(newTitle || fieldTemplateTitle || t('nodes.unknownField'));
|
||||
dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle }));
|
||||
},
|
||||
[label, fieldTemplateTitle, dispatch, nodeId, fieldName]
|
||||
[label, fieldTemplateTitle, dispatch, nodeId, fieldName, t]
|
||||
);
|
||||
|
||||
const handleChange = useCallback((newTitle: string) => {
|
||||
@ -56,8 +58,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
|
||||
useEffect(() => {
|
||||
// Another component may change the title; sync local title with global state
|
||||
setLocalTitle(label || fieldTemplateTitle || 'Unknown Field');
|
||||
}, [label, fieldTemplateTitle]);
|
||||
setLocalTitle(label || fieldTemplateTitle || t('nodes.unknownField'));
|
||||
}, [label, fieldTemplateTitle, t]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -30,6 +31,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
const label = useFieldLabel(nodeId, fieldName);
|
||||
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
|
||||
const input = useFieldInputKind(nodeId, fieldName);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
@ -119,7 +121,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup title={label || fieldTemplateTitle || 'Unknown Field'}>
|
||||
<MenuGroup
|
||||
title={label || fieldTemplateTitle || t('nodes.unknownField')}
|
||||
>
|
||||
{menuItems}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
} from 'features/nodes/types/types';
|
||||
import { startCase } from 'lodash-es';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@ -19,6 +20,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
||||
const field = useFieldData(nodeId, fieldName);
|
||||
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
|
||||
const isInputTemplate = isInputFieldTemplate(fieldTemplate);
|
||||
const { t } = useTranslation();
|
||||
const fieldTitle = useMemo(() => {
|
||||
if (isInputFieldValue(field)) {
|
||||
if (field.label && fieldTemplate?.title) {
|
||||
@ -33,11 +35,11 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
||||
return fieldTemplate.title;
|
||||
}
|
||||
|
||||
return 'Unknown Field';
|
||||
return t('nodes.unknownField');
|
||||
} else {
|
||||
return fieldTemplate?.title || 'Unknown Field';
|
||||
return fieldTemplate?.title || t('nodes.unknownField');
|
||||
}
|
||||
}, [field, fieldTemplate]);
|
||||
}, [field, fieldTemplate, t]);
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column' }}>
|
||||
|
@ -17,6 +17,7 @@ import { FaInfoCircle, FaTrash } from 'react-icons/fa';
|
||||
import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
import InputFieldRenderer from './InputFieldRenderer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -27,7 +28,7 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { isMouseOverNode, handleMouseOut, handleMouseOver } =
|
||||
useMouseOverNode(nodeId);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleRemoveField = useCallback(() => {
|
||||
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
|
||||
}, [dispatch, fieldName, nodeId]);
|
||||
@ -75,8 +76,8 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
<IAIIconButton
|
||||
aria-label="Remove from Linear View"
|
||||
tooltip="Remove from Linear View"
|
||||
aria-label={t('nodes.removeLinearView')}
|
||||
tooltip={t('nodes.removeLinearView')}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveField}
|
||||
|
@ -35,7 +35,11 @@ const EnumInputFieldComponent = (
|
||||
value={field.value}
|
||||
>
|
||||
{fieldTemplate.options.map((option) => (
|
||||
<option key={option}>{option}</option>
|
||||
<option key={option} value={option}>
|
||||
{fieldTemplate.ui_choice_labels
|
||||
? fieldTemplate.ui_choice_labels[option]
|
||||
: option}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ import { modelIdToLoRAModelParam } from 'features/parameters/util/modelIdToLoRAM
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LoRAModelInputFieldComponent = (
|
||||
props: FieldComponentProps<
|
||||
@ -25,6 +26,7 @@ const LoRAModelInputFieldComponent = (
|
||||
const lora = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: loraModels } = useGetLoRAModelsQuery();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!loraModels) {
|
||||
@ -92,9 +94,11 @@ const LoRAModelInputFieldComponent = (
|
||||
<IAIMantineSearchableSelect
|
||||
className="nowheel nodrag"
|
||||
value={selectedLoRAModel?.id ?? null}
|
||||
placeholder={data.length > 0 ? 'Select a LoRA' : 'No LoRAs available'}
|
||||
placeholder={
|
||||
data.length > 0 ? t('models.selectLoRA') : t('models.noLoRAsAvailable')
|
||||
}
|
||||
data={data}
|
||||
nothingFound="No matching LoRAs"
|
||||
nothingFound={t('models.noMatchingLoRAs')}
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={data.length === 0}
|
||||
filter={(value, item: SelectItem) =>
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
useGetMainModelsQuery,
|
||||
useGetOnnxModelsQuery,
|
||||
} from 'services/api/endpoints/models';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const MainModelInputFieldComponent = (
|
||||
props: FieldComponentProps<
|
||||
@ -29,7 +30,7 @@ const MainModelInputFieldComponent = (
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: onnxModels, isLoading: isLoadingOnnxModels } =
|
||||
useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS);
|
||||
const { data: mainModels, isLoading: isLoadingMainModels } =
|
||||
@ -127,7 +128,9 @@ const MainModelInputFieldComponent = (
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={
|
||||
data.length > 0 ? 'Select a model' : 'No models available'
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
|
@ -89,7 +89,7 @@ const RefinerModelInputFieldComponent = (
|
||||
return isLoading ? (
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.model')}
|
||||
placeholder="Loading..."
|
||||
placeholder={t('models.loading')}
|
||||
disabled={true}
|
||||
data={[]}
|
||||
/>
|
||||
@ -99,7 +99,11 @@ const RefinerModelInputFieldComponent = (
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={data.length > 0 ? 'Select a model' : 'No models available'}
|
||||
placeholder={
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
disabled={data.length === 0}
|
||||
|
@ -116,7 +116,7 @@ const ModelInputFieldComponent = (
|
||||
return isLoading ? (
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.model')}
|
||||
placeholder="Loading..."
|
||||
placeholder={t('models.loading')}
|
||||
disabled={true}
|
||||
data={[]}
|
||||
/>
|
||||
@ -126,7 +126,11 @@ const ModelInputFieldComponent = (
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={data.length > 0 ? 'Select a model' : 'No models available'}
|
||||
placeholder={
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
disabled={data.length === 0}
|
||||
|
@ -12,6 +12,7 @@ import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle'
|
||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -22,16 +23,17 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const label = useNodeLabel(nodeId);
|
||||
const templateTitle = useNodeTemplateTitle(nodeId);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [localTitle, setLocalTitle] = useState('');
|
||||
const handleSubmit = useCallback(
|
||||
async (newTitle: string) => {
|
||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||
setLocalTitle(
|
||||
newTitle || title || templateTitle || 'Problem Setting Title'
|
||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||
);
|
||||
},
|
||||
[dispatch, nodeId, title, templateTitle]
|
||||
[dispatch, nodeId, title, templateTitle, label, t]
|
||||
);
|
||||
|
||||
const handleChange = useCallback((newTitle: string) => {
|
||||
@ -40,8 +42,10 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
// Another component may change the title; sync local title with global state
|
||||
setLocalTitle(label || title || templateTitle || 'Problem Setting Title');
|
||||
}, [label, templateTitle, title]);
|
||||
setLocalTitle(
|
||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||
);
|
||||
}, [label, templateTitle, title, t]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
@ -8,10 +8,12 @@ import {
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function NodeOpacitySlider() {
|
||||
const dispatch = useAppDispatch();
|
||||
const nodeOpacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: number) => {
|
||||
@ -23,7 +25,7 @@ export default function NodeOpacitySlider() {
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<Slider
|
||||
aria-label="Node Opacity"
|
||||
aria-label={t('nodes.nodeOpacity')}
|
||||
value={nodeOpacity}
|
||||
min={0.5}
|
||||
max={1}
|
||||
|
@ -4,10 +4,11 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { FaPlus } from 'react-icons/fa';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const TopLeftPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const handleOpenAddNodePopover = useCallback(() => {
|
||||
dispatch(addNodePopoverOpened());
|
||||
}, [dispatch]);
|
||||
@ -15,8 +16,8 @@ const TopLeftPanel = () => {
|
||||
return (
|
||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}>
|
||||
<IAIIconButton
|
||||
tooltip="Add Node (Shift+A, Space)"
|
||||
aria-label="Add Node"
|
||||
tooltip={t('nodes.addNodeToolTip')}
|
||||
aria-label={t('nodes.addNode')}
|
||||
icon={<FaPlus />}
|
||||
onClick={handleOpenAddNodePopover}
|
||||
/>
|
||||
|
@ -29,6 +29,7 @@ import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { FaCog } from 'react-icons/fa';
|
||||
import { SelectionMode } from 'reactflow';
|
||||
import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
fontWeight: 600,
|
||||
@ -101,12 +102,14 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<IAIIconButton
|
||||
ref={ref}
|
||||
aria-label="Workflow Editor Settings"
|
||||
tooltip="Workflow Editor Settings"
|
||||
aria-label={t('nodes.workflowSettings')}
|
||||
tooltip={t('nodes.workflowSettings')}
|
||||
icon={<FaCog />}
|
||||
onClick={onOpen}
|
||||
/>
|
||||
@ -114,7 +117,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Workflow Editor Settings</ModalHeader>
|
||||
<ModalHeader>{t('nodes.workflowSettings')}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<Flex
|
||||
@ -129,31 +132,31 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
||||
formLabelProps={formLabelProps}
|
||||
onChange={handleChangeShouldAnimate}
|
||||
isChecked={shouldAnimateEdges}
|
||||
label="Animated Edges"
|
||||
helperText="Animate selected edges and edges connected to selected nodes"
|
||||
label={t('nodes.animatedEdges')}
|
||||
helperText={t('nodes.animatedEdgesHelp')}
|
||||
/>
|
||||
<Divider />
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnap}
|
||||
label="Snap to Grid"
|
||||
helperText="Snap nodes to grid when moved"
|
||||
label={t('nodes.snapToGrid')}
|
||||
helperText={t('nodes.snapToGridHelp')}
|
||||
/>
|
||||
<Divider />
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldColorEdges}
|
||||
onChange={handleChangeShouldColor}
|
||||
label="Color-Code Edges"
|
||||
helperText="Color-code edges according to their connected fields"
|
||||
label={t('nodes.colorCodeEdges')}
|
||||
helperText={t('nodes.colorCodeEdgesHelp')}
|
||||
/>
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={selectionModeIsChecked}
|
||||
onChange={handleChangeSelectionMode}
|
||||
label="Fully Contain Nodes to Select"
|
||||
helperText="Nodes must be fully inside the selection box to be selected"
|
||||
label={t('nodes.fullyContainNodes')}
|
||||
helperText={t('nodes.fullyContainNodesHelp')}
|
||||
/>
|
||||
<Heading size="sm" pt={4}>
|
||||
Advanced
|
||||
@ -162,8 +165,8 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldValidateGraph}
|
||||
onChange={handleChangeShouldValidate}
|
||||
label="Validate Connections and Graph"
|
||||
helperText="Prevent invalid connections from being made, and invalid graphs from being invoked"
|
||||
label={t('nodes.validateConnections')}
|
||||
helperText={t('nodes.validateConnectionsHelp')}
|
||||
/>
|
||||
<ReloadNodeTemplatesButton />
|
||||
</Flex>
|
||||
|
@ -9,6 +9,7 @@ import { memo } from 'react';
|
||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
||||
import NodeTitle from '../../flow/nodes/common/NodeTitle';
|
||||
import ScrollableContent from '../ScrollableContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -34,9 +35,12 @@ const selector = createSelector(
|
||||
|
||||
const InspectorDetailsTab = () => {
|
||||
const { data, template } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!template || !data) {
|
||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
return <Content data={data} template={template} />;
|
||||
|
@ -11,6 +11,7 @@ import { ImageOutput } from 'services/api/types';
|
||||
import { AnyResult } from 'services/events/types';
|
||||
import ScrollableContent from '../ScrollableContent';
|
||||
import ImageOutputPreview from './outputs/ImageOutputPreview';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -40,13 +41,18 @@ const selector = createSelector(
|
||||
|
||||
const InspectorOutputsTab = () => {
|
||||
const { node, template, nes } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!node || !nes || !isInvocationNode(node)) {
|
||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
if (nes.outputs.length === 0) {
|
||||
return <IAINoContentFallback label="No outputs recorded" icon={null} />;
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noOutputRecorded')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -77,7 +83,7 @@ const InspectorOutputsTab = () => {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<DataViewer data={nes.outputs} label="Node Outputs" />
|
||||
<DataViewer data={nes.outputs} label={t('nodes.nodesOutputs')} />
|
||||
)}
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
|
@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -29,12 +30,15 @@ const selector = createSelector(
|
||||
|
||||
const NodeTemplateInspector = () => {
|
||||
const { template } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!template) {
|
||||
return <IAINoContentFallback label="No node selected" icon={null} />;
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
return <DataViewer data={template} label="Node Template" />;
|
||||
return <DataViewer data={template} label={t('nodes.NodeTemplate')} />;
|
||||
};
|
||||
|
||||
export default memo(NodeTemplateInspector);
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import ScrollableContent from '../ScrollableContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -85,6 +86,8 @@ const WorkflowGeneralTab = () => {
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ScrollableContent>
|
||||
<Flex
|
||||
@ -96,28 +99,36 @@ const WorkflowGeneralTab = () => {
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<IAIInput label="Name" value={name} onChange={handleChangeName} />
|
||||
<IAIInput
|
||||
label="Version"
|
||||
label={t('nodes.workflowName')}
|
||||
value={name}
|
||||
onChange={handleChangeName}
|
||||
/>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowVersion')}
|
||||
value={version}
|
||||
onChange={handleChangeVersion}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<IAIInput
|
||||
label="Author"
|
||||
label={t('nodes.workflowAuthor')}
|
||||
value={author}
|
||||
onChange={handleChangeAuthor}
|
||||
/>
|
||||
<IAIInput
|
||||
label="Contact"
|
||||
label={t('nodes.workflowContact')}
|
||||
value={contact}
|
||||
onChange={handleChangeContact}
|
||||
/>
|
||||
</Flex>
|
||||
<IAIInput label="Tags" value={tags} onChange={handleChangeTags} />
|
||||
<IAIInput
|
||||
label={t('nodes.workflowTags')}
|
||||
value={tags}
|
||||
onChange={handleChangeTags}
|
||||
/>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column' }}>
|
||||
<FormLabel>Short Description</FormLabel>
|
||||
<FormLabel>{t('nodes.workflowDescription')}</FormLabel>
|
||||
<IAITextarea
|
||||
onChange={handleChangeDescription}
|
||||
value={description}
|
||||
@ -126,7 +137,7 @@ const WorkflowGeneralTab = () => {
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
||||
<FormLabel>Notes</FormLabel>
|
||||
<FormLabel>{t('nodes.workflowNotes')}</FormLabel>
|
||||
<IAITextarea
|
||||
onChange={handleChangeNotes}
|
||||
value={notes}
|
||||
|
@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const WorkflowJSONTab = () => {
|
||||
const workflow = useWorkflow();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -15,7 +17,7 @@ const WorkflowJSONTab = () => {
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<DataViewer data={workflow} label="Workflow" />
|
||||
<DataViewer data={workflow} label={t('nodes.workflow')} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { memo } from 'react';
|
||||
import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField';
|
||||
import ScrollableContent from '../ScrollableContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -20,6 +21,7 @@ const selector = createSelector(
|
||||
|
||||
const WorkflowLinearTab = () => {
|
||||
const { fields } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -51,7 +53,7 @@ const WorkflowLinearTab = () => {
|
||||
))
|
||||
) : (
|
||||
<IAINoContentFallback
|
||||
label="No fields added to Linear View"
|
||||
label={t('nodes.noFieldsLinearview')}
|
||||
icon={null}
|
||||
/>
|
||||
)}
|
||||
|
@ -9,10 +9,12 @@ import { memo, useCallback } from 'react';
|
||||
import { ZodError } from 'zod';
|
||||
import { fromZodError, fromZodIssue } from 'zod-validation-error';
|
||||
import { workflowLoadRequested } from '../store/actions';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useLoadWorkflowFromFile = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const logger = useLogger('nodes');
|
||||
const { t } = useTranslation();
|
||||
const loadWorkflowFromFile = useCallback(
|
||||
(file: File | null) => {
|
||||
if (!file) {
|
||||
@ -28,7 +30,7 @@ export const useLoadWorkflowFromFile = () => {
|
||||
|
||||
if (!result.success) {
|
||||
const { message } = fromZodError(result.error, {
|
||||
prefix: 'Workflow Validation Error',
|
||||
prefix: t('nodes.workflowValidation'),
|
||||
});
|
||||
|
||||
logger.error({ error: parseify(result.error) }, message);
|
||||
@ -36,7 +38,7 @@ export const useLoadWorkflowFromFile = () => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: 'Unable to Validate Workflow',
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
})
|
||||
@ -54,7 +56,7 @@ export const useLoadWorkflowFromFile = () => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: 'Unable to Load Workflow',
|
||||
title: t('nodes.unableToLoadWorkflow'),
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
@ -64,7 +66,7 @@ export const useLoadWorkflowFromFile = () => {
|
||||
|
||||
reader.readAsText(file);
|
||||
},
|
||||
[dispatch, logger]
|
||||
[dispatch, logger, t]
|
||||
);
|
||||
|
||||
return loadWorkflowFromFile;
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
} from 'features/nodes/types/constants';
|
||||
import { FieldType } from 'features/nodes/types/types';
|
||||
import { HandleType } from 'reactflow';
|
||||
import i18n from 'i18next';
|
||||
|
||||
/**
|
||||
* NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/hooks/useIsValidConnection.ts`
|
||||
@ -20,17 +21,17 @@ export const makeConnectionErrorSelector = (
|
||||
fieldName: string,
|
||||
handleType: HandleType,
|
||||
fieldType?: FieldType
|
||||
) =>
|
||||
createSelector(stateSelector, (state) => {
|
||||
) => {
|
||||
return createSelector(stateSelector, (state) => {
|
||||
if (!fieldType) {
|
||||
return 'No field type';
|
||||
return i18n.t('nodes.noFieldType');
|
||||
}
|
||||
|
||||
const { currentConnectionFieldType, connectionStartParams, nodes, edges } =
|
||||
state.nodes;
|
||||
|
||||
if (!connectionStartParams || !currentConnectionFieldType) {
|
||||
return 'No connection in progress';
|
||||
return i18n.t('nodes.noConnectionInProgress');
|
||||
}
|
||||
|
||||
const {
|
||||
@ -40,7 +41,7 @@ export const makeConnectionErrorSelector = (
|
||||
} = connectionStartParams;
|
||||
|
||||
if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
|
||||
return 'No connection data';
|
||||
return i18n.t('nodes.noConnectionData');
|
||||
}
|
||||
|
||||
const targetType =
|
||||
@ -49,14 +50,14 @@ export const makeConnectionErrorSelector = (
|
||||
handleType === 'source' ? fieldType : currentConnectionFieldType;
|
||||
|
||||
if (nodeId === connectionNodeId) {
|
||||
return 'Cannot connect to self';
|
||||
return i18n.t('nodes.cannotConnectToSelf');
|
||||
}
|
||||
|
||||
if (handleType === connectionHandleType) {
|
||||
if (handleType === 'source') {
|
||||
return 'Cannot connect output to output';
|
||||
return i18n.t('nodes.cannotConnectOutputToOutput');
|
||||
}
|
||||
return 'Cannot connect input to input';
|
||||
return i18n.t('nodes.cannotConnectInputToInput');
|
||||
}
|
||||
|
||||
if (
|
||||
@ -66,7 +67,7 @@ export const makeConnectionErrorSelector = (
|
||||
// except CollectionItem inputs can have multiples
|
||||
targetType !== 'CollectionItem'
|
||||
) {
|
||||
return 'Input may only have one connection';
|
||||
return i18n.t('nodes.inputMayOnlyHaveOneConnection');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -125,7 +126,7 @@ export const makeConnectionErrorSelector = (
|
||||
isIntToFloat
|
||||
)
|
||||
) {
|
||||
return 'Field types must match';
|
||||
return i18n.t('nodes.fieldTypesMustMatch');
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,8 +138,9 @@ export const makeConnectionErrorSelector = (
|
||||
);
|
||||
|
||||
if (!isGraphAcyclic) {
|
||||
return 'Connection would create a cycle';
|
||||
return i18n.t('nodes.connectionWouldCreateCycle');
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
@ -288,7 +288,7 @@ export type BooleanPolymorphicInputFieldValue = z.infer<
|
||||
|
||||
export const zEnumInputFieldValue = zInputFieldValueBase.extend({
|
||||
type: z.literal('enum'),
|
||||
value: z.union([z.string(), z.number()]).optional(),
|
||||
value: z.string().optional(),
|
||||
});
|
||||
export type EnumInputFieldValue = z.infer<typeof zEnumInputFieldValue>;
|
||||
|
||||
@ -861,10 +861,10 @@ export type IPAdapterInputFieldTemplate = InputFieldTemplateBase & {
|
||||
};
|
||||
|
||||
export type EnumInputFieldTemplate = InputFieldTemplateBase & {
|
||||
default: string | number;
|
||||
default: string;
|
||||
type: 'enum';
|
||||
enumType: 'string' | 'number';
|
||||
options: Array<string | number>;
|
||||
options: string[];
|
||||
labels?: { [key: string]: string };
|
||||
};
|
||||
|
||||
export type MainModelInputFieldTemplate = InputFieldTemplateBase & {
|
||||
|
@ -684,8 +684,8 @@ const buildEnumInputFieldTemplate = ({
|
||||
const template: EnumInputFieldTemplate = {
|
||||
...baseField,
|
||||
type: 'enum',
|
||||
enumType: (schemaObject.type as 'string' | 'number') ?? 'string', // TODO: dangerous?
|
||||
options: options,
|
||||
options,
|
||||
ui_choice_labels: schemaObject.ui_choice_labels,
|
||||
default: schemaObject.default ?? options[0],
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
||||
|
||||
const FIELD_VALUE_FALLBACK_MAP = {
|
||||
'enum.number': 0,
|
||||
'enum.string': '',
|
||||
enum: '',
|
||||
boolean: false,
|
||||
BooleanCollection: [],
|
||||
BooleanPolymorphic: false,
|
||||
@ -64,19 +63,8 @@ export const buildInputFieldValue = (
|
||||
fieldKind: 'input',
|
||||
} as InputFieldValue;
|
||||
|
||||
if (template.type === 'enum') {
|
||||
if (template.enumType === 'number') {
|
||||
fieldValue.value =
|
||||
template.default ?? FIELD_VALUE_FALLBACK_MAP['enum.number'];
|
||||
}
|
||||
if (template.enumType === 'string') {
|
||||
fieldValue.value =
|
||||
template.default ?? FIELD_VALUE_FALLBACK_MAP['enum.string'];
|
||||
}
|
||||
} else {
|
||||
fieldValue.value =
|
||||
template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type];
|
||||
}
|
||||
fieldValue.value =
|
||||
template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type];
|
||||
|
||||
return fieldValue;
|
||||
};
|
||||
|
@ -60,11 +60,23 @@ const isNotInDenylist = (schema: InvocationSchemaObject) =>
|
||||
!invocationDenylist.includes(schema.properties.type.default);
|
||||
|
||||
export const parseSchema = (
|
||||
openAPI: OpenAPIV3.Document
|
||||
openAPI: OpenAPIV3.Document,
|
||||
nodesAllowlistExtra: string[] | undefined = undefined,
|
||||
nodesDenylistExtra: string[] | undefined = undefined
|
||||
): Record<string, InvocationTemplate> => {
|
||||
const filteredSchemas = Object.values(openAPI.components?.schemas ?? {})
|
||||
.filter(isInvocationSchemaObject)
|
||||
.filter(isNotInDenylist);
|
||||
.filter(isNotInDenylist)
|
||||
.filter((schema) =>
|
||||
nodesAllowlistExtra
|
||||
? nodesAllowlistExtra.includes(schema.properties.type.default)
|
||||
: true
|
||||
)
|
||||
.filter((schema) =>
|
||||
nodesDenylistExtra
|
||||
? !nodesDenylistExtra.includes(schema.properties.type.default)
|
||||
: true
|
||||
);
|
||||
|
||||
const invocations = filteredSchemas.reduce<
|
||||
Record<string, InvocationTemplate>
|
||||
@ -73,7 +85,7 @@ export const parseSchema = (
|
||||
const title = schema.title.replace('Invocation', '');
|
||||
const tags = schema.tags ?? [];
|
||||
const description = schema.description ?? '';
|
||||
const version = schema.version ?? '';
|
||||
const version = schema.version;
|
||||
|
||||
const inputs = reduce(
|
||||
schema.properties,
|
||||
|
Reference in New Issue
Block a user