Translation update (#4503)

* Update Translations

* Fix Prettier Issue

* Fix Error in invokebutton.tsx

* More Translations

* few Fixes

* More Translations

* More Translations and lint Fixes

* Update constants.ts

Revert "Update constants.ts"

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
This commit is contained in:
mickr777 2023-09-13 17:31:34 +10:00 committed by GitHub
parent 30792cb259
commit 8c63173b0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 1502 additions and 961 deletions

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ import { isInvocationNode } from 'features/nodes/types/types';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { forEach, map } from 'lodash-es'; import { forEach, map } from 'lodash-es';
import { getConnectedEdges } from 'reactflow'; import { getConnectedEdges } from 'reactflow';
import i18n from 'i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector, activeTabNameSelector], [stateSelector, activeTabNameSelector],
@ -19,22 +20,22 @@ const selector = createSelector(
// Cannot generate if already processing an image // Cannot generate if already processing an image
if (isProcessing) { if (isProcessing) {
reasons.push('System busy'); reasons.push(i18n.t('parameters.invoke.systemBusy'));
} }
// Cannot generate if not connected // Cannot generate if not connected
if (!isConnected) { if (!isConnected) {
reasons.push('System disconnected'); reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
} }
if (activeTabName === 'img2img' && !initialImage) { if (activeTabName === 'img2img' && !initialImage) {
reasons.push('No initial image selected'); reasons.push(i18n.t('parameters.invoke.noInitialImageSelected'));
} }
if (activeTabName === 'nodes') { if (activeTabName === 'nodes') {
if (nodes.shouldValidateGraph) { if (nodes.shouldValidateGraph) {
if (!nodes.nodes.length) { if (!nodes.nodes.length) {
reasons.push('No nodes in graph'); reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
} }
nodes.nodes.forEach((node) => { nodes.nodes.forEach((node) => {
@ -46,7 +47,7 @@ const selector = createSelector(
if (!nodeTemplate) { if (!nodeTemplate) {
// Node type not found // Node type not found
reasons.push('Missing node template'); reasons.push(i18n.t('parameters.invoke.missingNodeTemplate'));
return; return;
} }
@ -60,7 +61,7 @@ const selector = createSelector(
); );
if (!fieldTemplate) { if (!fieldTemplate) {
reasons.push('Missing field template'); reasons.push(i18n.t('parameters.invoke.missingFieldTemplate'));
return; return;
} }
@ -70,9 +71,10 @@ const selector = createSelector(
!hasConnection !hasConnection
) { ) {
reasons.push( reasons.push(
`${node.data.label || nodeTemplate.title} -> ${ i18n.t('parameters.invoke.missingInputForField', {
field.label || fieldTemplate.title nodeLabel: node.data.label || nodeTemplate.title,
} missing input` fieldLabel: field.label || fieldTemplate.title,
})
); );
return; return;
} }
@ -81,7 +83,7 @@ const selector = createSelector(
} }
} else { } else {
if (!model) { if (!model) {
reasons.push('No model selected'); reasons.push(i18n.t('parameters.invoke.noModelSelected'));
} }
if (state.controlNet.isEnabled) { if (state.controlNet.isEnabled) {
@ -90,7 +92,9 @@ const selector = createSelector(
return; return;
} }
if (!controlNet.model) { if (!controlNet.model) {
reasons.push(`ControlNet ${i + 1} has no model selected.`); reasons.push(
i18n.t('parameters.invoke.noModelForControlNet', { index: i + 1 })
);
} }
if ( if (
@ -98,7 +102,11 @@ const selector = createSelector(
(!controlNet.processedControlImage && (!controlNet.processedControlImage &&
controlNet.processorType !== 'none') controlNet.processorType !== 'none')
) { ) {
reasons.push(`ControlNet ${i + 1} has no control image`); reasons.push(
i18n.t('parameters.invoke.noControlImageForControlNet', {
index: i + 1,
})
);
} }
}); });
} }

View File

@ -21,6 +21,7 @@ import {
useRemoveImagesFromBoardMutation, useRemoveImagesFromBoardMutation,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import { changeBoardReset, isModalOpenChanged } from '../store/slice'; import { changeBoardReset, isModalOpenChanged } from '../store/slice';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -42,10 +43,11 @@ const ChangeBoardModal = () => {
const { imagesToChange, isModalOpen } = useAppSelector(selector); const { imagesToChange, isModalOpen } = useAppSelector(selector);
const [addImagesToBoard] = useAddImagesToBoardMutation(); const [addImagesToBoard] = useAddImagesToBoardMutation();
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation(); const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
const { t } = useTranslation();
const data = useMemo(() => { const data = useMemo(() => {
const data: { label: string; value: string }[] = [ const data: { label: string; value: string }[] = [
{ label: 'Uncategorized', value: 'none' }, { label: t('boards.uncategorized'), value: 'none' },
]; ];
(boards ?? []).forEach((board) => (boards ?? []).forEach((board) =>
data.push({ data.push({
@ -55,7 +57,7 @@ const ChangeBoardModal = () => {
); );
return data; return data;
}, [boards]); }, [boards, t]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
dispatch(changeBoardReset()); dispatch(changeBoardReset());
@ -97,7 +99,7 @@ const ChangeBoardModal = () => {
<AlertDialogOverlay> <AlertDialogOverlay>
<AlertDialogContent> <AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold"> <AlertDialogHeader fontSize="lg" fontWeight="bold">
Change Board {t('boards.changeBoard')}
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogBody> <AlertDialogBody>
@ -107,7 +109,9 @@ const ChangeBoardModal = () => {
{`${imagesToChange.length > 1 ? 's' : ''}`} to board: {`${imagesToChange.length > 1 ? 's' : ''}`} to board:
</Text> </Text>
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
placeholder={isFetching ? 'Loading...' : 'Select Board'} placeholder={
isFetching ? t('boards.loading') : t('boards.selectBoard')
}
disabled={isFetching} disabled={isFetching}
onChange={(v) => setSelectedBoard(v)} onChange={(v) => setSelectedBoard(v)}
value={selectedBoard} value={selectedBoard}
@ -117,10 +121,10 @@ const ChangeBoardModal = () => {
</AlertDialogBody> </AlertDialogBody>
<AlertDialogFooter> <AlertDialogFooter>
<IAIButton ref={cancelRef} onClick={handleClose}> <IAIButton ref={cancelRef} onClick={handleClose}>
Cancel {t('boards.cancel')}
</IAIButton> </IAIButton>
<IAIButton colorScheme="accent" onClick={handleChangeBoard} ml={3}> <IAIButton colorScheme="accent" onClick={handleChangeBoard} ml={3}>
Move {t('boards.move')}
</IAIButton> </IAIButton>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>

View File

@ -28,6 +28,7 @@ import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode'; import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect'; import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode'; import ParamControlNetResizeMode from './parameters/ParamControlNetResizeMode';
import { useTranslation } from 'react-i18next';
type ControlNetProps = { type ControlNetProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -37,6 +38,7 @@ const ControlNet = (props: ControlNetProps) => {
const { controlNet } = props; const { controlNet } = props;
const { controlNetId } = controlNet; const { controlNetId } = controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const activeTabName = useAppSelector(activeTabNameSelector); const activeTabName = useAppSelector(activeTabNameSelector);
@ -95,8 +97,8 @@ const ControlNet = (props: ControlNetProps) => {
> >
<Flex sx={{ gap: 2, alignItems: 'center' }}> <Flex sx={{ gap: 2, alignItems: 'center' }}>
<IAISwitch <IAISwitch
tooltip="Toggle this ControlNet" tooltip={t('controlnet.toggleControlNet')}
aria-label="Toggle this ControlNet" aria-label={t('controlnet.toggleControlNet')}
isChecked={isEnabled} isChecked={isEnabled}
onChange={handleToggleIsEnabled} onChange={handleToggleIsEnabled}
/> />
@ -117,23 +119,31 @@ const ControlNet = (props: ControlNetProps) => {
)} )}
<IAIIconButton <IAIIconButton
size="sm" size="sm"
tooltip="Duplicate" tooltip={t('controlnet.duplicate')}
aria-label="Duplicate" aria-label={t('controlnet.duplicate')}
onClick={handleDuplicate} onClick={handleDuplicate}
icon={<FaCopy />} icon={<FaCopy />}
/> />
<IAIIconButton <IAIIconButton
size="sm" size="sm"
tooltip="Delete" tooltip={t('controlnet.delete')}
aria-label="Delete" aria-label={t('controlnet.delete')}
colorScheme="error" colorScheme="error"
onClick={handleDelete} onClick={handleDelete}
icon={<FaTrash />} icon={<FaTrash />}
/> />
<IAIIconButton <IAIIconButton
size="sm" size="sm"
tooltip={isExpanded ? 'Hide Advanced' : 'Show Advanced'} tooltip={
aria-label={isExpanded ? 'Hide Advanced' : 'Show Advanced'} isExpanded
? t('controlnet.hideAdvanced')
: t('controlnet.showAdvanced')
}
aria-label={
isExpanded
? t('controlnet.hideAdvanced')
: t('controlnet.showAdvanced')
}
onClick={toggleIsExpanded} onClick={toggleIsExpanded}
variant="ghost" variant="ghost"
sx={{ sx={{

View File

@ -26,6 +26,7 @@ import {
ControlNetConfig, ControlNetConfig,
controlNetImageChanged, controlNetImageChanged,
} from '../store/controlNetSlice'; } from '../store/controlNetSlice';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -56,6 +57,7 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
} = controlNet; } = controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const { pendingControlImages, autoAddBoardId } = useAppSelector(selector); const { pendingControlImages, autoAddBoardId } = useAppSelector(selector);
const activeTabName = useAppSelector(activeTabNameSelector); const activeTabName = useAppSelector(activeTabNameSelector);
@ -208,18 +210,18 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => {
<IAIDndImageIcon <IAIDndImageIcon
onClick={handleResetControlImage} onClick={handleResetControlImage}
icon={controlImage ? <FaUndo /> : undefined} icon={controlImage ? <FaUndo /> : undefined}
tooltip="Reset Control Image" tooltip={t('controlnet.resetControlImage')}
/> />
<IAIDndImageIcon <IAIDndImageIcon
onClick={handleSaveControlImage} onClick={handleSaveControlImage}
icon={controlImage ? <FaSave size={16} /> : undefined} icon={controlImage ? <FaSave size={16} /> : undefined}
tooltip="Save Control Image" tooltip={t('controlnet.saveControlImage')}
styleOverrides={{ marginTop: 6 }} styleOverrides={{ marginTop: 6 }}
/> />
<IAIDndImageIcon <IAIDndImageIcon
onClick={handleSetControlImageToDimensions} onClick={handleSetControlImageToDimensions}
icon={controlImage ? <FaRulerVertical size={16} /> : undefined} icon={controlImage ? <FaRulerVertical size={16} /> : undefined}
tooltip="Set Control Image Dimensions To W/H" tooltip={t('controlnet.setControlImageDimensions')}
styleOverrides={{ marginTop: 12 }} styleOverrides={{ marginTop: 12 }}
/> />
</> </>

View File

@ -6,6 +6,7 @@ import {
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
import { selectIsBusy } from 'features/system/store/systemSelectors'; import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -15,6 +16,7 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet; const { controlNetId, isEnabled, shouldAutoConfig } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleShouldAutoConfigChanged = useCallback(() => { const handleShouldAutoConfigChanged = useCallback(() => {
dispatch(controlNetAutoConfigToggled({ controlNetId })); dispatch(controlNetAutoConfigToggled({ controlNetId }));
@ -22,8 +24,8 @@ const ParamControlNetShouldAutoConfig = (props: Props) => {
return ( return (
<IAISwitch <IAISwitch
label="Auto configure processor" label={t('controlnet.autoConfigure')}
aria-label="Auto configure processor" aria-label={t('controlnet.autoConfigure')}
isChecked={shouldAutoConfig} isChecked={shouldAutoConfig}
onChange={handleShouldAutoConfigChanged} onChange={handleShouldAutoConfigChanged}
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}

View File

@ -8,6 +8,7 @@ import {
import { ControlNetConfig } from 'features/controlNet/store/controlNetSlice'; import { ControlNetConfig } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaImage, FaMask } from 'react-icons/fa'; import { FaImage, FaMask } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
type ControlNetCanvasImageImportsProps = { type ControlNetCanvasImageImportsProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -18,6 +19,7 @@ const ControlNetCanvasImageImports = (
) => { ) => {
const { controlNet } = props; const { controlNet } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleImportImageFromCanvas = useCallback(() => { const handleImportImageFromCanvas = useCallback(() => {
dispatch(canvasImageToControlNet({ controlNet })); dispatch(canvasImageToControlNet({ controlNet }));
@ -36,15 +38,15 @@ const ControlNetCanvasImageImports = (
<IAIIconButton <IAIIconButton
size="sm" size="sm"
icon={<FaImage />} icon={<FaImage />}
tooltip="Import Image From Canvas" tooltip={t('controlnet.importImageFromCanvas')}
aria-label="Import Image From Canvas" aria-label={t('controlnet.importImageFromCanvas')}
onClick={handleImportImageFromCanvas} onClick={handleImportImageFromCanvas}
/> />
<IAIIconButton <IAIIconButton
size="sm" size="sm"
icon={<FaMask />} icon={<FaMask />}
tooltip="Import Mask From Canvas" tooltip={t('controlnet.importMaskFromCanvas')}
aria-label="Import Mask From Canvas" aria-label={t('controlnet.importMaskFromCanvas')}
onClick={handleImportMaskFromCanvas} onClick={handleImportMaskFromCanvas}
/> />
</Flex> </Flex>

View File

@ -16,6 +16,7 @@ import {
controlNetEndStepPctChanged, controlNetEndStepPctChanged,
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -27,6 +28,7 @@ const ParamControlNetBeginEnd = (props: Props) => {
const { beginStepPct, endStepPct, isEnabled, controlNetId } = const { beginStepPct, endStepPct, isEnabled, controlNetId } =
props.controlNet; props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleStepPctChanged = useCallback( const handleStepPctChanged = useCallback(
(v: number[]) => { (v: number[]) => {
@ -48,10 +50,10 @@ const ParamControlNetBeginEnd = (props: Props) => {
return ( return (
<FormControl isDisabled={!isEnabled}> <FormControl isDisabled={!isEnabled}>
<FormLabel>Begin / End Step Percentage</FormLabel> <FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
<HStack w="100%" gap={2} alignItems="center"> <HStack w="100%" gap={2} alignItems="center">
<RangeSlider <RangeSlider
aria-label={['Begin Step %', 'End Step %']} aria-label={['Begin Step %', 'End Step %!']}
value={[beginStepPct, endStepPct]} value={[beginStepPct, endStepPct]}
onChange={handleStepPctChanged} onChange={handleStepPctChanged}
min={0} min={0}

View File

@ -6,23 +6,25 @@ import {
controlNetControlModeChanged, controlNetControlModeChanged,
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type ParamControlNetControlModeProps = { type ParamControlNetControlModeProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
}; };
const CONTROL_MODE_DATA = [
{ label: 'Balanced', value: 'balanced' },
{ label: 'Prompt', value: 'more_prompt' },
{ label: 'Control', value: 'more_control' },
{ label: 'Mega Control', value: 'unbalanced' },
];
export default function ParamControlNetControlMode( export default function ParamControlNetControlMode(
props: ParamControlNetControlModeProps props: ParamControlNetControlModeProps
) { ) {
const { controlMode, isEnabled, controlNetId } = props.controlNet; const { controlMode, isEnabled, controlNetId } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const CONTROL_MODE_DATA = [
{ label: t('controlnet.balanced'), value: 'balanced' },
{ label: t('controlnet.prompt'), value: 'more_prompt' },
{ label: t('controlnet.control'), value: 'more_control' },
{ label: t('controlnet.megaControl'), value: 'unbalanced' },
];
const handleControlModeChange = useCallback( const handleControlModeChange = useCallback(
(controlMode: ControlModes) => { (controlMode: ControlModes) => {
@ -34,7 +36,7 @@ export default function ParamControlNetControlMode(
return ( return (
<IAIMantineSelect <IAIMantineSelect
disabled={!isEnabled} disabled={!isEnabled}
label="Control Mode" label={t('controlnet.controlMode')}
data={CONTROL_MODE_DATA} data={CONTROL_MODE_DATA}
value={String(controlMode)} value={String(controlMode)}
onChange={handleControlModeChange} onChange={handleControlModeChange}

View File

@ -15,6 +15,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models'; import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
type ParamControlNetModelProps = { type ParamControlNetModelProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -35,6 +36,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { mainModel } = useAppSelector(selector); const { mainModel } = useAppSelector(selector);
const { t } = useTranslation();
const { data: controlNetModels } = useGetControlNetModelsQuery(); const { data: controlNetModels } = useGetControlNetModelsQuery();
@ -58,13 +60,13 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
group: MODEL_TYPE_MAP[model.base_model], group: MODEL_TYPE_MAP[model.base_model],
disabled, disabled,
tooltip: disabled tooltip: disabled
? `Incompatible base model: ${model.base_model}` ? `${t('controlnet.incompatibleBaseModel')} ${model.base_model}`
: undefined, : undefined,
}); });
}); });
return data; return data;
}, [controlNetModels, mainModel?.base_model]); }, [controlNetModels, mainModel?.base_model, t]);
// grab the full model entity from the RTK Query cache // grab the full model entity from the RTK Query cache
const selectedModel = useMemo( const selectedModel = useMemo(
@ -105,7 +107,7 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
error={ error={
!selectedModel || mainModel?.base_model !== selectedModel.base_model !selectedModel || mainModel?.base_model !== selectedModel.base_model
} }
placeholder="Select a model" placeholder={t('controlnet.selectModel')}
value={selectedModel?.id ?? null} value={selectedModel?.id ?? null}
onChange={handleModelChanged} onChange={handleModelChanged}
disabled={isBusy || !isEnabled} disabled={isBusy || !isEnabled}

View File

@ -15,6 +15,7 @@ import {
controlNetProcessorTypeChanged, controlNetProcessorTypeChanged,
} from '../../store/controlNetSlice'; } from '../../store/controlNetSlice';
import { ControlNetProcessorType } from '../../store/types'; import { ControlNetProcessorType } from '../../store/types';
import { useTranslation } from 'react-i18next';
type ParamControlNetProcessorSelectProps = { type ParamControlNetProcessorSelectProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -57,6 +58,7 @@ const ParamControlNetProcessorSelect = (
const { controlNetId, isEnabled, processorNode } = props.controlNet; const { controlNetId, isEnabled, processorNode } = props.controlNet;
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const controlNetProcessors = useAppSelector(selector); const controlNetProcessors = useAppSelector(selector);
const { t } = useTranslation();
const handleProcessorTypeChanged = useCallback( const handleProcessorTypeChanged = useCallback(
(v: string | null) => { (v: string | null) => {
@ -72,7 +74,7 @@ const ParamControlNetProcessorSelect = (
return ( return (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
label="Processor" label={t('controlnet.processor')}
value={processorNode.type ?? 'canny_image_processor'} value={processorNode.type ?? 'canny_image_processor'}
data={controlNetProcessors} data={controlNetProcessors}
onChange={handleProcessorTypeChanged} onChange={handleProcessorTypeChanged}

View File

@ -6,22 +6,24 @@ import {
controlNetResizeModeChanged, controlNetResizeModeChanged,
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type ParamControlNetResizeModeProps = { type ParamControlNetResizeModeProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
}; };
const RESIZE_MODE_DATA = [
{ label: 'Resize', value: 'just_resize' },
{ label: 'Crop', value: 'crop_resize' },
{ label: 'Fill', value: 'fill_resize' },
];
export default function ParamControlNetResizeMode( export default function ParamControlNetResizeMode(
props: ParamControlNetResizeModeProps props: ParamControlNetResizeModeProps
) { ) {
const { resizeMode, isEnabled, controlNetId } = props.controlNet; const { resizeMode, isEnabled, controlNetId } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const RESIZE_MODE_DATA = [
{ label: t('controlnet.resize'), value: 'just_resize' },
{ label: t('controlnet.crop'), value: 'crop_resize' },
{ label: t('controlnet.fill'), value: 'fill_resize' },
];
const handleResizeModeChange = useCallback( const handleResizeModeChange = useCallback(
(resizeMode: ResizeModes) => { (resizeMode: ResizeModes) => {
@ -33,7 +35,7 @@ export default function ParamControlNetResizeMode(
return ( return (
<IAIMantineSelect <IAIMantineSelect
disabled={!isEnabled} disabled={!isEnabled}
label="Resize Mode" label={t('controlnet.resizeMode')}
data={RESIZE_MODE_DATA} data={RESIZE_MODE_DATA}
value={String(resizeMode)} value={String(resizeMode)}
onChange={handleResizeModeChange} onChange={handleResizeModeChange}

View File

@ -5,6 +5,7 @@ import {
controlNetWeightChanged, controlNetWeightChanged,
} from 'features/controlNet/store/controlNetSlice'; } from 'features/controlNet/store/controlNetSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type ParamControlNetWeightProps = { type ParamControlNetWeightProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
@ -13,6 +14,7 @@ type ParamControlNetWeightProps = {
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => { const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
const { weight, isEnabled, controlNetId } = props.controlNet; const { weight, isEnabled, controlNetId } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleWeightChanged = useCallback( const handleWeightChanged = useCallback(
(weight: number) => { (weight: number) => {
dispatch(controlNetWeightChanged({ controlNetId, weight })); dispatch(controlNetWeightChanged({ controlNetId, weight }));
@ -23,7 +25,7 @@ const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
return ( return (
<IAISlider <IAISlider
isDisabled={!isEnabled} isDisabled={!isEnabled}
label="Weight" label={t('controlnet.weight')}
value={weight} value={weight}
onChange={handleWeightChanged} onChange={handleWeightChanged}
min={0} min={0}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.canny_image_processor
.default as RequiredCannyImageProcessorInvocation; .default as RequiredCannyImageProcessorInvocation;
@ -21,6 +22,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
const { low_threshold, high_threshold } = processorNode; const { low_threshold, high_threshold } = processorNode;
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const handleLowThresholdChanged = useCallback( const handleLowThresholdChanged = useCallback(
(v: number) => { (v: number) => {
@ -52,7 +54,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
label="Low Threshold" label={t('controlnet.lowThreshold')}
value={low_threshold} value={low_threshold}
onChange={handleLowThresholdChanged} onChange={handleLowThresholdChanged}
handleReset={handleLowThresholdReset} handleReset={handleLowThresholdReset}
@ -64,7 +66,7 @@ const CannyProcessor = (props: CannyProcessorProps) => {
/> />
<IAISlider <IAISlider
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
label="High Threshold" label={t('controlnet.highThreshold')}
value={high_threshold} value={high_threshold}
onChange={handleHighThresholdChanged} onChange={handleHighThresholdChanged}
handleReset={handleHighThresholdReset} handleReset={handleHighThresholdReset}

View File

@ -6,6 +6,7 @@ import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { selectIsBusy } from 'features/system/store/systemSelectors'; import { selectIsBusy } from 'features/system/store/systemSelectors';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.content_shuffle_image_processor
.default as RequiredContentShuffleImageProcessorInvocation; .default as RequiredContentShuffleImageProcessorInvocation;
@ -21,6 +22,7 @@ const ContentShuffleProcessor = (props: Props) => {
const { image_resolution, detect_resolution, w, h, f } = processorNode; const { image_resolution, detect_resolution, w, h, f } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -90,7 +92,7 @@ const ContentShuffleProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -102,7 +104,7 @@ const ContentShuffleProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -114,7 +116,7 @@ const ContentShuffleProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="W" label={t('controlnet.w')}
value={w} value={w}
onChange={handleWChanged} onChange={handleWChanged}
handleReset={handleWReset} handleReset={handleWReset}
@ -126,7 +128,7 @@ const ContentShuffleProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="H" label={t('controlnet.h')}
value={h} value={h}
onChange={handleHChanged} onChange={handleHChanged}
handleReset={handleHReset} handleReset={handleHReset}
@ -138,7 +140,7 @@ const ContentShuffleProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="F" label={t('controlnet.f')}
value={f} value={f}
onChange={handleFChanged} onChange={handleFChanged}
handleReset={handleFReset} handleReset={handleFReset}

View File

@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.hed_image_processor
.default as RequiredHedImageProcessorInvocation; .default as RequiredHedImageProcessorInvocation;
@ -25,6 +26,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
} = props; } = props;
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -62,7 +64,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -74,7 +76,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -86,7 +88,7 @@ const HedPreprocessor = (props: HedProcessorProps) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISwitch <IAISwitch
label="Scribble" label={t('controlnet.scribble')}
isChecked={scribble} isChecked={scribble}
onChange={handleScribbleChanged} onChange={handleScribbleChanged}
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.lineart_anime_image_processor
.default as RequiredLineartAnimeImageProcessorInvocation; .default as RequiredLineartAnimeImageProcessorInvocation;
@ -21,6 +22,7 @@ const LineartAnimeProcessor = (props: Props) => {
const { image_resolution, detect_resolution } = processorNode; const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -51,7 +53,7 @@ const LineartAnimeProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -63,7 +65,7 @@ const LineartAnimeProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}

View File

@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.lineart_image_processor
.default as RequiredLineartImageProcessorInvocation; .default as RequiredLineartImageProcessorInvocation;
@ -22,6 +23,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
const { image_resolution, detect_resolution, coarse } = processorNode; const { image_resolution, detect_resolution, coarse } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -59,7 +61,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -71,7 +73,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -83,7 +85,7 @@ const LineartProcessor = (props: LineartProcessorProps) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISwitch <IAISwitch
label="Coarse" label={t('controlnet.coarse')}
isChecked={coarse} isChecked={coarse}
onChange={handleCoarseChanged} onChange={handleCoarseChanged}
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor const DEFAULTS = CONTROLNET_PROCESSORS.mediapipe_face_processor
.default as RequiredMediapipeFaceProcessorInvocation; .default as RequiredMediapipeFaceProcessorInvocation;
@ -21,6 +22,7 @@ const MediapipeFaceProcessor = (props: Props) => {
const { max_faces, min_confidence } = processorNode; const { max_faces, min_confidence } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleMaxFacesChanged = useCallback( const handleMaxFacesChanged = useCallback(
(v: number) => { (v: number) => {
@ -47,7 +49,7 @@ const MediapipeFaceProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Max Faces" label={t('controlnet.maxFaces')}
value={max_faces} value={max_faces}
onChange={handleMaxFacesChanged} onChange={handleMaxFacesChanged}
handleReset={handleMaxFacesReset} handleReset={handleMaxFacesReset}
@ -59,7 +61,7 @@ const MediapipeFaceProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Min Confidence" label={t('controlnet.minConfidence')}
value={min_confidence} value={min_confidence}
onChange={handleMinConfidenceChanged} onChange={handleMinConfidenceChanged}
handleReset={handleMinConfidenceReset} handleReset={handleMinConfidenceReset}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.midas_depth_image_processor
.default as RequiredMidasDepthImageProcessorInvocation; .default as RequiredMidasDepthImageProcessorInvocation;
@ -21,6 +22,7 @@ const MidasDepthProcessor = (props: Props) => {
const { a_mult, bg_th } = processorNode; const { a_mult, bg_th } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleAMultChanged = useCallback( const handleAMultChanged = useCallback(
(v: number) => { (v: number) => {
@ -47,7 +49,7 @@ const MidasDepthProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="a_mult" label={t('controlnet.amult')}
value={a_mult} value={a_mult}
onChange={handleAMultChanged} onChange={handleAMultChanged}
handleReset={handleAMultReset} handleReset={handleAMultReset}
@ -60,7 +62,7 @@ const MidasDepthProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="bg_th" label={t('controlnet.bgth')}
value={bg_th} value={bg_th}
onChange={handleBgThChanged} onChange={handleBgThChanged}
handleReset={handleBgThReset} handleReset={handleBgThReset}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.mlsd_image_processor
.default as RequiredMlsdImageProcessorInvocation; .default as RequiredMlsdImageProcessorInvocation;
@ -21,6 +22,7 @@ const MlsdImageProcessor = (props: Props) => {
const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode; const { image_resolution, detect_resolution, thr_d, thr_v } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -73,7 +75,7 @@ const MlsdImageProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -85,7 +87,7 @@ const MlsdImageProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -97,7 +99,7 @@ const MlsdImageProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="W" label={t('controlnet.w')}
value={thr_d} value={thr_d}
onChange={handleThrDChanged} onChange={handleThrDChanged}
handleReset={handleThrDReset} handleReset={handleThrDReset}
@ -110,7 +112,7 @@ const MlsdImageProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="H" label={t('controlnet.h')}
value={thr_v} value={thr_v}
onChange={handleThrVChanged} onChange={handleThrVChanged}
handleReset={handleThrVReset} handleReset={handleThrVReset}

View File

@ -6,6 +6,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.normalbae_image_processor
.default as RequiredNormalbaeImageProcessorInvocation; .default as RequiredNormalbaeImageProcessorInvocation;
@ -21,6 +22,7 @@ const NormalBaeProcessor = (props: Props) => {
const { image_resolution, detect_resolution } = processorNode; const { image_resolution, detect_resolution } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -51,7 +53,7 @@ const NormalBaeProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -63,7 +65,7 @@ const NormalBaeProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}

View File

@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.openpose_image_processor
.default as RequiredOpenposeImageProcessorInvocation; .default as RequiredOpenposeImageProcessorInvocation;
@ -22,6 +23,7 @@ const OpenposeProcessor = (props: Props) => {
const { image_resolution, detect_resolution, hand_and_face } = processorNode; const { image_resolution, detect_resolution, hand_and_face } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -59,7 +61,7 @@ const OpenposeProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -71,7 +73,7 @@ const OpenposeProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -83,7 +85,7 @@ const OpenposeProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISwitch <IAISwitch
label="Hand and Face" label={t('controlnet.handAndFace')}
isChecked={hand_and_face} isChecked={hand_and_face}
onChange={handleHandAndFaceChanged} onChange={handleHandAndFaceChanged}
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}

View File

@ -7,6 +7,7 @@ import { selectIsBusy } from 'features/system/store/systemSelectors';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged'; import { useProcessorNodeChanged } from '../hooks/useProcessorNodeChanged';
import ProcessorWrapper from './common/ProcessorWrapper'; import ProcessorWrapper from './common/ProcessorWrapper';
import { useTranslation } from 'react-i18next';
const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor const DEFAULTS = CONTROLNET_PROCESSORS.pidi_image_processor
.default as RequiredPidiImageProcessorInvocation; .default as RequiredPidiImageProcessorInvocation;
@ -22,6 +23,7 @@ const PidiProcessor = (props: Props) => {
const { image_resolution, detect_resolution, scribble, safe } = processorNode; const { image_resolution, detect_resolution, scribble, safe } = processorNode;
const processorChanged = useProcessorNodeChanged(); const processorChanged = useProcessorNodeChanged();
const isBusy = useAppSelector(selectIsBusy); const isBusy = useAppSelector(selectIsBusy);
const { t } = useTranslation();
const handleDetectResolutionChanged = useCallback( const handleDetectResolutionChanged = useCallback(
(v: number) => { (v: number) => {
@ -66,7 +68,7 @@ const PidiProcessor = (props: Props) => {
return ( return (
<ProcessorWrapper> <ProcessorWrapper>
<IAISlider <IAISlider
label="Detect Resolution" label={t('controlnet.detectResolution')}
value={detect_resolution} value={detect_resolution}
onChange={handleDetectResolutionChanged} onChange={handleDetectResolutionChanged}
handleReset={handleDetectResolutionReset} handleReset={handleDetectResolutionReset}
@ -78,7 +80,7 @@ const PidiProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISlider <IAISlider
label="Image Resolution" label={t('controlnet.imageResolution')}
value={image_resolution} value={image_resolution}
onChange={handleImageResolutionChanged} onChange={handleImageResolutionChanged}
handleReset={handleImageResolutionReset} handleReset={handleImageResolutionReset}
@ -90,12 +92,12 @@ const PidiProcessor = (props: Props) => {
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}
/> />
<IAISwitch <IAISwitch
label="Scribble" label={t('controlnet.scribble')}
isChecked={scribble} isChecked={scribble}
onChange={handleScribbleChanged} onChange={handleScribbleChanged}
/> />
<IAISwitch <IAISwitch
label="Safe" label={t('controlnet.safe')}
isChecked={safe} isChecked={safe}
onChange={handleSafeChanged} onChange={handleSafeChanged}
isDisabled={isBusy || !isEnabled} isDisabled={isBusy || !isEnabled}

View File

@ -2,6 +2,7 @@ import {
ControlNetProcessorType, ControlNetProcessorType,
RequiredControlNetProcessorNode, RequiredControlNetProcessorNode,
} from './types'; } from './types';
import i18n from 'i18next';
type ControlNetProcessorsDict = Record< type ControlNetProcessorsDict = Record<
ControlNetProcessorType, ControlNetProcessorType,
@ -12,7 +13,6 @@ type ControlNetProcessorsDict = Record<
default: RequiredControlNetProcessorNode | { type: 'none' }; default: RequiredControlNetProcessorNode | { type: 'none' };
} }
>; >;
/** /**
* A dict of ControlNet processors, including: * A dict of ControlNet processors, including:
* - type * - type
@ -25,16 +25,24 @@ type ControlNetProcessorsDict = Record<
export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = { export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
none: { none: {
type: 'none', type: 'none',
label: 'none', get label() {
description: '', return i18n.t('controlnet.none');
},
get description() {
return i18n.t('controlnet.noneDescription');
},
default: { default: {
type: 'none', type: 'none',
}, },
}, },
canny_image_processor: { canny_image_processor: {
type: 'canny_image_processor', type: 'canny_image_processor',
label: 'Canny', get label() {
description: '', return i18n.t('controlnet.canny');
},
get description() {
return i18n.t('controlnet.cannyDescription');
},
default: { default: {
id: 'canny_image_processor', id: 'canny_image_processor',
type: 'canny_image_processor', type: 'canny_image_processor',
@ -44,8 +52,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
content_shuffle_image_processor: { content_shuffle_image_processor: {
type: 'content_shuffle_image_processor', type: 'content_shuffle_image_processor',
label: 'Content Shuffle', get label() {
description: '', return i18n.t('controlnet.contentShuffle');
},
get description() {
return i18n.t('controlnet.contentShuffleDescription');
},
default: { default: {
id: 'content_shuffle_image_processor', id: 'content_shuffle_image_processor',
type: 'content_shuffle_image_processor', type: 'content_shuffle_image_processor',
@ -58,8 +70,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
hed_image_processor: { hed_image_processor: {
type: 'hed_image_processor', type: 'hed_image_processor',
label: 'HED', get label() {
description: '', return i18n.t('controlnet.hed');
},
get description() {
return i18n.t('controlnet.hedDescription');
},
default: { default: {
id: 'hed_image_processor', id: 'hed_image_processor',
type: 'hed_image_processor', type: 'hed_image_processor',
@ -70,8 +86,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
lineart_anime_image_processor: { lineart_anime_image_processor: {
type: 'lineart_anime_image_processor', type: 'lineart_anime_image_processor',
label: 'Lineart Anime', get label() {
description: '', return i18n.t('controlnet.lineartAnime');
},
get description() {
return i18n.t('controlnet.lineartAnimeDescription');
},
default: { default: {
id: 'lineart_anime_image_processor', id: 'lineart_anime_image_processor',
type: 'lineart_anime_image_processor', type: 'lineart_anime_image_processor',
@ -81,8 +101,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
lineart_image_processor: { lineart_image_processor: {
type: 'lineart_image_processor', type: 'lineart_image_processor',
label: 'Lineart', get label() {
description: '', return i18n.t('controlnet.lineart');
},
get description() {
return i18n.t('controlnet.lineartDescription');
},
default: { default: {
id: 'lineart_image_processor', id: 'lineart_image_processor',
type: 'lineart_image_processor', type: 'lineart_image_processor',
@ -93,8 +117,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
mediapipe_face_processor: { mediapipe_face_processor: {
type: 'mediapipe_face_processor', type: 'mediapipe_face_processor',
label: 'Mediapipe Face', get label() {
description: '', return i18n.t('controlnet.mediapipeFace');
},
get description() {
return i18n.t('controlnet.mediapipeFaceDescription');
},
default: { default: {
id: 'mediapipe_face_processor', id: 'mediapipe_face_processor',
type: 'mediapipe_face_processor', type: 'mediapipe_face_processor',
@ -104,8 +132,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
midas_depth_image_processor: { midas_depth_image_processor: {
type: 'midas_depth_image_processor', type: 'midas_depth_image_processor',
label: 'Depth (Midas)', get label() {
description: '', return i18n.t('controlnet.depthMidas');
},
get description() {
return i18n.t('controlnet.depthMidasDescription');
},
default: { default: {
id: 'midas_depth_image_processor', id: 'midas_depth_image_processor',
type: 'midas_depth_image_processor', type: 'midas_depth_image_processor',
@ -115,8 +147,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
mlsd_image_processor: { mlsd_image_processor: {
type: 'mlsd_image_processor', type: 'mlsd_image_processor',
label: 'M-LSD', get label() {
description: '', return i18n.t('controlnet.mlsd');
},
get description() {
return i18n.t('controlnet.mlsdDescription');
},
default: { default: {
id: 'mlsd_image_processor', id: 'mlsd_image_processor',
type: 'mlsd_image_processor', type: 'mlsd_image_processor',
@ -128,8 +164,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
normalbae_image_processor: { normalbae_image_processor: {
type: 'normalbae_image_processor', type: 'normalbae_image_processor',
label: 'Normal BAE', get label() {
description: '', return i18n.t('controlnet.normalBae');
},
get description() {
return i18n.t('controlnet.normalBaeDescription');
},
default: { default: {
id: 'normalbae_image_processor', id: 'normalbae_image_processor',
type: 'normalbae_image_processor', type: 'normalbae_image_processor',
@ -139,8 +179,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
openpose_image_processor: { openpose_image_processor: {
type: 'openpose_image_processor', type: 'openpose_image_processor',
label: 'Openpose', get label() {
description: '', return i18n.t('controlnet.openPose');
},
get description() {
return i18n.t('controlnet.openPoseDescription');
},
default: { default: {
id: 'openpose_image_processor', id: 'openpose_image_processor',
type: 'openpose_image_processor', type: 'openpose_image_processor',
@ -151,8 +195,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
pidi_image_processor: { pidi_image_processor: {
type: 'pidi_image_processor', type: 'pidi_image_processor',
label: 'PIDI', get label() {
description: '', return i18n.t('controlnet.pidi');
},
get description() {
return i18n.t('controlnet.pidiDescription');
},
default: { default: {
id: 'pidi_image_processor', id: 'pidi_image_processor',
type: 'pidi_image_processor', type: 'pidi_image_processor',
@ -164,8 +212,12 @@ export const CONTROLNET_PROCESSORS: ControlNetProcessorsDict = {
}, },
zoe_depth_image_processor: { zoe_depth_image_processor: {
type: 'zoe_depth_image_processor', type: 'zoe_depth_image_processor',
label: 'Depth (Zoe)', get label() {
description: '', return i18n.t('controlnet.depthZoe');
},
get description() {
return i18n.t('controlnet.depthZoeDescription');
},
default: { default: {
id: 'zoe_depth_image_processor', id: 'zoe_depth_image_processor',
type: 'zoe_depth_image_processor', type: 'zoe_depth_image_processor',
@ -186,4 +238,6 @@ export const CONTROLNET_MODEL_DEFAULT_PROCESSORS: {
shuffle: 'content_shuffle_image_processor', shuffle: 'content_shuffle_image_processor',
openpose: 'openpose_image_processor', openpose: 'openpose_image_processor',
mediapipe: 'mediapipe_face_processor', mediapipe: 'mediapipe_face_processor',
pidi: 'pidi_image_processor',
zoe: 'zoe_depth_image_processor',
}; };

View File

@ -2,16 +2,19 @@ import { ListItem, Text, UnorderedList } from '@chakra-ui/react';
import { some } from 'lodash-es'; import { some } from 'lodash-es';
import { memo } from 'react'; import { memo } from 'react';
import { ImageUsage } from '../store/types'; import { ImageUsage } from '../store/types';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
imageUsage?: ImageUsage; imageUsage?: ImageUsage;
topMessage?: string; topMessage?: string;
bottomMessage?: string; bottomMessage?: string;
}; };
const ImageUsageMessage = (props: Props) => { const ImageUsageMessage = (props: Props) => {
const { t } = useTranslation();
const { const {
imageUsage, imageUsage,
topMessage = 'This image is currently in use in the following features:', topMessage = t('gallery.currentlyInUse'),
bottomMessage = 'If you delete this image, those features will immediately be reset.', bottomMessage = t('gallery.featuresWillReset'),
} = props; } = props;
if (!imageUsage) { if (!imageUsage) {
@ -26,10 +29,18 @@ const ImageUsageMessage = (props: Props) => {
<> <>
<Text>{topMessage}</Text> <Text>{topMessage}</Text>
<UnorderedList sx={{ paddingInlineStart: 6 }}> <UnorderedList sx={{ paddingInlineStart: 6 }}>
{imageUsage.isInitialImage && <ListItem>Image to Image</ListItem>} {imageUsage.isInitialImage && (
{imageUsage.isCanvasImage && <ListItem>Unified Canvas</ListItem>} <ListItem>{t('common.img2img')}</ListItem>
{imageUsage.isControlNetImage && <ListItem>ControlNet</ListItem>} )}
{imageUsage.isNodesImage && <ListItem>Node Editor</ListItem>} {imageUsage.isCanvasImage && (
<ListItem>{t('common.unifiedCanvas')}</ListItem>
)}
{imageUsage.isControlNetImage && (
<ListItem>{t('common.controlNet')}</ListItem>
)}
{imageUsage.isNodesImage && (
<ListItem>{t('common.nodeEditor')}</ListItem>
)}
</UnorderedList> </UnorderedList>
<Text>{bottomMessage}</Text> <Text>{bottomMessage}</Text>
</> </>

View File

@ -9,6 +9,7 @@ import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial'; import ParamDynamicPromptsCombinatorial from './ParamDynamicPromptsCombinatorial';
import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled'; import ParamDynamicPromptsToggle from './ParamDynamicPromptsEnabled';
import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts'; import ParamDynamicPromptsMaxPrompts from './ParamDynamicPromptsMaxPrompts';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -22,6 +23,7 @@ const selector = createSelector(
const ParamDynamicPromptsCollapse = () => { const ParamDynamicPromptsCollapse = () => {
const { activeLabel } = useAppSelector(selector); const { activeLabel } = useAppSelector(selector);
const { t } = useTranslation();
const isDynamicPromptingEnabled = const isDynamicPromptingEnabled =
useFeatureStatus('dynamicPrompting').isFeatureEnabled; useFeatureStatus('dynamicPrompting').isFeatureEnabled;
@ -31,7 +33,7 @@ const ParamDynamicPromptsCollapse = () => {
} }
return ( return (
<IAICollapse label="Dynamic Prompts" activeLabel={activeLabel}> <IAICollapse label={t('prompt.dynamicPrompts')} activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDir: 'column' }}> <Flex sx={{ gap: 2, flexDir: 'column' }}>
<ParamDynamicPromptsToggle /> <ParamDynamicPromptsToggle />
<ParamDynamicPromptsCombinatorial /> <ParamDynamicPromptsCombinatorial />

View File

@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { combinatorialToggled } from '../store/dynamicPromptsSlice'; import { combinatorialToggled } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -19,6 +20,7 @@ const selector = createSelector(
const ParamDynamicPromptsCombinatorial = () => { const ParamDynamicPromptsCombinatorial = () => {
const { combinatorial, isDisabled } = useAppSelector(selector); const { combinatorial, isDisabled } = useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChange = useCallback(() => { const handleChange = useCallback(() => {
dispatch(combinatorialToggled()); dispatch(combinatorialToggled());
@ -27,7 +29,7 @@ const ParamDynamicPromptsCombinatorial = () => {
return ( return (
<IAISwitch <IAISwitch
isDisabled={isDisabled} isDisabled={isDisabled}
label="Combinatorial Generation" label={t('prompt.combinatorial')}
isChecked={combinatorial} isChecked={combinatorial}
onChange={handleChange} onChange={handleChange}
/> />

View File

@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { isEnabledToggled } from '../store/dynamicPromptsSlice'; import { isEnabledToggled } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -19,6 +20,7 @@ const selector = createSelector(
const ParamDynamicPromptsToggle = () => { const ParamDynamicPromptsToggle = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isEnabled } = useAppSelector(selector); const { isEnabled } = useAppSelector(selector);
const { t } = useTranslation();
const handleToggleIsEnabled = useCallback(() => { const handleToggleIsEnabled = useCallback(() => {
dispatch(isEnabledToggled()); dispatch(isEnabledToggled());
@ -26,7 +28,7 @@ const ParamDynamicPromptsToggle = () => {
return ( return (
<IAISwitch <IAISwitch
label="Enable Dynamic Prompts" label={t('prompt.enableDynamicPrompts')}
isChecked={isEnabled} isChecked={isEnabled}
onChange={handleToggleIsEnabled} onChange={handleToggleIsEnabled}
/> />

View File

@ -8,6 +8,7 @@ import {
maxPromptsChanged, maxPromptsChanged,
maxPromptsReset, maxPromptsReset,
} from '../store/dynamicPromptsSlice'; } from '../store/dynamicPromptsSlice';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -31,6 +32,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
const { maxPrompts, min, sliderMax, inputMax, isDisabled } = const { maxPrompts, min, sliderMax, inputMax, isDisabled } =
useAppSelector(selector); useAppSelector(selector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => { (v: number) => {
@ -45,7 +47,7 @@ const ParamDynamicPromptsMaxPrompts = () => {
return ( return (
<IAISlider <IAISlider
label="Max Prompts" label={t('prompt.maxPrompts')}
isDisabled={isDisabled} isDisabled={isDisabled}
min={min} min={min}
max={sliderMax} max={sliderMax}

View File

@ -1,6 +1,7 @@
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { memo } from 'react'; import { memo } from 'react';
import { FaCode } from 'react-icons/fa'; import { FaCode } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
onClick: () => void; onClick: () => void;
@ -8,11 +9,12 @@ type Props = {
const AddEmbeddingButton = (props: Props) => { const AddEmbeddingButton = (props: Props) => {
const { onClick } = props; const { onClick } = props;
const { t } = useTranslation();
return ( return (
<IAIIconButton <IAIIconButton
size="sm" size="sm"
aria-label="Add Embedding" aria-label={t('embedding.addEmbedding')}
tooltip="Add Embedding" tooltip={t('embedding.addEmbedding')}
icon={<FaCode />} icon={<FaCode />}
sx={{ sx={{
p: 2, p: 2,

View File

@ -16,6 +16,7 @@ import { forEach } from 'lodash-es';
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react'; import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models'; import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
import { useTranslation } from 'react-i18next';
type Props = PropsWithChildren & { type Props = PropsWithChildren & {
onSelect: (v: string) => void; onSelect: (v: string) => void;
@ -27,6 +28,7 @@ const ParamEmbeddingPopover = (props: Props) => {
const { onSelect, isOpen, onClose, children } = props; const { onSelect, isOpen, onClose, children } = props;
const { data: embeddingQueryData } = useGetTextualInversionModelsQuery(); const { data: embeddingQueryData } = useGetTextualInversionModelsQuery();
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();
const currentMainModel = useAppSelector( const currentMainModel = useAppSelector(
(state: RootState) => state.generation.model (state: RootState) => state.generation.model
@ -52,7 +54,7 @@ const ParamEmbeddingPopover = (props: Props) => {
group: MODEL_TYPE_MAP[embedding.base_model], group: MODEL_TYPE_MAP[embedding.base_model],
disabled, disabled,
tooltip: disabled tooltip: disabled
? `Incompatible base model: ${embedding.base_model}` ? `${t('embedding.incompatibleModel')} ${embedding.base_model}`
: undefined, : undefined,
}); });
}); });
@ -63,7 +65,7 @@ const ParamEmbeddingPopover = (props: Props) => {
); );
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1)); return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
}, [embeddingQueryData, currentMainModel?.base_model]); }, [embeddingQueryData, currentMainModel?.base_model, t]);
const handleChange = useCallback( const handleChange = useCallback(
(v: string | null) => { (v: string | null) => {
@ -118,10 +120,10 @@ const ParamEmbeddingPopover = (props: Props) => {
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
inputRef={inputRef} inputRef={inputRef}
autoFocus autoFocus
placeholder="Add Embedding" placeholder={t('embedding.addEmbedding')}
value={null} value={null}
data={data} data={data}
nothingFound="No matching Embeddings" nothingFound={t('embedding.noMatchingEmbedding')}
itemComponent={IAIMantineSelectItemWithTooltip} itemComponent={IAIMantineSelectItemWithTooltip}
disabled={data.length === 0} disabled={data.length === 0}
onDropdownClose={onClose} onDropdownClose={onClose}

View File

@ -8,6 +8,7 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice'; import { autoAddBoardIdChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -26,6 +27,7 @@ const selector = createSelector(
const BoardAutoAddSelect = () => { const BoardAutoAddSelect = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } = const { autoAddBoardId, autoAssignBoardOnClick, isProcessing } =
useAppSelector(selector); useAppSelector(selector);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -63,13 +65,13 @@ const BoardAutoAddSelect = () => {
return ( return (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
label="Auto-Add Board" label={t('boards.autoAddBoard')}
inputRef={inputRef} inputRef={inputRef}
autoFocus autoFocus
placeholder="Select a Board" placeholder={t('boards.selectBoard')}
value={autoAddBoardId} value={autoAddBoardId}
data={boards} data={boards}
nothingFound="No matching Boards" nothingFound={t('boards.noMatching')}
itemComponent={IAIMantineSelectItemWithTooltip} itemComponent={IAIMantineSelectItemWithTooltip}
disabled={!hasBoards || autoAssignBoardOnClick || isProcessing} disabled={!hasBoards || autoAssignBoardOnClick || isProcessing}
filter={(value, item: SelectItem) => filter={(value, item: SelectItem) =>

View File

@ -16,6 +16,7 @@ import { menuListMotionProps } from 'theme/components/menu';
import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems'; import GalleryBoardContextMenuItems from './GalleryBoardContextMenuItems';
import NoBoardContextMenuItems from './NoBoardContextMenuItems'; import NoBoardContextMenuItems from './NoBoardContextMenuItems';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
board?: BoardDTO; board?: BoardDTO;
@ -59,6 +60,8 @@ const BoardContextMenu = ({
e.preventDefault(); e.preventDefault();
}, []); }, []);
const { t } = useTranslation();
return ( return (
<IAIContextMenu<HTMLDivElement> <IAIContextMenu<HTMLDivElement>
menuProps={{ size: 'sm', isLazy: true }} menuProps={{ size: 'sm', isLazy: true }}
@ -78,7 +81,7 @@ const BoardContextMenu = ({
isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick} isDisabled={isAutoAdd || isProcessing || autoAssignBoardOnClick}
onClick={handleSetAutoAdd} onClick={handleSetAutoAdd}
> >
Auto-add to this Board {t('boards.menuItemAutoAdd')}
</MenuItem> </MenuItem>
{!board && <NoBoardContextMenuItems />} {!board && <NoBoardContextMenuItems />}
{board && ( {board && (

View File

@ -2,22 +2,22 @@ import IAIIconButton from 'common/components/IAIIconButton';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaPlus } from 'react-icons/fa'; import { FaPlus } from 'react-icons/fa';
import { useCreateBoardMutation } from 'services/api/endpoints/boards'; import { useCreateBoardMutation } from 'services/api/endpoints/boards';
import { useTranslation } from 'react-i18next';
const DEFAULT_BOARD_NAME = 'My Board';
const AddBoardButton = () => { const AddBoardButton = () => {
const { t } = useTranslation();
const [createBoard, { isLoading }] = useCreateBoardMutation(); const [createBoard, { isLoading }] = useCreateBoardMutation();
const DEFAULT_BOARD_NAME = t('boards.myBoard');
const handleCreateBoard = useCallback(() => { const handleCreateBoard = useCallback(() => {
createBoard(DEFAULT_BOARD_NAME); createBoard(DEFAULT_BOARD_NAME);
}, [createBoard]); }, [createBoard, DEFAULT_BOARD_NAME]);
return ( return (
<IAIIconButton <IAIIconButton
icon={<FaPlus />} icon={<FaPlus />}
isLoading={isLoading} isLoading={isLoading}
tooltip="Add Board" tooltip={t('boards.addBoard')}
aria-label="Add Board" aria-label={t('boards.addBoard')}
onClick={handleCreateBoard} onClick={handleCreateBoard}
size="sm" size="sm"
/> />

View File

@ -18,6 +18,7 @@ import {
useEffect, useEffect,
useRef, useRef,
} from 'react'; } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -32,6 +33,7 @@ const BoardsSearch = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { boardSearchText } = useAppSelector(selector); const { boardSearchText } = useAppSelector(selector);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const { t } = useTranslation();
const handleBoardSearch = useCallback( const handleBoardSearch = useCallback(
(searchTerm: string) => { (searchTerm: string) => {
@ -73,7 +75,7 @@ const BoardsSearch = () => {
<InputGroup> <InputGroup>
<Input <Input
ref={inputRef} ref={inputRef}
placeholder="Search Boards..." placeholder={t('boards.searchBoard')}
value={boardSearchText} value={boardSearchText}
onKeyDown={handleKeydown} onKeyDown={handleKeydown}
onChange={handleChange} onChange={handleChange}
@ -84,7 +86,7 @@ const BoardsSearch = () => {
onClick={clearBoardSearch} onClick={clearBoardSearch}
size="xs" size="xs"
variant="ghost" variant="ghost"
aria-label="Clear Search" aria-label={t('boards.clearSearch')}
opacity={0.5} opacity={0.5}
icon={<CloseIcon boxSize={2} />} icon={<CloseIcon boxSize={2} />}
/> />

View File

@ -132,8 +132,8 @@ const DeleteBoardModal = (props: Props) => {
) : ( ) : (
<ImageUsageMessage <ImageUsageMessage
imageUsage={imageUsageSummary} imageUsage={imageUsageSummary}
topMessage="This board contains images used in the following features:" topMessage={t('boards.topMessage')}
bottomMessage="Deleting this board and its images will reset any features currently using them." bottomMessage={t('boards.bottomMessage')}
/> />
)} )}
<Text>Deleted boards cannot be restored.</Text> <Text>Deleted boards cannot be restored.</Text>

View File

@ -19,6 +19,7 @@ import { FaImage } from 'react-icons/fa';
import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { useGetImageDTOQuery } from 'services/api/endpoints/images';
import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer'; import ImageMetadataViewer from '../ImageMetadataViewer/ImageMetadataViewer';
import NextPrevImageButtons from '../NextPrevImageButtons'; import NextPrevImageButtons from '../NextPrevImageButtons';
import { useTranslation } from 'react-i18next';
export const imagesSelector = createSelector( export const imagesSelector = createSelector(
[stateSelector, selectLastSelectedImage], [stateSelector, selectLastSelectedImage],
@ -117,6 +118,8 @@ const CurrentImagePreview = () => {
const timeoutId = useRef(0); const timeoutId = useRef(0);
const { t } = useTranslation();
const handleMouseOver = useCallback(() => { const handleMouseOver = useCallback(() => {
setShouldShowNextPrevButtons(true); setShouldShowNextPrevButtons(true);
window.clearTimeout(timeoutId.current); window.clearTimeout(timeoutId.current);
@ -164,7 +167,7 @@ const CurrentImagePreview = () => {
isUploadDisabled={true} isUploadDisabled={true}
fitContainer fitContainer
useThumbailFallback useThumbailFallback
dropLabel="Set as Current Image" dropLabel={t('gallery.setCurrentImage')}
noContentFallback={ noContentFallback={
<IAINoContentFallback icon={FaImage} label="No image selected" /> <IAINoContentFallback icon={FaImage} label="No image selected" />
} }

View File

@ -18,6 +18,7 @@ import {
useUnstarImagesMutation, useUnstarImagesMutation,
} from 'services/api/endpoints/images'; } from 'services/api/endpoints/images';
import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon'; import IAIDndImageIcon from '../../../../common/components/IAIDndImageIcon';
import { useTranslation } from 'react-i18next';
interface HoverableImageProps { interface HoverableImageProps {
imageName: string; imageName: string;
@ -28,6 +29,7 @@ const GalleryImage = (props: HoverableImageProps) => {
const { imageName } = props; const { imageName } = props;
const { currentData: imageDTO } = useGetImageDTOQuery(imageName); const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
const shift = useAppSelector((state) => state.hotkeys.shift); const shift = useAppSelector((state) => state.hotkeys.shift);
const { t } = useTranslation();
const { handleClick, isSelected, selection, selectionCount } = const { handleClick, isSelected, selection, selectionCount } =
useMultiselect(imageDTO); useMultiselect(imageDTO);
@ -136,7 +138,7 @@ const GalleryImage = (props: HoverableImageProps) => {
<IAIDndImageIcon <IAIDndImageIcon
onClick={handleDelete} onClick={handleDelete}
icon={<FaTrash />} icon={<FaTrash />}
tooltip="Delete" tooltip={t('gallery.deleteImage')}
styleOverrides={{ styleOverrides={{
bottom: 2, bottom: 2,
top: 'auto', top: 'auto',

View File

@ -95,7 +95,7 @@ const GalleryImageGrid = () => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<IAINoContentFallback label="Loading..." icon={FaImage} /> <IAINoContentFallback label={t('gallery.loading')} icon={FaImage} />
</Flex> </Flex>
); );
} }
@ -140,7 +140,7 @@ const GalleryImageGrid = () => {
onClick={handleLoadMoreImages} onClick={handleLoadMoreImages}
isDisabled={!areMoreAvailable} isDisabled={!areMoreAvailable}
isLoading={isFetching} isLoading={isFetching}
loadingText="Loading" loadingText={t('gallery.loading')}
flexShrink={0} flexShrink={0}
> >
{`Load More (${currentData.ids.length} of ${currentViewTotal})`} {`Load More (${currentData.ids.length} of ${currentViewTotal})`}
@ -153,7 +153,7 @@ const GalleryImageGrid = () => {
return ( return (
<Box sx={{ w: 'full', h: 'full' }}> <Box sx={{ w: 'full', h: 'full' }}>
<IAINoContentFallback <IAINoContentFallback
label="Unable to load Gallery" label={t('gallery.unableToLoad')}
icon={FaExclamationCircle} icon={FaExclamationCircle}
/> />
</Box> </Box>

View File

@ -3,6 +3,7 @@ import { isString } from 'lodash-es';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { FaCopy, FaDownload } from 'react-icons/fa'; import { FaCopy, FaDownload } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
label: string; label: string;
@ -33,6 +34,8 @@ const DataViewer = (props: Props) => {
a.remove(); a.remove();
}, [dataString, label, fileName]); }, [dataString, label, fileName]);
const { t } = useTranslation();
return ( return (
<Flex <Flex
layerStyle="second" layerStyle="second"
@ -73,9 +76,9 @@ const DataViewer = (props: Props) => {
</Box> </Box>
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}> <Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
{withDownload && ( {withDownload && (
<Tooltip label={`Download ${label} JSON`}> <Tooltip label={`${t('gallery.download')} ${label} JSON`}>
<IconButton <IconButton
aria-label={`Download ${label} JSON`} aria-label={`${t('gallery.download')} ${label} JSON`}
icon={<FaDownload />} icon={<FaDownload />}
variant="ghost" variant="ghost"
opacity={0.7} opacity={0.7}
@ -84,9 +87,9 @@ const DataViewer = (props: Props) => {
</Tooltip> </Tooltip>
)} )}
{withCopy && ( {withCopy && (
<Tooltip label={`Copy ${label} JSON`}> <Tooltip label={`${t('gallery.copy')} ${label} JSON`}>
<IconButton <IconButton
aria-label={`Copy ${label} JSON`} aria-label={`${t('gallery.copy')} ${label} JSON`}
icon={<FaCopy />} icon={<FaCopy />}
variant="ghost" variant="ghost"
opacity={0.7} opacity={0.7}

View File

@ -2,6 +2,7 @@ import { CoreMetadata } from 'features/nodes/types/types';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import ImageMetadataItem from './ImageMetadataItem'; import ImageMetadataItem from './ImageMetadataItem';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
metadata?: CoreMetadata; metadata?: CoreMetadata;
@ -10,6 +11,8 @@ type Props = {
const ImageMetadataActions = (props: Props) => { const ImageMetadataActions = (props: Props) => {
const { metadata } = props; const { metadata } = props;
const { t } = useTranslation();
const { const {
recallPositivePrompt, recallPositivePrompt,
recallNegativePrompt, recallNegativePrompt,
@ -70,17 +73,20 @@ const ImageMetadataActions = (props: Props) => {
return ( return (
<> <>
{metadata.created_by && ( {metadata.created_by && (
<ImageMetadataItem label="Created By" value={metadata.created_by} /> <ImageMetadataItem
label={t('metadata.createdBy')}
value={metadata.created_by}
/>
)} )}
{metadata.generation_mode && ( {metadata.generation_mode && (
<ImageMetadataItem <ImageMetadataItem
label="Generation Mode" label={t('metadata.generationMode')}
value={metadata.generation_mode} value={metadata.generation_mode}
/> />
)} )}
{metadata.positive_prompt && ( {metadata.positive_prompt && (
<ImageMetadataItem <ImageMetadataItem
label="Positive Prompt" label={t('metadata.positivePrompt')}
labelPosition="top" labelPosition="top"
value={metadata.positive_prompt} value={metadata.positive_prompt}
onClick={handleRecallPositivePrompt} onClick={handleRecallPositivePrompt}
@ -88,7 +94,7 @@ const ImageMetadataActions = (props: Props) => {
)} )}
{metadata.negative_prompt && ( {metadata.negative_prompt && (
<ImageMetadataItem <ImageMetadataItem
label="Negative Prompt" label={t('metadata.NegativePrompt')}
labelPosition="top" labelPosition="top"
value={metadata.negative_prompt} value={metadata.negative_prompt}
onClick={handleRecallNegativePrompt} onClick={handleRecallNegativePrompt}
@ -96,7 +102,7 @@ const ImageMetadataActions = (props: Props) => {
)} )}
{metadata.seed !== undefined && metadata.seed !== null && ( {metadata.seed !== undefined && metadata.seed !== null && (
<ImageMetadataItem <ImageMetadataItem
label="Seed" label={t('metadata.seed')}
value={metadata.seed} value={metadata.seed}
onClick={handleRecallSeed} onClick={handleRecallSeed}
/> />
@ -105,63 +111,63 @@ const ImageMetadataActions = (props: Props) => {
metadata.model !== null && metadata.model !== null &&
metadata.model.model_name && ( metadata.model.model_name && (
<ImageMetadataItem <ImageMetadataItem
label="Model" label={t('metadata.model')}
value={metadata.model.model_name} value={metadata.model.model_name}
onClick={handleRecallModel} onClick={handleRecallModel}
/> />
)} )}
{metadata.width && ( {metadata.width && (
<ImageMetadataItem <ImageMetadataItem
label="Width" label={t('metadata.width')}
value={metadata.width} value={metadata.width}
onClick={handleRecallWidth} onClick={handleRecallWidth}
/> />
)} )}
{metadata.height && ( {metadata.height && (
<ImageMetadataItem <ImageMetadataItem
label="Height" label={t('metadata.height')}
value={metadata.height} value={metadata.height}
onClick={handleRecallHeight} onClick={handleRecallHeight}
/> />
)} )}
{/* {metadata.threshold !== undefined && ( {/* {metadata.threshold !== undefined && (
<MetadataItem <MetadataItem
label="Noise Threshold" label={t('metadata.threshold')}
value={metadata.threshold} value={metadata.threshold}
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))} onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
/> />
)} )}
{metadata.perlin !== undefined && ( {metadata.perlin !== undefined && (
<MetadataItem <MetadataItem
label="Perlin Noise" label={t('metadata.perlin')}
value={metadata.perlin} value={metadata.perlin}
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))} onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
/> />
)} */} )} */}
{metadata.scheduler && ( {metadata.scheduler && (
<ImageMetadataItem <ImageMetadataItem
label="Scheduler" label={t('metadata.scheduler')}
value={metadata.scheduler} value={metadata.scheduler}
onClick={handleRecallScheduler} onClick={handleRecallScheduler}
/> />
)} )}
{metadata.steps && ( {metadata.steps && (
<ImageMetadataItem <ImageMetadataItem
label="Steps" label={t('metadata.steps')}
value={metadata.steps} value={metadata.steps}
onClick={handleRecallSteps} onClick={handleRecallSteps}
/> />
)} )}
{metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && ( {metadata.cfg_scale !== undefined && metadata.cfg_scale !== null && (
<ImageMetadataItem <ImageMetadataItem
label="CFG scale" label={t('metadata.cfgScale')}
value={metadata.cfg_scale} value={metadata.cfg_scale}
onClick={handleRecallCfgScale} onClick={handleRecallCfgScale}
/> />
)} )}
{/* {metadata.variations && metadata.variations.length > 0 && ( {/* {metadata.variations && metadata.variations.length > 0 && (
<MetadataItem <MetadataItem
label="Seed-weight pairs" label="{t('metadata.variations')}
value={seedWeightsToString(metadata.variations)} value={seedWeightsToString(metadata.variations)}
onClick={() => onClick={() =>
dispatch( dispatch(
@ -172,14 +178,14 @@ const ImageMetadataActions = (props: Props) => {
)} )}
{metadata.seamless && ( {metadata.seamless && (
<MetadataItem <MetadataItem
label="Seamless" label={t('metadata.seamless')}
value={metadata.seamless} value={metadata.seamless}
onClick={() => dispatch(setSeamless(metadata.seamless))} onClick={() => dispatch(setSeamless(metadata.seamless))}
/> />
)} )}
{metadata.hires_fix && ( {metadata.hires_fix && (
<MetadataItem <MetadataItem
label="High Resolution Optimization" label={t('metadata.hiresFix')}
value={metadata.hires_fix} value={metadata.hires_fix}
onClick={() => dispatch(setHiresFix(metadata.hires_fix))} onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
/> />
@ -187,7 +193,7 @@ const ImageMetadataActions = (props: Props) => {
{/* {init_image_path && ( {/* {init_image_path && (
<MetadataItem <MetadataItem
label="Initial image" label={t('metadata.initImage')}
value={init_image_path} value={init_image_path}
isLink isLink
onClick={() => dispatch(setInitialImage(init_image_path))} onClick={() => dispatch(setInitialImage(init_image_path))}
@ -195,14 +201,14 @@ const ImageMetadataActions = (props: Props) => {
)} */} )} */}
{metadata.strength && ( {metadata.strength && (
<ImageMetadataItem <ImageMetadataItem
label="Image to image strength" label={t('metadata.strength')}
value={metadata.strength} value={metadata.strength}
onClick={handleRecallStrength} onClick={handleRecallStrength}
/> />
)} )}
{/* {metadata.fit && ( {/* {metadata.fit && (
<MetadataItem <MetadataItem
label="Image to image fit" label={t('metadata.fit')}
value={metadata.fit} value={metadata.fit}
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))} onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))}
/> />

View File

@ -17,6 +17,7 @@ import DataViewer from './DataViewer';
import ImageMetadataActions from './ImageMetadataActions'; import ImageMetadataActions from './ImageMetadataActions';
import { useAppSelector } from '../../../../app/store/storeHooks'; import { useAppSelector } from '../../../../app/store/storeHooks';
import { configSelector } from '../../../system/store/configSelectors'; import { configSelector } from '../../../system/store/configSelectors';
import { useTranslation } from 'react-i18next';
type ImageMetadataViewerProps = { type ImageMetadataViewerProps = {
image: ImageDTO; image: ImageDTO;
@ -28,6 +29,7 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
// useHotkeys('esc', () => { // useHotkeys('esc', () => {
// dispatch(setShouldShowImageDetails(false)); // dispatch(setShouldShowImageDetails(false));
// }); // });
const { t } = useTranslation();
const { shouldFetchMetadataFromApi } = useAppSelector(configSelector); const { shouldFetchMetadataFromApi } = useAppSelector(configSelector);
@ -70,31 +72,31 @@ const ImageMetadataViewer = ({ image }: ImageMetadataViewerProps) => {
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }} sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
> >
<TabList> <TabList>
<Tab>Metadata</Tab> <Tab>{t('metadata.metadata')}</Tab>
<Tab>Image Details</Tab> <Tab>{t('metadata.imageDetails')}</Tab>
<Tab>Workflow</Tab> <Tab>{t('metadata.workflow')}</Tab>
</TabList> </TabList>
<TabPanels> <TabPanels>
<TabPanel> <TabPanel>
{metadata ? ( {metadata ? (
<DataViewer data={metadata} label="Metadata" /> <DataViewer data={metadata} label={t('metadata.metadata')} />
) : ( ) : (
<IAINoContentFallback label="No metadata found" /> <IAINoContentFallback label={t('metadata.noMetaData')} />
)} )}
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
{image ? ( {image ? (
<DataViewer data={image} label="Image Details" /> <DataViewer data={image} label={t('metadata.imageDetails')} />
) : ( ) : (
<IAINoContentFallback label="No image details found" /> <IAINoContentFallback label={t('metadata.noImageDetails')} />
)} )}
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
{workflow ? ( {workflow ? (
<DataViewer data={workflow} label="Workflow" /> <DataViewer data={workflow} label={t('metadata.workflow')} />
) : ( ) : (
<IAINoContentFallback label="No workflow found" /> <IAINoContentFallback label={t('metadata.noWorkFlow')} />
)} )}
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>

View File

@ -12,9 +12,11 @@ import TopCenterPanel from './flow/panels/TopCenterPanel/TopCenterPanel';
import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel'; import TopRightPanel from './flow/panels/TopRightPanel/TopRightPanel';
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel'; import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel'; import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
import { useTranslation } from 'react-i18next';
const NodeEditor = () => { const NodeEditor = () => {
const isReady = useAppSelector((state) => state.nodes.isReady); const isReady = useAppSelector((state) => state.nodes.isReady);
const { t } = useTranslation();
return ( return (
<Flex <Flex
layerStyle="first" layerStyle="first"
@ -82,7 +84,7 @@ const NodeEditor = () => {
}} }}
> >
<IAINoContentFallback <IAINoContentFallback
label="Loading Nodes..." label={t('nodes.loadingNodes')}
icon={MdDeviceHub} icon={MdDeviceHub}
/> />
</Flex> </Flex>

View File

@ -24,6 +24,7 @@ import { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import { AnyInvocationType } from 'services/events/types'; import { AnyInvocationType } from 'services/events/types';
import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem'; import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem';
import { useTranslation } from 'react-i18next';
type NodeTemplate = { type NodeTemplate = {
label: string; 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 AddNodePopover = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const buildInvocation = useBuildNodeData(); const buildInvocation = useBuildNodeData();
const toaster = useAppToaster(); 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 { data } = useAppSelector(selector);
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen); const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@ -92,18 +95,20 @@ const AddNodePopover = () => {
const addNode = useCallback( const addNode = useCallback(
(nodeType: AnyInvocationType) => { (nodeType: AnyInvocationType) => {
const invocation = buildInvocation(nodeType); const invocation = buildInvocation(nodeType);
if (!invocation) { if (!invocation) {
const errorMessage = t('nodes.unknownInvocation', {
nodeType: nodeType,
});
toaster({ toaster({
status: 'error', status: 'error',
title: `Unknown Invocation type ${nodeType}`, title: errorMessage,
}); });
return; return;
} }
dispatch(nodeAdded(invocation)); dispatch(nodeAdded(invocation));
}, },
[dispatch, buildInvocation, toaster] [dispatch, buildInvocation, toaster, t]
); );
const handleChange = useCallback( const handleChange = useCallback(
@ -179,11 +184,11 @@ const AddNodePopover = () => {
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
inputRef={inputRef} inputRef={inputRef}
selectOnBlur={false} selectOnBlur={false}
placeholder="Search for nodes" placeholder={t('nodes.nodeSearch')}
value={null} value={null}
data={data} data={data}
maxDropdownHeight={400} maxDropdownHeight={400}
nothingFound="No matching nodes" nothingFound={t('nodes.noMatchingNodes')}
itemComponent={AddNodePopoverSelectItem} itemComponent={AddNodePopoverSelectItem}
filter={filter} filter={filter}
onChange={handleChange} onChange={handleChange}

View File

@ -22,6 +22,7 @@ import { memo, useMemo } from 'react';
import { FaInfoCircle } from 'react-icons/fa'; import { FaInfoCircle } from 'react-icons/fa';
import NotesTextarea from './NotesTextarea'; import NotesTextarea from './NotesTextarea';
import { useDoNodeVersionsMatch } from 'features/nodes/hooks/useDoNodeVersionsMatch'; import { useDoNodeVersionsMatch } from 'features/nodes/hooks/useDoNodeVersionsMatch';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
nodeId: string; nodeId: string;
@ -32,6 +33,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
const label = useNodeLabel(nodeId); const label = useNodeLabel(nodeId);
const title = useNodeTemplateTitle(nodeId); const title = useNodeTemplateTitle(nodeId);
const doVersionsMatch = useDoNodeVersionsMatch(nodeId); const doVersionsMatch = useDoNodeVersionsMatch(nodeId);
const { t } = useTranslation();
return ( return (
<> <>
@ -65,7 +67,7 @@ const InvocationNodeNotes = ({ nodeId }: Props) => {
<Modal isOpen={isOpen} onClose={onClose} isCentered> <Modal isOpen={isOpen} onClose={onClose} isCentered>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>{label || title || 'Unknown Node'}</ModalHeader> <ModalHeader>{label || title || t('nodes.unknownNode')}</ModalHeader>
<ModalCloseButton /> <ModalCloseButton />
<ModalBody> <ModalBody>
<NotesTextarea nodeId={nodeId} /> <NotesTextarea nodeId={nodeId} />
@ -82,6 +84,7 @@ export default memo(InvocationNodeNotes);
const TooltipContent = memo(({ nodeId }: { nodeId: string }) => { const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
const data = useNodeData(nodeId); const data = useNodeData(nodeId);
const nodeTemplate = useNodeTemplate(nodeId); const nodeTemplate = useNodeTemplate(nodeId);
const { t } = useTranslation();
const title = useMemo(() => { const title = useMemo(() => {
if (data?.label && nodeTemplate?.title) { if (data?.label && nodeTemplate?.title) {
@ -96,8 +99,8 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
return nodeTemplate.title; return nodeTemplate.title;
} }
return 'Unknown Node'; return t('nodes.unknownNode');
}, [data, nodeTemplate]); }, [data, nodeTemplate, t]);
const versionComponent = useMemo(() => { const versionComponent = useMemo(() => {
if (!isInvocationNodeData(data) || !nodeTemplate) { if (!isInvocationNodeData(data) || !nodeTemplate) {
@ -107,7 +110,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
if (!data.version) { if (!data.version) {
return ( return (
<Text as="span" sx={{ color: 'error.500' }}> <Text as="span" sx={{ color: 'error.500' }}>
Version unknown {t('nodes.versionUnknown')}
</Text> </Text>
); );
} }
@ -115,7 +118,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
if (!nodeTemplate.version) { if (!nodeTemplate.version) {
return ( return (
<Text as="span" sx={{ color: 'error.500' }}> <Text as="span" sx={{ color: 'error.500' }}>
Version {data.version} (unknown template) {t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')})
</Text> </Text>
); );
} }
@ -123,7 +126,7 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
if (compare(data.version, nodeTemplate.version, '<')) { if (compare(data.version, nodeTemplate.version, '<')) {
return ( return (
<Text as="span" sx={{ color: 'error.500' }}> <Text as="span" sx={{ color: 'error.500' }}>
Version {data.version} (update node) {t('nodes.version')} {data.version} ({t('nodes.updateNode')})
</Text> </Text>
); );
} }
@ -131,16 +134,20 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
if (compare(data.version, nodeTemplate.version, '>')) { if (compare(data.version, nodeTemplate.version, '>')) {
return ( return (
<Text as="span" sx={{ color: 'error.500' }}> <Text as="span" sx={{ color: 'error.500' }}>
Version {data.version} (update app) {t('nodes.version')} {data.version} ({t('nodes.updateApp')})
</Text> </Text>
); );
} }
return <Text as="span">Version {data.version}</Text>; return (
}, [data, nodeTemplate]); <Text as="span">
{t('nodes.version')} {data.version}
</Text>
);
}, [data, nodeTemplate, t]);
if (!isInvocationNodeData(data)) { if (!isInvocationNodeData(data)) {
return <Text sx={{ fontWeight: 600 }}>Unknown Node</Text>; return <Text sx={{ fontWeight: 600 }}>{t('nodes.unknownNode')}</Text>;
} }
return ( return (

View File

@ -14,6 +14,7 @@ import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types'; import { NodeExecutionState, NodeStatus } from 'features/nodes/types/types';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa'; import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -72,10 +73,10 @@ type TooltipLabelProps = {
const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => { const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
const { status, progress, progressImage } = nodeExecutionState; const { status, progress, progressImage } = nodeExecutionState;
const { t } = useTranslation();
if (status === NodeStatus.PENDING) { if (status === NodeStatus.PENDING) {
return <Text>Pending</Text>; return <Text>Pending</Text>;
} }
if (status === NodeStatus.IN_PROGRESS) { if (status === NodeStatus.IN_PROGRESS) {
if (progressImage) { if (progressImage) {
return ( return (
@ -97,18 +98,22 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
} }
if (progress !== null) { 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) { if (status === NodeStatus.COMPLETED) {
return <Text>Completed</Text>; return <Text>{t('nodes.executionStateCompleted')}</Text>;
} }
if (status === NodeStatus.FAILED) { if (status === NodeStatus.FAILED) {
return <Text>nodeExecutionState.error</Text>; return <Text>{t('nodes.executionStateError')}</Text>;
} }
return null; return null;

View File

@ -5,10 +5,12 @@ import { useNodeData } from 'features/nodes/hooks/useNodeData';
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice'; import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
import { isInvocationNodeData } from 'features/nodes/types/types'; import { isInvocationNodeData } from 'features/nodes/types/types';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const NotesTextarea = ({ nodeId }: { nodeId: string }) => { const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const data = useNodeData(nodeId); const data = useNodeData(nodeId);
const { t } = useTranslation();
const handleNotesChanged = useCallback( const handleNotesChanged = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => { (e: ChangeEvent<HTMLTextAreaElement>) => {
dispatch(nodeNotesChanged({ nodeId, notes: e.target.value })); dispatch(nodeNotesChanged({ nodeId, notes: e.target.value }));
@ -20,7 +22,7 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
} }
return ( return (
<FormControl> <FormControl>
<FormLabel>Notes</FormLabel> <FormLabel>{t('nodes.notes')}</FormLabel>
<IAITextarea <IAITextarea
value={data?.notes} value={data?.notes}
onChange={handleNotesChanged} onChange={handleNotesChanged}

View File

@ -14,6 +14,7 @@ import { fieldLabelChanged } from 'features/nodes/store/nodesSlice';
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react'; import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
import FieldTooltipContent from './FieldTooltipContent'; import FieldTooltipContent from './FieldTooltipContent';
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants'; import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
nodeId: string; nodeId: string;
@ -33,10 +34,11 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
} = props; } = props;
const label = useFieldLabel(nodeId, fieldName); const label = useFieldLabel(nodeId, fieldName);
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [localTitle, setLocalTitle] = useState( const [localTitle, setLocalTitle] = useState(
label || fieldTemplateTitle || 'Unknown Field' label || fieldTemplateTitle || t('nodes.unknownFeild')
); );
const handleSubmit = useCallback( const handleSubmit = useCallback(
@ -44,10 +46,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) { if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) {
return; return;
} }
setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field'); setLocalTitle(newTitle || fieldTemplateTitle || t('nodes.unknownField'));
dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle })); dispatch(fieldLabelChanged({ nodeId, fieldName, label: newTitle }));
}, },
[label, fieldTemplateTitle, dispatch, nodeId, fieldName] [label, fieldTemplateTitle, dispatch, nodeId, fieldName, t]
); );
const handleChange = useCallback((newTitle: string) => { const handleChange = useCallback((newTitle: string) => {
@ -56,8 +58,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
useEffect(() => { useEffect(() => {
// Another component may change the title; sync local title with global state // Another component may change the title; sync local title with global state
setLocalTitle(label || fieldTemplateTitle || 'Unknown Field'); setLocalTitle(label || fieldTemplateTitle || t('nodes.unknownField'));
}, [label, fieldTemplateTitle]); }, [label, fieldTemplateTitle, t]);
return ( return (
<Tooltip <Tooltip

View File

@ -17,6 +17,7 @@ import {
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react'; import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
import { FaMinus, FaPlus } from 'react-icons/fa'; import { FaMinus, FaPlus } from 'react-icons/fa';
import { menuListMotionProps } from 'theme/components/menu'; import { menuListMotionProps } from 'theme/components/menu';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -30,6 +31,7 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
const label = useFieldLabel(nodeId, fieldName); const label = useFieldLabel(nodeId, fieldName);
const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind); const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
const input = useFieldInputKind(nodeId, fieldName); const input = useFieldInputKind(nodeId, fieldName);
const { t } = useTranslation();
const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => { const skipEvent = useCallback((e: MouseEvent<HTMLDivElement>) => {
e.preventDefault(); e.preventDefault();
@ -119,7 +121,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
motionProps={menuListMotionProps} motionProps={menuListMotionProps}
onContextMenu={skipEvent} onContextMenu={skipEvent}
> >
<MenuGroup title={label || fieldTemplateTitle || 'Unknown Field'}> <MenuGroup
title={label || fieldTemplateTitle || t('nodes.unknownField')}
>
{menuItems} {menuItems}
</MenuGroup> </MenuGroup>
</MenuList> </MenuList>

View File

@ -8,6 +8,7 @@ import {
} from 'features/nodes/types/types'; } from 'features/nodes/types/types';
import { startCase } from 'lodash-es'; import { startCase } from 'lodash-es';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
interface Props { interface Props {
nodeId: string; nodeId: string;
@ -19,6 +20,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
const field = useFieldData(nodeId, fieldName); const field = useFieldData(nodeId, fieldName);
const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind); const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
const isInputTemplate = isInputFieldTemplate(fieldTemplate); const isInputTemplate = isInputFieldTemplate(fieldTemplate);
const { t } = useTranslation();
const fieldTitle = useMemo(() => { const fieldTitle = useMemo(() => {
if (isInputFieldValue(field)) { if (isInputFieldValue(field)) {
if (field.label && fieldTemplate?.title) { if (field.label && fieldTemplate?.title) {
@ -33,11 +35,11 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
return fieldTemplate.title; return fieldTemplate.title;
} }
return 'Unknown Field'; return t('nodes.unknownField');
} else { } else {
return fieldTemplate?.title || 'Unknown Field'; return fieldTemplate?.title || t('nodes.unknownField');
} }
}, [field, fieldTemplate]); }, [field, fieldTemplate, t]);
return ( return (
<Flex sx={{ flexDir: 'column' }}> <Flex sx={{ flexDir: 'column' }}>

View File

@ -17,6 +17,7 @@ import { FaInfoCircle, FaTrash } from 'react-icons/fa';
import EditableFieldTitle from './EditableFieldTitle'; import EditableFieldTitle from './EditableFieldTitle';
import FieldTooltipContent from './FieldTooltipContent'; import FieldTooltipContent from './FieldTooltipContent';
import InputFieldRenderer from './InputFieldRenderer'; import InputFieldRenderer from './InputFieldRenderer';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -27,7 +28,7 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = const { isMouseOverNode, handleMouseOut, handleMouseOver } =
useMouseOverNode(nodeId); useMouseOverNode(nodeId);
const { t } = useTranslation();
const handleRemoveField = useCallback(() => { const handleRemoveField = useCallback(() => {
dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); dispatch(workflowExposedFieldRemoved({ nodeId, fieldName }));
}, [dispatch, fieldName, nodeId]); }, [dispatch, fieldName, nodeId]);
@ -75,8 +76,8 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
</Flex> </Flex>
</Tooltip> </Tooltip>
<IAIIconButton <IAIIconButton
aria-label="Remove from Linear View" aria-label={t('nodes.removeLinearView')}
tooltip="Remove from Linear View" tooltip={t('nodes.removeLinearView')}
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={handleRemoveField} onClick={handleRemoveField}

View File

@ -14,6 +14,7 @@ import { modelIdToLoRAModelParam } from 'features/parameters/util/modelIdToLoRAM
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models'; import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
const LoRAModelInputFieldComponent = ( const LoRAModelInputFieldComponent = (
props: FieldComponentProps< props: FieldComponentProps<
@ -25,6 +26,7 @@ const LoRAModelInputFieldComponent = (
const lora = field.value; const lora = field.value;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { data: loraModels } = useGetLoRAModelsQuery(); const { data: loraModels } = useGetLoRAModelsQuery();
const { t } = useTranslation();
const data = useMemo(() => { const data = useMemo(() => {
if (!loraModels) { if (!loraModels) {
@ -92,9 +94,11 @@ const LoRAModelInputFieldComponent = (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
className="nowheel nodrag" className="nowheel nodrag"
value={selectedLoRAModel?.id ?? null} 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} data={data}
nothingFound="No matching LoRAs" nothingFound={t('models.noMatchingLoRAs')}
itemComponent={IAIMantineSelectItemWithTooltip} itemComponent={IAIMantineSelectItemWithTooltip}
disabled={data.length === 0} disabled={data.length === 0}
filter={(value, item: SelectItem) => filter={(value, item: SelectItem) =>

View File

@ -19,6 +19,7 @@ import {
useGetMainModelsQuery, useGetMainModelsQuery,
useGetOnnxModelsQuery, useGetOnnxModelsQuery,
} from 'services/api/endpoints/models'; } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
const MainModelInputFieldComponent = ( const MainModelInputFieldComponent = (
props: FieldComponentProps< props: FieldComponentProps<
@ -29,7 +30,7 @@ const MainModelInputFieldComponent = (
const { nodeId, field } = props; const { nodeId, field } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
const { t } = useTranslation();
const { data: onnxModels, isLoading: isLoadingOnnxModels } = const { data: onnxModels, isLoading: isLoadingOnnxModels } =
useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS); useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS);
const { data: mainModels, isLoading: isLoadingMainModels } = const { data: mainModels, isLoading: isLoadingMainModels } =
@ -127,7 +128,9 @@ const MainModelInputFieldComponent = (
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
value={selectedModel?.id} value={selectedModel?.id}
placeholder={ placeholder={
data.length > 0 ? 'Select a model' : 'No models available' data.length > 0
? t('models.selectModel')
: t('models.noModelsAvailable')
} }
data={data} data={data}
error={!selectedModel} error={!selectedModel}

View File

@ -89,7 +89,7 @@ const RefinerModelInputFieldComponent = (
return isLoading ? ( return isLoading ? (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
label={t('modelManager.model')} label={t('modelManager.model')}
placeholder="Loading..." placeholder={t('models.loading')}
disabled={true} disabled={true}
data={[]} data={[]}
/> />
@ -99,7 +99,11 @@ const RefinerModelInputFieldComponent = (
className="nowheel nodrag" className="nowheel nodrag"
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
value={selectedModel?.id} 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} data={data}
error={!selectedModel} error={!selectedModel}
disabled={data.length === 0} disabled={data.length === 0}

View File

@ -116,7 +116,7 @@ const ModelInputFieldComponent = (
return isLoading ? ( return isLoading ? (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
label={t('modelManager.model')} label={t('modelManager.model')}
placeholder="Loading..." placeholder={t('models.loading')}
disabled={true} disabled={true}
data={[]} data={[]}
/> />
@ -126,7 +126,11 @@ const ModelInputFieldComponent = (
className="nowheel nodrag" className="nowheel nodrag"
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
value={selectedModel?.id} 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} data={data}
error={!selectedModel} error={!selectedModel}
disabled={data.length === 0} disabled={data.length === 0}

View File

@ -12,6 +12,7 @@ import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle'
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice'; import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants'; import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react'; import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -22,16 +23,17 @@ const NodeTitle = ({ nodeId, title }: Props) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const label = useNodeLabel(nodeId); const label = useNodeLabel(nodeId);
const templateTitle = useNodeTemplateTitle(nodeId); const templateTitle = useNodeTemplateTitle(nodeId);
const { t } = useTranslation();
const [localTitle, setLocalTitle] = useState(''); const [localTitle, setLocalTitle] = useState('');
const handleSubmit = useCallback( const handleSubmit = useCallback(
async (newTitle: string) => { async (newTitle: string) => {
dispatch(nodeLabelChanged({ nodeId, label: newTitle })); dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
setLocalTitle( 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) => { const handleChange = useCallback((newTitle: string) => {
@ -40,8 +42,10 @@ const NodeTitle = ({ nodeId, title }: Props) => {
useEffect(() => { useEffect(() => {
// Another component may change the title; sync local title with global state // Another component may change the title; sync local title with global state
setLocalTitle(label || title || templateTitle || 'Problem Setting Title'); setLocalTitle(
}, [label, templateTitle, title]); label || title || templateTitle || t('nodes.problemSettingTitle')
);
}, [label, templateTitle, title, t]);
return ( return (
<Flex <Flex

View File

@ -8,10 +8,12 @@ import {
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice'; import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export default function NodeOpacitySlider() { export default function NodeOpacitySlider() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const nodeOpacity = useAppSelector((state) => state.nodes.nodeOpacity); const nodeOpacity = useAppSelector((state) => state.nodes.nodeOpacity);
const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => { (v: number) => {
@ -23,7 +25,7 @@ export default function NodeOpacitySlider() {
return ( return (
<Flex alignItems="center"> <Flex alignItems="center">
<Slider <Slider
aria-label="Node Opacity" aria-label={t('nodes.nodeOpacity')}
value={nodeOpacity} value={nodeOpacity}
min={0.5} min={0.5}
max={1} max={1}

View File

@ -4,10 +4,11 @@ import IAIIconButton from 'common/components/IAIIconButton';
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice'; import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { FaPlus } from 'react-icons/fa'; import { FaPlus } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
const TopLeftPanel = () => { const TopLeftPanel = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleOpenAddNodePopover = useCallback(() => { const handleOpenAddNodePopover = useCallback(() => {
dispatch(addNodePopoverOpened()); dispatch(addNodePopoverOpened());
}, [dispatch]); }, [dispatch]);
@ -15,8 +16,8 @@ const TopLeftPanel = () => {
return ( return (
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}> <Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}>
<IAIIconButton <IAIIconButton
tooltip="Add Node (Shift+A, Space)" tooltip={t('nodes.addNodeToolTip')}
aria-label="Add Node" aria-label={t('nodes.addNode')}
icon={<FaPlus />} icon={<FaPlus />}
onClick={handleOpenAddNodePopover} onClick={handleOpenAddNodePopover}
/> />

View File

@ -29,6 +29,7 @@ import { ChangeEvent, memo, useCallback } from 'react';
import { FaCog } from 'react-icons/fa'; import { FaCog } from 'react-icons/fa';
import { SelectionMode } from 'reactflow'; import { SelectionMode } from 'reactflow';
import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton'; import ReloadNodeTemplatesButton from '../TopCenterPanel/ReloadSchemaButton';
import { useTranslation } from 'react-i18next';
const formLabelProps: FormLabelProps = { const formLabelProps: FormLabelProps = {
fontWeight: 600, fontWeight: 600,
@ -101,12 +102,14 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
[dispatch] [dispatch]
); );
const { t } = useTranslation();
return ( return (
<> <>
<IAIIconButton <IAIIconButton
ref={ref} ref={ref}
aria-label="Workflow Editor Settings" aria-label={t('nodes.workflowSettings')}
tooltip="Workflow Editor Settings" tooltip={t('nodes.workflowSettings')}
icon={<FaCog />} icon={<FaCog />}
onClick={onOpen} onClick={onOpen}
/> />
@ -114,7 +117,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered> <Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>
<ModalHeader>Workflow Editor Settings</ModalHeader> <ModalHeader>{t('nodes.workflowSettings')}</ModalHeader>
<ModalCloseButton /> <ModalCloseButton />
<ModalBody> <ModalBody>
<Flex <Flex
@ -129,31 +132,31 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
formLabelProps={formLabelProps} formLabelProps={formLabelProps}
onChange={handleChangeShouldAnimate} onChange={handleChangeShouldAnimate}
isChecked={shouldAnimateEdges} isChecked={shouldAnimateEdges}
label="Animated Edges" label={t('nodes.animatedEdges')}
helperText="Animate selected edges and edges connected to selected nodes" helperText={t('nodes.animatedEdgesHelp')}
/> />
<Divider /> <Divider />
<IAISwitch <IAISwitch
formLabelProps={formLabelProps} formLabelProps={formLabelProps}
isChecked={shouldSnapToGrid} isChecked={shouldSnapToGrid}
onChange={handleChangeShouldSnap} onChange={handleChangeShouldSnap}
label="Snap to Grid" label={t('nodes.snapToGrid')}
helperText="Snap nodes to grid when moved" helperText={t('nodes.snapToGridHelp')}
/> />
<Divider /> <Divider />
<IAISwitch <IAISwitch
formLabelProps={formLabelProps} formLabelProps={formLabelProps}
isChecked={shouldColorEdges} isChecked={shouldColorEdges}
onChange={handleChangeShouldColor} onChange={handleChangeShouldColor}
label="Color-Code Edges" label={t('nodes.colorCodeEdges')}
helperText="Color-code edges according to their connected fields" helperText={t('nodes.colorCodeEdgesHelp')}
/> />
<IAISwitch <IAISwitch
formLabelProps={formLabelProps} formLabelProps={formLabelProps}
isChecked={selectionModeIsChecked} isChecked={selectionModeIsChecked}
onChange={handleChangeSelectionMode} onChange={handleChangeSelectionMode}
label="Fully Contain Nodes to Select" label={t('nodes.fullyContainNodes')}
helperText="Nodes must be fully inside the selection box to be selected" helperText={t('nodes.fullyContainNodesHelp')}
/> />
<Heading size="sm" pt={4}> <Heading size="sm" pt={4}>
Advanced Advanced
@ -162,8 +165,8 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
formLabelProps={formLabelProps} formLabelProps={formLabelProps}
isChecked={shouldValidateGraph} isChecked={shouldValidateGraph}
onChange={handleChangeShouldValidate} onChange={handleChangeShouldValidate}
label="Validate Connections and Graph" label={t('nodes.validateConnections')}
helperText="Prevent invalid connections from being made, and invalid graphs from being invoked" helperText={t('nodes.validateConnectionsHelp')}
/> />
<ReloadNodeTemplatesButton /> <ReloadNodeTemplatesButton />
</Flex> </Flex>

View File

@ -9,6 +9,7 @@ import { memo } from 'react';
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea'; import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
import NodeTitle from '../../flow/nodes/common/NodeTitle'; import NodeTitle from '../../flow/nodes/common/NodeTitle';
import ScrollableContent from '../ScrollableContent'; import ScrollableContent from '../ScrollableContent';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -34,9 +35,12 @@ const selector = createSelector(
const InspectorDetailsTab = () => { const InspectorDetailsTab = () => {
const { data, template } = useAppSelector(selector); const { data, template } = useAppSelector(selector);
const { t } = useTranslation();
if (!template || !data) { 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} />; return <Content data={data} template={template} />;

View File

@ -11,6 +11,7 @@ import { ImageOutput } from 'services/api/types';
import { AnyResult } from 'services/events/types'; import { AnyResult } from 'services/events/types';
import ScrollableContent from '../ScrollableContent'; import ScrollableContent from '../ScrollableContent';
import ImageOutputPreview from './outputs/ImageOutputPreview'; import ImageOutputPreview from './outputs/ImageOutputPreview';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -40,13 +41,18 @@ const selector = createSelector(
const InspectorOutputsTab = () => { const InspectorOutputsTab = () => {
const { node, template, nes } = useAppSelector(selector); const { node, template, nes } = useAppSelector(selector);
const { t } = useTranslation();
if (!node || !nes || !isInvocationNode(node)) { 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) { if (nes.outputs.length === 0) {
return <IAINoContentFallback label="No outputs recorded" icon={null} />; return (
<IAINoContentFallback label={t('nodes.noOutputRecorded')} icon={null} />
);
} }
return ( return (
@ -77,7 +83,7 @@ const InspectorOutputsTab = () => {
/> />
)) ))
) : ( ) : (
<DataViewer data={nes.outputs} label="Node Outputs" /> <DataViewer data={nes.outputs} label={t('nodes.nodesOutputs')} />
)} )}
</Flex> </Flex>
</ScrollableContent> </ScrollableContent>

View File

@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -29,12 +30,15 @@ const selector = createSelector(
const NodeTemplateInspector = () => { const NodeTemplateInspector = () => {
const { template } = useAppSelector(selector); const { template } = useAppSelector(selector);
const { t } = useTranslation();
if (!template) { 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); export default memo(NodeTemplateInspector);

View File

@ -16,6 +16,7 @@ import {
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { ChangeEvent, memo, useCallback } from 'react'; import { ChangeEvent, memo, useCallback } from 'react';
import ScrollableContent from '../ScrollableContent'; import ScrollableContent from '../ScrollableContent';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -85,6 +86,8 @@ const WorkflowGeneralTab = () => {
[dispatch] [dispatch]
); );
const { t } = useTranslation();
return ( return (
<ScrollableContent> <ScrollableContent>
<Flex <Flex
@ -96,28 +99,36 @@ const WorkflowGeneralTab = () => {
}} }}
> >
<Flex sx={{ gap: 2, w: 'full' }}> <Flex sx={{ gap: 2, w: 'full' }}>
<IAIInput label="Name" value={name} onChange={handleChangeName} />
<IAIInput <IAIInput
label="Version" label={t('nodes.workflowName')}
value={name}
onChange={handleChangeName}
/>
<IAIInput
label={t('nodes.workflowVersion')}
value={version} value={version}
onChange={handleChangeVersion} onChange={handleChangeVersion}
/> />
</Flex> </Flex>
<Flex sx={{ gap: 2, w: 'full' }}> <Flex sx={{ gap: 2, w: 'full' }}>
<IAIInput <IAIInput
label="Author" label={t('nodes.workflowAuthor')}
value={author} value={author}
onChange={handleChangeAuthor} onChange={handleChangeAuthor}
/> />
<IAIInput <IAIInput
label="Contact" label={t('nodes.workflowContact')}
value={contact} value={contact}
onChange={handleChangeContact} onChange={handleChangeContact}
/> />
</Flex> </Flex>
<IAIInput label="Tags" value={tags} onChange={handleChangeTags} /> <IAIInput
label={t('nodes.workflowTags')}
value={tags}
onChange={handleChangeTags}
/>
<FormControl as={Flex} sx={{ flexDir: 'column' }}> <FormControl as={Flex} sx={{ flexDir: 'column' }}>
<FormLabel>Short Description</FormLabel> <FormLabel>{t('nodes.workflowDescription')}</FormLabel>
<IAITextarea <IAITextarea
onChange={handleChangeDescription} onChange={handleChangeDescription}
value={description} value={description}
@ -126,7 +137,7 @@ const WorkflowGeneralTab = () => {
/> />
</FormControl> </FormControl>
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}> <FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
<FormLabel>Notes</FormLabel> <FormLabel>{t('nodes.workflowNotes')}</FormLabel>
<IAITextarea <IAITextarea
onChange={handleChangeNotes} onChange={handleChangeNotes}
value={notes} value={notes}

View File

@ -2,9 +2,11 @@ import { Flex } from '@chakra-ui/react';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer'; import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { useWorkflow } from 'features/nodes/hooks/useWorkflow'; import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const WorkflowJSONTab = () => { const WorkflowJSONTab = () => {
const workflow = useWorkflow(); const workflow = useWorkflow();
const { t } = useTranslation();
return ( return (
<Flex <Flex
@ -15,7 +17,7 @@ const WorkflowJSONTab = () => {
h: 'full', h: 'full',
}} }}
> >
<DataViewer data={workflow} label="Workflow" /> <DataViewer data={workflow} label={t('nodes.workflow')} />
</Flex> </Flex>
); );
}; };

View File

@ -7,6 +7,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { memo } from 'react'; import { memo } from 'react';
import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField'; import LinearViewField from '../../flow/nodes/Invocation/fields/LinearViewField';
import ScrollableContent from '../ScrollableContent'; import ScrollableContent from '../ScrollableContent';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -20,6 +21,7 @@ const selector = createSelector(
const WorkflowLinearTab = () => { const WorkflowLinearTab = () => {
const { fields } = useAppSelector(selector); const { fields } = useAppSelector(selector);
const { t } = useTranslation();
return ( return (
<Box <Box
@ -51,7 +53,7 @@ const WorkflowLinearTab = () => {
)) ))
) : ( ) : (
<IAINoContentFallback <IAINoContentFallback
label="No fields added to Linear View" label={t('nodes.noFieldsLinearview')}
icon={null} icon={null}
/> />
)} )}

View File

@ -9,10 +9,12 @@ import { memo, useCallback } from 'react';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { fromZodError, fromZodIssue } from 'zod-validation-error'; import { fromZodError, fromZodIssue } from 'zod-validation-error';
import { workflowLoadRequested } from '../store/actions'; import { workflowLoadRequested } from '../store/actions';
import { useTranslation } from 'react-i18next';
export const useLoadWorkflowFromFile = () => { export const useLoadWorkflowFromFile = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const logger = useLogger('nodes'); const logger = useLogger('nodes');
const { t } = useTranslation();
const loadWorkflowFromFile = useCallback( const loadWorkflowFromFile = useCallback(
(file: File | null) => { (file: File | null) => {
if (!file) { if (!file) {
@ -28,7 +30,7 @@ export const useLoadWorkflowFromFile = () => {
if (!result.success) { if (!result.success) {
const { message } = fromZodError(result.error, { const { message } = fromZodError(result.error, {
prefix: 'Workflow Validation Error', prefix: t('nodes.workflowValidation'),
}); });
logger.error({ error: parseify(result.error) }, message); logger.error({ error: parseify(result.error) }, message);
@ -36,7 +38,7 @@ export const useLoadWorkflowFromFile = () => {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Unable to Validate Workflow', title: t('nodes.unableToValidateWorkflow'),
status: 'error', status: 'error',
duration: 5000, duration: 5000,
}) })
@ -54,7 +56,7 @@ export const useLoadWorkflowFromFile = () => {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Unable to Load Workflow', title: t('nodes.unableToLoadWorkflow'),
status: 'error', status: 'error',
}) })
) )
@ -64,7 +66,7 @@ export const useLoadWorkflowFromFile = () => {
reader.readAsText(file); reader.readAsText(file);
}, },
[dispatch, logger] [dispatch, logger, t]
); );
return loadWorkflowFromFile; return loadWorkflowFromFile;

View File

@ -9,6 +9,7 @@ import {
} from 'features/nodes/types/constants'; } from 'features/nodes/types/constants';
import { FieldType } from 'features/nodes/types/types'; import { FieldType } from 'features/nodes/types/types';
import { HandleType } from 'reactflow'; 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` * 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, fieldName: string,
handleType: HandleType, handleType: HandleType,
fieldType?: FieldType fieldType?: FieldType
) => ) => {
createSelector(stateSelector, (state) => { return createSelector(stateSelector, (state) => {
if (!fieldType) { if (!fieldType) {
return 'No field type'; return i18n.t('nodes.noFieldType');
} }
const { currentConnectionFieldType, connectionStartParams, nodes, edges } = const { currentConnectionFieldType, connectionStartParams, nodes, edges } =
state.nodes; state.nodes;
if (!connectionStartParams || !currentConnectionFieldType) { if (!connectionStartParams || !currentConnectionFieldType) {
return 'No connection in progress'; return i18n.t('nodes.noConnectionInProgress');
} }
const { const {
@ -40,7 +41,7 @@ export const makeConnectionErrorSelector = (
} = connectionStartParams; } = connectionStartParams;
if (!connectionHandleType || !connectionNodeId || !connectionFieldName) { if (!connectionHandleType || !connectionNodeId || !connectionFieldName) {
return 'No connection data'; return i18n.t('nodes.noConnectionData');
} }
const targetType = const targetType =
@ -49,14 +50,14 @@ export const makeConnectionErrorSelector = (
handleType === 'source' ? fieldType : currentConnectionFieldType; handleType === 'source' ? fieldType : currentConnectionFieldType;
if (nodeId === connectionNodeId) { if (nodeId === connectionNodeId) {
return 'Cannot connect to self'; return i18n.t('nodes.cannotConnectToSelf');
} }
if (handleType === connectionHandleType) { if (handleType === connectionHandleType) {
if (handleType === 'source') { 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 ( if (
@ -66,7 +67,7 @@ export const makeConnectionErrorSelector = (
// except CollectionItem inputs can have multiples // except CollectionItem inputs can have multiples
targetType !== 'CollectionItem' targetType !== 'CollectionItem'
) { ) {
return 'Input may only have one connection'; return i18n.t('nodes.inputMayOnlyHaveOneConnection');
} }
/** /**
@ -125,7 +126,7 @@ export const makeConnectionErrorSelector = (
isIntToFloat isIntToFloat
) )
) { ) {
return 'Field types must match'; return i18n.t('nodes.fieldTypesMustMatch');
} }
} }
@ -137,8 +138,9 @@ export const makeConnectionErrorSelector = (
); );
if (!isGraphAcyclic) { if (!isGraphAcyclic) {
return 'Connection would create a cycle'; return i18n.t('nodes.connectionWouldCreateCycle');
} }
return null; return null;
}); });
};

View File

@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAICollapse from 'common/components/IAICollapse'; import IAICollapse from 'common/components/IAICollapse';
import ParamClipSkip from './ParamClipSkip'; import ParamClipSkip from './ParamClipSkip';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -22,13 +23,13 @@ export default function ParamAdvancedCollapse() {
const shouldShowAdvancedOptions = useAppSelector( const shouldShowAdvancedOptions = useAppSelector(
(state: RootState) => state.generation.shouldShowAdvancedOptions (state: RootState) => state.generation.shouldShowAdvancedOptions
); );
const { t } = useTranslation();
if (!shouldShowAdvancedOptions) { if (!shouldShowAdvancedOptions) {
return null; return null;
} }
return ( return (
<IAICollapse label="Advanced" activeLabel={activeLabel}> <IAICollapse label={t('common.advanced')} activeLabel={activeLabel}>
<Flex sx={{ flexDir: 'column', gap: 2 }}> <Flex sx={{ flexDir: 'column', gap: 2 }}>
<ParamClipSkip /> <ParamClipSkip />
</Flex> </Flex>

View File

@ -5,6 +5,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice'; import { shouldUseCpuNoiseChanged } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -21,6 +22,7 @@ const selector = createSelector(
export const ParamCpuNoiseToggle = () => { export const ParamCpuNoiseToggle = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector); const { isDisabled, shouldUseCpuNoise } = useAppSelector(selector);
const { t } = useTranslation();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(shouldUseCpuNoiseChanged(e.target.checked)); dispatch(shouldUseCpuNoiseChanged(e.target.checked));
@ -28,7 +30,7 @@ export const ParamCpuNoiseToggle = () => {
return ( return (
<IAISwitch <IAISwitch
isDisabled={isDisabled} isDisabled={isDisabled}
label="Use CPU Noise" label={t('parameters.useCpuNoise')}
isChecked={shouldUseCpuNoise} isChecked={shouldUseCpuNoise}
onChange={handleChange} onChange={handleChange}
/> />

View File

@ -3,9 +3,11 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice'; import { setShouldUseNoiseSettings } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export const ParamNoiseToggle = () => { export const ParamNoiseToggle = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const shouldUseNoiseSettings = useAppSelector( const shouldUseNoiseSettings = useAppSelector(
(state: RootState) => state.generation.shouldUseNoiseSettings (state: RootState) => state.generation.shouldUseNoiseSettings
@ -16,7 +18,7 @@ export const ParamNoiseToggle = () => {
return ( return (
<IAISwitch <IAISwitch
label="Enable Noise Settings" label={t('parameters.enableNoiseSettings')}
isChecked={shouldUseNoiseSettings} isChecked={shouldUseNoiseSettings}
onChange={handleChange} onChange={handleChange}
/> />

View File

@ -146,7 +146,7 @@ const CancelButton = (props: Props) => {
id="cancel-button" id="cancel-button"
{...rest} {...rest}
> >
Cancel {t('parameters.cancel.cancel')}
</IAIButton> </IAIButton>
)} )}
<Menu closeOnSelect={false}> <Menu closeOnSelect={false}>

View File

@ -76,7 +76,7 @@ export default function InvokeButton(props: InvokeButton) {
)} )}
{asIconButton ? ( {asIconButton ? (
<IAIIconButton <IAIIconButton
aria-label={t('parameters.invoke')} aria-label={t('parameters.invoke.invoke')}
type="submit" type="submit"
icon={<FaPlay />} icon={<FaPlay />}
isDisabled={!isReady} isDisabled={!isReady}
@ -96,7 +96,7 @@ export default function InvokeButton(props: InvokeButton) {
) : ( ) : (
<IAIButton <IAIButton
tooltip={<InvokeButtonTooltipContent />} tooltip={<InvokeButtonTooltipContent />}
aria-label={t('parameters.invoke')} aria-label={t('parameters.invoke.invoke')}
type="submit" type="submit"
data-progress={isProcessing} data-progress={isProcessing}
isDisabled={!isReady} isDisabled={!isReady}
@ -105,7 +105,7 @@ export default function InvokeButton(props: InvokeButton) {
id="invoke-button" id="invoke-button"
leftIcon={isProcessing ? undefined : <FaPlay />} leftIcon={isProcessing ? undefined : <FaPlay />}
isLoading={isProcessing} isLoading={isProcessing}
loadingText={t('parameters.invoke')} loadingText={t('parameters.invoke.invoke')}
sx={{ sx={{
w: 'full', w: 'full',
flexGrow: 1, flexGrow: 1,
@ -138,11 +138,14 @@ export const InvokeButtonTooltipContent = memo(() => {
const { isReady, reasons } = useIsReadyToInvoke(); const { isReady, reasons } = useIsReadyToInvoke();
const { autoAddBoardId } = useAppSelector(tooltipSelector); const { autoAddBoardId } = useAppSelector(tooltipSelector);
const autoAddBoardName = useBoardName(autoAddBoardId); const autoAddBoardName = useBoardName(autoAddBoardId);
const { t } = useTranslation();
return ( return (
<Flex flexDir="column" gap={1}> <Flex flexDir="column" gap={1}>
<Text fontWeight={600}> <Text fontWeight={600}>
{isReady ? 'Ready to Invoke' : 'Unable to Invoke'} {isReady
? t('parameters.invoke.readyToInvoke')
: t('parameters.invoke.unableToInvoke')}
</Text> </Text>
{reasons.length > 0 && ( {reasons.length > 0 && (
<UnorderedList> <UnorderedList>
@ -159,7 +162,7 @@ export const InvokeButtonTooltipContent = memo(() => {
_dark={{ borderColor: 'base.900' }} _dark={{ borderColor: 'base.900' }}
/> />
<Text fontWeight={400} fontStyle="oblique 10deg"> <Text fontWeight={400} fontStyle="oblique 10deg">
Adding images to{' '} {t('parameters.invoke.addingImagesTo')}{' '}
<Text as="span" fontWeight={600}> <Text as="span" fontWeight={600}>
{autoAddBoardName || 'Uncategorized'} {autoAddBoardName || 'Uncategorized'}
</Text> </Text>

View File

@ -3,6 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import { FaLink } from 'react-icons/fa'; import { FaLink } from 'react-icons/fa';
import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice'; import { setShouldConcatSDXLStylePrompt } from '../store/sdxlSlice';
import { useTranslation } from 'react-i18next';
export default function ParamSDXLConcatButton() { export default function ParamSDXLConcatButton() {
const shouldConcatSDXLStylePrompt = useAppSelector( const shouldConcatSDXLStylePrompt = useAppSelector(
@ -10,6 +11,7 @@ export default function ParamSDXLConcatButton() {
); );
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleShouldConcatPromptChange = () => { const handleShouldConcatPromptChange = () => {
dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt)); dispatch(setShouldConcatSDXLStylePrompt(!shouldConcatSDXLStylePrompt));
@ -17,8 +19,8 @@ export default function ParamSDXLConcatButton() {
return ( return (
<IAIIconButton <IAIIconButton
aria-label="Concatenate Prompt & Style" aria-label={t('sdxl.concatPromptStyle')}
tooltip="Concatenate Prompt & Style" tooltip={t('sdxl.concatPromptStyle')}
variant="outline" variant="outline"
isChecked={shouldConcatSDXLStylePrompt} isChecked={shouldConcatSDXLStylePrompt}
onClick={handleShouldConcatPromptChange} onClick={handleShouldConcatPromptChange}

View File

@ -37,7 +37,7 @@ const ParamSDXLImg2ImgDenoisingStrength = () => {
return ( return (
<SubParametersWrapper> <SubParametersWrapper>
<IAISlider <IAISlider
label={`${t('parameters.denoisingStrength')}`} label={t('sdxl.denoisingStrength')}
step={0.01} step={0.01}
min={0} min={0}
max={1} max={1}

View File

@ -6,6 +6,7 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useTranslation } from 'react-i18next';
import { userInvoked } from 'app/store/actions'; import { userInvoked } from 'app/store/actions';
import IAITextarea from 'common/components/IAITextarea'; import IAITextarea from 'common/components/IAITextarea';
@ -45,6 +46,7 @@ const ParamSDXLNegativeStyleConditioning = () => {
const isReady = useIsReadyToInvoke(); const isReady = useIsReadyToInvoke();
const promptRef = useRef<HTMLTextAreaElement>(null); const promptRef = useRef<HTMLTextAreaElement>(null);
const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen, onClose, onOpen } = useDisclosure();
const { t } = useTranslation();
const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = const { prompt, activeTabName, shouldConcatSDXLStylePrompt } =
useAppSelector(promptInputSelector); useAppSelector(promptInputSelector);
@ -143,7 +145,7 @@ const ParamSDXLNegativeStyleConditioning = () => {
name="prompt" name="prompt"
ref={promptRef} ref={promptRef}
value={prompt} value={prompt}
placeholder="Negative Style Prompt" placeholder={t('sdxl.negStylePrompt')}
onChange={handleChangePrompt} onChange={handleChangePrompt}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
resize="vertical" resize="vertical"

View File

@ -6,6 +6,7 @@ import { ChangeEvent, KeyboardEvent, memo, useCallback, useRef } from 'react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useTranslation } from 'react-i18next';
import { userInvoked } from 'app/store/actions'; import { userInvoked } from 'app/store/actions';
import IAITextarea from 'common/components/IAITextarea'; import IAITextarea from 'common/components/IAITextarea';
@ -45,6 +46,7 @@ const ParamSDXLPositiveStyleConditioning = () => {
const isReady = useIsReadyToInvoke(); const isReady = useIsReadyToInvoke();
const promptRef = useRef<HTMLTextAreaElement>(null); const promptRef = useRef<HTMLTextAreaElement>(null);
const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen, onClose, onOpen } = useDisclosure();
const { t } = useTranslation();
const { prompt, activeTabName, shouldConcatSDXLStylePrompt } = const { prompt, activeTabName, shouldConcatSDXLStylePrompt } =
useAppSelector(promptInputSelector); useAppSelector(promptInputSelector);
@ -143,7 +145,7 @@ const ParamSDXLPositiveStyleConditioning = () => {
name="prompt" name="prompt"
ref={promptRef} ref={promptRef}
value={prompt} value={prompt}
placeholder="Positive Style Prompt" placeholder={t('sdxl.posStylePrompt')}
onChange={handleChangePrompt} onChange={handleChangePrompt}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
resize="vertical" resize="vertical"

View File

@ -13,6 +13,7 @@ import ParamSDXLRefinerScheduler from './SDXLRefiner/ParamSDXLRefinerScheduler';
import ParamSDXLRefinerStart from './SDXLRefiner/ParamSDXLRefinerStart'; import ParamSDXLRefinerStart from './SDXLRefiner/ParamSDXLRefinerStart';
import ParamSDXLRefinerSteps from './SDXLRefiner/ParamSDXLRefinerSteps'; import ParamSDXLRefinerSteps from './SDXLRefiner/ParamSDXLRefinerSteps';
import ParamUseSDXLRefiner from './SDXLRefiner/ParamUseSDXLRefiner'; import ParamUseSDXLRefiner from './SDXLRefiner/ParamUseSDXLRefiner';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -29,9 +30,10 @@ const selector = createSelector(
const ParamSDXLRefinerCollapse = () => { const ParamSDXLRefinerCollapse = () => {
const { activeLabel, shouldUseSliders } = useAppSelector(selector); const { activeLabel, shouldUseSliders } = useAppSelector(selector);
const { t } = useTranslation();
return ( return (
<IAICollapse label="Refiner" activeLabel={activeLabel}> <IAICollapse label={t('sdxl.refiner')} activeLabel={activeLabel}>
<Flex sx={{ gap: 2, flexDir: 'column' }}> <Flex sx={{ gap: 2, flexDir: 'column' }}>
<ParamUseSDXLRefiner /> <ParamUseSDXLRefiner />
<ParamSDXLRefinerModelSelect /> <ParamSDXLRefinerModelSelect />

View File

@ -43,7 +43,7 @@ const ParamSDXLRefinerCFGScale = () => {
return shouldUseSliders ? ( return shouldUseSliders ? (
<IAISlider <IAISlider
label={t('parameters.cfgScale')} label={t('sdxl.cfgScale')}
step={shift ? 0.1 : 0.5} step={shift ? 0.1 : 0.5}
min={1} min={1}
max={20} max={20}
@ -59,7 +59,7 @@ const ParamSDXLRefinerCFGScale = () => {
/> />
) : ( ) : (
<IAINumberInput <IAINumberInput
label={t('parameters.cfgScale')} label={t('sdxl.cfgScale')}
step={0.5} step={0.5}
min={1} min={1}
max={200} max={200}

View File

@ -14,6 +14,7 @@ import { forEach } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { REFINER_BASE_MODELS } from 'services/api/constants'; import { REFINER_BASE_MODELS } from 'services/api/constants';
import { useGetMainModelsQuery } from 'services/api/endpoints/models'; import { useGetMainModelsQuery } from 'services/api/endpoints/models';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
stateSelector, stateSelector,
@ -26,6 +27,7 @@ const ParamSDXLRefinerModelSelect = () => {
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled; const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
const { model } = useAppSelector(selector); const { model } = useAppSelector(selector);
const { t } = useTranslation();
const { data: refinerModels, isLoading } = const { data: refinerModels, isLoading } =
useGetMainModelsQuery(REFINER_BASE_MODELS); useGetMainModelsQuery(REFINER_BASE_MODELS);
@ -81,8 +83,8 @@ const ParamSDXLRefinerModelSelect = () => {
return isLoading ? ( return isLoading ? (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
label="Refiner Model" label={t('sdxl.refinermodel')}
placeholder="Loading..." placeholder={t('sdxl.loading')}
disabled={true} disabled={true}
data={[]} data={[]}
/> />
@ -90,9 +92,11 @@ const ParamSDXLRefinerModelSelect = () => {
<Flex w="100%" alignItems="center" gap={2}> <Flex w="100%" alignItems="center" gap={2}>
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
tooltip={selectedModel?.description} tooltip={selectedModel?.description}
label="Refiner Model" label={t('sdxl.refinermodel')}
value={selectedModel?.id} value={selectedModel?.id}
placeholder={data.length > 0 ? 'Select a model' : 'No models available'} placeholder={
data.length > 0 ? t('sdxl.selectAModel') : t('sdxl.noModelsAvailable')
}
data={data} data={data}
error={data.length === 0} error={data.length === 0}
disabled={data.length === 0} disabled={data.length === 0}

View File

@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider';
import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice'; import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -27,6 +28,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => {
const isRefinerAvailable = useIsRefinerAvailable(); const isRefinerAvailable = useIsRefinerAvailable();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => dispatch(setRefinerNegativeAestheticScore(v)), (v: number) => dispatch(setRefinerNegativeAestheticScore(v)),
@ -40,7 +42,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => {
return ( return (
<IAISlider <IAISlider
label="Negative Aesthetic Score" label={t('sdxl.negAestheticScore')}
step={shift ? 0.1 : 0.5} step={shift ? 0.1 : 0.5}
min={1} min={1}
max={10} max={10}

View File

@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider';
import { setRefinerPositiveAestheticScore } from 'features/sdxl/store/sdxlSlice'; import { setRefinerPositiveAestheticScore } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -27,6 +28,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => {
const isRefinerAvailable = useIsRefinerAvailable(); const isRefinerAvailable = useIsRefinerAvailable();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleChange = useCallback( const handleChange = useCallback(
(v: number) => dispatch(setRefinerPositiveAestheticScore(v)), (v: number) => dispatch(setRefinerPositiveAestheticScore(v)),
@ -40,7 +42,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => {
return ( return (
<IAISlider <IAISlider
label="Positive Aesthetic Score" label={t('sdxl.posAestheticScore')}
step={shift ? 0.1 : 0.5} step={shift ? 0.1 : 0.5}
min={1} min={1}
max={10} max={10}

View File

@ -53,7 +53,7 @@ const ParamSDXLRefinerScheduler = () => {
return ( return (
<IAIMantineSearchableSelect <IAIMantineSearchableSelect
w="100%" w="100%"
label={t('parameters.scheduler')} label={t('sdxl.scheduler')}
value={refinerScheduler} value={refinerScheduler}
data={data} data={data}
onChange={handleChange} onChange={handleChange}

View File

@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider';
import { setRefinerStart } from 'features/sdxl/store/sdxlSlice'; import { setRefinerStart } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable';
import { useTranslation } from 'react-i18next';
const selector = createSelector( const selector = createSelector(
[stateSelector], [stateSelector],
@ -26,6 +27,7 @@ const ParamSDXLRefinerStart = () => {
(v: number) => dispatch(setRefinerStart(v)), (v: number) => dispatch(setRefinerStart(v)),
[dispatch] [dispatch]
); );
const { t } = useTranslation();
const handleReset = useCallback( const handleReset = useCallback(
() => dispatch(setRefinerStart(0.8)), () => dispatch(setRefinerStart(0.8)),
@ -34,7 +36,7 @@ const ParamSDXLRefinerStart = () => {
return ( return (
<IAISlider <IAISlider
label="Refiner Start" label={t('sdxl.refinerStart')}
step={0.01} step={0.01}
min={0} min={0}
max={1} max={1}

View File

@ -42,7 +42,7 @@ const ParamSDXLRefinerSteps = () => {
return shouldUseSliders ? ( return shouldUseSliders ? (
<IAISlider <IAISlider
label={t('parameters.steps')} label={t('sdxl.steps')}
min={1} min={1}
max={100} max={100}
step={1} step={1}
@ -57,7 +57,7 @@ const ParamSDXLRefinerSteps = () => {
/> />
) : ( ) : (
<IAINumberInput <IAINumberInput
label={t('parameters.steps')} label={t('sdxl.steps')}
min={1} min={1}
max={500} max={500}
step={1} step={1}

View File

@ -4,6 +4,7 @@ import IAISwitch from 'common/components/IAISwitch';
import { setShouldUseSDXLRefiner } from 'features/sdxl/store/sdxlSlice'; import { setShouldUseSDXLRefiner } from 'features/sdxl/store/sdxlSlice';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable'; import { useIsRefinerAvailable } from 'services/api/hooks/useIsRefinerAvailable';
import { useTranslation } from 'react-i18next';
export default function ParamUseSDXLRefiner() { export default function ParamUseSDXLRefiner() {
const shouldUseSDXLRefiner = useAppSelector( const shouldUseSDXLRefiner = useAppSelector(
@ -12,6 +13,7 @@ export default function ParamUseSDXLRefiner() {
const isRefinerAvailable = useIsRefinerAvailable(); const isRefinerAvailable = useIsRefinerAvailable();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const handleUseSDXLRefinerChange = (e: ChangeEvent<HTMLInputElement>) => { const handleUseSDXLRefinerChange = (e: ChangeEvent<HTMLInputElement>) => {
dispatch(setShouldUseSDXLRefiner(e.target.checked)); dispatch(setShouldUseSDXLRefiner(e.target.checked));
@ -19,7 +21,7 @@ export default function ParamUseSDXLRefiner() {
return ( return (
<IAISwitch <IAISwitch
label="Use Refiner" label={t('sdxl.useRefiner')}
isChecked={shouldUseSDXLRefiner} isChecked={shouldUseSDXLRefiner}
onChange={handleUseSDXLRefinerChange} onChange={handleUseSDXLRefinerChange}
isDisabled={!isRefinerAvailable} isDisabled={!isRefinerAvailable}

View File

@ -55,7 +55,9 @@ export default function AdvancedAddCheckpoint(
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: `Model Added: ${values.model_name}`, title: t('modelManager.modelAdded', {
modelName: values.model_name,
}),
status: 'success', status: 'success',
}) })
) )
@ -72,7 +74,7 @@ export default function AdvancedAddCheckpoint(
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Model Add Failed', title: t('toast.modelAddFailed'),
status: 'error', status: 'error',
}) })
) )
@ -90,15 +92,16 @@ export default function AdvancedAddCheckpoint(
> >
<Flex flexDirection="column" gap={2}> <Flex flexDirection="column" gap={2}>
<IAIMantineTextInput <IAIMantineTextInput
label="Model Name" label={t('modelManager.model')}
required required
{...advancedAddCheckpointForm.getInputProps('model_name')} {...advancedAddCheckpointForm.getInputProps('model_name')}
/> />
<BaseModelSelect <BaseModelSelect
label={t('modelManager.baseModel')}
{...advancedAddCheckpointForm.getInputProps('base_model')} {...advancedAddCheckpointForm.getInputProps('base_model')}
/> />
<IAIMantineTextInput <IAIMantineTextInput
label="Model Location" label={t('modelManager.modelLocation')}
required required
{...advancedAddCheckpointForm.getInputProps('path')} {...advancedAddCheckpointForm.getInputProps('path')}
onBlur={(e) => { onBlur={(e) => {
@ -114,14 +117,15 @@ export default function AdvancedAddCheckpoint(
}} }}
/> />
<IAIMantineTextInput <IAIMantineTextInput
label="Description" label={t('modelManager.description')}
{...advancedAddCheckpointForm.getInputProps('description')} {...advancedAddCheckpointForm.getInputProps('description')}
/> />
<IAIMantineTextInput <IAIMantineTextInput
label="VAE Location" label={t('modelManager.vaeLocation')}
{...advancedAddCheckpointForm.getInputProps('vae')} {...advancedAddCheckpointForm.getInputProps('vae')}
/> />
<ModelVariantSelect <ModelVariantSelect
label={t('modelManager.variant')}
{...advancedAddCheckpointForm.getInputProps('variant')} {...advancedAddCheckpointForm.getInputProps('variant')}
/> />
<Flex flexDirection="column" width="100%" gap={2}> <Flex flexDirection="column" width="100%" gap={2}>
@ -134,14 +138,14 @@ export default function AdvancedAddCheckpoint(
) : ( ) : (
<IAIMantineTextInput <IAIMantineTextInput
required required
label="Custom Config File Location" label={t('modelManager.customConfigFileLocation')}
{...advancedAddCheckpointForm.getInputProps('config')} {...advancedAddCheckpointForm.getInputProps('config')}
/> />
)} )}
<IAISimpleCheckbox <IAISimpleCheckbox
isChecked={useCustomConfig} isChecked={useCustomConfig}
onChange={() => setUseCustomConfig(!useCustomConfig)} onChange={() => setUseCustomConfig(!useCustomConfig)}
label="Use Custom Config" label={t('modelManager.useCustomConfig')}
/> />
<IAIButton mt={2} type="submit"> <IAIButton mt={2} type="submit">
{t('modelManager.addModel')} {t('modelManager.addModel')}

View File

@ -47,7 +47,9 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: `Model Added: ${values.model_name}`, title: t('modelManager.modelAdded', {
modelName: values.model_name,
}),
status: 'success', status: 'success',
}) })
) )
@ -63,7 +65,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Model Add Failed', title: t('toast.modelAddFailed'),
status: 'error', status: 'error',
}) })
) )
@ -82,16 +84,17 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
<Flex flexDirection="column" gap={2}> <Flex flexDirection="column" gap={2}>
<IAIMantineTextInput <IAIMantineTextInput
required required
label="Model Name" label={t('modelManager.model')}
{...advancedAddDiffusersForm.getInputProps('model_name')} {...advancedAddDiffusersForm.getInputProps('model_name')}
/> />
<BaseModelSelect <BaseModelSelect
label={t('modelManager.baseModel')}
{...advancedAddDiffusersForm.getInputProps('base_model')} {...advancedAddDiffusersForm.getInputProps('base_model')}
/> />
<IAIMantineTextInput <IAIMantineTextInput
required required
label="Model Location" label={t('modelManager.modelLocation')}
placeholder="Provide the path to a local folder where your Diffusers Model is stored" placeholder={t('modelManager.modelLocationValidationMsg')}
{...advancedAddDiffusersForm.getInputProps('path')} {...advancedAddDiffusersForm.getInputProps('path')}
onBlur={(e) => { onBlur={(e) => {
if (advancedAddDiffusersForm.values['model_name'] === '') { if (advancedAddDiffusersForm.values['model_name'] === '') {
@ -106,14 +109,15 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
}} }}
/> />
<IAIMantineTextInput <IAIMantineTextInput
label="Description" label={t('modelManager.description')}
{...advancedAddDiffusersForm.getInputProps('description')} {...advancedAddDiffusersForm.getInputProps('description')}
/> />
<IAIMantineTextInput <IAIMantineTextInput
label="VAE Location" label={t('modelManager.vaeLocation')}
{...advancedAddDiffusersForm.getInputProps('vae')} {...advancedAddDiffusersForm.getInputProps('vae')}
/> />
<ModelVariantSelect <ModelVariantSelect
label={t('modelManager.variant')}
{...advancedAddDiffusersForm.getInputProps('variant')} {...advancedAddDiffusersForm.getInputProps('variant')}
/> />
<IAIButton mt={2} type="submit"> <IAIButton mt={2} type="submit">

View File

@ -4,6 +4,7 @@ import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useState } from 'react'; import { useState } from 'react';
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
import AdvancedAddDiffusers from './AdvancedAddDiffusers'; import AdvancedAddDiffusers from './AdvancedAddDiffusers';
import { useTranslation } from 'react-i18next';
export const advancedAddModeData: SelectItem[] = [ export const advancedAddModeData: SelectItem[] = [
{ label: 'Diffusers', value: 'diffusers' }, { label: 'Diffusers', value: 'diffusers' },
@ -16,10 +17,12 @@ export default function AdvancedAddModels() {
const [advancedAddMode, setAdvancedAddMode] = const [advancedAddMode, setAdvancedAddMode] =
useState<ManualAddMode>('diffusers'); useState<ManualAddMode>('diffusers');
const { t } = useTranslation();
return ( return (
<Flex flexDirection="column" gap={4} width="100%"> <Flex flexDirection="column" gap={4} width="100%">
<IAIMantineSelect <IAIMantineSelect
label="Model Type" label={t('modelManager.modelType')}
value={advancedAddMode} value={advancedAddMode}
data={advancedAddModeData} data={advancedAddModeData}
onChange={(v) => { onChange={(v) => {

View File

@ -77,7 +77,7 @@ export default function FoundModelsList() {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Failed To Add Model', title: t('toast.modelAddFailed'),
status: 'error', status: 'error',
}) })
) )
@ -85,7 +85,7 @@ export default function FoundModelsList() {
} }
}); });
}, },
[dispatch, importMainModel] [dispatch, importMainModel, t]
); );
const handleSearchFilter = useCallback((e: ChangeEvent<HTMLInputElement>) => { const handleSearchFilter = useCallback((e: ChangeEvent<HTMLInputElement>) => {
@ -137,13 +137,13 @@ export default function FoundModelsList() {
onClick={quickAddHandler} onClick={quickAddHandler}
isLoading={isLoading} isLoading={isLoading}
> >
Quick Add {t('modelManager.quickAdd')}
</IAIButton> </IAIButton>
<IAIButton <IAIButton
onClick={() => dispatch(setAdvancedAddScanModel(model))} onClick={() => dispatch(setAdvancedAddScanModel(model))}
isLoading={isLoading} isLoading={isLoading}
> >
Advanced {t('modelManager.advanced')}
</IAIButton> </IAIButton>
</Flex> </Flex>
) : ( ) : (
@ -189,7 +189,7 @@ export default function FoundModelsList() {
}, },
}} }}
> >
<Text variant="subtext">No Models Found</Text> <Text variant="subtext">{t('modelManager.noModels')}</Text>
</Flex> </Flex>
); );
} }

View File

@ -10,12 +10,15 @@ import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint'; import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
import AdvancedAddDiffusers from './AdvancedAddDiffusers'; import AdvancedAddDiffusers from './AdvancedAddDiffusers';
import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels'; import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels';
import { useTranslation } from 'react-i18next';
export default function ScanAdvancedAddModels() { export default function ScanAdvancedAddModels() {
const advancedAddScanModel = useAppSelector( const advancedAddScanModel = useAppSelector(
(state: RootState) => state.modelmanager.advancedAddScanModel (state: RootState) => state.modelmanager.advancedAddScanModel
); );
const { t } = useTranslation();
const [advancedAddMode, setAdvancedAddMode] = const [advancedAddMode, setAdvancedAddMode] =
useState<ManualAddMode>('diffusers'); useState<ManualAddMode>('diffusers');
@ -64,13 +67,13 @@ export default function ScanAdvancedAddModels() {
</Text> </Text>
<IAIIconButton <IAIIconButton
icon={<FaTimes />} icon={<FaTimes />}
aria-label="Close Advanced" aria-label={t('modelManager.closeAdvanced')}
onClick={() => dispatch(setAdvancedAddScanModel(null))} onClick={() => dispatch(setAdvancedAddScanModel(null))}
size="sm" size="sm"
/> />
</Flex> </Flex>
<IAIMantineSelect <IAIMantineSelect
label="Model Type" label={t('modelManager.modelType')}
value={advancedAddMode} value={advancedAddMode}
data={advancedAddModeData} data={advancedAddModeData}
onChange={(v) => { onChange={(v) => {

View File

@ -55,7 +55,7 @@ export default function SimpleAddModels() {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Model Added', title: t('toast.modelAddSimple'),
status: 'success', status: 'success',
}) })
) )
@ -84,13 +84,13 @@ export default function SimpleAddModels() {
> >
<Flex flexDirection="column" width="100%" gap={4}> <Flex flexDirection="column" width="100%" gap={4}>
<IAIMantineTextInput <IAIMantineTextInput
label="Model Location" label={t('modelManager.modelLocation')}
placeholder="Provide a path to a local Diffusers model, local checkpoint / safetensors model a HuggingFace Repo ID, or a checkpoint/diffusers model URL." placeholder={t('modelManager.simpleModelDesc')}
w="100%" w="100%"
{...addModelForm.getInputProps('location')} {...addModelForm.getInputProps('location')}
/> />
<IAIMantineSelect <IAIMantineSelect
label="Prediction Type (for Stable Diffusion 2.x Models only)" label={t('modelManager.predictionType')}
data={predictionSelectData} data={predictionSelectData}
defaultValue="none" defaultValue="none"
{...addModelForm.getInputProps('prediction_type')} {...addModelForm.getInputProps('prediction_type')}