Merge branch 'main' into feat/taesd

This commit is contained in:
Millun Atluri 2023-09-19 13:48:12 +10:00 committed by GitHub
commit 578e682562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 510 additions and 231 deletions

View File

@ -619,56 +619,174 @@
"addNodeToolTip": "Add Node (Shift+A, Space)", "addNodeToolTip": "Add Node (Shift+A, Space)",
"animatedEdges": "Animated Edges", "animatedEdges": "Animated Edges",
"animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes", "animatedEdgesHelp": "Animate selected edges and edges connected to selected nodes",
"boolean": "Booleans",
"booleanCollection": "Boolean Collection",
"booleanCollectionDescription": "A collection of booleans.",
"booleanDescription": "Booleans are true or false.",
"booleanPolymorphic": "Boolean Polymorphic",
"booleanPolymorphicDescription": "A collection of booleans.",
"cannotConnectInputToInput": "Cannot connect input to input", "cannotConnectInputToInput": "Cannot connect input to input",
"cannotConnectOutputToOutput": "Cannot connect output to output", "cannotConnectOutputToOutput": "Cannot connect output to output",
"cannotConnectToSelf": "Cannot connect to self", "cannotConnectToSelf": "Cannot connect to self",
"clipField": "Clip",
"clipFieldDescription": "Tokenizer and text_encoder submodels.",
"collection": "Collection",
"collectionDescription": "TODO",
"collectionItem": "Collection Item",
"collectionItemDescription": "TODO",
"colorCodeEdges": "Color-Code Edges", "colorCodeEdges": "Color-Code Edges",
"colorCodeEdgesHelp": "Color-code edges according to their connected fields", "colorCodeEdgesHelp": "Color-code edges according to their connected fields",
"colorCollectionDescription": "A collection of colors.",
"colorField": "Color",
"colorFieldDescription": "A RGBA color.",
"colorPolymorphic": "Color Polymorphic",
"colorPolymorphicDescription": "A collection of colors.",
"conditioningCollection": "Conditioning Collection",
"conditioningCollectionDescription": "Conditioning may be passed between nodes.",
"conditioningField": "Conditioning",
"conditioningFieldDescription": "Conditioning may be passed between nodes.",
"conditioningPolymorphic": "Conditioning Polymorphic",
"conditioningPolymorphicDescription": "Conditioning may be passed between nodes.",
"connectionWouldCreateCycle": "Connection would create a cycle", "connectionWouldCreateCycle": "Connection would create a cycle",
"controlCollection": "Control Collection",
"controlCollectionDescription": "Control info passed between nodes.",
"controlField": "Control",
"controlFieldDescription": "Control info passed between nodes.",
"currentImage": "Current Image", "currentImage": "Current Image",
"currentImageDescription": "Displays the current image in the Node Editor", "currentImageDescription": "Displays the current image in the Node Editor",
"denoiseMaskField": "Denoise Mask",
"denoiseMaskFieldDescription": "Denoise Mask may be passed between nodes",
"doesNotExist": "does not exist",
"downloadWorkflow": "Download Workflow JSON", "downloadWorkflow": "Download Workflow JSON",
"edge": "Edge",
"enum": "Enum",
"enumDescription": "Enums are values that may be one of a number of options.",
"executionStateCompleted": "Completed",
"executionStateError": "Error",
"executionStateInProgress": "In Progress",
"fieldTypesMustMatch": "Field types must match", "fieldTypesMustMatch": "Field types must match",
"fitViewportNodes": "Fit View", "fitViewportNodes": "Fit View",
"float": "Float",
"floatCollection": "Float Collection",
"floatCollectionDescription": "A collection of floats.",
"floatDescription": "Floats are numbers with a decimal point.",
"floatPolymorphic": "Float Polymorphic",
"floatPolymorphicDescription": "A collection of floats.",
"fullyContainNodes": "Fully Contain Nodes to Select", "fullyContainNodes": "Fully Contain Nodes to Select",
"fullyContainNodesHelp": "Nodes must be fully inside the selection box to be selected", "fullyContainNodesHelp": "Nodes must be fully inside the selection box to be selected",
"hideGraphNodes": "Hide Graph Overlay", "hideGraphNodes": "Hide Graph Overlay",
"hideLegendNodes": "Hide Field Type Legend", "hideLegendNodes": "Hide Field Type Legend",
"hideMinimapnodes": "Hide MiniMap", "hideMinimapnodes": "Hide MiniMap",
"imageCollection": "Image Collection",
"imageCollectionDescription": "A collection of images.",
"imageField": "Image",
"imageFieldDescription": "Images may be passed between nodes.",
"imagePolymorphic": "Image Polymorphic",
"imagePolymorphicDescription": "A collection of images.",
"inputFields": "Input Feilds",
"inputMayOnlyHaveOneConnection": "Input may only have one connection", "inputMayOnlyHaveOneConnection": "Input may only have one connection",
"inputNode": "Input Node",
"integer": "Integer",
"integerCollection": "Integer Collection",
"integerCollectionDescription": "A collection of integers.",
"integerDescription": "Integers are whole numbers, without a decimal point.",
"integerPolymorphic": "Integer Polymorphic",
"integerPolymorphicDescription": "A collection of integers.",
"invalidOutputSchema": "Invalid output schema",
"latentsCollection": "Latents Collection",
"latentsCollectionDescription": "Latents may be passed between nodes.",
"latentsField": "Latents",
"latentsFieldDescription": "Latents may be passed between nodes.",
"latentsPolymorphic": "Latents Polymorphic",
"latentsPolymorphicDescription": "Latents may be passed between nodes.",
"loadingNodes": "Loading Nodes...", "loadingNodes": "Loading Nodes...",
"loadWorkflow": "Load Workflow", "loadWorkflow": "Load Workflow",
"loRAModelField": "LoRA",
"loRAModelFieldDescription": "TODO",
"mainModelField": "Model",
"mainModelFieldDescription": "TODO",
"maybeIncompatible": "May be Incompatible With Installed",
"mismatchedVersion": "Has Mismatched Version",
"missingCanvaInitImage": "Missing canvas init image",
"missingCanvaInitMaskImages": "Missing canvas init and mask images",
"missingTemplate": "Missing Template",
"noConnectionData": "No connection data", "noConnectionData": "No connection data",
"noConnectionInProgress": "No connection in progress", "noConnectionInProgress": "No connection in progress",
"node": "Node",
"nodeOutputs": "Node Outputs", "nodeOutputs": "Node Outputs",
"nodeSearch": "Search for nodes", "nodeSearch": "Search for nodes",
"nodeTemplate": "Node Template", "nodeTemplate": "Node Template",
"nodeType": "Node Type",
"noFieldsLinearview": "No fields added to Linear View", "noFieldsLinearview": "No fields added to Linear View",
"noFieldType": "No field type", "noFieldType": "No field type",
"noImageFoundState": "No initial image found in state",
"noMatchingNodes": "No matching nodes", "noMatchingNodes": "No matching nodes",
"noNodeSelected": "No node selected", "noNodeSelected": "No node selected",
"noOpacity": "Node Opacity", "noOpacity": "Node Opacity",
"noOutputRecorded": "No outputs recorded", "noOutputRecorded": "No outputs recorded",
"noOutputSchemaName": "No output schema name found in ref object",
"notes": "Notes", "notes": "Notes",
"notesDescription": "Add notes about your workflow", "notesDescription": "Add notes about your workflow",
"oNNXModelField": "ONNX Model",
"oNNXModelFieldDescription": "ONNX model field.",
"outputFields": "Output Feilds",
"outputNode": "Output node",
"outputSchemaNotFound": "Output schema not found",
"pickOne": "Pick One", "pickOne": "Pick One",
"problemReadingMetadata": "Problem reading metadata from image",
"problemReadingWorkflow": "Problem reading workflow from image",
"problemSettingTitle": "Problem Setting Title", "problemSettingTitle": "Problem Setting Title",
"reloadNodeTemplates": "Reload Node Templates", "reloadNodeTemplates": "Reload Node Templates",
"removeLinearView": "Remove from Linear View", "removeLinearView": "Remove from Linear View",
"resetWorkflow": "Reset Workflow", "resetWorkflow": "Reset Workflow",
"resetWorkflowDesc": "Are you sure you want to reset this workflow?", "resetWorkflowDesc": "Are you sure you want to reset this workflow?",
"resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.", "resetWorkflowDesc2": "Resetting the workflow will clear all nodes, edges and workflow details.",
"scheduler": "Scheduler",
"schedulerDescription": "TODO",
"sDXLMainModelField": "SDXL Model",
"sDXLMainModelFieldDescription": "SDXL model field.",
"sDXLRefinerModelField": "Refiner Model",
"sDXLRefinerModelFieldDescription": "TODO",
"showGraphNodes": "Show Graph Overlay", "showGraphNodes": "Show Graph Overlay",
"showLegendNodes": "Show Field Type Legend", "showLegendNodes": "Show Field Type Legend",
"showMinimapnodes": "Show MiniMap", "showMinimapnodes": "Show MiniMap",
"skipped": "Skipped",
"skippedReservedInput": "Skipped reserved input field",
"skippedReservedOutput": "Skipped reserved output field",
"skippingInputNoTemplate": "Skipping input field with no template",
"skippingReservedFieldType": "Skipping reserved field type",
"skippingUnknownInputType": "Skipping unknown input field type",
"skippingUnknownOutputType": "Skipping unknown output field type",
"snapToGrid": "Snap to Grid", "snapToGrid": "Snap to Grid",
"snapToGridHelp": "Snap nodes to grid when moved", "snapToGridHelp": "Snap nodes to grid when moved",
"sourceNode": "Source node",
"string": "String",
"stringCollection": "String Collection",
"stringCollectionDescription": "A collection of strings.",
"stringDescription": "Strings are text.",
"stringPolymorphic": "String Polymorphic",
"stringPolymorphicDescription": "A collection of strings.",
"unableToLoadWorkflow": "Unable to Validate Workflow", "unableToLoadWorkflow": "Unable to Validate Workflow",
"unableToParseEdge": "Unable to parse edge",
"unableToParseNode": "Unable to parse node",
"unableToValidateWorkflow": "Unable to Validate Workflow", "unableToValidateWorkflow": "Unable to Validate Workflow",
"uNetField": "UNet",
"uNetFieldDescription": "UNet submodel.",
"unhandledInputProperty": "Unhandled input property",
"unhandledOutputProperty": "Unhandled output property",
"unknownField": "Unknown Field", "unknownField": "Unknown Field",
"unknownNode": "Unknown Node",
"unknownTemplate": "Unknown Template",
"unkownInvocation": "Unknown Invocation type", "unkownInvocation": "Unknown Invocation type",
"updateApp": "Update App",
"vaeField": "Vae",
"vaeFieldDescription": "Vae submodel.",
"vaeModelField": "VAE",
"vaeModelFieldDescription": "TODO",
"validateConnections": "Validate Connections and Graph", "validateConnections": "Validate Connections and Graph",
"validateConnectionsHelp": "Prevent invalid connections from being made, and invalid graphs from being invoked", "validateConnectionsHelp": "Prevent invalid connections from being made, and invalid graphs from being invoked",
"version": "Version",
"versionUnknown": " Version Unknown",
"workflow": "Workflow", "workflow": "Workflow",
"workflowAuthor": "Author", "workflowAuthor": "Author",
"workflowContact": "Contact", "workflowContact": "Contact",
@ -680,15 +798,7 @@
"workflowValidation": "Workflow Validation Error", "workflowValidation": "Workflow Validation Error",
"workflowVersion": "Version", "workflowVersion": "Version",
"zoomInNodes": "Zoom In", "zoomInNodes": "Zoom In",
"zoomOutNodes": "Zoom Out", "zoomOutNodes": "Zoom Out"
"executionStateError": "Error",
"executionStateCompleted": "Completed",
"executionStateInProgress": "In Progress",
"versionUnknown": " Version Unknown",
"unknownNode": "Unknown Node",
"version": "Version",
"updateApp": "Update App",
"unknownTemplate": "Unknown Template"
}, },
"parameters": { "parameters": {
"aspectRatio": "Ratio", "aspectRatio": "Ratio",
@ -853,8 +963,14 @@
"useSlidersForAll": "Use Sliders For All Options" "useSlidersForAll": "Use Sliders For All Options"
}, },
"toast": { "toast": {
"addedToBoard": "Added to board",
"baseModelChangedCleared": "Base model changed, cleared",
"canceled": "Processing Canceled", "canceled": "Processing Canceled",
"canvasCopiedClipboard": "Canvas Copied to Clipboard",
"canvasDownloaded": "Canvas Downloaded",
"canvasMerged": "Canvas Merged", "canvasMerged": "Canvas Merged",
"canvasSavedGallery": "Canvas Saved to Gallery",
"canvasSentControlnetAssets": "Canvas Sent to ControlNet & Assets",
"connected": "Connected to Server", "connected": "Connected to Server",
"disconnected": "Disconnected from Server", "disconnected": "Disconnected from Server",
"downloadImageStarted": "Image Download Started", "downloadImageStarted": "Image Download Started",
@ -863,10 +979,18 @@
"imageLinkCopied": "Image Link Copied", "imageLinkCopied": "Image Link Copied",
"imageNotLoaded": "No Image Loaded", "imageNotLoaded": "No Image Loaded",
"imageNotLoadedDesc": "Could not find image", "imageNotLoadedDesc": "Could not find image",
"imageSaved": "Image Saved",
"imageSavedToGallery": "Image Saved to Gallery", "imageSavedToGallery": "Image Saved to Gallery",
"imageSavingFailed": "Image Saving Failed",
"imageUploaded": "Image Uploaded",
"imageUploadFailed": "Image Upload Failed",
"incompatibleSubmodel": "incompatible submodel",
"initialImageNotSet": "Initial Image Not Set", "initialImageNotSet": "Initial Image Not Set",
"initialImageNotSetDesc": "Could not load initial image", "initialImageNotSetDesc": "Could not load initial image",
"initialImageSet": "Initial Image Set", "initialImageSet": "Initial Image Set",
"loadedWithWarnings": "Workflow Loaded with Warnings",
"maskSavedAssets": "Mask Saved to Assets",
"maskSentControlnetAssets": "Mask Sent to ControlNet & Assets",
"metadataLoadFailed": "Failed to load metadata", "metadataLoadFailed": "Failed to load metadata",
"modelAdded": "Model Added: {{modelName}}", "modelAdded": "Model Added: {{modelName}}",
"modelAddedSimple": "Model Added", "modelAddedSimple": "Model Added",
@ -887,8 +1011,20 @@
"parametersNotSet": "Parameters Not Set", "parametersNotSet": "Parameters Not Set",
"parametersNotSetDesc": "No metadata found for this image.", "parametersNotSetDesc": "No metadata found for this image.",
"parametersSet": "Parameters Set", "parametersSet": "Parameters Set",
"problemCopyingCanvas": "Problem Copying Canvas",
"problemCopyingCanvasDesc": "Unable to export base layer",
"problemCopyingImage": "Unable to Copy Image", "problemCopyingImage": "Unable to Copy Image",
"problemCopyingImageLink": "Unable to Copy Image Link", "problemCopyingImageLink": "Unable to Copy Image Link",
"problemDownloadingCanvas": "Problem Downloading Canvas",
"problemDownloadingCanvasDesc": "Unable to export base layer",
"problemImportingMask": "Problem Importing Mask",
"problemImportingMaskDesc": "Unable to export mask",
"problemMergingCanvas": "Problem Merging Canvas",
"problemMergingCanvasDesc": "Unable to export base layer",
"problemSavingCanvas": "Problem Saving Canvas",
"problemSavingCanvasDesc": "Unable to export base layer",
"problemSavingMask": "Problem Saving Mask",
"problemSavingMaskDesc": "Unable to export mask",
"promptNotSet": "Prompt Not Set", "promptNotSet": "Prompt Not Set",
"promptNotSetDesc": "Could not find prompt for this image.", "promptNotSetDesc": "Could not find prompt for this image.",
"promptSet": "Prompt Set", "promptSet": "Prompt Set",
@ -898,11 +1034,16 @@
"sentToImageToImage": "Sent To Image To Image", "sentToImageToImage": "Sent To Image To Image",
"sentToUnifiedCanvas": "Sent to Unified Canvas", "sentToUnifiedCanvas": "Sent to Unified Canvas",
"serverError": "Server Error", "serverError": "Server Error",
"setCanvasInitialImage": "Set as canvas initial image",
"setControlImage": "Set as control image",
"setInitialImage": "Set as initial image",
"setNodeField": "Set as node field",
"tempFoldersEmptied": "Temp Folder Emptied", "tempFoldersEmptied": "Temp Folder Emptied",
"uploadFailed": "Upload failed", "uploadFailed": "Upload failed",
"uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image", "uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image",
"uploadFailedUnableToLoadDesc": "Unable to load file", "uploadFailedUnableToLoadDesc": "Unable to load file",
"upscalingFailed": "Upscaling Failed" "upscalingFailed": "Upscaling Failed",
"workflowLoaded": "Workflow Loaded"
}, },
"tooltip": { "tooltip": {
"feature": { "feature": {

View File

@ -4,6 +4,7 @@ import { $logger } from 'app/logging/logger';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { copyBlobToClipboard } from 'features/canvas/util/copyBlobToClipboard'; import { copyBlobToClipboard } from 'features/canvas/util/copyBlobToClipboard';
import { t } from 'i18next';
export const addCanvasCopiedToClipboardListener = () => { export const addCanvasCopiedToClipboardListener = () => {
startAppListening({ startAppListening({
@ -20,8 +21,8 @@ export const addCanvasCopiedToClipboardListener = () => {
moduleLog.error('Problem getting base layer blob'); moduleLog.error('Problem getting base layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Copying Canvas', title: t('toast.problemCopyingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemCopyingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -32,7 +33,7 @@ export const addCanvasCopiedToClipboardListener = () => {
dispatch( dispatch(
addToast({ addToast({
title: 'Canvas Copied to Clipboard', title: t('toast.canvasCopiedClipboard'),
status: 'success', status: 'success',
}) })
); );

View File

@ -4,6 +4,7 @@ import { $logger } from 'app/logging/logger';
import { downloadBlob } from 'features/canvas/util/downloadBlob'; import { downloadBlob } from 'features/canvas/util/downloadBlob';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next';
export const addCanvasDownloadedAsImageListener = () => { export const addCanvasDownloadedAsImageListener = () => {
startAppListening({ startAppListening({
@ -20,8 +21,8 @@ export const addCanvasDownloadedAsImageListener = () => {
moduleLog.error('Problem getting base layer blob'); moduleLog.error('Problem getting base layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Downloading Canvas', title: t('toast.problemDownloadingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemDownloadingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -29,7 +30,9 @@ export const addCanvasDownloadedAsImageListener = () => {
} }
downloadBlob(blob, 'canvas.png'); downloadBlob(blob, 'canvas.png');
dispatch(addToast({ title: 'Canvas Downloaded', status: 'success' })); dispatch(
addToast({ title: t('toast.canvasDownloaded'), status: 'success' })
);
}, },
}); });
}; };

View File

@ -5,6 +5,7 @@ import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlic
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasImageToControlNetListener = () => { export const addCanvasImageToControlNetListener = () => {
startAppListening({ startAppListening({
@ -19,8 +20,8 @@ export const addCanvasImageToControlNetListener = () => {
log.error('Problem getting base layer blob'); log.error('Problem getting base layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Saving Canvas', title: t('toast.problemSavingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemSavingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -40,7 +41,7 @@ export const addCanvasImageToControlNetListener = () => {
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: 'Canvas Sent to ControlNet & Assets' }, toastOptions: { title: t('toast.canvasSentControlnetAssets') },
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -4,6 +4,7 @@ import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasMaskSavedToGalleryListener = () => { export const addCanvasMaskSavedToGalleryListener = () => {
startAppListening({ startAppListening({
@ -30,8 +31,8 @@ export const addCanvasMaskSavedToGalleryListener = () => {
log.error('Problem getting mask layer blob'); log.error('Problem getting mask layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Saving Mask', title: t('toast.problemSavingMask'),
description: 'Unable to export mask', description: t('toast.problemSavingMaskDesc'),
status: 'error', status: 'error',
}) })
); );
@ -51,7 +52,7 @@ export const addCanvasMaskSavedToGalleryListener = () => {
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: 'Mask Saved to Assets' }, toastOptions: { title: t('toast.maskSavedAssets') },
}, },
}) })
); );

View File

@ -5,6 +5,7 @@ import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlic
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasMaskToControlNetListener = () => { export const addCanvasMaskToControlNetListener = () => {
startAppListening({ startAppListening({
@ -31,8 +32,8 @@ export const addCanvasMaskToControlNetListener = () => {
log.error('Problem getting mask layer blob'); log.error('Problem getting mask layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Importing Mask', title: t('toast.problemImportingMask'),
description: 'Unable to export mask', description: t('toast.problemImportingMaskDesc'),
status: 'error', status: 'error',
}) })
); );
@ -52,7 +53,7 @@ export const addCanvasMaskToControlNetListener = () => {
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: 'Mask Sent to ControlNet & Assets' }, toastOptions: { title: t('toast.maskSentControlnetAssets') },
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -6,6 +6,7 @@ import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasMergedListener = () => { export const addCanvasMergedListener = () => {
startAppListening({ startAppListening({
@ -20,8 +21,8 @@ export const addCanvasMergedListener = () => {
moduleLog.error('Problem getting base layer blob'); moduleLog.error('Problem getting base layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Merging Canvas', title: t('toast.problemMergingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemMergingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -34,8 +35,8 @@ export const addCanvasMergedListener = () => {
moduleLog.error('Problem getting canvas base layer'); moduleLog.error('Problem getting canvas base layer');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Merging Canvas', title: t('toast.problemMergingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemMergingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -55,7 +56,7 @@ export const addCanvasMergedListener = () => {
is_intermediate: true, is_intermediate: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: 'Canvas Merged' }, toastOptions: { title: t('toast.canvasMerged') },
}, },
}) })
).unwrap(); ).unwrap();

View File

@ -4,6 +4,7 @@ import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addCanvasSavedToGalleryListener = () => { export const addCanvasSavedToGalleryListener = () => {
startAppListening({ startAppListening({
@ -18,8 +19,8 @@ export const addCanvasSavedToGalleryListener = () => {
log.error('Problem getting base layer blob'); log.error('Problem getting base layer blob');
dispatch( dispatch(
addToast({ addToast({
title: 'Problem Saving Canvas', title: t('toast.problemSavingCanvas'),
description: 'Unable to export base layer', description: t('toast.problemSavingCanvasDesc'),
status: 'error', status: 'error',
}) })
); );
@ -39,7 +40,7 @@ export const addCanvasSavedToGalleryListener = () => {
crop_visible: true, crop_visible: true,
postUploadAction: { postUploadAction: {
type: 'TOAST', type: 'TOAST',
toastOptions: { title: 'Canvas Saved to Gallery' }, toastOptions: { title: t('toast.canvasSavedGallery') },
}, },
}) })
); );

View File

@ -9,9 +9,10 @@ import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards'; import { boardsApi } from 'services/api/endpoints/boards';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { imagesApi } from '../../../../../services/api/endpoints/images'; import { imagesApi } from '../../../../../services/api/endpoints/images';
import { t } from 'i18next';
const DEFAULT_UPLOADED_TOAST: UseToastOptions = { const DEFAULT_UPLOADED_TOAST: UseToastOptions = {
title: 'Image Uploaded', title: t('toast.imageUploaded'),
status: 'success', status: 'success',
}; };
@ -57,8 +58,8 @@ export const addImageUploadedFulfilledListener = () => {
// Fall back to just the board id if we can't find the board for some reason // Fall back to just the board id if we can't find the board for some reason
const board = data?.find((b) => b.board_id === autoAddBoardId); const board = data?.find((b) => b.board_id === autoAddBoardId);
const description = board const description = board
? `Added to board ${board.board_name}` ? `${t('toast.addedToBoard')} ${board.board_name}`
: `Added to board ${autoAddBoardId}`; : `${t('toast.addedToBoard')} ${autoAddBoardId}`;
dispatch( dispatch(
addToast({ addToast({
@ -75,7 +76,7 @@ export const addImageUploadedFulfilledListener = () => {
dispatch( dispatch(
addToast({ addToast({
...DEFAULT_UPLOADED_TOAST, ...DEFAULT_UPLOADED_TOAST,
description: 'Set as canvas initial image', description: t('toast.setCanvasInitialImage'),
}) })
); );
return; return;
@ -92,7 +93,7 @@ export const addImageUploadedFulfilledListener = () => {
dispatch( dispatch(
addToast({ addToast({
...DEFAULT_UPLOADED_TOAST, ...DEFAULT_UPLOADED_TOAST,
description: 'Set as control image', description: t('toast.setControlImage'),
}) })
); );
return; return;
@ -103,7 +104,7 @@ export const addImageUploadedFulfilledListener = () => {
dispatch( dispatch(
addToast({ addToast({
...DEFAULT_UPLOADED_TOAST, ...DEFAULT_UPLOADED_TOAST,
description: 'Set as initial image', description: t('toast.setInitialImage'),
}) })
); );
return; return;
@ -117,7 +118,7 @@ export const addImageUploadedFulfilledListener = () => {
dispatch( dispatch(
addToast({ addToast({
...DEFAULT_UPLOADED_TOAST, ...DEFAULT_UPLOADED_TOAST,
description: `Set as node field ${fieldName}`, description: `${t('toast.setNodeField')} ${fieldName}`,
}) })
); );
return; return;
@ -140,7 +141,7 @@ export const addImageUploadedRejectedListener = () => {
log.error({ ...sanitizedData }, 'Image upload failed'); log.error({ ...sanitizedData }, 'Image upload failed');
dispatch( dispatch(
addToast({ addToast({
title: 'Image Upload Failed', title: t('toast.imageUploadFailed'),
description: action.error.message, description: action.error.message,
status: 'error', status: 'error',
}) })

View File

@ -14,6 +14,7 @@ import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addModelSelectedListener = () => { export const addModelSelectedListener = () => {
startAppListening({ startAppListening({
@ -67,7 +68,9 @@ export const addModelSelectedListener = () => {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: `Base model changed, cleared ${modelsCleared} incompatible submodel${ title: `${t(
'toast.baseModelChangedCleared'
)} ${modelsCleared} ${t('toast.incompatibleSubmodel')}${
modelsCleared === 1 ? '' : 's' modelsCleared === 1 ? '' : 's'
}`, }`,
status: 'warning', status: 'warning',

View File

@ -2,6 +2,7 @@ import { stagingAreaImageSaved } from 'features/canvas/store/actions';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addStagingAreaImageSavedListener = () => { export const addStagingAreaImageSavedListener = () => {
startAppListening({ startAppListening({
@ -28,11 +29,11 @@ export const addStagingAreaImageSavedListener = () => {
}) })
); );
} }
dispatch(addToast({ title: 'Image Saved', status: 'success' })); dispatch(addToast({ title: t('toast.imageSaved'), status: 'success' }));
} catch (error) { } catch (error) {
dispatch( dispatch(
addToast({ addToast({
title: 'Image Saving Failed', title: t('toast.imageSavingFailed'),
description: (error as Error)?.message, description: (error as Error)?.message,
status: 'error', status: 'error',
}) })

View File

@ -7,6 +7,7 @@ import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { t } from 'i18next';
export const addWorkflowLoadedListener = () => { export const addWorkflowLoadedListener = () => {
startAppListening({ startAppListening({
@ -27,7 +28,7 @@ export const addWorkflowLoadedListener = () => {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Workflow Loaded', title: t('toast.workflowLoaded'),
status: 'success', status: 'success',
}) })
) )
@ -36,7 +37,7 @@ export const addWorkflowLoadedListener = () => {
dispatch( dispatch(
addToast( addToast(
makeToast({ makeToast({
title: 'Workflow Loaded with Warnings', title: t('toast.loadedWithWarnings'),
status: 'warning', status: 'warning',
}) })
) )

View File

@ -1,8 +1,9 @@
import { CoreMetadata } from 'features/nodes/types/types'; import { CoreMetadata, LoRAMetadataItem } 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 { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isValidLoRAModel } from '../../../parameters/types/parameterSchemas';
import ImageMetadataItem from './ImageMetadataItem';
type Props = { type Props = {
metadata?: CoreMetadata; metadata?: CoreMetadata;
@ -24,6 +25,7 @@ const ImageMetadataActions = (props: Props) => {
recallWidth, recallWidth,
recallHeight, recallHeight,
recallStrength, recallStrength,
recallLoRA,
} = useRecallParameters(); } = useRecallParameters();
const handleRecallPositivePrompt = useCallback(() => { const handleRecallPositivePrompt = useCallback(() => {
@ -66,6 +68,13 @@ const ImageMetadataActions = (props: Props) => {
recallStrength(metadata?.strength); recallStrength(metadata?.strength);
}, [metadata?.strength, recallStrength]); }, [metadata?.strength, recallStrength]);
const handleRecallLoRA = useCallback(
(lora: LoRAMetadataItem) => {
recallLoRA(lora);
},
[recallLoRA]
);
if (!metadata || Object.keys(metadata).length === 0) { if (!metadata || Object.keys(metadata).length === 0) {
return null; return null;
} }
@ -130,20 +139,6 @@ const ImageMetadataActions = (props: Props) => {
onClick={handleRecallHeight} onClick={handleRecallHeight}
/> />
)} )}
{/* {metadata.threshold !== undefined && (
<MetadataItem
label={t('metadata.threshold')}
value={metadata.threshold}
onClick={() => dispatch(setThreshold(Number(metadata.threshold)))}
/>
)}
{metadata.perlin !== undefined && (
<MetadataItem
label={t('metadata.perlin')}
value={metadata.perlin}
onClick={() => dispatch(setPerlin(Number(metadata.perlin)))}
/>
)} */}
{metadata.scheduler && ( {metadata.scheduler && (
<ImageMetadataItem <ImageMetadataItem
label={t('metadata.scheduler')} label={t('metadata.scheduler')}
@ -165,40 +160,6 @@ const ImageMetadataActions = (props: Props) => {
onClick={handleRecallCfgScale} onClick={handleRecallCfgScale}
/> />
)} )}
{/* {metadata.variations && metadata.variations.length > 0 && (
<MetadataItem
label="{t('metadata.variations')}
value={seedWeightsToString(metadata.variations)}
onClick={() =>
dispatch(
setSeedWeights(seedWeightsToString(metadata.variations))
)
}
/>
)}
{metadata.seamless && (
<MetadataItem
label={t('metadata.seamless')}
value={metadata.seamless}
onClick={() => dispatch(setSeamless(metadata.seamless))}
/>
)}
{metadata.hires_fix && (
<MetadataItem
label={t('metadata.hiresFix')}
value={metadata.hires_fix}
onClick={() => dispatch(setHiresFix(metadata.hires_fix))}
/>
)} */}
{/* {init_image_path && (
<MetadataItem
label={t('metadata.initImage')}
value={init_image_path}
isLink
onClick={() => dispatch(setInitialImage(init_image_path))}
/>
)} */}
{metadata.strength && ( {metadata.strength && (
<ImageMetadataItem <ImageMetadataItem
label={t('metadata.strength')} label={t('metadata.strength')}
@ -206,13 +167,19 @@ const ImageMetadataActions = (props: Props) => {
onClick={handleRecallStrength} onClick={handleRecallStrength}
/> />
)} )}
{/* {metadata.fit && ( {metadata.loras &&
<MetadataItem metadata.loras.map((lora, index) => {
label={t('metadata.fit')} if (isValidLoRAModel(lora.lora)) {
value={metadata.fit} return (
onClick={() => dispatch(setShouldFitToWidthHeight(metadata.fit))} <ImageMetadataItem
/> key={index}
)} */} label="LoRA"
value={`${lora.lora.model_name} - ${lora.weight}`}
onClick={() => handleRecallLoRA(lora)}
/>
);
}
})}
</> </>
); );
}; };

View File

@ -27,6 +27,13 @@ export const loraSlice = createSlice({
const { model_name, id, base_model } = action.payload; const { model_name, id, base_model } = action.payload;
state.loras[id] = { id, model_name, base_model, ...defaultLoRAConfig }; state.loras[id] = { id, model_name, base_model, ...defaultLoRAConfig };
}, },
loraRecalled: (
state,
action: PayloadAction<LoRAModelConfigEntity & { weight: number }>
) => {
const { model_name, id, base_model, weight } = action.payload;
state.loras[id] = { id, model_name, base_model, weight };
},
loraRemoved: (state, action: PayloadAction<string>) => { loraRemoved: (state, action: PayloadAction<string>) => {
const id = action.payload; const id = action.payload;
delete state.loras[id]; delete state.loras[id];
@ -62,6 +69,7 @@ export const {
loraWeightChanged, loraWeightChanged,
loraWeightReset, loraWeightReset,
lorasCleared, lorasCleared,
loraRecalled,
} = loraSlice.actions; } = loraSlice.actions;
export default loraSlice.reducer; export default loraSlice.reducer;

View File

@ -1,4 +1,5 @@
import { FieldType, FieldUIConfig } from './types'; import { FieldType, FieldUIConfig } from './types';
import { t } from 'i18next';
export const HANDLE_TOOLTIP_OPEN_DELAY = 500; export const HANDLE_TOOLTIP_OPEN_DELAY = 500;
export const COLOR_TOKEN_VALUE = 500; export const COLOR_TOKEN_VALUE = 500;
@ -102,73 +103,73 @@ export const isPolymorphicItemType = (
export const FIELDS: Record<FieldType, FieldUIConfig> = { export const FIELDS: Record<FieldType, FieldUIConfig> = {
boolean: { boolean: {
color: 'green.500', color: 'green.500',
description: 'Booleans are true or false.', description: t('nodes.booleanDescription'),
title: 'Boolean', title: t('nodes.boolean'),
}, },
BooleanCollection: { BooleanCollection: {
color: 'green.500', color: 'green.500',
description: 'A collection of booleans.', description: t('nodes.booleanCollectionDescription'),
title: 'Boolean Collection', title: t('nodes.booleanCollection'),
}, },
BooleanPolymorphic: { BooleanPolymorphic: {
color: 'green.500', color: 'green.500',
description: 'A collection of booleans.', description: t('nodes.booleanPolymorphicDescription'),
title: 'Boolean Polymorphic', title: t('nodes.booleanPolymorphic'),
}, },
ClipField: { ClipField: {
color: 'green.500', color: 'green.500',
description: 'Tokenizer and text_encoder submodels.', description: t('nodes.clipFieldDescription'),
title: 'Clip', title: t('nodes.clipField'),
}, },
Collection: { Collection: {
color: 'base.500', color: 'base.500',
description: 'TODO', description: t('nodes.collectionDescription'),
title: 'Collection', title: t('nodes.collection'),
}, },
CollectionItem: { CollectionItem: {
color: 'base.500', color: 'base.500',
description: 'TODO', description: t('nodes.collectionItemDescription'),
title: 'Collection Item', title: t('nodes.collectionItem'),
}, },
ColorCollection: { ColorCollection: {
color: 'pink.300', color: 'pink.300',
description: 'A collection of colors.', description: t('nodes.colorCollectionDescription'),
title: 'Color Collection', title: t('nodes.colorCollection'),
}, },
ColorField: { ColorField: {
color: 'pink.300', color: 'pink.300',
description: 'A RGBA color.', description: t('nodes.colorFieldDescription'),
title: 'Color', title: t('nodes.colorField'),
}, },
ColorPolymorphic: { ColorPolymorphic: {
color: 'pink.300', color: 'pink.300',
description: 'A collection of colors.', description: t('nodes.colorPolymorphicDescription'),
title: 'Color Polymorphic', title: t('nodes.colorPolymorphic'),
}, },
ConditioningCollection: { ConditioningCollection: {
color: 'cyan.500', color: 'cyan.500',
description: 'Conditioning may be passed between nodes.', description: t('nodes.conditioningCollectionDescription'),
title: 'Conditioning Collection', title: t('nodes.conditioningCollection'),
}, },
ConditioningField: { ConditioningField: {
color: 'cyan.500', color: 'cyan.500',
description: 'Conditioning may be passed between nodes.', description: t('nodes.conditioningFieldDescription'),
title: 'Conditioning', title: t('nodes.conditioningField'),
}, },
ConditioningPolymorphic: { ConditioningPolymorphic: {
color: 'cyan.500', color: 'cyan.500',
description: 'Conditioning may be passed between nodes.', description: t('nodes.conditioningPolymorphicDescription'),
title: 'Conditioning Polymorphic', title: t('nodes.conditioningPolymorphic'),
}, },
ControlCollection: { ControlCollection: {
color: 'teal.500', color: 'teal.500',
description: 'Control info passed between nodes.', description: t('nodes.controlCollectionDescription'),
title: 'Control Collection', title: t('nodes.controlCollection'),
}, },
ControlField: { ControlField: {
color: 'teal.500', color: 'teal.500',
description: 'Control info passed between nodes.', description: t('nodes.controlFieldDescription'),
title: 'Control', title: t('nodes.controlField'),
}, },
ControlNetModelField: { ControlNetModelField: {
color: 'teal.500', color: 'teal.500',
@ -182,132 +183,132 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
}, },
DenoiseMaskField: { DenoiseMaskField: {
color: 'blue.300', color: 'blue.300',
description: 'Denoise Mask may be passed between nodes', description: t('nodes.denoiseMaskFieldDescription'),
title: 'Denoise Mask', title: t('nodes.denoiseMaskField'),
}, },
enum: { enum: {
color: 'blue.500', color: 'blue.500',
description: 'Enums are values that may be one of a number of options.', description: t('nodes.enumDescription'),
title: 'Enum', title: t('nodes.enum'),
}, },
float: { float: {
color: 'orange.500', color: 'orange.500',
description: 'Floats are numbers with a decimal point.', description: t('nodes.floatDescription'),
title: 'Float', title: t('nodes.float'),
}, },
FloatCollection: { FloatCollection: {
color: 'orange.500', color: 'orange.500',
description: 'A collection of floats.', description: t('nodes.floatCollectionDescription'),
title: 'Float Collection', title: t('nodes.floatCollection'),
}, },
FloatPolymorphic: { FloatPolymorphic: {
color: 'orange.500', color: 'orange.500',
description: 'A collection of floats.', description: t('nodes.floatPolymorphicDescription'),
title: 'Float Polymorphic', title: t('nodes.floatPolymorphic'),
}, },
ImageCollection: { ImageCollection: {
color: 'purple.500', color: 'purple.500',
description: 'A collection of images.', description: t('nodes.imageCollectionDescription'),
title: 'Image Collection', title: t('nodes.imageCollection'),
}, },
ImageField: { ImageField: {
color: 'purple.500', color: 'purple.500',
description: 'Images may be passed between nodes.', description: t('nodes.imageFieldDescription'),
title: 'Image', title: t('nodes.imageField'),
}, },
ImagePolymorphic: { ImagePolymorphic: {
color: 'purple.500', color: 'purple.500',
description: 'A collection of images.', description: t('nodes.imagePolymorphicDescription'),
title: 'Image Polymorphic', title: t('nodes.imagePolymorphic'),
}, },
integer: { integer: {
color: 'red.500', color: 'red.500',
description: 'Integers are whole numbers, without a decimal point.', description: t('nodes.integerDescription'),
title: 'Integer', title: t('nodes.integer'),
}, },
IntegerCollection: { IntegerCollection: {
color: 'red.500', color: 'red.500',
description: 'A collection of integers.', description: t('nodes.integerCollectionDescription'),
title: 'Integer Collection', title: t('nodes.integerCollection'),
}, },
IntegerPolymorphic: { IntegerPolymorphic: {
color: 'red.500', color: 'red.500',
description: 'A collection of integers.', description: t('nodes.integerPolymorphicDescription'),
title: 'Integer Polymorphic', title: t('nodes.integerPolymorphic'),
}, },
LatentsCollection: { LatentsCollection: {
color: 'pink.500', color: 'pink.500',
description: 'Latents may be passed between nodes.', description: t('nodes.latentsCollectionDescription'),
title: 'Latents Collection', title: t('nodes.latentsCollection'),
}, },
LatentsField: { LatentsField: {
color: 'pink.500', color: 'pink.500',
description: 'Latents may be passed between nodes.', description: t('nodes.latentsFieldDescription'),
title: 'Latents', title: t('nodes.latentsField'),
}, },
LatentsPolymorphic: { LatentsPolymorphic: {
color: 'pink.500', color: 'pink.500',
description: 'Latents may be passed between nodes.', description: t('nodes.latentsPolymorphicDescription'),
title: 'Latents Polymorphic', title: t('nodes.latentsPolymorphic'),
}, },
LoRAModelField: { LoRAModelField: {
color: 'teal.500', color: 'teal.500',
description: 'TODO', description: t('nodes.loRAModelFieldDescription'),
title: 'LoRA', title: t('nodes.loRAModelField'),
}, },
MainModelField: { MainModelField: {
color: 'teal.500', color: 'teal.500',
description: 'TODO', description: t('nodes.mainModelFieldDescription'),
title: 'Model', title: t('nodes.mainModelField'),
}, },
ONNXModelField: { ONNXModelField: {
color: 'teal.500', color: 'teal.500',
description: 'ONNX model field.', description: t('nodes.oNNXModelFieldDescription'),
title: 'ONNX Model', title: t('nodes.oNNXModelField'),
}, },
Scheduler: { Scheduler: {
color: 'base.500', color: 'base.500',
description: 'TODO', description: t('nodes.schedulerDescription'),
title: 'Scheduler', title: t('nodes.scheduler'),
}, },
SDXLMainModelField: { SDXLMainModelField: {
color: 'teal.500', color: 'teal.500',
description: 'SDXL model field.', description: t('nodes.sDXLMainModelFieldDescription'),
title: 'SDXL Model', title: t('nodes.sDXLMainModelField'),
}, },
SDXLRefinerModelField: { SDXLRefinerModelField: {
color: 'teal.500', color: 'teal.500',
description: 'TODO', description: t('nodes.sDXLRefinerModelFieldDescription'),
title: 'Refiner Model', title: t('nodes.sDXLRefinerModelField'),
}, },
string: { string: {
color: 'yellow.500', color: 'yellow.500',
description: 'Strings are text.', description: t('nodes.stringDescription'),
title: 'String', title: t('nodes.string'),
}, },
StringCollection: { StringCollection: {
color: 'yellow.500', color: 'yellow.500',
description: 'A collection of strings.', description: t('nodes.stringCollectionDescription'),
title: 'String Collection', title: t('nodes.stringCollection'),
}, },
StringPolymorphic: { StringPolymorphic: {
color: 'yellow.500', color: 'yellow.500',
description: 'A collection of strings.', description: t('nodes.stringPolymorphicDescription'),
title: 'String Polymorphic', title: t('nodes.stringPolymorphic'),
}, },
UNetField: { UNetField: {
color: 'red.500', color: 'red.500',
description: 'UNet submodel.', description: t('nodes.uNetFieldDescription'),
title: 'UNet', title: t('nodes.uNetField'),
}, },
VaeField: { VaeField: {
color: 'blue.500', color: 'blue.500',
description: 'Vae submodel.', description: t('nodes.vaeFieldDescription'),
title: 'Vae', title: t('nodes.vaeField'),
}, },
VaeModelField: { VaeModelField: {
color: 'teal.500', color: 'teal.500',
description: 'TODO', description: t('nodes.vaeModelFieldDescription'),
title: 'VAE', title: t('nodes.vaeModelField'),
}, },
}; };

View File

@ -20,6 +20,7 @@ import {
import { O } from 'ts-toolbelt'; import { O } from 'ts-toolbelt';
import { JsonObject } from 'type-fest'; import { JsonObject } from 'type-fest';
import { z } from 'zod'; import { z } from 'zod';
import i18n from 'i18next';
export type NonNullableGraph = O.Required<Graph, 'nodes' | 'edges'>; export type NonNullableGraph = O.Required<Graph, 'nodes' | 'edges'>;
@ -1056,6 +1057,13 @@ export const isInvocationFieldSchema = (
export type InvocationEdgeExtra = { type: 'default' | 'collapsed' }; export type InvocationEdgeExtra = { type: 'default' | 'collapsed' };
const zLoRAMetadataItem = z.object({
lora: zLoRAModelField.deepPartial(),
weight: z.number(),
});
export type LoRAMetadataItem = z.infer<typeof zLoRAMetadataItem>;
export const zCoreMetadata = z export const zCoreMetadata = z
.object({ .object({
app_version: z.string().nullish(), app_version: z.string().nullish(),
@ -1075,14 +1083,7 @@ export const zCoreMetadata = z
.union([zMainModel.deepPartial(), zOnnxModel.deepPartial()]) .union([zMainModel.deepPartial(), zOnnxModel.deepPartial()])
.nullish(), .nullish(),
controlnets: z.array(zControlField.deepPartial()).nullish(), controlnets: z.array(zControlField.deepPartial()).nullish(),
loras: z loras: z.array(zLoRAMetadataItem).nullish(),
.array(
z.object({
lora: zLoRAModelField.deepPartial(),
weight: z.number(),
})
)
.nullish(),
vae: zVaeModelField.nullish(), vae: zVaeModelField.nullish(),
strength: z.number().nullish(), strength: z.number().nullish(),
init_image: z.string().nullish(), init_image: z.string().nullish(),
@ -1258,23 +1259,35 @@ export const zValidatedWorkflow = zWorkflow.transform((workflow) => {
const targetNode = keyedNodes[edge.target]; const targetNode = keyedNodes[edge.target];
const issues: string[] = []; const issues: string[] = [];
if (!sourceNode) { if (!sourceNode) {
issues.push(`Output node ${edge.source} does not exist`); issues.push(
`${i18n.t('nodes.outputNode')} ${edge.source} ${i18n.t(
'nodes.doesNotExist'
)}`
);
} else if ( } else if (
edge.type === 'default' && edge.type === 'default' &&
!(edge.sourceHandle in sourceNode.data.outputs) !(edge.sourceHandle in sourceNode.data.outputs)
) { ) {
issues.push( issues.push(
`Output field "${edge.source}.${edge.sourceHandle}" does not exist` `${i18n.t('nodes.outputField')}"${edge.source}.${
edge.sourceHandle
}" ${i18n.t('nodes.doesNotExist')}`
); );
} }
if (!targetNode) { if (!targetNode) {
issues.push(`Input node ${edge.target} does not exist`); issues.push(
`${i18n.t('nodes.inputNode')} ${edge.target} ${i18n.t(
'nodes.doesNotExist'
)}`
);
} else if ( } else if (
edge.type === 'default' && edge.type === 'default' &&
!(edge.targetHandle in targetNode.data.inputs) !(edge.targetHandle in targetNode.data.inputs)
) { ) {
issues.push( issues.push(
`Input field "${edge.target}.${edge.targetHandle}" does not exist` `${i18n.t('nodes.inputField')} "${edge.target}.${
edge.targetHandle
}" ${i18n.t('nodes.doesNotExist')}`
); );
} }
if (issues.length) { if (issues.length) {
@ -1282,7 +1295,9 @@ export const zValidatedWorkflow = zWorkflow.transform((workflow) => {
const src = edge.type === 'default' ? edge.sourceHandle : edge.source; const src = edge.type === 'default' ? edge.sourceHandle : edge.source;
const tgt = edge.type === 'default' ? edge.targetHandle : edge.target; const tgt = edge.type === 'default' ? edge.targetHandle : edge.target;
warnings.push({ warnings.push({
message: `Edge "${src} -> ${tgt}" skipped`, message: `${i18n.t('nodes.edge')} "${src} -> ${tgt}" ${i18n.t(
'nodes.skipped'
)}`,
issues, issues,
data: edge, data: edge,
}); });

View File

@ -3,6 +3,7 @@ import { NodesState } from '../store/types';
import { Workflow, zWorkflowEdge, zWorkflowNode } from '../types/types'; import { Workflow, zWorkflowEdge, zWorkflowNode } from '../types/types';
import { fromZodError } from 'zod-validation-error'; import { fromZodError } from 'zod-validation-error';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import i18n from 'i18next';
export const buildWorkflow = (nodesState: NodesState): Workflow => { export const buildWorkflow = (nodesState: NodesState): Workflow => {
const { workflow: workflowMeta, nodes, edges } = nodesState; const { workflow: workflowMeta, nodes, edges } = nodesState;
@ -20,7 +21,7 @@ export const buildWorkflow = (nodesState: NodesState): Workflow => {
const result = zWorkflowNode.safeParse(node); const result = zWorkflowNode.safeParse(node);
if (!result.success) { if (!result.success) {
const { message } = fromZodError(result.error, { const { message } = fromZodError(result.error, {
prefix: 'Unable to parse node', prefix: i18n.t('nodes.unableToParseNode'),
}); });
logger('nodes').warn({ node: parseify(node) }, message); logger('nodes').warn({ node: parseify(node) }, message);
return; return;
@ -32,7 +33,7 @@ export const buildWorkflow = (nodesState: NodesState): Workflow => {
const result = zWorkflowEdge.safeParse(edge); const result = zWorkflowEdge.safeParse(edge);
if (!result.success) { if (!result.success) {
const { message } = fromZodError(result.error, { const { message } = fromZodError(result.error, {
prefix: 'Unable to parse edge', prefix: i18n.t('nodes.unableToParseEdge'),
}); });
logger('nodes').warn({ edge: parseify(edge) }, message); logger('nodes').warn({ edge: parseify(edge) }, message);
return; return;

View File

@ -79,8 +79,8 @@ export const buildCanvasInpaintGraph = (
} = state.generation; } = state.generation;
if (!model) { if (!model) {
log.error('No model found in state'); log.error('No Image found in state');
throw new Error('No model found in state'); throw new Error('No Image found in state');
} }
// The bounding box determines width and height, not the width and height params // The bounding box determines width and height, not the width and height params

View File

@ -7,6 +7,7 @@ import {
isWorkflowInvocationNode, isWorkflowInvocationNode,
} from '../types/types'; } from '../types/types';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import i18n from 'i18next';
export const validateWorkflow = ( export const validateWorkflow = (
workflow: Workflow, workflow: Workflow,
@ -25,8 +26,14 @@ export const validateWorkflow = (
const nodeTemplate = nodeTemplates[node.data.type]; const nodeTemplate = nodeTemplates[node.data.type];
if (!nodeTemplate) { if (!nodeTemplate) {
errors.push({ errors.push({
message: `Node "${node.data.type}" skipped`, message: `${i18n.t('nodes.node')} "${node.data.type}" ${i18n.t(
issues: [`Node type "${node.data.type}" does not exist`], 'nodes.skipped'
)}`,
issues: [
`${i18n.t('nodes.nodeType')}"${node.data.type}" ${i18n.t(
'nodes.doesNotExist'
)}`,
],
data: node, data: node,
}); });
return; return;
@ -38,9 +45,13 @@ export const validateWorkflow = (
compareVersions(nodeTemplate.version, node.data.version) !== 0 compareVersions(nodeTemplate.version, node.data.version) !== 0
) { ) {
errors.push({ errors.push({
message: `Node "${node.data.type}" has mismatched version`, message: `${i18n.t('nodes.node')} "${node.data.type}" ${i18n.t(
'nodes.mismatchedVersion'
)}`,
issues: [ issues: [
`Node "${node.data.type}" v${node.data.version} may be incompatible with installed v${nodeTemplate.version}`, `${i18n.t('nodes.node')} "${node.data.type}" v${
node.data.version
} ${i18n.t('nodes.maybeIncompatible')} v${nodeTemplate.version}`,
], ],
data: { node, nodeTemplate: parseify(nodeTemplate) }, data: { node, nodeTemplate: parseify(nodeTemplate) },
}); });
@ -52,33 +63,49 @@ export const validateWorkflow = (
const targetNode = keyedNodes[edge.target]; const targetNode = keyedNodes[edge.target];
const issues: string[] = []; const issues: string[] = [];
if (!sourceNode) { if (!sourceNode) {
issues.push(`Output node ${edge.source} does not exist`); issues.push(
`${i18n.t('nodes.outputNode')} ${edge.source} ${i18n.t(
'nodes.doesNotExist'
)}`
);
} else if ( } else if (
edge.type === 'default' && edge.type === 'default' &&
!(edge.sourceHandle in sourceNode.data.outputs) !(edge.sourceHandle in sourceNode.data.outputs)
) { ) {
issues.push( issues.push(
`Output field "${edge.source}.${edge.sourceHandle}" does not exist` `${i18n.t('nodes.outputNodes')} "${edge.source}.${
edge.sourceHandle
}" ${i18n.t('nodes.doesNotExist')}`
); );
} }
if (!targetNode) { if (!targetNode) {
issues.push(`Input node ${edge.target} does not exist`); issues.push(
`${i18n.t('nodes.inputNode')} ${edge.target} ${i18n.t(
'nodes.doesNotExist'
)}`
);
} else if ( } else if (
edge.type === 'default' && edge.type === 'default' &&
!(edge.targetHandle in targetNode.data.inputs) !(edge.targetHandle in targetNode.data.inputs)
) { ) {
issues.push( issues.push(
`Input field "${edge.target}.${edge.targetHandle}" does not exist` `${i18n.t('nodes.inputFeilds')} "${edge.target}.${
edge.targetHandle
}" ${i18n.t('nodes.doesNotExist')}`
); );
} }
if (!nodeTemplates[sourceNode?.data.type ?? '__UNKNOWN_NODE_TYPE__']) { if (!nodeTemplates[sourceNode?.data.type ?? '__UNKNOWN_NODE_TYPE__']) {
issues.push( issues.push(
`Source node "${edge.source}" missing template "${sourceNode?.data.type}"` `${i18n.t('nodes.sourceNode')} "${edge.source}" ${i18n.t(
'nodes.missingTemplate'
)} "${sourceNode?.data.type}"`
); );
} }
if (!nodeTemplates[targetNode?.data.type ?? '__UNKNOWN_NODE_TYPE__']) { if (!nodeTemplates[targetNode?.data.type ?? '__UNKNOWN_NODE_TYPE__']) {
issues.push( issues.push(
`Source node "${edge.target}" missing template "${targetNode?.data.type}"` `${i18n.t('nodes.sourceNode')}"${edge.target}" ${i18n.t(
'nodes.missingTemplate'
)} "${targetNode?.data.type}"`
); );
} }
if (issues.length) { if (issues.length) {

View File

@ -1,6 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { stateSelector } from 'app/store/store';
import { CoreMetadata } from 'features/nodes/types/types'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { CoreMetadata, LoRAMetadataItem } from 'features/nodes/types/types';
import { import {
refinerModelChanged, refinerModelChanged,
setNegativeStylePromptSDXL, setNegativeStylePromptSDXL,
@ -15,6 +17,11 @@ import {
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ImageDTO } from 'services/api/types'; import { ImageDTO } from 'services/api/types';
import {
loraModelsAdapter,
useGetLoRAModelsQuery,
} from '../../../services/api/endpoints/models';
import { loraRecalled } from '../../lora/store/loraSlice';
import { initialImageSelected, modelSelected } from '../store/actions'; import { initialImageSelected, modelSelected } from '../store/actions';
import { import {
setCfgScale, setCfgScale,
@ -30,6 +37,7 @@ import {
import { import {
isValidCfgScale, isValidCfgScale,
isValidHeight, isValidHeight,
isValidLoRAModel,
isValidMainModel, isValidMainModel,
isValidNegativePrompt, isValidNegativePrompt,
isValidPositivePrompt, isValidPositivePrompt,
@ -46,10 +54,16 @@ import {
isValidWidth, isValidWidth,
} from '../types/parameterSchemas'; } from '../types/parameterSchemas';
const selector = createSelector(stateSelector, ({ generation }) => {
const { model } = generation;
return { model };
});
export const useRecallParameters = () => { export const useRecallParameters = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const toaster = useAppToaster(); const toaster = useAppToaster();
const { t } = useTranslation(); const { t } = useTranslation();
const { model } = useAppSelector(selector);
const parameterSetToast = useCallback(() => { const parameterSetToast = useCallback(() => {
toaster({ toaster({
@ -60,14 +74,18 @@ export const useRecallParameters = () => {
}); });
}, [t, toaster]); }, [t, toaster]);
const parameterNotSetToast = useCallback(() => { const parameterNotSetToast = useCallback(
toaster({ (description?: string) => {
title: t('toast.parameterNotSet'), toaster({
status: 'warning', title: t('toast.parameterNotSet'),
duration: 2500, description,
isClosable: true, status: 'warning',
}); duration: 2500,
}, [t, toaster]); isClosable: true,
});
},
[t, toaster]
);
const allParameterSetToast = useCallback(() => { const allParameterSetToast = useCallback(() => {
toaster({ toaster({
@ -78,14 +96,18 @@ export const useRecallParameters = () => {
}); });
}, [t, toaster]); }, [t, toaster]);
const allParameterNotSetToast = useCallback(() => { const allParameterNotSetToast = useCallback(
toaster({ (description?: string) => {
title: t('toast.parametersNotSet'), toaster({
status: 'warning', title: t('toast.parametersNotSet'),
duration: 2500, status: 'warning',
isClosable: true, description,
}); duration: 2500,
}, [t, toaster]); isClosable: true,
});
},
[t, toaster]
);
/** /**
* Recall both prompts with toast * Recall both prompts with toast
@ -307,6 +329,67 @@ export const useRecallParameters = () => {
[dispatch, parameterSetToast, parameterNotSetToast] [dispatch, parameterSetToast, parameterNotSetToast]
); );
/**
* Recall LoRA with toast
*/
const { loras } = useGetLoRAModelsQuery(undefined, {
selectFromResult: (result) => ({
loras: result.data
? loraModelsAdapter.getSelectors().selectAll(result.data)
: [],
}),
});
const prepareLoRAMetadataItem = useCallback(
(loraMetadataItem: LoRAMetadataItem) => {
if (!isValidLoRAModel(loraMetadataItem.lora)) {
return { lora: null, error: 'Invalid LoRA model' };
}
const { base_model, model_name } = loraMetadataItem.lora;
const matchingLoRA = loras.find(
(l) => l.base_model === base_model && l.model_name === model_name
);
if (!matchingLoRA) {
return { lora: null, error: 'LoRA model is not installed' };
}
const isCompatibleBaseModel =
matchingLoRA?.base_model === model?.base_model;
if (!isCompatibleBaseModel) {
return {
lora: null,
error: 'LoRA incompatible with currently-selected model',
};
}
return { lora: matchingLoRA, error: null };
},
[loras, model?.base_model]
);
const recallLoRA = useCallback(
(loraMetadataItem: LoRAMetadataItem) => {
const result = prepareLoRAMetadataItem(loraMetadataItem);
if (!result.lora) {
parameterNotSetToast(result.error);
return;
}
dispatch(
loraRecalled({ ...result.lora, weight: loraMetadataItem.weight })
);
parameterSetToast();
},
[prepareLoRAMetadataItem, dispatch, parameterSetToast, parameterNotSetToast]
);
/* /*
* Sets image as initial image with toast * Sets image as initial image with toast
*/ */
@ -344,6 +427,7 @@ export const useRecallParameters = () => {
refiner_positive_aesthetic_score, refiner_positive_aesthetic_score,
refiner_negative_aesthetic_score, refiner_negative_aesthetic_score,
refiner_start, refiner_start,
loras,
} = metadata; } = metadata;
if (isValidCfgScale(cfg_scale)) { if (isValidCfgScale(cfg_scale)) {
@ -425,9 +509,21 @@ export const useRecallParameters = () => {
dispatch(setRefinerStart(refiner_start)); dispatch(setRefinerStart(refiner_start));
} }
loras?.forEach((lora) => {
const result = prepareLoRAMetadataItem(lora);
if (result.lora) {
dispatch(loraRecalled({ ...result.lora, weight: lora.weight }));
}
});
allParameterSetToast(); allParameterSetToast();
}, },
[allParameterNotSetToast, allParameterSetToast, dispatch] [
allParameterNotSetToast,
allParameterSetToast,
dispatch,
prepareLoRAMetadataItem,
]
); );
return { return {
@ -444,6 +540,7 @@ export const useRecallParameters = () => {
recallWidth, recallWidth,
recallHeight, recallHeight,
recallStrength, recallStrength,
recallLoRA,
recallAllParameters, recallAllParameters,
sendToImageToImage, sendToImageToImage,
}; };

View File

@ -80,7 +80,7 @@ export const initialGenerationState: GenerationState = {
scheduler: 'euler', scheduler: 'euler',
maskBlur: 16, maskBlur: 16,
maskBlurMethod: 'box', maskBlurMethod: 'box',
canvasCoherenceMode: 'edge', canvasCoherenceMode: 'unmasked',
canvasCoherenceSteps: 20, canvasCoherenceSteps: 20,
canvasCoherenceStrength: 0.3, canvasCoherenceStrength: 0.3,
seed: 0, seed: 0,

View File

@ -128,7 +128,7 @@ export const mainModelsAdapter = createEntityAdapter<MainModelConfigEntity>({
const onnxModelsAdapter = createEntityAdapter<OnnxModelConfigEntity>({ const onnxModelsAdapter = createEntityAdapter<OnnxModelConfigEntity>({
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name), sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
}); });
const loraModelsAdapter = createEntityAdapter<LoRAModelConfigEntity>({ export const loraModelsAdapter = createEntityAdapter<LoRAModelConfigEntity>({
sortComparer: (a, b) => a.model_name.localeCompare(b.model_name), sortComparer: (a, b) => a.model_name.localeCompare(b.model_name),
}); });
export const controlNetModelsAdapter = export const controlNetModelsAdapter =

View File

@ -198,6 +198,13 @@ output = "coverage/index.xml"
max-line-length = 120 max-line-length = 120
ignore = ["E203", "E266", "E501", "W503"] ignore = ["E203", "E266", "E501", "W503"]
select = ["B", "C", "E", "F", "W", "T4"] select = ["B", "C", "E", "F", "W", "T4"]
exclude = [
".git",
"__pycache__",
"build",
"dist",
"invokeai/frontend/web/node_modules/"
]
[tool.black] [tool.black]
line-length = 120 line-length = 120