mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-01-15 23:28:03 -05:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3f28f7a35 | ||
|
|
c900a63842 | ||
|
|
4eb5f004e6 | ||
|
|
bcae735d7c | ||
|
|
861f06c459 | ||
|
|
c493628272 | ||
|
|
46a90ca402 | ||
|
|
d45c33b446 | ||
|
|
88025d32c2 | ||
|
|
af64764082 | ||
|
|
70487f0c2e | ||
|
|
55d7d9cc75 | ||
|
|
106674175c | ||
|
|
dd1d5bdb25 | ||
|
|
6259ac0bec | ||
|
|
ba31f8a9a9 | ||
|
|
0ba57d6dc5 | ||
|
|
abc133e936 | ||
|
|
57743239d7 | ||
|
|
4a394c60cf | ||
|
|
624d28a93d | ||
|
|
29e1ea59fc | ||
|
|
2e5d24f272 | ||
|
|
1afa340b1a | ||
|
|
3b381b5a8c | ||
|
|
f2b9684de8 | ||
|
|
a66b3497e0 | ||
|
|
683ec8e5f2 | ||
|
|
f31f0cf733 | ||
|
|
38265b3123 | ||
|
|
caca28286c | ||
|
|
38320a5100 | ||
|
|
7badaab17d | ||
|
|
aa0c59bb51 | ||
|
|
e4acaa5c8f | ||
|
|
9ba47cae20 | ||
|
|
bf4310ca71 | ||
|
|
e75f98317f | ||
|
|
1249d4a6e3 |
@@ -165,7 +165,7 @@ Additionally, each section can be expanded with the "Show Advanced" button in o
|
||||
There are several ways to install IP-Adapter models with an existing InvokeAI installation:
|
||||
|
||||
1. Through the command line interface launched from the invoke.sh / invoke.bat scripts, option [4] to download models.
|
||||
2. Through the Model Manager UI with models from the *Tools* section of [www.models.invoke.ai](https://www.models.invoke.ai). To do this, copy the repo ID from the desired model page, and paste it in the Add Model field of the model manager. **Note** Both the IP-Adapter and the Image Encoder must be installed for IP-Adapter to work. For example, the [SD 1.5 IP-Adapter](https://models.invoke.ai/InvokeAI/ip_adapter_plus_sd15) and [SD1.5 Image Encoder](https://models.invoke.ai/InvokeAI/ip_adapter_sd_image_encoder) must be installed to use IP-Adapter with SD1.5 based models.
|
||||
2. Through the Model Manager UI with models from the *Tools* section of [models.invoke.ai](https://models.invoke.ai). To do this, copy the repo ID from the desired model page, and paste it in the Add Model field of the model manager. **Note** Both the IP-Adapter and the Image Encoder must be installed for IP-Adapter to work. For example, the [SD 1.5 IP-Adapter](https://models.invoke.ai/InvokeAI/ip_adapter_plus_sd15) and [SD1.5 Image Encoder](https://models.invoke.ai/InvokeAI/ip_adapter_sd_image_encoder) must be installed to use IP-Adapter with SD1.5 based models.
|
||||
3. **Advanced -- Not recommended ** Manually downloading the IP-Adapter and Image Encoder files - Image Encoder folders shouid be placed in the `models\any\clip_vision` folders. IP Adapter Model folders should be placed in the relevant `ip-adapter` folder of relevant base model folder of Invoke root directory. For example, for the SDXL IP-Adapter, files should be added to the `model/sdxl/ip_adapter/` folder.
|
||||
|
||||
#### Using IP-Adapter
|
||||
|
||||
@@ -10,8 +10,7 @@ set INVOKEAI_ROOT=.
|
||||
echo Desired action:
|
||||
echo 1. Generate images with the browser-based interface
|
||||
echo 2. Open the developer console
|
||||
echo 3. Run the InvokeAI image database maintenance script
|
||||
echo 4. Command-line help
|
||||
echo 3. Command-line help
|
||||
echo Q - Quit
|
||||
echo.
|
||||
echo To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest.
|
||||
@@ -34,9 +33,6 @@ IF /I "%choice%" == "1" (
|
||||
echo *** Type `exit` to quit this shell and deactivate the Python virtual environment ***
|
||||
call cmd /k
|
||||
) ELSE IF /I "%choice%" == "3" (
|
||||
echo Running the db maintenance script...
|
||||
python .venv\Scripts\invokeai-db-maintenance.exe
|
||||
) ELSE IF /I "%choice%" == "4" (
|
||||
echo Displaying command line help...
|
||||
python .venv\Scripts\invokeai-web.exe --help %*
|
||||
pause
|
||||
|
||||
@@ -47,11 +47,6 @@ do_choice() {
|
||||
bash --init-file "$file_name"
|
||||
;;
|
||||
3)
|
||||
clear
|
||||
printf "Running the db maintenance script\n"
|
||||
invokeai-db-maintenance --root ${INVOKEAI_ROOT}
|
||||
;;
|
||||
4)
|
||||
clear
|
||||
printf "Command-line help\n"
|
||||
invokeai-web --help
|
||||
@@ -71,8 +66,7 @@ do_line_input() {
|
||||
printf "What would you like to do?\n"
|
||||
printf "1: Generate images using the browser-based interface\n"
|
||||
printf "2: Open the developer console\n"
|
||||
printf "3: Run the InvokeAI image database maintenance script\n"
|
||||
printf "4: Command-line help\n"
|
||||
printf "3: Command-line help\n"
|
||||
printf "Q: Quit\n\n"
|
||||
printf "To update, download and run the installer from https://github.com/invoke-ai/InvokeAI/releases/latest.\n\n"
|
||||
read -p "Please enter 1-4, Q: [1] " yn
|
||||
|
||||
@@ -69,7 +69,7 @@ async def upload_image(
|
||||
if isinstance(metadata_raw, str):
|
||||
_metadata = metadata_raw
|
||||
else:
|
||||
ApiDependencies.invoker.services.logger.warn("Failed to parse metadata for uploaded image")
|
||||
ApiDependencies.invoker.services.logger.debug("Failed to parse metadata for uploaded image")
|
||||
pass
|
||||
|
||||
# attempt to parse workflow from image
|
||||
@@ -77,7 +77,7 @@ async def upload_image(
|
||||
if isinstance(workflow_raw, str):
|
||||
_workflow = workflow_raw
|
||||
else:
|
||||
ApiDependencies.invoker.services.logger.warn("Failed to parse workflow for uploaded image")
|
||||
ApiDependencies.invoker.services.logger.debug("Failed to parse workflow for uploaded image")
|
||||
pass
|
||||
|
||||
# attempt to extract graph from image
|
||||
@@ -85,7 +85,7 @@ async def upload_image(
|
||||
if isinstance(graph_raw, str):
|
||||
_graph = graph_raw
|
||||
else:
|
||||
ApiDependencies.invoker.services.logger.warn("Failed to parse graph for uploaded image")
|
||||
ApiDependencies.invoker.services.logger.debug("Failed to parse graph for uploaded image")
|
||||
pass
|
||||
|
||||
try:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"accessibility": {
|
||||
"about": "About",
|
||||
"createIssue": "Create Issue",
|
||||
"submitSupportTicket": "Submit Support Ticket",
|
||||
"invokeProgressBar": "Invoke progress bar",
|
||||
"menu": "Menu",
|
||||
"mode": "Mode",
|
||||
@@ -146,7 +147,9 @@
|
||||
"viewing": "Viewing",
|
||||
"viewingDesc": "Review images in a large gallery view",
|
||||
"editing": "Editing",
|
||||
"editingDesc": "Edit on the Control Layers canvas"
|
||||
"editingDesc": "Edit on the Control Layers canvas",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"controlnet": {
|
||||
"controlAdapter_one": "Control Adapter",
|
||||
@@ -897,7 +900,10 @@
|
||||
"zoomInNodes": "Zoom In",
|
||||
"zoomOutNodes": "Zoom Out",
|
||||
"betaDesc": "This invocation is in beta. Until it is stable, it may have breaking changes during app updates. We plan to support this invocation long-term.",
|
||||
"prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time."
|
||||
"prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time.",
|
||||
"imageAccessError": "Unable to find image {{image_name}}, resetting to default",
|
||||
"boardAccessError": "Unable to find board {{board_id}}, resetting to default",
|
||||
"modelAccessError": "Unable to find model {{key}}, resetting to default"
|
||||
},
|
||||
"parameters": {
|
||||
"aspect": "Aspect",
|
||||
@@ -1070,8 +1076,9 @@
|
||||
},
|
||||
"toast": {
|
||||
"addedToBoard": "Added to board",
|
||||
"baseModelChangedCleared_one": "Base model changed, cleared or disabled {{count}} incompatible submodel",
|
||||
"baseModelChangedCleared_other": "Base model changed, cleared or disabled {{count}} incompatible submodels",
|
||||
"baseModelChanged": "Base Model Changed",
|
||||
"baseModelChangedCleared_one": "Cleared or disabled {{count}} incompatible submodel",
|
||||
"baseModelChangedCleared_other": "Cleared or disabled {{count}} incompatible submodels",
|
||||
"canceled": "Processing Canceled",
|
||||
"canvasCopiedClipboard": "Canvas Copied to Clipboard",
|
||||
"canvasDownloaded": "Canvas Downloaded",
|
||||
@@ -1092,10 +1099,17 @@
|
||||
"metadataLoadFailed": "Failed to load metadata",
|
||||
"modelAddedSimple": "Model Added to Queue",
|
||||
"modelImportCanceled": "Model Import Canceled",
|
||||
"outOfMemoryError": "Out of Memory Error",
|
||||
"outOfMemoryErrorDesc": "Your current generation settings exceed system capacity. Please adjust your settings and try again.",
|
||||
"parameters": "Parameters",
|
||||
"parameterNotSet": "{{parameter}} not set",
|
||||
"parameterSet": "{{parameter}} set",
|
||||
"parametersNotSet": "Parameters Not Set",
|
||||
"parameterSet": "Parameter Recalled",
|
||||
"parameterSetDesc": "Recalled {{parameter}}",
|
||||
"parameterNotSet": "Parameter Recalled",
|
||||
"parameterNotSetDesc": "Unable to recall {{parameter}}",
|
||||
"parameterNotSetDescWithMessage": "Unable to recall {{parameter}}: {{message}}",
|
||||
"parametersSet": "Parameters Recalled",
|
||||
"parametersNotSet": "Parameters Not Recalled",
|
||||
"errorCopied": "Error Copied",
|
||||
"problemCopyingCanvas": "Problem Copying Canvas",
|
||||
"problemCopyingCanvasDesc": "Unable to export base layer",
|
||||
"problemCopyingImage": "Unable to Copy Image",
|
||||
@@ -1115,11 +1129,13 @@
|
||||
"sentToImageToImage": "Sent To Image To Image",
|
||||
"sentToUnifiedCanvas": "Sent to Unified Canvas",
|
||||
"serverError": "Server Error",
|
||||
"sessionRef": "Session: {{sessionId}}",
|
||||
"setAsCanvasInitialImage": "Set as canvas initial image",
|
||||
"setCanvasInitialImage": "Set canvas initial image",
|
||||
"setControlImage": "Set as control image",
|
||||
"setInitialImage": "Set as initial image",
|
||||
"setNodeField": "Set as node field",
|
||||
"somethingWentWrong": "Something Went Wrong",
|
||||
"uploadFailed": "Upload failed",
|
||||
"uploadFailedInvalidUploadDesc": "Must be single PNG or JPEG image",
|
||||
"uploadInitialImage": "Upload Initial Image",
|
||||
@@ -1559,7 +1575,6 @@
|
||||
"controlLayers": "Control Layers",
|
||||
"globalMaskOpacity": "Global Mask Opacity",
|
||||
"autoNegative": "Auto Negative",
|
||||
"toggleVisibility": "Toggle Layer Visibility",
|
||||
"deletePrompt": "Delete Prompt",
|
||||
"resetRegion": "Reset Region",
|
||||
"debugLayers": "Debug Layers",
|
||||
|
||||
@@ -382,7 +382,7 @@
|
||||
"canvasMerged": "Lienzo consolidado",
|
||||
"sentToImageToImage": "Enviar hacia Imagen a Imagen",
|
||||
"sentToUnifiedCanvas": "Enviar hacia Lienzo Consolidado",
|
||||
"parametersNotSet": "Parámetros no establecidos",
|
||||
"parametersNotSet": "Parámetros no recuperados",
|
||||
"metadataLoadFailed": "Error al cargar metadatos",
|
||||
"serverError": "Error en el servidor",
|
||||
"canceled": "Procesando la cancelación",
|
||||
@@ -390,7 +390,8 @@
|
||||
"uploadFailedInvalidUploadDesc": "Debe ser una sola imagen PNG o JPEG",
|
||||
"parameterSet": "Conjunto de parámetros",
|
||||
"parameterNotSet": "Parámetro no configurado",
|
||||
"problemCopyingImage": "No se puede copiar la imagen"
|
||||
"problemCopyingImage": "No se puede copiar la imagen",
|
||||
"errorCopied": "Error al copiar"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
|
||||
@@ -524,7 +524,20 @@
|
||||
"missingNodeTemplate": "Modello di nodo mancante",
|
||||
"missingInputForField": "{{nodeLabel}} -> {{fieldLabel}} ingresso mancante",
|
||||
"missingFieldTemplate": "Modello di campo mancante",
|
||||
"imageNotProcessedForControlAdapter": "L'immagine dell'adattatore di controllo #{{number}} non è stata elaborata"
|
||||
"imageNotProcessedForControlAdapter": "L'immagine dell'adattatore di controllo #{{number}} non è stata elaborata",
|
||||
"layer": {
|
||||
"initialImageNoImageSelected": "Nessuna immagine iniziale selezionata",
|
||||
"t2iAdapterIncompatibleDimensions": "L'adattatore T2I richiede che la dimensione dell'immagine sia un multiplo di {{multiple}}",
|
||||
"controlAdapterNoModelSelected": "Nessun modello di Adattatore di Controllo selezionato",
|
||||
"controlAdapterIncompatibleBaseModel": "Il modello base dell'adattatore di controllo non è compatibile",
|
||||
"controlAdapterNoImageSelected": "Nessuna immagine dell'adattatore di controllo selezionata",
|
||||
"controlAdapterImageNotProcessed": "Immagine dell'adattatore di controllo non elaborata",
|
||||
"ipAdapterNoModelSelected": "Nessun adattatore IP selezionato",
|
||||
"ipAdapterIncompatibleBaseModel": "Il modello base dell'adattatore IP non è compatibile",
|
||||
"ipAdapterNoImageSelected": "Nessuna immagine dell'adattatore IP selezionata",
|
||||
"rgNoPromptsOrIPAdapters": "Nessun prompt o adattatore IP",
|
||||
"rgNoRegion": "Nessuna regione selezionata"
|
||||
}
|
||||
},
|
||||
"useCpuNoise": "Usa la CPU per generare rumore",
|
||||
"iterations": "Iterazioni",
|
||||
@@ -824,8 +837,8 @@
|
||||
"unableToUpdateNodes_other": "Impossibile aggiornare {{count}} nodi",
|
||||
"addLinearView": "Aggiungi alla vista Lineare",
|
||||
"unknownErrorValidatingWorkflow": "Errore sconosciuto durante la convalida del flusso di lavoro",
|
||||
"collectionFieldType": "{{name}} Raccolta",
|
||||
"collectionOrScalarFieldType": "{{name}} Raccolta|Scalare",
|
||||
"collectionFieldType": "{{name}} (Raccolta)",
|
||||
"collectionOrScalarFieldType": "{{name}} (Singola o Raccolta)",
|
||||
"nodeVersion": "Versione Nodo",
|
||||
"inputFieldTypeParseError": "Impossibile analizzare il tipo di campo di input {{node}}.{{field}} ({{message}})",
|
||||
"unsupportedArrayItemType": "Tipo di elemento dell'array non supportato \"{{type}}\"",
|
||||
@@ -863,7 +876,13 @@
|
||||
"edit": "Modifica",
|
||||
"graph": "Grafico",
|
||||
"showEdgeLabelsHelp": "Mostra etichette sui collegamenti, che indicano i nodi collegati",
|
||||
"showEdgeLabels": "Mostra le etichette del collegamento"
|
||||
"showEdgeLabels": "Mostra le etichette del collegamento",
|
||||
"cannotMixAndMatchCollectionItemTypes": "Impossibile combinare e abbinare i tipi di elementi della raccolta",
|
||||
"noGraph": "Nessun grafico",
|
||||
"missingNode": "Nodo di invocazione mancante",
|
||||
"missingInvocationTemplate": "Modello di invocazione mancante",
|
||||
"missingFieldTemplate": "Modello di campo mancante",
|
||||
"singleFieldType": "{{name}} (Singola)"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
||||
@@ -1034,7 +1053,16 @@
|
||||
"graphFailedToQueue": "Impossibile mettere in coda il grafico",
|
||||
"batchFieldValues": "Valori Campi Lotto",
|
||||
"time": "Tempo",
|
||||
"openQueue": "Apri coda"
|
||||
"openQueue": "Apri coda",
|
||||
"iterations_one": "Iterazione",
|
||||
"iterations_many": "Iterazioni",
|
||||
"iterations_other": "Iterazioni",
|
||||
"prompts_one": "Prompt",
|
||||
"prompts_many": "Prompt",
|
||||
"prompts_other": "Prompt",
|
||||
"generations_one": "Generazione",
|
||||
"generations_many": "Generazioni",
|
||||
"generations_other": "Generazioni"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "Nessun modello corrispondente",
|
||||
@@ -1563,7 +1591,6 @@
|
||||
"brushSize": "Dimensioni del pennello",
|
||||
"globalMaskOpacity": "Opacità globale della maschera",
|
||||
"autoNegative": "Auto Negativo",
|
||||
"toggleVisibility": "Attiva/disattiva la visibilità dei livelli",
|
||||
"deletePrompt": "Cancella il prompt",
|
||||
"debugLayers": "Debug dei Livelli",
|
||||
"rectangle": "Rettangolo",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"settingsLabel": "Instellingen",
|
||||
"img2img": "Afbeelding naar afbeelding",
|
||||
"unifiedCanvas": "Centraal canvas",
|
||||
"nodes": "Werkstroom-editor",
|
||||
"nodes": "Werkstromen",
|
||||
"upload": "Upload",
|
||||
"load": "Laad",
|
||||
"statusDisconnected": "Niet verbonden",
|
||||
@@ -34,7 +34,60 @@
|
||||
"controlNet": "ControlNet",
|
||||
"imageFailedToLoad": "Kan afbeelding niet laden",
|
||||
"learnMore": "Meer informatie",
|
||||
"advanced": "Uitgebreid"
|
||||
"advanced": "Uitgebreid",
|
||||
"file": "Bestand",
|
||||
"installed": "Geïnstalleerd",
|
||||
"notInstalled": "Niet $t(common.installed)",
|
||||
"simple": "Eenvoudig",
|
||||
"somethingWentWrong": "Er ging iets mis",
|
||||
"add": "Voeg toe",
|
||||
"checkpoint": "Checkpoint",
|
||||
"details": "Details",
|
||||
"outputs": "Uitvoeren",
|
||||
"save": "Bewaar",
|
||||
"nextPage": "Volgende pagina",
|
||||
"blue": "Blauw",
|
||||
"alpha": "Alfa",
|
||||
"red": "Rood",
|
||||
"editor": "Editor",
|
||||
"folder": "Map",
|
||||
"format": "structuur",
|
||||
"goTo": "Ga naar",
|
||||
"template": "Sjabloon",
|
||||
"input": "Invoer",
|
||||
"loglevel": "Logboekniveau",
|
||||
"safetensors": "Safetensors",
|
||||
"saveAs": "Bewaar als",
|
||||
"created": "Gemaakt",
|
||||
"green": "Groen",
|
||||
"tab": "Tab",
|
||||
"positivePrompt": "Positieve prompt",
|
||||
"negativePrompt": "Negatieve prompt",
|
||||
"selected": "Geselecteerd",
|
||||
"orderBy": "Sorteer op",
|
||||
"prevPage": "Vorige pagina",
|
||||
"beta": "Bèta",
|
||||
"copyError": "$t(gallery.copy) Fout",
|
||||
"toResolve": "Op te lossen",
|
||||
"aboutDesc": "Gebruik je Invoke voor het werk? Kijk dan naar:",
|
||||
"aboutHeading": "Creatieve macht voor jou",
|
||||
"copy": "Kopieer",
|
||||
"data": "Gegevens",
|
||||
"or": "of",
|
||||
"updated": "Bijgewerkt",
|
||||
"outpaint": "outpainten",
|
||||
"viewing": "Bekijken",
|
||||
"viewingDesc": "Beoordeel afbeelding in een grote galerijweergave",
|
||||
"editing": "Bewerken",
|
||||
"editingDesc": "Bewerk op het canvas Stuurlagen",
|
||||
"ai": "ai",
|
||||
"inpaint": "inpainten",
|
||||
"unknown": "Onbekend",
|
||||
"delete": "Verwijder",
|
||||
"direction": "Richting",
|
||||
"error": "Fout",
|
||||
"localSystem": "Lokaal systeem",
|
||||
"unknownError": "Onbekende fout"
|
||||
},
|
||||
"gallery": {
|
||||
"galleryImageSize": "Afbeeldingsgrootte",
|
||||
@@ -310,10 +363,41 @@
|
||||
"modelSyncFailed": "Synchronisatie modellen mislukt",
|
||||
"modelDeleteFailed": "Model kon niet verwijderd worden",
|
||||
"convertingModelBegin": "Model aan het converteren. Even geduld.",
|
||||
"predictionType": "Soort voorspelling (voor Stable Diffusion 2.x-modellen en incidentele Stable Diffusion 1.x-modellen)",
|
||||
"predictionType": "Soort voorspelling",
|
||||
"advanced": "Uitgebreid",
|
||||
"modelType": "Soort model",
|
||||
"vaePrecision": "Nauwkeurigheid VAE"
|
||||
"vaePrecision": "Nauwkeurigheid VAE",
|
||||
"loraTriggerPhrases": "LoRA-triggerzinnen",
|
||||
"urlOrLocalPathHelper": "URL's zouden moeten wijzen naar een los bestand. Lokale paden kunnen wijzen naar een los bestand of map voor een individueel Diffusers-model.",
|
||||
"modelName": "Modelnaam",
|
||||
"path": "Pad",
|
||||
"triggerPhrases": "Triggerzinnen",
|
||||
"typePhraseHere": "Typ zin hier in",
|
||||
"useDefaultSettings": "Gebruik standaardinstellingen",
|
||||
"modelImageDeleteFailed": "Fout bij verwijderen modelafbeelding",
|
||||
"modelImageUpdated": "Modelafbeelding bijgewerkt",
|
||||
"modelImageUpdateFailed": "Fout bij bijwerken modelafbeelding",
|
||||
"noMatchingModels": "Geen overeenkomende modellen",
|
||||
"scanPlaceholder": "Pad naar een lokale map",
|
||||
"noModelsInstalled": "Geen modellen geïnstalleerd",
|
||||
"noModelsInstalledDesc1": "Installeer modellen met de",
|
||||
"noModelSelected": "Geen model geselecteerd",
|
||||
"starterModels": "Beginnermodellen",
|
||||
"textualInversions": "Tekstuele omkeringen",
|
||||
"upcastAttention": "Upcast-aandacht",
|
||||
"uploadImage": "Upload afbeelding",
|
||||
"mainModelTriggerPhrases": "Triggerzinnen hoofdmodel",
|
||||
"urlOrLocalPath": "URL of lokaal pad",
|
||||
"scanFolderHelper": "De map zal recursief worden ingelezen voor modellen. Dit kan enige tijd in beslag nemen voor erg grote mappen.",
|
||||
"simpleModelPlaceholder": "URL of pad naar een lokaal pad of Diffusers-map",
|
||||
"modelSettings": "Modelinstellingen",
|
||||
"pathToConfig": "Pad naar configuratie",
|
||||
"prune": "Snoei",
|
||||
"pruneTooltip": "Snoei voltooide importeringen uit wachtrij",
|
||||
"repoVariant": "Repovariant",
|
||||
"scanFolder": "Lees map in",
|
||||
"scanResults": "Resultaten inlezen",
|
||||
"source": "Bron"
|
||||
},
|
||||
"parameters": {
|
||||
"images": "Afbeeldingen",
|
||||
@@ -353,13 +437,13 @@
|
||||
"copyImage": "Kopieer afbeelding",
|
||||
"denoisingStrength": "Sterkte ontruisen",
|
||||
"scheduler": "Planner",
|
||||
"seamlessXAxis": "X-as",
|
||||
"seamlessYAxis": "Y-as",
|
||||
"seamlessXAxis": "Naadloze tegels in x-as",
|
||||
"seamlessYAxis": "Naadloze tegels in y-as",
|
||||
"clipSkip": "Overslaan CLIP",
|
||||
"negativePromptPlaceholder": "Negatieve prompt",
|
||||
"controlNetControlMode": "Aansturingsmodus",
|
||||
"positivePromptPlaceholder": "Positieve prompt",
|
||||
"maskBlur": "Vervaag",
|
||||
"maskBlur": "Vervaging van masker",
|
||||
"invoke": {
|
||||
"noNodesInGraph": "Geen knooppunten in graaf",
|
||||
"noModelSelected": "Geen model ingesteld",
|
||||
@@ -369,11 +453,25 @@
|
||||
"missingInputForField": "{{nodeLabel}} -> {{fieldLabel}} invoer ontbreekt",
|
||||
"noControlImageForControlAdapter": "Controle-adapter #{{number}} heeft geen controle-afbeelding",
|
||||
"noModelForControlAdapter": "Control-adapter #{{number}} heeft geen model ingesteld staan.",
|
||||
"incompatibleBaseModelForControlAdapter": "Model van controle-adapter #{{number}} is ongeldig in combinatie met het hoofdmodel.",
|
||||
"incompatibleBaseModelForControlAdapter": "Model van controle-adapter #{{number}} is niet compatibel met het hoofdmodel.",
|
||||
"systemDisconnected": "Systeem is niet verbonden",
|
||||
"missingNodeTemplate": "Knooppuntsjabloon ontbreekt",
|
||||
"missingFieldTemplate": "Veldsjabloon ontbreekt",
|
||||
"addingImagesTo": "Bezig met toevoegen van afbeeldingen aan"
|
||||
"addingImagesTo": "Bezig met toevoegen van afbeeldingen aan",
|
||||
"layer": {
|
||||
"initialImageNoImageSelected": "geen initiële afbeelding geselecteerd",
|
||||
"controlAdapterNoModelSelected": "geen controle-adaptermodel geselecteerd",
|
||||
"controlAdapterIncompatibleBaseModel": "niet-compatibele basismodel voor controle-adapter",
|
||||
"controlAdapterNoImageSelected": "geen afbeelding voor controle-adapter geselecteerd",
|
||||
"controlAdapterImageNotProcessed": "Afbeelding voor controle-adapter niet verwerkt",
|
||||
"ipAdapterIncompatibleBaseModel": "niet-compatibele basismodel voor IP-adapter",
|
||||
"ipAdapterNoImageSelected": "geen afbeelding voor IP-adapter geselecteerd",
|
||||
"rgNoRegion": "geen gebied geselecteerd",
|
||||
"rgNoPromptsOrIPAdapters": "geen tekstprompts of IP-adapters",
|
||||
"t2iAdapterIncompatibleDimensions": "T2I-adapter vereist een afbeelding met afmetingen met een veelvoud van 64",
|
||||
"ipAdapterNoModelSelected": "geen IP-adapter geselecteerd"
|
||||
},
|
||||
"imageNotProcessedForControlAdapter": "De afbeelding van controle-adapter #{{number}} is niet verwerkt"
|
||||
},
|
||||
"isAllowedToUpscale": {
|
||||
"useX2Model": "Afbeelding is te groot om te vergroten met het x4-model. Gebruik hiervoor het x2-model",
|
||||
@@ -383,7 +481,26 @@
|
||||
"useCpuNoise": "Gebruik CPU-ruis",
|
||||
"imageActions": "Afbeeldingshandeling",
|
||||
"iterations": "Iteraties",
|
||||
"coherenceMode": "Modus"
|
||||
"coherenceMode": "Modus",
|
||||
"infillColorValue": "Vulkleur",
|
||||
"remixImage": "Meng afbeelding opnieuw",
|
||||
"setToOptimalSize": "Optimaliseer grootte voor het model",
|
||||
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (is mogelijk te klein)",
|
||||
"aspect": "Beeldverhouding",
|
||||
"infillMosaicTileWidth": "Breedte tegel",
|
||||
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (is mogelijk te groot)",
|
||||
"lockAspectRatio": "Zet beeldverhouding vast",
|
||||
"infillMosaicTileHeight": "Hoogte tegel",
|
||||
"globalNegativePromptPlaceholder": "Globale negatieve prompt",
|
||||
"globalPositivePromptPlaceholder": "Globale positieve prompt",
|
||||
"useSize": "Gebruik grootte",
|
||||
"swapDimensions": "Wissel afmetingen om",
|
||||
"globalSettings": "Globale instellingen",
|
||||
"coherenceEdgeSize": "Randgrootte",
|
||||
"coherenceMinDenoise": "Min. ontruising",
|
||||
"infillMosaicMinColor": "Min. kleur",
|
||||
"infillMosaicMaxColor": "Max. kleur",
|
||||
"cfgRescaleMultiplier": "Vermenigvuldiger voor CFG-herschaling"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Modellen",
|
||||
@@ -410,7 +527,12 @@
|
||||
"intermediatesCleared_one": "{{count}} tussentijdse afbeelding gewist",
|
||||
"intermediatesCleared_other": "{{count}} tussentijdse afbeeldingen gewist",
|
||||
"clearIntermediatesDesc1": "Als je tussentijdse afbeeldingen wist, dan wordt de staat hersteld van je canvas en van ControlNet.",
|
||||
"intermediatesClearedFailed": "Fout bij wissen van tussentijdse afbeeldingen"
|
||||
"intermediatesClearedFailed": "Fout bij wissen van tussentijdse afbeeldingen",
|
||||
"clearIntermediatesDisabled": "Wachtrij moet leeg zijn om tussentijdse afbeeldingen te kunnen leegmaken",
|
||||
"enableInformationalPopovers": "Schakel informatieve hulpballonnen in",
|
||||
"enableInvisibleWatermark": "Schakel onzichtbaar watermerk in",
|
||||
"enableNSFWChecker": "Schakel NSFW-controle in",
|
||||
"reloadingIn": "Opnieuw laden na"
|
||||
},
|
||||
"toast": {
|
||||
"uploadFailed": "Upload mislukt",
|
||||
@@ -425,8 +547,8 @@
|
||||
"connected": "Verbonden met server",
|
||||
"canceled": "Verwerking geannuleerd",
|
||||
"uploadFailedInvalidUploadDesc": "Moet een enkele PNG- of JPEG-afbeelding zijn",
|
||||
"parameterNotSet": "Parameter niet ingesteld",
|
||||
"parameterSet": "Instellen parameters",
|
||||
"parameterNotSet": "{{parameter}} niet ingesteld",
|
||||
"parameterSet": "{{parameter}} ingesteld",
|
||||
"problemCopyingImage": "Kan Afbeelding Niet Kopiëren",
|
||||
"baseModelChangedCleared_one": "Basismodel is gewijzigd: {{count}} niet-compatibel submodel weggehaald of uitgeschakeld",
|
||||
"baseModelChangedCleared_other": "Basismodel is gewijzigd: {{count}} niet-compatibele submodellen weggehaald of uitgeschakeld",
|
||||
@@ -443,11 +565,11 @@
|
||||
"maskSavedAssets": "Masker bewaard in Assets",
|
||||
"problemDownloadingCanvas": "Fout bij downloaden van canvas",
|
||||
"problemMergingCanvas": "Fout bij samenvoegen canvas",
|
||||
"setCanvasInitialImage": "Ingesteld als initiële canvasafbeelding",
|
||||
"setCanvasInitialImage": "Initiële canvasafbeelding ingesteld",
|
||||
"imageUploaded": "Afbeelding geüpload",
|
||||
"addedToBoard": "Toegevoegd aan bord",
|
||||
"workflowLoaded": "Werkstroom geladen",
|
||||
"modelAddedSimple": "Model toegevoegd",
|
||||
"modelAddedSimple": "Model toegevoegd aan wachtrij",
|
||||
"problemImportingMaskDesc": "Kan masker niet exporteren",
|
||||
"problemCopyingCanvas": "Fout bij kopiëren canvas",
|
||||
"problemSavingCanvas": "Fout bij bewaren canvas",
|
||||
@@ -459,7 +581,18 @@
|
||||
"maskSentControlnetAssets": "Masker gestuurd naar ControlNet en Assets",
|
||||
"canvasSavedGallery": "Canvas bewaard in galerij",
|
||||
"imageUploadFailed": "Fout bij uploaden afbeelding",
|
||||
"problemImportingMask": "Fout bij importeren masker"
|
||||
"problemImportingMask": "Fout bij importeren masker",
|
||||
"workflowDeleted": "Werkstroom verwijderd",
|
||||
"invalidUpload": "Ongeldige upload",
|
||||
"uploadInitialImage": "Initiële afbeelding uploaden",
|
||||
"setAsCanvasInitialImage": "Ingesteld als initiële afbeelding voor canvas",
|
||||
"problemRetrievingWorkflow": "Fout bij ophalen van werkstroom",
|
||||
"parameters": "Parameters",
|
||||
"modelImportCanceled": "Importeren model geannuleerd",
|
||||
"problemDeletingWorkflow": "Fout bij verwijderen van werkstroom",
|
||||
"prunedQueue": "Wachtrij gesnoeid",
|
||||
"problemDownloadingImage": "Fout bij downloaden afbeelding",
|
||||
"resetInitialImage": "Initiële afbeelding hersteld"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@@ -533,7 +666,11 @@
|
||||
"showOptionsPanel": "Toon zijscherm",
|
||||
"menu": "Menu",
|
||||
"showGalleryPanel": "Toon deelscherm Galerij",
|
||||
"loadMore": "Laad meer"
|
||||
"loadMore": "Laad meer",
|
||||
"about": "Over",
|
||||
"mode": "Modus",
|
||||
"resetUI": "$t(accessibility.reset) UI",
|
||||
"createIssue": "Maak probleem aan"
|
||||
},
|
||||
"nodes": {
|
||||
"zoomOutNodes": "Uitzoomen",
|
||||
@@ -547,7 +684,7 @@
|
||||
"loadWorkflow": "Laad werkstroom",
|
||||
"downloadWorkflow": "Download JSON van werkstroom",
|
||||
"scheduler": "Planner",
|
||||
"missingTemplate": "Ontbrekende sjabloon",
|
||||
"missingTemplate": "Ongeldig knooppunt: knooppunt {{node}} van het soort {{type}} heeft een ontbrekend sjabloon (niet geïnstalleerd?)",
|
||||
"workflowDescription": "Korte beschrijving",
|
||||
"versionUnknown": " Versie onbekend",
|
||||
"noNodeSelected": "Geen knooppunt gekozen",
|
||||
@@ -563,7 +700,7 @@
|
||||
"integer": "Geheel getal",
|
||||
"nodeTemplate": "Sjabloon knooppunt",
|
||||
"nodeOpacity": "Dekking knooppunt",
|
||||
"unableToLoadWorkflow": "Kan werkstroom niet valideren",
|
||||
"unableToLoadWorkflow": "Fout bij laden werkstroom",
|
||||
"snapToGrid": "Lijn uit op raster",
|
||||
"noFieldsLinearview": "Geen velden toegevoegd aan lineaire weergave",
|
||||
"nodeSearch": "Zoek naar knooppunten",
|
||||
@@ -614,11 +751,56 @@
|
||||
"unknownField": "Onbekend veld",
|
||||
"colorCodeEdges": "Kleurgecodeerde randen",
|
||||
"unknownNode": "Onbekend knooppunt",
|
||||
"mismatchedVersion": "Heeft niet-overeenkomende versie",
|
||||
"mismatchedVersion": "Ongeldig knooppunt: knooppunt {{node}} van het soort {{type}} heeft een niet-overeenkomende versie (probeer het bij te werken?)",
|
||||
"addNodeToolTip": "Voeg knooppunt toe (Shift+A, spatie)",
|
||||
"loadingNodes": "Bezig met laden van knooppunten...",
|
||||
"snapToGridHelp": "Lijn knooppunten uit op raster bij verplaatsing",
|
||||
"workflowSettings": "Instellingen werkstroomeditor"
|
||||
"workflowSettings": "Instellingen werkstroomeditor",
|
||||
"addLinearView": "Voeg toe aan lineaire weergave",
|
||||
"nodePack": "Knooppuntpakket",
|
||||
"unknownInput": "Onbekende invoer: {{name}}",
|
||||
"sourceNodeFieldDoesNotExist": "Ongeldige rand: bron-/uitvoerveld {{node}}.{{field}} bestaat niet",
|
||||
"collectionFieldType": "Verzameling {{name}}",
|
||||
"deletedInvalidEdge": "Ongeldige hoek {{source}} -> {{target}} verwijderd",
|
||||
"graph": "Grafiek",
|
||||
"targetNodeDoesNotExist": "Ongeldige rand: doel-/invoerknooppunt {{node}} bestaat niet",
|
||||
"resetToDefaultValue": "Herstel naar standaardwaarden",
|
||||
"editMode": "Bewerk in Werkstroom-editor",
|
||||
"showEdgeLabels": "Toon randlabels",
|
||||
"showEdgeLabelsHelp": "Toon labels aan randen, waarmee de verbonden knooppunten mee worden aangegeven",
|
||||
"clearWorkflowDesc2": "Je huidige werkstroom heeft niet-bewaarde wijzigingen.",
|
||||
"unableToParseFieldType": "fout bij bepalen soort veld",
|
||||
"sourceNodeDoesNotExist": "Ongeldige rand: bron-/uitvoerknooppunt {{node}} bestaat niet",
|
||||
"unsupportedArrayItemType": "niet-ondersteunde soort van het array-onderdeel \"{{type}}\"",
|
||||
"targetNodeFieldDoesNotExist": "Ongeldige rand: doel-/invoerveld {{node}}.{{field}} bestaat niet",
|
||||
"reorderLinearView": "Herorden lineaire weergave",
|
||||
"newWorkflowDesc": "Een nieuwe werkstroom aanmaken?",
|
||||
"collectionOrScalarFieldType": "Verzameling|scalair {{name}}",
|
||||
"newWorkflow": "Nieuwe werkstroom",
|
||||
"unknownErrorValidatingWorkflow": "Onbekende fout bij valideren werkstroom",
|
||||
"unsupportedAnyOfLength": "te veel union-leden ({{count}})",
|
||||
"unknownOutput": "Onbekende uitvoer: {{name}}",
|
||||
"viewMode": "Gebruik in lineaire weergave",
|
||||
"unableToExtractSchemaNameFromRef": "fout bij het extraheren van de schemanaam via de ref",
|
||||
"unsupportedMismatchedUnion": "niet-overeenkomende soort CollectionOrScalar met basissoorten {{firstType}} en {{secondType}}",
|
||||
"unknownNodeType": "Onbekend soort knooppunt",
|
||||
"edit": "Bewerk",
|
||||
"updateAllNodes": "Werk knooppunten bij",
|
||||
"allNodesUpdated": "Alle knooppunten bijgewerkt",
|
||||
"nodeVersion": "Knooppuntversie",
|
||||
"newWorkflowDesc2": "Je huidige werkstroom heeft niet-bewaarde wijzigingen.",
|
||||
"clearWorkflow": "Maak werkstroom leeg",
|
||||
"clearWorkflowDesc": "Deze werkstroom leegmaken en met een nieuwe beginnen?",
|
||||
"inputFieldTypeParseError": "Fout bij bepalen van het soort invoerveld {{node}}.{{field}} ({{message}})",
|
||||
"outputFieldTypeParseError": "Fout bij het bepalen van het soort uitvoerveld {{node}}.{{field}} ({{message}})",
|
||||
"unableToExtractEnumOptions": "fout bij extraheren enumeratie-opties",
|
||||
"unknownFieldType": "Soort $t(nodes.unknownField): {{type}}",
|
||||
"unableToGetWorkflowVersion": "Fout bij ophalen schemaversie van werkstroom",
|
||||
"betaDesc": "Deze uitvoering is in bèta. Totdat deze stabiel is kunnen er wijzigingen voorkomen gedurende app-updates die zaken kapotmaken. We zijn van plan om deze uitvoering op lange termijn te gaan ondersteunen.",
|
||||
"prototypeDesc": "Deze uitvoering is een prototype. Er kunnen wijzigingen voorkomen gedurende app-updates die zaken kapotmaken. Deze kunnen op een willekeurig moment verwijderd worden.",
|
||||
"noFieldsViewMode": "Deze werkstroom heeft geen geselecteerde velden om te tonen. Bekijk de volledige werkstroom om de waarden te configureren.",
|
||||
"unableToUpdateNodes_one": "Fout bij bijwerken van {{count}} knooppunt",
|
||||
"unableToUpdateNodes_other": "Fout bij bijwerken van {{count}} knooppunten"
|
||||
},
|
||||
"controlnet": {
|
||||
"amult": "a_mult",
|
||||
@@ -691,9 +873,28 @@
|
||||
"canny": "Canny",
|
||||
"depthZoeDescription": "Genereer diepteblad via Zoe",
|
||||
"hedDescription": "Herkenning van holistisch-geneste randen",
|
||||
"setControlImageDimensions": "Stel afmetingen controle-afbeelding in op B/H",
|
||||
"setControlImageDimensions": "Kopieer grootte naar B/H (optimaliseer voor model)",
|
||||
"scribble": "Krabbel",
|
||||
"maxFaces": "Max. gezichten"
|
||||
"maxFaces": "Max. gezichten",
|
||||
"dwOpenpose": "DW Openpose",
|
||||
"depthAnything": "Depth Anything",
|
||||
"base": "Basis",
|
||||
"hands": "Handen",
|
||||
"selectCLIPVisionModel": "Selecteer een CLIP Vision-model",
|
||||
"modelSize": "Modelgrootte",
|
||||
"small": "Klein",
|
||||
"large": "Groot",
|
||||
"resizeSimple": "Wijzig grootte (eenvoudig)",
|
||||
"beginEndStepPercentShort": "Begin-/eind-%",
|
||||
"depthAnythingDescription": "Genereren dieptekaart d.m.v. de techniek Depth Anything",
|
||||
"face": "Gezicht",
|
||||
"body": "Lichaam",
|
||||
"dwOpenposeDescription": "Schatting menselijke pose d.m.v. DW Openpose",
|
||||
"ipAdapterMethod": "Methode",
|
||||
"full": "Volledig",
|
||||
"style": "Alleen stijl",
|
||||
"composition": "Alleen samenstelling",
|
||||
"setControlImageDimensionsForce": "Kopieer grootte naar B/H (negeer model)"
|
||||
},
|
||||
"dynamicPrompts": {
|
||||
"seedBehaviour": {
|
||||
@@ -706,7 +907,10 @@
|
||||
"maxPrompts": "Max. prompts",
|
||||
"promptsWithCount_one": "{{count}} prompt",
|
||||
"promptsWithCount_other": "{{count}} prompts",
|
||||
"dynamicPrompts": "Dynamische prompts"
|
||||
"dynamicPrompts": "Dynamische prompts",
|
||||
"showDynamicPrompts": "Toon dynamische prompts",
|
||||
"loading": "Genereren van dynamische prompts...",
|
||||
"promptsPreview": "Voorvertoning prompts"
|
||||
},
|
||||
"popovers": {
|
||||
"noiseUseCPU": {
|
||||
@@ -719,7 +923,7 @@
|
||||
},
|
||||
"paramScheduler": {
|
||||
"paragraphs": [
|
||||
"De planner bepaalt hoe ruis per iteratie wordt toegevoegd aan een afbeelding of hoe een monster wordt bijgewerkt op basis van de uitvoer van een model."
|
||||
"De planner gebruikt gedurende het genereringsproces."
|
||||
],
|
||||
"heading": "Planner"
|
||||
},
|
||||
@@ -806,8 +1010,8 @@
|
||||
},
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"Kies hoeveel CLIP-modellagen je wilt overslaan.",
|
||||
"Bepaalde modellen werken beter met bepaalde Overslaan CLIP-instellingen."
|
||||
"Aantal over te slaan CLIP-modellagen.",
|
||||
"Bepaalde modellen zijn beter geschikt met bepaalde Overslaan CLIP-instellingen."
|
||||
],
|
||||
"heading": "Overslaan CLIP"
|
||||
},
|
||||
@@ -991,17 +1195,26 @@
|
||||
"denoisingStrength": "Sterkte ontruising",
|
||||
"refinermodel": "Verfijningsmodel",
|
||||
"posAestheticScore": "Positieve esthetische score",
|
||||
"concatPromptStyle": "Plak prompt- en stijltekst aan elkaar",
|
||||
"concatPromptStyle": "Koppelen van prompt en stijl",
|
||||
"loading": "Bezig met laden...",
|
||||
"steps": "Stappen",
|
||||
"posStylePrompt": "Positieve-stijlprompt"
|
||||
"posStylePrompt": "Positieve-stijlprompt",
|
||||
"freePromptStyle": "Handmatige stijlprompt",
|
||||
"refinerSteps": "Aantal stappen verfijner"
|
||||
},
|
||||
"models": {
|
||||
"noMatchingModels": "Geen overeenkomend modellen",
|
||||
"loading": "bezig met laden",
|
||||
"noMatchingLoRAs": "Geen overeenkomende LoRA's",
|
||||
"noModelsAvailable": "Geen modellen beschikbaar",
|
||||
"selectModel": "Kies een model"
|
||||
"selectModel": "Kies een model",
|
||||
"noLoRAsInstalled": "Geen LoRA's geïnstalleerd",
|
||||
"noRefinerModelsInstalled": "Geen SDXL-verfijningsmodellen geïnstalleerd",
|
||||
"defaultVAE": "Standaard-VAE",
|
||||
"lora": "LoRA",
|
||||
"esrganModel": "ESRGAN-model",
|
||||
"addLora": "Voeg LoRA toe",
|
||||
"concepts": "Concepten"
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Voeg automatisch bord toe",
|
||||
@@ -1019,7 +1232,13 @@
|
||||
"downloadBoard": "Download bord",
|
||||
"changeBoard": "Wijzig bord",
|
||||
"loading": "Bezig met laden...",
|
||||
"clearSearch": "Maak zoekopdracht leeg"
|
||||
"clearSearch": "Maak zoekopdracht leeg",
|
||||
"deleteBoard": "Verwijder bord",
|
||||
"deleteBoardAndImages": "Verwijder bord en afbeeldingen",
|
||||
"deleteBoardOnly": "Verwijder alleen bord",
|
||||
"deletedBoardsCannotbeRestored": "Verwijderde borden kunnen niet worden hersteld",
|
||||
"movingImagesToBoard_one": "Verplaatsen van {{count}} afbeelding naar bord:",
|
||||
"movingImagesToBoard_other": "Verplaatsen van {{count}} afbeeldingen naar bord:"
|
||||
},
|
||||
"invocationCache": {
|
||||
"disable": "Schakel uit",
|
||||
@@ -1036,5 +1255,39 @@
|
||||
"clear": "Wis",
|
||||
"maxCacheSize": "Max. grootte cache",
|
||||
"cacheSize": "Grootte cache"
|
||||
},
|
||||
"accordions": {
|
||||
"generation": {
|
||||
"title": "Genereren"
|
||||
},
|
||||
"image": {
|
||||
"title": "Afbeelding"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Geavanceerd",
|
||||
"options": "$t(accordions.advanced.title) Opties"
|
||||
},
|
||||
"control": {
|
||||
"title": "Besturing"
|
||||
},
|
||||
"compositing": {
|
||||
"title": "Samenstellen",
|
||||
"coherenceTab": "Coherentiefase",
|
||||
"infillTab": "Invullen"
|
||||
}
|
||||
},
|
||||
"hrf": {
|
||||
"upscaleMethod": "Opschaalmethode",
|
||||
"metadata": {
|
||||
"strength": "Sterkte oplossing voor hoge resolutie",
|
||||
"method": "Methode oplossing voor hoge resolutie",
|
||||
"enabled": "Oplossing voor hoge resolutie ingeschakeld"
|
||||
},
|
||||
"hrf": "Oplossing voor hoge resolutie",
|
||||
"enableHrf": "Schakel oplossing in voor hoge resolutie"
|
||||
},
|
||||
"prompt": {
|
||||
"addPromptTrigger": "Voeg prompttrigger toe",
|
||||
"compatibleEmbeddings": "Compatibele embeddings"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1594,7 +1594,6 @@
|
||||
"deleteAll": "Удалить всё",
|
||||
"addLayer": "Добавить слой",
|
||||
"moveToFront": "На передний план",
|
||||
"toggleVisibility": "Переключить видимость слоя",
|
||||
"addPositivePrompt": "Добавить $t(common.positivePrompt)",
|
||||
"addIPAdapter": "Добавить $t(common.ipAdapter)",
|
||||
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
||||
|
||||
@@ -25,7 +25,6 @@ import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
|
||||
|
||||
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
|
||||
import PreselectedImage from './PreselectedImage';
|
||||
import Toaster from './Toaster';
|
||||
|
||||
const DEFAULT_CONFIG = {};
|
||||
|
||||
@@ -96,7 +95,6 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
||||
<DeleteImageModal />
|
||||
<ChangeBoardModal />
|
||||
<DynamicPromptsModal />
|
||||
<Toaster />
|
||||
<PreselectedImage selectedImage={selectedImage} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Button, Flex, Heading, Link, Text, useToast } from '@invoke-ai/ui-library';
|
||||
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import newGithubIssueUrl from 'new-github-issue-url';
|
||||
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiArrowCounterClockwiseBold, PiArrowSquareOutBold, PiCopyBold } from 'react-icons/pi';
|
||||
@@ -11,31 +14,39 @@ type Props = {
|
||||
};
|
||||
|
||||
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
const toast = useToast();
|
||||
const { t } = useTranslation();
|
||||
const isLocal = useAppSelector((s) => s.config.isLocal);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
const text = JSON.stringify(serializeError(error), null, 2);
|
||||
navigator.clipboard.writeText(`\`\`\`\n${text}\n\`\`\``);
|
||||
toast({
|
||||
title: 'Error Copied',
|
||||
id: 'ERROR_COPIED',
|
||||
title: t('toast.errorCopied'),
|
||||
});
|
||||
}, [error, toast]);
|
||||
}, [error, t]);
|
||||
|
||||
const url = useMemo(
|
||||
() =>
|
||||
newGithubIssueUrl({
|
||||
const url = useMemo(() => {
|
||||
if (isLocal) {
|
||||
return newGithubIssueUrl({
|
||||
user: 'invoke-ai',
|
||||
repo: 'InvokeAI',
|
||||
template: 'BUG_REPORT.yml',
|
||||
title: `[bug]: ${error.name}: ${error.message}`,
|
||||
}),
|
||||
[error.message, error.name]
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return 'https://support.invoke.ai/support/tickets/new';
|
||||
}
|
||||
}, [error.message, error.name, isLocal]);
|
||||
|
||||
return (
|
||||
<Flex layerStyle="body" w="100vw" h="100vh" alignItems="center" justifyContent="center" p={4}>
|
||||
<Flex layerStyle="first" flexDir="column" borderRadius="base" justifyContent="center" gap={8} p={16}>
|
||||
<Heading>{t('common.somethingWentWrong')}</Heading>
|
||||
<Flex alignItems="center" gap="2">
|
||||
<Image src={InvokeLogoYellow} alt="invoke-logo" w="24px" h="24px" minW="24px" minH="24px" userSelect="none" />
|
||||
<Heading fontSize="2xl">{t('common.somethingWentWrong')}</Heading>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
layerStyle="second"
|
||||
px={8}
|
||||
@@ -57,7 +68,9 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||
{t('common.copyError')}
|
||||
</Button>
|
||||
<Link href={url} isExternal>
|
||||
<Button leftIcon={<PiArrowSquareOutBold />}>{t('accessibility.createIssue')}</Button>
|
||||
<Button leftIcon={<PiArrowSquareOutBold />}>
|
||||
{isLocal ? t('accessibility.createIssue') : t('accessibility.submitSupportTicket')}
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
|
||||
import type { MakeToastArg } from 'features/system/util/makeToast';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { memo, useCallback, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Logical component. Watches the toast queue and makes toasts when the queue is not empty.
|
||||
* @returns null
|
||||
*/
|
||||
const Toaster = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const toastQueue = useAppSelector((s) => s.system.toastQueue);
|
||||
const toast = useToast();
|
||||
useEffect(() => {
|
||||
toastQueue.forEach((t) => {
|
||||
toast(t);
|
||||
});
|
||||
toastQueue.length > 0 && dispatch(clearToastQueue());
|
||||
}, [dispatch, toast, toastQueue]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a function that can be used to make a toast.
|
||||
* @example
|
||||
* const toaster = useAppToaster();
|
||||
* toaster('Hello world!');
|
||||
* toaster({ title: 'Hello world!', status: 'success' });
|
||||
* @returns A function that can be used to make a toast.
|
||||
* @see makeToast
|
||||
* @see MakeToastArg
|
||||
* @see UseToastOptions
|
||||
*/
|
||||
export const useAppToaster = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const toaster = useCallback((arg: MakeToastArg) => dispatch(addToast(makeToast(arg))), [dispatch]);
|
||||
|
||||
return toaster;
|
||||
};
|
||||
|
||||
export default memo(Toaster);
|
||||
@@ -41,12 +41,10 @@ import { addGeneratorProgressEventListener } from 'app/store/middleware/listener
|
||||
import { addGraphExecutionStateCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGraphExecutionStateComplete';
|
||||
import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete';
|
||||
import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError';
|
||||
import { addInvocationRetrievalErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationRetrievalError';
|
||||
import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted';
|
||||
import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall';
|
||||
import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad';
|
||||
import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged';
|
||||
import { addSessionRetrievalErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketSessionRetrievalError';
|
||||
import { addSocketSubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketSubscribed';
|
||||
import { addSocketUnsubscribedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketUnsubscribed';
|
||||
import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved';
|
||||
@@ -114,8 +112,6 @@ addSocketSubscribedEventListener(startAppListening);
|
||||
addSocketUnsubscribedEventListener(startAppListening);
|
||||
addModelLoadEventListener(startAppListening);
|
||||
addModelInstallEventListener(startAppListening);
|
||||
addSessionRetrievalErrorEventListener(startAppListening);
|
||||
addInvocationRetrievalErrorEventListener(startAppListening);
|
||||
addSocketQueueItemStatusChangedEventListener(startAppListening);
|
||||
addBulkDownloadListeners(startAppListening);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
resetCanvas,
|
||||
setInitialCanvasImage,
|
||||
} from 'features/canvas/store/canvasSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
|
||||
@@ -30,22 +30,20 @@ export const addCommitStagingAreaImageListener = (startAppListening: AppStartLis
|
||||
req.reset();
|
||||
if (canceled > 0) {
|
||||
log.debug(`Canceled ${canceled} canvas batches`);
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelBatchSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_SUCCEEDED',
|
||||
title: t('queue.cancelBatchSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
}
|
||||
dispatch(canvasBatchIdsReset());
|
||||
} catch {
|
||||
log.error('Failed to cancel canvas batches');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelBatchFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_FAILED',
|
||||
title: t('queue.cancelBatchFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { toast } from 'common/util/toast';
|
||||
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { truncate, upperFirst } from 'lodash-es';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
@@ -16,18 +16,15 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
||||
const arg = action.meta.arg.originalArgs;
|
||||
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
|
||||
|
||||
if (!toast.isActive('batch-queued')) {
|
||||
toast({
|
||||
id: 'batch-queued',
|
||||
title: t('queue.batchQueued'),
|
||||
description: t('queue.batchQueuedDesc', {
|
||||
count: response.enqueued,
|
||||
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
||||
}),
|
||||
duration: 1000,
|
||||
status: 'success',
|
||||
});
|
||||
}
|
||||
toast({
|
||||
id: 'QUEUE_BATCH_SUCCEEDED',
|
||||
title: t('queue.batchQueued'),
|
||||
status: 'success',
|
||||
description: t('queue.batchQueuedDesc', {
|
||||
count: response.enqueued,
|
||||
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,9 +37,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
||||
|
||||
if (!response) {
|
||||
toast({
|
||||
id: 'QUEUE_BATCH_FAILED',
|
||||
title: t('queue.batchFailedToQueue'),
|
||||
status: 'error',
|
||||
description: 'Unknown Error',
|
||||
description: t('common.unknownError'),
|
||||
});
|
||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||
return;
|
||||
@@ -52,7 +50,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
||||
if (result.success) {
|
||||
result.data.data.detail.map((e) => {
|
||||
toast({
|
||||
id: 'batch-failed-to-queue',
|
||||
id: 'QUEUE_BATCH_FAILED',
|
||||
title: truncate(upperFirst(e.msg), { length: 128 }),
|
||||
status: 'error',
|
||||
description: truncate(
|
||||
@@ -64,9 +62,10 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
||||
});
|
||||
} else if (response.status !== 403) {
|
||||
toast({
|
||||
id: 'QUEUE_BATCH_FAILED',
|
||||
title: t('queue.batchFailedToQueue'),
|
||||
description: t('common.unknownError'),
|
||||
status: 'error',
|
||||
description: t('common.unknownError'),
|
||||
});
|
||||
}
|
||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { UseToastOptions } from '@invoke-ai/ui-library';
|
||||
import { ExternalLink } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { toast } from 'common/util/toast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import {
|
||||
@@ -28,7 +27,6 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
||||
// Show the response message if it exists, otherwise show the default message
|
||||
description: action.payload.response || t('gallery.bulkDownloadRequestedDesc'),
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -40,9 +38,9 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
||||
|
||||
// There isn't any toast to update if we get this event.
|
||||
toast({
|
||||
id: 'BULK_DOWNLOAD_REQUEST_FAILED',
|
||||
title: t('gallery.bulkDownloadRequestFailed'),
|
||||
status: 'success',
|
||||
isClosable: true,
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -65,7 +63,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
||||
// TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first
|
||||
const url = `/api/v1/images/download/${bulk_download_item_name}`;
|
||||
|
||||
const toastOptions: UseToastOptions = {
|
||||
toast({
|
||||
id: bulk_download_item_name,
|
||||
title: t('gallery.bulkDownloadReady', 'Download ready'),
|
||||
status: 'success',
|
||||
@@ -77,14 +75,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
||||
/>
|
||||
),
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
};
|
||||
|
||||
if (toast.isActive(bulk_download_item_name)) {
|
||||
toast.update(bulk_download_item_name, toastOptions);
|
||||
} else {
|
||||
toast(toastOptions);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -95,20 +86,13 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
||||
|
||||
const { bulk_download_item_name } = action.payload.data;
|
||||
|
||||
const toastOptions: UseToastOptions = {
|
||||
toast({
|
||||
id: bulk_download_item_name,
|
||||
title: t('gallery.bulkDownloadFailed'),
|
||||
status: 'error',
|
||||
description: action.payload.data.error,
|
||||
duration: null,
|
||||
isClosable: true,
|
||||
};
|
||||
|
||||
if (toast.isActive(bulk_download_item_name)) {
|
||||
toast.update(bulk_download_item_name, toastOptions);
|
||||
} else {
|
||||
toast(toastOptions);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,14 +2,14 @@ import { $logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
|
||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: canvasCopiedToClipboard,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
effect: async (action, { getState }) => {
|
||||
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||
const state = getState();
|
||||
|
||||
@@ -19,22 +19,20 @@ export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartLi
|
||||
copyBlobToClipboard(blob);
|
||||
} catch (err) {
|
||||
moduleLog.error(String(err));
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemCopyingCanvas'),
|
||||
description: t('toast.problemCopyingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANVAS_COPY_FAILED',
|
||||
title: t('toast.problemCopyingCanvas'),
|
||||
description: t('toast.problemCopyingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.canvasCopiedClipboard'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANVAS_COPY_SUCCEEDED',
|
||||
title: t('toast.canvasCopiedClipboard'),
|
||||
status: 'success',
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,13 +3,13 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
|
||||
import { downloadBlob } from 'features/canvas/util/downloadBlob';
|
||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: canvasDownloadedAsImage,
|
||||
effect: async (action, { dispatch, getState }) => {
|
||||
effect: async (action, { getState }) => {
|
||||
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
|
||||
const state = getState();
|
||||
|
||||
@@ -18,18 +18,17 @@ export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartLi
|
||||
blob = await getBaseLayerBlob(state);
|
||||
} catch (err) {
|
||||
moduleLog.error(String(err));
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemDownloadingCanvas'),
|
||||
description: t('toast.problemDownloadingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANVAS_DOWNLOAD_FAILED',
|
||||
title: t('toast.problemDownloadingCanvas'),
|
||||
description: t('toast.problemDownloadingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
downloadBlob(blob, 'canvas.png');
|
||||
dispatch(addToast({ title: t('toast.canvasDownloaded'), status: 'success' }));
|
||||
toast({ id: 'CANVAS_DOWNLOAD_SUCCEEDED', title: t('toast.canvasDownloaded'), status: 'success' });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
|
||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -20,13 +20,12 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
|
||||
blob = await getBaseLayerBlob(state, true);
|
||||
} catch (err) {
|
||||
log.error(String(err));
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemSavingCanvas'),
|
||||
description: t('toast.problemSavingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'PROBLEM_SAVING_CANVAS',
|
||||
title: t('toast.problemSavingCanvas'),
|
||||
description: t('toast.problemSavingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ export const addCanvasImageToControlNetListener = (startAppListening: AppStartLi
|
||||
crop_visible: false,
|
||||
postUploadAction: {
|
||||
type: 'TOAST',
|
||||
toastOptions: { title: t('toast.canvasSentControlnetAssets') },
|
||||
title: t('toast.canvasSentControlnetAssets'),
|
||||
},
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
|
||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -29,13 +29,12 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL
|
||||
|
||||
if (!maskBlob) {
|
||||
log.error('Problem getting mask layer blob');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemSavingMask'),
|
||||
description: t('toast.problemSavingMaskDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'PROBLEM_SAVING_MASK',
|
||||
title: t('toast.problemSavingMask'),
|
||||
description: t('toast.problemSavingMaskDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartL
|
||||
crop_visible: true,
|
||||
postUploadAction: {
|
||||
type: 'TOAST',
|
||||
toastOptions: { title: t('toast.maskSavedAssets') },
|
||||
title: t('toast.maskSavedAssets'),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { canvasMaskToControlAdapter } from 'features/canvas/store/actions';
|
||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -30,13 +30,12 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
|
||||
|
||||
if (!maskBlob) {
|
||||
log.error('Problem getting mask layer blob');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemImportingMask'),
|
||||
description: t('toast.problemImportingMaskDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'PROBLEM_IMPORTING_MASK',
|
||||
title: t('toast.problemImportingMask'),
|
||||
description: t('toast.problemImportingMaskDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,7 +52,7 @@ export const addCanvasMaskToControlNetListener = (startAppListening: AppStartLis
|
||||
crop_visible: false,
|
||||
postUploadAction: {
|
||||
type: 'TOAST',
|
||||
toastOptions: { title: t('toast.maskSentControlnetAssets') },
|
||||
title: t('toast.maskSentControlnetAssets'),
|
||||
},
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { canvasMerged } from 'features/canvas/store/actions';
|
||||
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
|
||||
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -17,13 +17,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
|
||||
|
||||
if (!blob) {
|
||||
moduleLog.error('Problem getting base layer blob');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemMergingCanvas'),
|
||||
description: t('toast.problemMergingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'PROBLEM_MERGING_CANVAS',
|
||||
title: t('toast.problemMergingCanvas'),
|
||||
description: t('toast.problemMergingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,13 +30,12 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
|
||||
|
||||
if (!canvasBaseLayer) {
|
||||
moduleLog.error('Problem getting canvas base layer');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemMergingCanvas'),
|
||||
description: t('toast.problemMergingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'PROBLEM_MERGING_CANVAS',
|
||||
title: t('toast.problemMergingCanvas'),
|
||||
description: t('toast.problemMergingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -54,7 +52,7 @@ export const addCanvasMergedListener = (startAppListening: AppStartListening) =>
|
||||
is_intermediate: true,
|
||||
postUploadAction: {
|
||||
type: 'TOAST',
|
||||
toastOptions: { title: t('toast.canvasMerged') },
|
||||
title: t('toast.canvasMerged'),
|
||||
},
|
||||
})
|
||||
).unwrap();
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -19,13 +19,12 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe
|
||||
blob = await getBaseLayerBlob(state);
|
||||
} catch (err) {
|
||||
log.error(String(err));
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.problemSavingCanvas'),
|
||||
description: t('toast.problemSavingCanvasDesc'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANVAS_SAVE_FAILED',
|
||||
title: t('toast.problemSavingCanvas'),
|
||||
description: t('toast.problemSavingCanvasDesc'),
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListe
|
||||
crop_visible: true,
|
||||
postUploadAction: {
|
||||
type: 'TOAST',
|
||||
toastOptions: { title: t('toast.canvasSavedGallery') },
|
||||
title: t('toast.canvasSavedGallery'),
|
||||
},
|
||||
metadata: {
|
||||
_canvas_objects: parseify(state.canvas.layerState.objects),
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
||||
import { isImageOutput } from 'features/nodes/types/common';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { getImageDTO } from 'services/api/endpoints/images';
|
||||
@@ -174,12 +174,11 @@ export const addControlAdapterPreprocessor = (startAppListening: AppStartListeni
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'GRAPH_QUEUE_FAILED',
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
req.reset();
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
||||
import { isImageOutput } from 'features/nodes/types/common';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
@@ -108,12 +108,11 @@ export const addControlNetImageProcessedListener = (startAppListening: AppStartL
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'GRAPH_QUEUE_FAILED',
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { UseToastOptions } from '@invoke-ai/ui-library';
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
} from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { omit } from 'lodash-es';
|
||||
import { boardsApi } from 'services/api/endpoints/boards';
|
||||
@@ -42,16 +41,17 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
return;
|
||||
}
|
||||
|
||||
const DEFAULT_UPLOADED_TOAST: UseToastOptions = {
|
||||
const DEFAULT_UPLOADED_TOAST = {
|
||||
id: 'IMAGE_UPLOADED',
|
||||
title: t('toast.imageUploaded'),
|
||||
status: 'success',
|
||||
};
|
||||
} as const;
|
||||
|
||||
// default action - just upload and alert user
|
||||
if (postUploadAction?.type === 'TOAST') {
|
||||
const { toastOptions } = postUploadAction;
|
||||
if (!autoAddBoardId || autoAddBoardId === 'none') {
|
||||
dispatch(addToast({ ...DEFAULT_UPLOADED_TOAST, ...toastOptions }));
|
||||
const title = postUploadAction.title || DEFAULT_UPLOADED_TOAST.title;
|
||||
toast({ ...DEFAULT_UPLOADED_TOAST, title });
|
||||
} else {
|
||||
// Add this image to the board
|
||||
dispatch(
|
||||
@@ -70,24 +70,20 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
? `${t('toast.addedToBoard')} ${board.board_name}`
|
||||
: `${t('toast.addedToBoard')} ${autoAddBoardId}`;
|
||||
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description,
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
||||
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setAsCanvasInitialImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setAsCanvasInitialImage'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,68 +101,56 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
controlImage: imageDTO.image_name,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
});
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
});
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') {
|
||||
const { layerId, ipAdapterId } = postUploadAction;
|
||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
});
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
|
||||
const { layerId } = postUploadAction;
|
||||
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: t('toast.setControlImage'),
|
||||
});
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||
const { nodeId, fieldName } = postUploadAction;
|
||||
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
||||
dispatch(
|
||||
addToast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: `${t('toast.setNodeField')} ${fieldName}`,
|
||||
})
|
||||
);
|
||||
toast({
|
||||
...DEFAULT_UPLOADED_TOAST,
|
||||
description: `${t('toast.setNodeField')} ${fieldName}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
@@ -174,7 +158,7 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
|
||||
startAppListening({
|
||||
matcher: imagesApi.endpoints.uploadImage.matchRejected,
|
||||
effect: (action, { dispatch }) => {
|
||||
effect: (action) => {
|
||||
const log = logger('images');
|
||||
const sanitizedData = {
|
||||
arg: {
|
||||
@@ -183,13 +167,11 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
||||
},
|
||||
};
|
||||
log.error({ ...sanitizedData }, 'Image upload failed');
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.imageUploadFailed'),
|
||||
description: action.error.message,
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
title: t('toast.imageUploadFailed'),
|
||||
description: action.error.message,
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,8 +8,7 @@ import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { forEach } from 'lodash-es';
|
||||
|
||||
@@ -60,16 +59,14 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
||||
});
|
||||
|
||||
if (modelsCleared > 0) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.baseModelChangedCleared', {
|
||||
count: modelsCleared,
|
||||
}),
|
||||
status: 'warning',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'BASE_MODEL_CHANGED',
|
||||
title: t('toast.baseModelChanged'),
|
||||
description: t('toast.baseModelChangedCleared', {
|
||||
count: modelsCleared,
|
||||
}),
|
||||
status: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ import {
|
||||
isParameterWidth,
|
||||
zParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||
import { isNonRefinerMainModelConfig } from 'services/api/types';
|
||||
@@ -109,7 +108,7 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(addToast(makeToast({ title: t('toast.parameterSet', { parameter: 'Default settings' }) })));
|
||||
toast({ id: 'PARAMETER_SET', title: t('toast.parameterSet', { parameter: 'Default settings' }) });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,16 +3,43 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import ToastWithSessionRefDescription from 'features/toast/ToastWithSessionRefDescription';
|
||||
import { t } from 'i18next';
|
||||
import { startCase } from 'lodash-es';
|
||||
import { socketInvocationError } from 'services/events/actions';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
const getTitle = (errorType: string) => {
|
||||
if (errorType === 'OutOfMemoryError') {
|
||||
return t('toast.outOfMemoryError');
|
||||
}
|
||||
return t('toast.serverError');
|
||||
};
|
||||
|
||||
const getDescription = (errorType: string, sessionId: string, isLocal?: boolean) => {
|
||||
if (!isLocal) {
|
||||
if (errorType === 'OutOfMemoryError') {
|
||||
return ToastWithSessionRefDescription({
|
||||
message: t('toast.outOfMemoryDescription'),
|
||||
sessionId,
|
||||
});
|
||||
}
|
||||
return ToastWithSessionRefDescription({
|
||||
message: errorType,
|
||||
sessionId,
|
||||
});
|
||||
}
|
||||
return errorType;
|
||||
};
|
||||
|
||||
export const addInvocationErrorEventListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationError,
|
||||
effect: (action) => {
|
||||
effect: (action, { getState }) => {
|
||||
log.error(action.payload, `Invocation error (${action.payload.data.node.type})`);
|
||||
const { source_node_id } = action.payload.data;
|
||||
const { source_node_id, error_type, graph_execution_state_id } = action.payload.data;
|
||||
const nes = deepClone($nodeExecutionStates.get()[source_node_id]);
|
||||
if (nes) {
|
||||
nes.status = zNodeStatus.enum.FAILED;
|
||||
@@ -21,6 +48,19 @@ export const addInvocationErrorEventListener = (startAppListening: AppStartListe
|
||||
nes.progressImage = null;
|
||||
upsertExecutionState(nes.nodeId, nes);
|
||||
}
|
||||
|
||||
const errorType = startCase(error_type);
|
||||
const sessionId = graph_execution_state_id;
|
||||
const { isLocal } = getState().config;
|
||||
|
||||
toast({
|
||||
id: `INVOCATION_ERROR_${errorType}`,
|
||||
title: getTitle(errorType),
|
||||
status: 'error',
|
||||
duration: null,
|
||||
description: getDescription(errorType, sessionId, isLocal),
|
||||
updateDescription: isLocal ? true : false,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { socketInvocationRetrievalError } from 'services/events/actions';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
export const addInvocationRetrievalErrorEventListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: socketInvocationRetrievalError,
|
||||
effect: (action) => {
|
||||
log.error(action.payload, `Invocation retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { socketSessionRetrievalError } from 'services/events/actions';
|
||||
|
||||
const log = logger('socketio');
|
||||
|
||||
export const addSessionRetrievalErrorEventListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: socketSessionRetrievalError,
|
||||
effect: (action) => {
|
||||
log.error(action.payload, `Session retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
|
||||
@@ -29,15 +29,14 @@ export const addStagingAreaImageSavedListener = (startAppListening: AppStartList
|
||||
})
|
||||
);
|
||||
}
|
||||
dispatch(addToast({ title: t('toast.imageSaved'), status: 'success' }));
|
||||
toast({ id: 'IMAGE_SAVED', title: t('toast.imageSaved'), status: 'success' });
|
||||
} catch (error) {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('toast.imageSavingFailed'),
|
||||
description: (error as Error)?.message,
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'IMAGE_SAVE_FAILED',
|
||||
title: t('toast.imageSavingFailed'),
|
||||
description: (error as Error)?.message,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,8 +5,7 @@ import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
|
||||
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
|
||||
@@ -50,24 +49,18 @@ export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartLi
|
||||
count: unableToUpdateCount,
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.unableToUpdateNodes', {
|
||||
count: unableToUpdateCount,
|
||||
}),
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'UNABLE_TO_UPDATE_NODES',
|
||||
title: t('nodes.unableToUpdateNodes', {
|
||||
count: unableToUpdateCount,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.allNodesUpdated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'ALL_NODES_UPDATED',
|
||||
title: t('nodes.allNodesUpdated'),
|
||||
status: 'success',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph';
|
||||
import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { queueApi } from 'services/api/endpoints/queue';
|
||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||
@@ -29,12 +29,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening
|
||||
{ imageDTO },
|
||||
t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge') // should never coalesce
|
||||
);
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'NOT_ALLOWED_TO_UPSCALE',
|
||||
title: t(detailTKey ?? 'parameters.isAllowedToUpscale.tooLarge'), // should never coalesce
|
||||
status: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -65,12 +64,11 @@ export const addUpscaleRequestedListener = (startAppListening: AppStartListening
|
||||
if (error instanceof Object && 'status' in error && error.status === 403) {
|
||||
return;
|
||||
} else {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'GRAPH_QUEUE_FAILED',
|
||||
title: t('queue.graphFailedToQueue'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,23 +8,23 @@ import type { Templates } from 'features/nodes/store/types';
|
||||
import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error';
|
||||
import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow';
|
||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
|
||||
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
|
||||
import { z } from 'zod';
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
|
||||
const getWorkflow = (data: GraphAndWorkflowResponse, templates: Templates) => {
|
||||
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
|
||||
if (data.workflow) {
|
||||
// Prefer to load the workflow if it's available - it has more information
|
||||
const parsed = JSON.parse(data.workflow);
|
||||
return validateWorkflow(parsed, templates);
|
||||
return await validateWorkflow(parsed, templates, checkImageAccess, checkBoardAccess, checkModelAccess);
|
||||
} else if (data.graph) {
|
||||
// Else we fall back on the graph, using the graphToWorkflow function to convert and do layout
|
||||
const parsed = JSON.parse(data.graph);
|
||||
const workflow = graphToWorkflow(parsed as NonNullableGraph, true);
|
||||
return validateWorkflow(workflow, templates);
|
||||
return await validateWorkflow(workflow, templates, checkImageAccess, checkBoardAccess, checkModelAccess);
|
||||
} else {
|
||||
throw new Error('No workflow or graph provided');
|
||||
}
|
||||
@@ -33,13 +33,13 @@ const getWorkflow = (data: GraphAndWorkflowResponse, templates: Templates) => {
|
||||
export const addWorkflowLoadRequestedListener = (startAppListening: AppStartListening) => {
|
||||
startAppListening({
|
||||
actionCreator: workflowLoadRequested,
|
||||
effect: (action, { dispatch }) => {
|
||||
effect: async (action, { dispatch }) => {
|
||||
const log = logger('nodes');
|
||||
const { data, asCopy } = action.payload;
|
||||
const nodeTemplates = $templates.get();
|
||||
|
||||
try {
|
||||
const { workflow, warnings } = getWorkflow(data, nodeTemplates);
|
||||
const { workflow, warnings } = await getWorkflow(data, nodeTemplates);
|
||||
|
||||
if (asCopy) {
|
||||
// If we're loading a copy, we need to remove the ID so that the backend will create a new workflow
|
||||
@@ -48,23 +48,18 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
||||
|
||||
dispatch(workflowLoaded(workflow));
|
||||
if (!warnings.length) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.workflowLoaded'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'WORKFLOW_LOADED',
|
||||
title: t('toast.workflowLoaded'),
|
||||
status: 'success',
|
||||
});
|
||||
} else {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.loadedWithWarnings'),
|
||||
status: 'warning',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'WORKFLOW_LOADED',
|
||||
title: t('toast.loadedWithWarnings'),
|
||||
status: 'warning',
|
||||
});
|
||||
|
||||
warnings.forEach(({ message, ...rest }) => {
|
||||
log.warn(rest, message);
|
||||
});
|
||||
@@ -77,54 +72,42 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
||||
if (e instanceof WorkflowVersionError) {
|
||||
// The workflow version was not recognized in the valid list of versions
|
||||
log.error({ error: parseify(e) }, e.message);
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: e.message,
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: e.message,
|
||||
});
|
||||
} else if (e instanceof WorkflowMigrationError) {
|
||||
// There was a problem migrating the workflow to the latest version
|
||||
log.error({ error: parseify(e) }, e.message);
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: e.message,
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: e.message,
|
||||
});
|
||||
} else if (e instanceof z.ZodError) {
|
||||
// There was a problem validating the workflow itself
|
||||
const { message } = fromZodError(e, {
|
||||
prefix: t('nodes.workflowValidation'),
|
||||
});
|
||||
log.error({ error: parseify(e) }, message);
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: message,
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: message,
|
||||
});
|
||||
} else {
|
||||
// Some other error occurred
|
||||
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: t('nodes.unknownErrorValidatingWorkflow'),
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||
title: t('nodes.unableToValidateWorkflow'),
|
||||
status: 'error',
|
||||
description: t('nodes.unknownErrorValidatingWorkflow'),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ export type AppConfig = {
|
||||
maxUpscalePixels?: number;
|
||||
metadataFetchDebounce?: number;
|
||||
workflowFetchDebounce?: number;
|
||||
isLocal?: boolean;
|
||||
sd: {
|
||||
defaultModel?: string;
|
||||
disabledControlNetModels: string[];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useImageUrlToBlob } from 'common/hooks/useImageUrlToBlob';
|
||||
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useCopyImageToClipboard = () => {
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
const imageUrlToBlob = useImageUrlToBlob();
|
||||
|
||||
@@ -16,12 +15,11 @@ export const useCopyImageToClipboard = () => {
|
||||
const copyImageToClipboard = useCallback(
|
||||
async (image_url: string) => {
|
||||
if (!isClipboardAPIAvailable) {
|
||||
toaster({
|
||||
toast({
|
||||
id: 'PROBLEM_COPYING_IMAGE',
|
||||
title: t('toast.problemCopyingImage'),
|
||||
description: "Your browser doesn't support the Clipboard API.",
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
try {
|
||||
@@ -33,23 +31,21 @@ export const useCopyImageToClipboard = () => {
|
||||
|
||||
copyBlobToClipboard(blob);
|
||||
|
||||
toaster({
|
||||
toast({
|
||||
id: 'IMAGE_COPIED',
|
||||
title: t('toast.imageCopied'),
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
} catch (err) {
|
||||
toaster({
|
||||
toast({
|
||||
id: 'PROBLEM_COPYING_IMAGE',
|
||||
title: t('toast.problemCopyingImage'),
|
||||
description: String(err),
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
[imageUrlToBlob, isClipboardAPIAvailable, t, toaster]
|
||||
[imageUrlToBlob, isClipboardAPIAvailable, t]
|
||||
);
|
||||
|
||||
return { isClipboardAPIAvailable, copyImageToClipboard };
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { $authToken } from 'app/store/nanostores/authToken';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { imageDownloaded } from 'features/gallery/store/actions';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useDownloadImage = () => {
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const authToken = useStore($authToken);
|
||||
@@ -37,16 +36,15 @@ export const useDownloadImage = () => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
dispatch(imageDownloaded());
|
||||
} catch (err) {
|
||||
toaster({
|
||||
toast({
|
||||
id: 'PROBLEM_DOWNLOADING_IMAGE',
|
||||
title: t('toast.problemDownloadingImage'),
|
||||
description: String(err),
|
||||
status: 'error',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
[t, toaster, dispatch, authToken]
|
||||
[t, dispatch, authToken]
|
||||
);
|
||||
|
||||
return { downloadImage };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { Accept, FileRejection } from 'react-dropzone';
|
||||
@@ -26,7 +26,6 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
|
||||
|
||||
export const useFullscreenDropzone = () => {
|
||||
const { t } = useTranslation();
|
||||
const toaster = useAppToaster();
|
||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||
@@ -37,13 +36,14 @@ export const useFullscreenDropzone = () => {
|
||||
(rejection: FileRejection) => {
|
||||
setIsHandlingUpload(true);
|
||||
|
||||
toaster({
|
||||
toast({
|
||||
id: 'UPLOAD_FAILED',
|
||||
title: t('toast.uploadFailed'),
|
||||
description: rejection.errors.map((error) => error.message).join('\n'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
[t, toaster]
|
||||
[t]
|
||||
);
|
||||
|
||||
const fileAcceptedCallback = useCallback(
|
||||
@@ -62,7 +62,8 @@ export const useFullscreenDropzone = () => {
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: Array<File>, fileRejections: Array<FileRejection>) => {
|
||||
if (fileRejections.length > 1) {
|
||||
toaster({
|
||||
toast({
|
||||
id: 'UPLOAD_FAILED',
|
||||
title: t('toast.uploadFailed'),
|
||||
description: t('toast.uploadFailedInvalidUploadDesc'),
|
||||
status: 'error',
|
||||
@@ -78,7 +79,7 @@ export const useFullscreenDropzone = () => {
|
||||
fileAcceptedCallback(file);
|
||||
});
|
||||
},
|
||||
[t, toaster, fileAcceptedCallback, fileRejectionCallback]
|
||||
[t, fileAcceptedCallback, fileRejectionCallback]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback(() => {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||
|
||||
export const { toast } = createStandaloneToast({
|
||||
theme: theme,
|
||||
defaultOptions: TOAST_OPTIONS.defaultOptions,
|
||||
});
|
||||
@@ -4,7 +4,7 @@ import { CALayerControlAdapterWrapper } from 'features/controlLayers/components/
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectCALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -26,7 +26,7 @@ export const CALayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="control_adapter_layer" />
|
||||
<Spacer />
|
||||
<CALayerOpacity layerId={layerId} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import { InitialImagePreview } from 'features/controlLayers/components/IILayer/I
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import {
|
||||
iiLayerDenoisingStrengthChanged,
|
||||
@@ -66,7 +66,7 @@ export const IILayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={layer.isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="initial_image_layer" />
|
||||
<Spacer />
|
||||
<IILayerOpacity layerId={layerId} />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { IPALayerIPAdapterWrapper } from 'features/controlLayers/components/IPALayer/IPALayerIPAdapterWrapper';
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import { layerSelected, selectIPALayerOrThrow } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
@@ -22,7 +22,7 @@ export const IPALayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? 'base.400' : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="ip_adapter_layer" />
|
||||
<Spacer />
|
||||
<LayerDeleteButton layerId={layerId} />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { stopPropagation } from 'common/util/stopPropagation';
|
||||
import { useLayerIsVisible } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { layerVisibilityToggled } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { useLayerIsEnabled } from 'features/controlLayers/hooks/layerStateHooks';
|
||||
import { layerIsEnabledToggled } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCheckBold } from 'react-icons/pi';
|
||||
@@ -11,21 +11,21 @@ type Props = {
|
||||
layerId: string;
|
||||
};
|
||||
|
||||
export const LayerVisibilityToggle = memo(({ layerId }: Props) => {
|
||||
export const LayerIsEnabledToggle = memo(({ layerId }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isVisible = useLayerIsVisible(layerId);
|
||||
const isEnabled = useLayerIsEnabled(layerId);
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(layerVisibilityToggled(layerId));
|
||||
dispatch(layerIsEnabledToggled(layerId));
|
||||
}, [dispatch, layerId]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
size="sm"
|
||||
aria-label={t('controlLayers.toggleVisibility')}
|
||||
tooltip={t('controlLayers.toggleVisibility')}
|
||||
aria-label={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||
tooltip={t(isEnabled ? 'common.enabled' : 'common.disabled')}
|
||||
variant="outline"
|
||||
icon={isVisible ? <PiCheckBold /> : undefined}
|
||||
icon={isEnabled ? <PiCheckBold /> : undefined}
|
||||
onClick={onClick}
|
||||
colorScheme="base"
|
||||
onDoubleClick={stopPropagation} // double click expands the layer
|
||||
@@ -33,4 +33,4 @@ export const LayerVisibilityToggle = memo(({ layerId }: Props) => {
|
||||
);
|
||||
});
|
||||
|
||||
LayerVisibilityToggle.displayName = 'LayerVisibilityToggle';
|
||||
LayerIsEnabledToggle.displayName = 'LayerVisibilityToggle';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AddPromptButtons } from 'features/controlLayers/components/AddPromptBut
|
||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||
import { LayerTitle } from 'features/controlLayers/components/LayerCommon/LayerTitle';
|
||||
import { LayerVisibilityToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerIsEnabledToggle } from 'features/controlLayers/components/LayerCommon/LayerVisibilityToggle';
|
||||
import { LayerWrapper } from 'features/controlLayers/components/LayerCommon/LayerWrapper';
|
||||
import {
|
||||
isRegionalGuidanceLayer,
|
||||
@@ -55,7 +55,7 @@ export const RGLayer = memo(({ layerId }: Props) => {
|
||||
return (
|
||||
<LayerWrapper onClick={onClick} borderColor={isSelected ? color : 'base.800'}>
|
||||
<Flex gap={3} alignItems="center" p={3} cursor="pointer" onDoubleClick={onToggle}>
|
||||
<LayerVisibilityToggle layerId={layerId} />
|
||||
<LayerIsEnabledToggle layerId={layerId} />
|
||||
<LayerTitle type="regional_guidance_layer" />
|
||||
<Spacer />
|
||||
{autoNegative === 'invert' && (
|
||||
|
||||
@@ -45,7 +45,6 @@ export const RGLayerNegativePrompt = memo(({ layerId }: Props) => {
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
fontSize="sm"
|
||||
spellCheck={false}
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<RGLayerPromptDeleteButton layerId={layerId} polarity="negative" />
|
||||
|
||||
@@ -45,7 +45,6 @@ export const RGLayerPositivePrompt = memo(({ layerId }: Props) => {
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
minH={28}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<RGLayerPromptDeleteButton layerId={layerId} polarity="positive" />
|
||||
|
||||
@@ -39,7 +39,7 @@ export const useLayerNegativePrompt = (layerId: string) => {
|
||||
return prompt;
|
||||
};
|
||||
|
||||
export const useLayerIsVisible = (layerId: string) => {
|
||||
export const useLayerIsEnabled = (layerId: string) => {
|
||||
const selectLayer = useMemo(
|
||||
() =>
|
||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||
|
||||
@@ -139,7 +139,7 @@ export const controlLayersSlice = createSlice({
|
||||
layerSelected: (state, action: PayloadAction<string>) => {
|
||||
exclusivelySelectLayer(state, action.payload);
|
||||
},
|
||||
layerVisibilityToggled: (state, action: PayloadAction<string>) => {
|
||||
layerIsEnabledToggled: (state, action: PayloadAction<string>) => {
|
||||
const layer = state.layers.find((l) => l.id === action.payload);
|
||||
if (layer) {
|
||||
layer.isEnabled = !layer.isEnabled;
|
||||
@@ -791,7 +791,7 @@ class LayerColors {
|
||||
export const {
|
||||
// Any Layer Type
|
||||
layerSelected,
|
||||
layerVisibilityToggled,
|
||||
layerIsEnabledToggled,
|
||||
layerTranslated,
|
||||
layerBboxChanged,
|
||||
layerReset,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Flex, MenuDivider, MenuItem, Spinner } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { $customStarUI } from 'app/store/nanostores/customStarUI';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||
@@ -14,6 +13,7 @@ import { sentImageToCanvas, sentImageToImg2Img } from 'features/gallery/store/ac
|
||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
|
||||
import { size } from 'lodash-es';
|
||||
@@ -46,7 +46,6 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const toaster = useAppToaster();
|
||||
const isCanvasEnabled = useFeatureStatus('canvas');
|
||||
const customStarUi = useStore($customStarUI);
|
||||
const { downloadImage } = useDownloadImage();
|
||||
@@ -86,13 +85,12 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
|
||||
});
|
||||
dispatch(setInitialCanvasImage(imageDTO, optimalDimension));
|
||||
|
||||
toaster({
|
||||
toast({
|
||||
id: 'SENT_TO_CANVAS',
|
||||
title: t('toast.sentToUnifiedCanvas'),
|
||||
status: 'success',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}, [dispatch, imageDTO, t, toaster, optimalDimension]);
|
||||
}, [dispatch, imageDTO, t, optimalDimension]);
|
||||
|
||||
const handleChangeBoard = useCallback(() => {
|
||||
dispatch(imagesToChangeSelected([imageDTO]));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IconButton } from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||
import { setShouldShowImageDetails } from 'features/ui/store/uiSlice';
|
||||
@@ -14,7 +13,6 @@ export const ToggleMetadataViewerButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldShowImageDetails = useAppSelector((s) => s.ui.shouldShowImageDetails);
|
||||
const lastSelectedImage = useAppSelector(selectLastSelectedImage);
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { currentData: imageDTO } = useGetImageDTOQuery(lastSelectedImage?.image_name ?? skipToken);
|
||||
@@ -24,7 +22,7 @@ export const ToggleMetadataViewerButton = memo(() => {
|
||||
[dispatch, shouldShowImageDetails]
|
||||
);
|
||||
|
||||
useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails, toaster]);
|
||||
useHotkeys('i', toggleMetadataViewer, { enabled: Boolean(imageDTO) }, [imageDTO, shouldShowImageDetails]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
|
||||
@@ -53,7 +53,7 @@ export const useImageActions = (image_name?: string) => {
|
||||
|
||||
const recallSeed = useCallback(() => {
|
||||
handlers.seed.parse(metadata).then((seed) => {
|
||||
handlers.seed.recall && handlers.seed.recall(seed);
|
||||
handlers.seed.recall && handlers.seed.recall(seed, true);
|
||||
});
|
||||
}, [metadata]);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { objectKeys } from 'common/util/objectKeys';
|
||||
import { toast } from 'common/util/toast';
|
||||
import type { Layer } from 'features/controlLayers/store/types';
|
||||
import type { LoRA } from 'features/lora/store/loraSlice';
|
||||
import type {
|
||||
@@ -15,6 +14,7 @@ import type {
|
||||
import { fetchModelConfig } from 'features/metadata/util/modelFetchingHelpers';
|
||||
import { validators } from 'features/metadata/util/validators';
|
||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
@@ -89,23 +89,23 @@ const renderLayersValue: MetadataRenderValueFunc<Layer[]> = async (layers) => {
|
||||
return `${layers.length} ${t('controlLayers.layers', { count: layers.length })}`;
|
||||
};
|
||||
|
||||
const parameterSetToast = (parameter: string, description?: string) => {
|
||||
const parameterSetToast = (parameter: string) => {
|
||||
toast({
|
||||
title: t('toast.parameterSet', { parameter }),
|
||||
description,
|
||||
id: 'PARAMETER_SET',
|
||||
title: t('toast.parameterSet'),
|
||||
description: t('toast.parameterSetDesc', { parameter }),
|
||||
status: 'info',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
const parameterNotSetToast = (parameter: string, description?: string) => {
|
||||
const parameterNotSetToast = (parameter: string, message?: string) => {
|
||||
toast({
|
||||
title: t('toast.parameterNotSet', { parameter }),
|
||||
description,
|
||||
id: 'PARAMETER_NOT_SET',
|
||||
title: t('toast.parameterNotSet'),
|
||||
description: message
|
||||
? t('toast.parameterNotSetDescWithMessage', { parameter, message })
|
||||
: t('toast.parameterNotSetDesc', { parameter }),
|
||||
status: 'warning',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -458,7 +458,18 @@ export const parseAndRecallAllMetadata = async (
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (results.some((result) => result.status === 'fulfilled')) {
|
||||
parameterSetToast(t('toast.parameters'));
|
||||
toast({
|
||||
id: 'PARAMETER_SET',
|
||||
title: t('toast.parametersSet'),
|
||||
status: 'info',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
id: 'PARAMETER_SET',
|
||||
title: t('toast.parametersNotSet'),
|
||||
status: 'warning',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
|
||||
type InstallModelArg = {
|
||||
source: string;
|
||||
inplace?: boolean;
|
||||
onSuccess?: () => void;
|
||||
onError?: (error: unknown) => void;
|
||||
};
|
||||
|
||||
export const useInstallModel = () => {
|
||||
const { t } = useTranslation();
|
||||
const [_installModel, request] = useInstallModelMutation();
|
||||
|
||||
const installModel = useCallback(
|
||||
({ source, inplace, onSuccess, onError }: InstallModelArg) => {
|
||||
_installModel({ source, inplace })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_QUEUED',
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (onError) {
|
||||
onError(error);
|
||||
}
|
||||
if (error) {
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_QUEUE_FAILED',
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[_installModel, t]
|
||||
);
|
||||
|
||||
return [installModel, request] as const;
|
||||
};
|
||||
@@ -17,7 +17,11 @@ export const useStarterModelsToast = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (toast.isActive(TOAST_ID)) {
|
||||
return;
|
||||
if (mainModels.length === 0) {
|
||||
return;
|
||||
} else {
|
||||
toast.close(TOAST_ID);
|
||||
}
|
||||
}
|
||||
if (data && mainModels.length === 0 && !didToast && isEnabled) {
|
||||
toast({
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Button, Flex, FormControl, FormErrorMessage, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useInstallModelMutation, useLazyGetHuggingFaceModelsQuery } from 'services/api/endpoints/models';
|
||||
import { useLazyGetHuggingFaceModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import { HuggingFaceResults } from './HuggingFaceResults';
|
||||
|
||||
@@ -14,50 +12,19 @@ export const HuggingFaceForm = () => {
|
||||
const [displayResults, setDisplayResults] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [_getHuggingFaceModels, { isLoading, data }] = useLazyGetHuggingFaceModelsQuery();
|
||||
const [installModel] = useInstallModelMutation();
|
||||
|
||||
const handleInstallModel = useCallback(
|
||||
(source: string) => {
|
||||
installModel({ source })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
[installModel, dispatch, t]
|
||||
);
|
||||
const [installModel] = useInstallModel();
|
||||
|
||||
const getModels = useCallback(async () => {
|
||||
_getHuggingFaceModels(huggingFaceRepo)
|
||||
.unwrap()
|
||||
.then((response) => {
|
||||
if (response.is_diffusers) {
|
||||
handleInstallModel(huggingFaceRepo);
|
||||
installModel({ source: huggingFaceRepo });
|
||||
setDisplayResults(false);
|
||||
} else if (response.urls?.length === 1 && response.urls[0]) {
|
||||
handleInstallModel(response.urls[0]);
|
||||
installModel({ source: response.urls[0] });
|
||||
setDisplayResults(false);
|
||||
} else {
|
||||
setDisplayResults(true);
|
||||
@@ -66,7 +33,7 @@ export const HuggingFaceForm = () => {
|
||||
.catch((error) => {
|
||||
setErrorMessage(error.data.detail || '');
|
||||
});
|
||||
}, [_getHuggingFaceModels, handleInstallModel, huggingFaceRepo]);
|
||||
}, [_getHuggingFaceModels, installModel, huggingFaceRepo]);
|
||||
|
||||
const handleSetHuggingFaceRepo: ChangeEventHandler<HTMLInputElement> = useCallback((e) => {
|
||||
setHuggingFaceRepo(e.target.value);
|
||||
|
||||
@@ -1,47 +1,20 @@
|
||||
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import { useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
|
||||
type Props = {
|
||||
result: string;
|
||||
};
|
||||
export const HuggingFaceResultItem = ({ result }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [installModel] = useInstallModelMutation();
|
||||
const [installModel] = useInstallModel();
|
||||
|
||||
const handleInstall = useCallback(() => {
|
||||
installModel({ source: result })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [installModel, result, dispatch, t]);
|
||||
const onClick = useCallback(() => {
|
||||
installModel({ source: result });
|
||||
}, [installModel, result]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>
|
||||
@@ -51,7 +24,7 @@ export const HuggingFaceResultItem = ({ result }: Props) => {
|
||||
{result}
|
||||
</Text>
|
||||
</Flex>
|
||||
<IconButton aria-label={t('modelManager.install')} icon={<PiPlusBold />} onClick={handleInstall} size="sm" />
|
||||
<IconButton aria-label={t('modelManager.install')} icon={<PiPlusBold />} onClick={onClick} size="sm" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,15 +8,12 @@ import {
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
import { useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
|
||||
import { HuggingFaceResultItem } from './HuggingFaceResultItem';
|
||||
|
||||
@@ -27,9 +24,8 @@ type HuggingFaceResultsProps = {
|
||||
export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [installModel] = useInstallModelMutation();
|
||||
const [installModel] = useInstallModel();
|
||||
|
||||
const filteredResults = useMemo(() => {
|
||||
return results.filter((result) => {
|
||||
@@ -46,34 +42,11 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
|
||||
setSearchTerm('');
|
||||
}, []);
|
||||
|
||||
const handleAddAll = useCallback(() => {
|
||||
const onClickAddAll = useCallback(() => {
|
||||
for (const result of filteredResults) {
|
||||
installModel({ source: result })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
installModel({ source: result });
|
||||
}
|
||||
}, [filteredResults, installModel, dispatch, t]);
|
||||
}, [filteredResults, installModel]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -82,7 +55,7 @@ export const HuggingFaceResults = ({ results }: HuggingFaceResultsProps) => {
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Heading size="sm">{t('modelManager.availableModels')}</Heading>
|
||||
<Flex alignItems="center" gap={3}>
|
||||
<Button size="sm" onClick={handleAddAll} isDisabled={results.length === 0} flexShrink={0}>
|
||||
<Button size="sm" onClick={onClickAddAll} isDisabled={results.length === 0} flexShrink={0}>
|
||||
{t('modelManager.installAll')}
|
||||
</Button>
|
||||
<InputGroup w={64} size="xs">
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Button, Checkbox, Flex, FormControl, FormHelperText, FormLabel, Input } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
|
||||
type SimpleImportModelConfig = {
|
||||
location: string;
|
||||
@@ -14,9 +11,7 @@ type SimpleImportModelConfig = {
|
||||
};
|
||||
|
||||
export const InstallModelForm = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [installModel, { isLoading }] = useInstallModelMutation();
|
||||
const [installModel, { isLoading }] = useInstallModel();
|
||||
|
||||
const { register, handleSubmit, formState, reset } = useForm<SimpleImportModelConfig>({
|
||||
defaultValues: {
|
||||
@@ -26,40 +21,22 @@ export const InstallModelForm = () => {
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const resetForm = useCallback(() => reset(undefined, { keepValues: true }), [reset]);
|
||||
|
||||
const onSubmit = useCallback<SubmitHandler<SimpleImportModelConfig>>(
|
||||
(values) => {
|
||||
if (!values?.location) {
|
||||
return;
|
||||
}
|
||||
|
||||
installModel({ source: values.location, inplace: values.inplace })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
reset(undefined, { keepValues: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
reset(undefined, { keepValues: true });
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
installModel({
|
||||
source: values.location,
|
||||
inplace: values.inplace,
|
||||
onSuccess: resetForm,
|
||||
onError: resetForm,
|
||||
});
|
||||
},
|
||||
[dispatch, reset, installModel]
|
||||
[installModel, resetForm]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Box, Button, Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } from 'services/api/endpoints/models';
|
||||
@@ -10,8 +8,6 @@ import { useListModelInstallsQuery, usePruneCompletedModelInstallsMutation } fro
|
||||
import { ModelInstallQueueItem } from './ModelInstallQueueItem';
|
||||
|
||||
export const ModelInstallQueue = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data } = useListModelInstallsQuery();
|
||||
|
||||
const [_pruneCompletedModelInstalls] = usePruneCompletedModelInstallsMutation();
|
||||
@@ -20,28 +16,22 @@ export const ModelInstallQueue = () => {
|
||||
_pruneCompletedModelInstalls()
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.prunedQueue'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_QUEUE_PRUNED',
|
||||
title: t('toast.prunedQueue'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_QUEUE_PRUNE_FAILED',
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [_pruneCompletedModelInstalls, dispatch]);
|
||||
}, [_pruneCompletedModelInstalls]);
|
||||
|
||||
const pruneAvailable = useMemo(() => {
|
||||
return data?.some(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Flex, IconButton, Progress, Text, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { t } from 'i18next';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@@ -29,7 +27,6 @@ const formatBytes = (bytes: number) => {
|
||||
|
||||
export const ModelInstallQueueItem = (props: ModelListItemProps) => {
|
||||
const { installJob } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [deleteImportModel] = useCancelModelInstallMutation();
|
||||
|
||||
@@ -37,28 +34,22 @@ export const ModelInstallQueueItem = (props: ModelListItemProps) => {
|
||||
deleteImportModel(installJob.id)
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelImportCanceled'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_CANCELED',
|
||||
title: t('toast.modelImportCanceled'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_INSTALL_CANCEL_FAILED',
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
}, [deleteImportModel, installJob, dispatch]);
|
||||
}, [deleteImportModel, installJob]);
|
||||
|
||||
const sourceLocation = useMemo(() => {
|
||||
switch (installJob.source.type) {
|
||||
|
||||
@@ -11,15 +11,13 @@ import {
|
||||
InputGroup,
|
||||
InputRightElement,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import type { ChangeEvent, ChangeEventHandler } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
import { type ScanFolderResponse, useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
import type { ScanFolderResponse } from 'services/api/endpoints/models';
|
||||
|
||||
import { ScanModelResultItem } from './ScanFolderResultItem';
|
||||
|
||||
@@ -30,9 +28,8 @@ type ScanModelResultsProps = {
|
||||
export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const dispatch = useAppDispatch();
|
||||
const [inplace, setInplace] = useState(true);
|
||||
const [installModel] = useInstallModelMutation();
|
||||
const [installModel] = useInstallModel();
|
||||
|
||||
const filteredResults = useMemo(() => {
|
||||
return results.filter((result) => {
|
||||
@@ -58,61 +55,15 @@ export const ScanModelsResults = ({ results }: ScanModelResultsProps) => {
|
||||
if (result.is_installed) {
|
||||
continue;
|
||||
}
|
||||
installModel({ source: result.path, inplace })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
installModel({ source: result.path, inplace });
|
||||
}
|
||||
}, [filteredResults, installModel, inplace, dispatch, t]);
|
||||
}, [filteredResults, installModel, inplace]);
|
||||
|
||||
const handleInstallOne = useCallback(
|
||||
(source: string) => {
|
||||
installModel({ source, inplace })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
installModel({ source, inplace });
|
||||
},
|
||||
[installModel, inplace, dispatch, t]
|
||||
[installModel, inplace]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { Badge, Box, Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { useInstallModel } from 'features/modelManagerV2/hooks/useInstallModel';
|
||||
import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import type { GetStarterModelsResponse } from 'services/api/endpoints/models';
|
||||
import { useInstallModelMutation } from 'services/api/endpoints/models';
|
||||
|
||||
type Props = {
|
||||
result: GetStarterModelsResponse[number];
|
||||
};
|
||||
export const StarterModelsResultItem = ({ result }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const allSources = useMemo(() => {
|
||||
const _allSources = [result.source];
|
||||
if (result.dependencies) {
|
||||
@@ -22,36 +18,13 @@ export const StarterModelsResultItem = ({ result }: Props) => {
|
||||
}
|
||||
return _allSources;
|
||||
}, [result]);
|
||||
const [installModel] = useInstallModelMutation();
|
||||
const [installModel] = useInstallModel();
|
||||
|
||||
const handleQuickAdd = useCallback(() => {
|
||||
const onClick = useCallback(() => {
|
||||
for (const source of allSources) {
|
||||
installModel({ source })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.modelAddedSimple'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
installModel({ source });
|
||||
}
|
||||
}, [allSources, installModel, dispatch, t]);
|
||||
}, [allSources, installModel]);
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" justifyContent="space-between" w="100%" gap={3}>
|
||||
@@ -67,7 +40,7 @@ export const StarterModelsResultItem = ({ result }: Props) => {
|
||||
{result.is_installed ? (
|
||||
<Badge>{t('common.installed')}</Badge>
|
||||
) : (
|
||||
<IconButton aria-label={t('modelManager.install')} icon={<PiPlusBold />} onClick={handleQuickAdd} size="sm" />
|
||||
<IconButton aria-label={t('modelManager.install')} icon={<PiPlusBold />} onClick={onClick} size="sm" />
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setSelectedModelKey } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||
import ModelBaseBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelBaseBadge';
|
||||
import ModelFormatBadge from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelFormatBadge';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -53,25 +52,19 @@ const ModelListItem = (props: ModelListItemProps) => {
|
||||
deleteModel({ key: model.key })
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelDeleted')}: ${model.name}`,
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_DELETED',
|
||||
title: `${t('modelManager.modelDeleted')}: ${model.name}`,
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_DELETE_FAILED',
|
||||
title: `${t('modelManager.modelDeleteFailed')}: ${model.name}`,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
dispatch(setSelectedModelKey(null));
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useControlNetOrT2IAdapterDefaultSettings } from 'features/modelManagerV2/hooks/useControlNetOrT2IAdapterDefaultSettings';
|
||||
import { DefaultPreprocessor } from 'features/modelManagerV2/subpanels/ModelPanel/ControlNetOrT2IAdapterDefaultSettings/DefaultPreprocessor';
|
||||
import type { FormField } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/MainModelDefaultSettings';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -19,7 +18,6 @@ export type ControlNetOrT2IAdapterDefaultSettingsFormData = {
|
||||
export const ControlNetOrT2IAdapterDefaultSettings = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { defaultSettingsDefaults, isLoading: isLoadingDefaultSettings } =
|
||||
useControlNetOrT2IAdapterDefaultSettings(selectedModelKey);
|
||||
@@ -46,30 +44,24 @@ export const ControlNetOrT2IAdapterDefaultSettings = () => {
|
||||
})
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.defaultSettingsSaved'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'DEFAULT_SETTINGS_SAVED',
|
||||
title: t('modelManager.defaultSettingsSaved'),
|
||||
status: 'success',
|
||||
});
|
||||
reset(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'DEFAULT_SETTINGS_SAVE_FAILED',
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[selectedModelKey, dispatch, reset, updateModel, t]
|
||||
[selectedModelKey, reset, updateModel, t]
|
||||
);
|
||||
|
||||
if (isLoadingDefaultSettings) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Box, Button, Flex, Icon, IconButton, Image, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { typedMemo } from 'common/util/typedMemo';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -15,7 +13,6 @@ type Props = {
|
||||
};
|
||||
|
||||
const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [image, setImage] = useState<string | null>(model_image || null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -34,27 +31,21 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
setImage(URL.createObjectURL(file));
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelImageUpdated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_IMAGE_UPDATED',
|
||||
title: t('modelManager.modelImageUpdated'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelImageUpdateFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
.catch(() => {
|
||||
toast({
|
||||
id: 'MODEL_IMAGE_UPDATE_FAILED',
|
||||
title: t('modelManager.modelImageUpdateFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
},
|
||||
[dispatch, model_key, t, updateModelImage]
|
||||
[model_key, t, updateModelImage]
|
||||
);
|
||||
|
||||
const handleResetImage = useCallback(() => {
|
||||
@@ -65,26 +56,20 @@ const ModelImageUpload = ({ model_key, model_image }: Props) => {
|
||||
deleteModelImage(model_key)
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelImageDeleted'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_IMAGE_DELETED',
|
||||
title: t('modelManager.modelImageDeleted'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelImageDeleteFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
.catch(() => {
|
||||
toast({
|
||||
id: 'MODEL_IMAGE_DELETE_FAILED',
|
||||
title: t('modelManager.modelImageDeleteFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
}, [dispatch, model_key, t, deleteModelImage]);
|
||||
}, [model_key, t, deleteModelImage]);
|
||||
|
||||
const { getInputProps, getRootProps } = useDropzone({
|
||||
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { Button, Flex, Heading, SimpleGrid, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useMainModelDefaultSettings } from 'features/modelManagerV2/hooks/useMainModelDefaultSettings';
|
||||
import { DefaultHeight } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultHeight';
|
||||
import { DefaultWidth } from 'features/modelManagerV2/subpanels/ModelPanel/MainModelDefaultSettings/DefaultWidth';
|
||||
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -39,7 +38,6 @@ export type MainModelDefaultSettingsFormData = {
|
||||
export const MainModelDefaultSettings = () => {
|
||||
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
defaultSettingsDefaults,
|
||||
@@ -76,30 +74,24 @@ export const MainModelDefaultSettings = () => {
|
||||
})
|
||||
.unwrap()
|
||||
.then((_) => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.defaultSettingsSaved'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'DEFAULT_SETTINGS_SAVED',
|
||||
title: t('modelManager.defaultSettingsSaved'),
|
||||
status: 'success',
|
||||
});
|
||||
reset(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'DEFAULT_SETTINGS_SAVE_FAILED',
|
||||
title: `${error.data.detail} `,
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[selectedModelKey, dispatch, reset, updateModel, t]
|
||||
[selectedModelKey, reset, updateModel, t]
|
||||
);
|
||||
|
||||
if (isLoadingDefaultSettings) {
|
||||
|
||||
@@ -4,8 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||
import { ModelConvertButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelConvertButton';
|
||||
import { ModelEditButton } from 'features/modelManagerV2/subpanels/ModelPanel/ModelEditButton';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import type { SubmitHandler } from 'react-hook-form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -47,25 +46,19 @@ export const Model = () => {
|
||||
.then((payload) => {
|
||||
form.reset(payload, { keepDefaultValues: true });
|
||||
dispatch(setSelectedModelMode('view'));
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelUpdated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_UPDATED',
|
||||
title: t('modelManager.modelUpdated'),
|
||||
status: 'success',
|
||||
});
|
||||
})
|
||||
.catch((_) => {
|
||||
form.reset();
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('modelManager.modelUpdateFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'MODEL_UPDATE_FAILED',
|
||||
title: t('modelManager.modelUpdateFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
},
|
||||
[dispatch, data?.key, form, t, updateModel]
|
||||
|
||||
@@ -9,9 +9,7 @@ import {
|
||||
useDisclosure,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useConvertModelMutation, useGetModelConfigQuery } from 'services/api/endpoints/models';
|
||||
@@ -22,7 +20,6 @@ interface ModelConvertProps {
|
||||
|
||||
export const ModelConvertButton = (props: ModelConvertProps) => {
|
||||
const { modelKey } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const { data } = useGetModelConfigQuery(modelKey ?? skipToken);
|
||||
const [convertModel, { isLoading }] = useConvertModelMutation();
|
||||
@@ -33,38 +30,26 @@ export const ModelConvertButton = (props: ModelConvertProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`,
|
||||
status: 'info',
|
||||
})
|
||||
)
|
||||
);
|
||||
const toastId = `CONVERTING_MODEL_${data.key}`;
|
||||
toast({
|
||||
id: toastId,
|
||||
title: `${t('modelManager.convertingModelBegin')}: ${data?.name}`,
|
||||
status: 'info',
|
||||
});
|
||||
|
||||
convertModel(data?.key)
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelConverted')}: ${data?.name}`,
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({ id: toastId, title: `${t('modelManager.modelConverted')}: ${data?.name}`, status: 'success' });
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`,
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: toastId,
|
||||
title: `${t('modelManager.modelConversionFailed')}: ${data?.name}`,
|
||||
status: 'error',
|
||||
});
|
||||
});
|
||||
}, [data, isLoading, dispatch, t, convertModel]);
|
||||
}, [data, isLoading, t, convertModel]);
|
||||
|
||||
if (data?.format !== 'checkpoint') {
|
||||
return;
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'reactflow/dist/style.css';
|
||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||
import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
|
||||
import type { SelectInstance } from 'chakra-react-select';
|
||||
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
|
||||
@@ -24,6 +23,7 @@ import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
|
||||
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
|
||||
import type { AnyNode } from 'features/nodes/types/invocation';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { filter, map, memoize, some } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
@@ -60,7 +60,6 @@ const filterOption = memoize((option: FilterOptionOption<ComboboxOption>, inputV
|
||||
const AddNodePopover = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const buildInvocation = useBuildNode();
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
const selectRef = useRef<SelectInstance<ComboboxOption> | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -127,7 +126,7 @@ const AddNodePopover = () => {
|
||||
const errorMessage = t('nodes.unknownNode', {
|
||||
nodeType: nodeType,
|
||||
});
|
||||
toaster({
|
||||
toast({
|
||||
status: 'error',
|
||||
title: errorMessage,
|
||||
});
|
||||
@@ -163,7 +162,7 @@ const AddNodePopover = () => {
|
||||
}
|
||||
return node;
|
||||
},
|
||||
[buildInvocation, store, dispatch, t, toaster]
|
||||
[buildInvocation, store, dispatch, t]
|
||||
);
|
||||
|
||||
const onChange = useCallback<ComboboxOnChange>(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Flex, Grid, GridItem } from '@invoke-ai/ui-library';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
|
||||
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
|
||||
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
|
||||
import { useFieldNames } from 'features/nodes/hooks/useFieldNames';
|
||||
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
|
||||
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
|
||||
import { memo } from 'react';
|
||||
@@ -20,8 +20,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId);
|
||||
const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId);
|
||||
const fieldNames = useFieldNames(nodeId);
|
||||
const withFooter = useWithFooter(nodeId);
|
||||
const outputFieldNames = useOutputFieldNames(nodeId);
|
||||
|
||||
@@ -41,9 +40,11 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
>
|
||||
<Flex flexDir="column" px={2} w="full" h="full">
|
||||
<Grid gridTemplateColumns="1fr auto" gridAutoRows="1fr">
|
||||
{inputConnectionFieldNames.map((fieldName, i) => (
|
||||
{fieldNames.connectionFields.map((fieldName, i) => (
|
||||
<GridItem gridColumnStart={1} gridRowStart={i + 1} key={`${nodeId}.${fieldName}.input-field`}>
|
||||
<InputField nodeId={nodeId} fieldName={fieldName} />
|
||||
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
|
||||
<InputField nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvocationInputFieldCheck>
|
||||
</GridItem>
|
||||
))}
|
||||
{outputFieldNames.map((fieldName, i) => (
|
||||
@@ -52,8 +53,23 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
|
||||
</GridItem>
|
||||
))}
|
||||
</Grid>
|
||||
{inputAnyOrDirectFieldNames.map((fieldName) => (
|
||||
<InputField key={`${nodeId}.${fieldName}.input-field`} nodeId={nodeId} fieldName={fieldName} />
|
||||
{fieldNames.anyOrDirectFields.map((fieldName) => (
|
||||
<InvocationInputFieldCheck
|
||||
key={`${nodeId}.${fieldName}.input-field`}
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
>
|
||||
<InputField nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvocationInputFieldCheck>
|
||||
))}
|
||||
{fieldNames.missingFields.map((fieldName) => (
|
||||
<InvocationInputFieldCheck
|
||||
key={`${nodeId}.${fieldName}.input-field`}
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
>
|
||||
<InputField nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvocationInputFieldCheck>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useDoesFieldExist } from 'features/nodes/hooks/useDoesFieldExist';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
nodeId: string;
|
||||
fieldName?: string;
|
||||
}>;
|
||||
|
||||
export const MissingFallback = memo((props: Props) => {
|
||||
// We must be careful here to avoid race conditions where a deleted node is still referenced as an exposed field
|
||||
const exists = useDoesFieldExist(props.nodeId, props.fieldName);
|
||||
if (!exists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return props.children;
|
||||
});
|
||||
|
||||
MissingFallback.displayName = 'MissingFallback';
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { Flex, FormControl } from '@invoke-ai/ui-library';
|
||||
import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
|
||||
import { useDoesInputHaveValue } from 'features/nodes/hooks/useDoesInputHaveValue';
|
||||
import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
|
||||
import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldHandle from './FieldHandle';
|
||||
import FieldLinearViewToggle from './FieldLinearViewToggle';
|
||||
import InputFieldRenderer from './InputFieldRenderer';
|
||||
import { InputFieldWrapper } from './InputFieldWrapper';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
@@ -18,9 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
|
||||
const fieldInstance = useFieldInputInstance(nodeId, fieldName);
|
||||
const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
@@ -55,20 +51,6 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
if (!fieldTemplate || !fieldInstance) {
|
||||
return (
|
||||
<InputFieldWrapper shouldDim={shouldDim}>
|
||||
<FormControl alignItems="stretch" justifyContent="space-between" flexDir="column" gap={2} h="full" w="full">
|
||||
<FormLabel display="flex" alignItems="center" mb={0} px={1} gap={2} h="full">
|
||||
{t('nodes.unknownInput', {
|
||||
name: fieldInstance?.label ?? fieldTemplate?.title ?? fieldName,
|
||||
})}
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</InputFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (fieldTemplate.input === 'connection' || isConnected) {
|
||||
return (
|
||||
<InputFieldWrapper shouldDim={shouldDim}>
|
||||
@@ -134,27 +116,3 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
};
|
||||
|
||||
export default memo(InputField);
|
||||
|
||||
type InputFieldWrapperProps = PropsWithChildren<{
|
||||
shouldDim: boolean;
|
||||
}>;
|
||||
|
||||
const InputFieldWrapper = memo(({ shouldDim, children }: InputFieldWrapperProps) => {
|
||||
return (
|
||||
<Flex
|
||||
position="relative"
|
||||
minH={8}
|
||||
py={0.5}
|
||||
alignItems="center"
|
||||
opacity={shouldDim ? 0.5 : 1}
|
||||
transitionProperty="opacity"
|
||||
transitionDuration="0.1s"
|
||||
w="full"
|
||||
h="full"
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
InputFieldWrapper.displayName = 'InputFieldWrapper';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
type InputFieldWrapperProps = PropsWithChildren<{
|
||||
shouldDim: boolean;
|
||||
}>;
|
||||
|
||||
export const InputFieldWrapper = memo(({ shouldDim, children }: InputFieldWrapperProps) => {
|
||||
return (
|
||||
<Flex
|
||||
position="relative"
|
||||
minH={8}
|
||||
py={0.5}
|
||||
alignItems="center"
|
||||
opacity={shouldDim ? 0.5 : 1}
|
||||
transitionProperty="opacity"
|
||||
transitionDuration="0.1s"
|
||||
w="full"
|
||||
h="full"
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
InputFieldWrapper.displayName = 'InputFieldWrapper';
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||
import { selectInvocationNode } from 'features/nodes/store/selectors';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = PropsWithChildren<{
|
||||
nodeId: string;
|
||||
fieldName: string;
|
||||
}>;
|
||||
|
||||
export const InvocationInputFieldCheck = memo(({ nodeId, fieldName, children }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const templates = useStore($templates);
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(selectNodesSlice, (nodesSlice) => {
|
||||
const node = selectInvocationNode(nodesSlice, nodeId);
|
||||
const instance = node.data.inputs[fieldName];
|
||||
const template = templates[node.data.type];
|
||||
const fieldTemplate = template?.inputs[fieldName];
|
||||
return {
|
||||
name: instance?.label || fieldTemplate?.title || fieldName,
|
||||
hasInstance: Boolean(instance),
|
||||
hasTemplate: Boolean(fieldTemplate),
|
||||
};
|
||||
}),
|
||||
[fieldName, nodeId, templates]
|
||||
);
|
||||
const { hasInstance, hasTemplate, name } = useAppSelector(selector);
|
||||
|
||||
if (!hasTemplate || !hasInstance) {
|
||||
return (
|
||||
<Flex position="relative" minH={8} py={0.5} alignItems="center" w="full" h="full">
|
||||
<FormControl
|
||||
isInvalid={true}
|
||||
alignItems="stretch"
|
||||
justifyContent="center"
|
||||
flexDir="column"
|
||||
gap={2}
|
||||
h="full"
|
||||
w="full"
|
||||
>
|
||||
<FormLabel display="flex" mb={0} px={1} py={2} gap={2}>
|
||||
{t('nodes.unknownInput', { name })}
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
});
|
||||
|
||||
InvocationInputFieldCheck.displayName = 'InvocationInputFieldCheck';
|
||||
@@ -3,7 +3,7 @@ import { CSS } from '@dnd-kit/utilities';
|
||||
import { Flex, Icon, IconButton, Spacer, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
|
||||
import { MissingFallback } from 'features/nodes/components/flow/nodes/Invocation/MissingFallback';
|
||||
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
|
||||
import { useFieldOriginalValue } from 'features/nodes/hooks/useFieldOriginalValue';
|
||||
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||
import { workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
|
||||
@@ -102,9 +102,9 @@ const LinearViewFieldInternal = ({ nodeId, fieldName }: Props) => {
|
||||
|
||||
const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
||||
return (
|
||||
<MissingFallback nodeId={nodeId} fieldName={fieldName}>
|
||||
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
|
||||
<LinearViewFieldInternal nodeId={nodeId} fieldName={fieldName} />
|
||||
</MissingFallback>
|
||||
</InvocationInputFieldCheck>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ const NotesNode = (props: NodeProps<NotesNodeData>) => {
|
||||
gap={1}
|
||||
>
|
||||
<Flex className="nopan" w="full" h="full" flexDir="column">
|
||||
<Textarea value={notes} onChange={handleChange} rows={8} resize="none" fontSize="sm" />
|
||||
<Textarea className="nodrag" value={notes} onChange={handleChange} rows={8} resize="none" fontSize="sm" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ConfirmationAlertDialog, Flex, IconButton, Text, useDisclosure } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiTrashSimpleFill } from 'react-icons/pi';
|
||||
@@ -16,14 +15,11 @@ const ClearFlowButton = () => {
|
||||
const handleNewWorkflow = useCallback(() => {
|
||||
dispatch(nodeEditorReset());
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('workflows.workflowCleared'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
toast({
|
||||
id: 'WORKFLOW_CLEARED',
|
||||
title: t('workflows.workflowCleared'),
|
||||
status: 'success',
|
||||
});
|
||||
|
||||
onClose();
|
||||
}, [dispatch, onClose, t]);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Flex, FormLabel, Icon, IconButton, Spacer, Tooltip } from '@invoke-ai/ui-library';
|
||||
import FieldTooltipContent from 'features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent';
|
||||
import InputFieldRenderer from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer';
|
||||
import { MissingFallback } from 'features/nodes/components/flow/nodes/Invocation/MissingFallback';
|
||||
import { InvocationInputFieldCheck } from 'features/nodes/components/flow/nodes/Invocation/fields/InvocationFieldCheck';
|
||||
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
|
||||
import { useFieldOriginalValue } from 'features/nodes/hooks/useFieldOriginalValue';
|
||||
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
|
||||
@@ -53,9 +53,9 @@ const WorkflowFieldInternal = ({ nodeId, fieldName }: Props) => {
|
||||
|
||||
const WorkflowField = ({ nodeId, fieldName }: Props) => {
|
||||
return (
|
||||
<MissingFallback nodeId={nodeId} fieldName={fieldName}>
|
||||
<InvocationInputFieldCheck nodeId={nodeId} fieldName={fieldName}>
|
||||
<WorkflowFieldInternal nodeId={nodeId} fieldName={fieldName} />
|
||||
</MissingFallback>
|
||||
</InvocationInputFieldCheck>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import { isSingleOrCollection } from 'features/nodes/types/field';
|
||||
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
|
||||
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
|
||||
import { keys, map } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useAnyOrDirectInputFieldNames = (nodeId: string): string[] => {
|
||||
const template = useNodeTemplate(nodeId);
|
||||
|
||||
const fieldNames = useMemo(() => {
|
||||
const fields = map(template.inputs).filter((field) => {
|
||||
return (
|
||||
(['any', 'direct'].includes(field.input) || isSingleOrCollection(field.type)) &&
|
||||
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
|
||||
);
|
||||
});
|
||||
const _fieldNames = getSortedFilteredFieldNames(fields);
|
||||
if (_fieldNames.length === 0) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
return _fieldNames;
|
||||
}, [template.inputs]);
|
||||
|
||||
return fieldNames;
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { EMPTY_ARRAY } from 'app/store/constants';
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import { isSingleOrCollection } from 'features/nodes/types/field';
|
||||
import { getSortedFilteredFieldNames } from 'features/nodes/util/node/getSortedFilteredFieldNames';
|
||||
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
|
||||
import { keys, map } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useConnectionInputFieldNames = (nodeId: string): string[] => {
|
||||
const template = useNodeTemplate(nodeId);
|
||||
const fieldNames = useMemo(() => {
|
||||
// get the visible fields
|
||||
const fields = map(template.inputs).filter(
|
||||
(field) =>
|
||||
(field.input === 'connection' && !isSingleOrCollection(field.type)) ||
|
||||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
|
||||
);
|
||||
|
||||
const _fieldNames = getSortedFilteredFieldNames(fields);
|
||||
|
||||
if (_fieldNames.length === 0) {
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
return _fieldNames;
|
||||
}, [template.inputs]);
|
||||
|
||||
return fieldNames;
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
|
||||
export const useDoesFieldExist = (nodeId: string, fieldName?: string) => {
|
||||
const doesFieldExist = useAppSelector((s) => {
|
||||
const node = s.nodes.present.nodes.find((n) => n.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return false;
|
||||
}
|
||||
if (fieldName === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (!node.data.inputs[fieldName]) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return doesFieldExist;
|
||||
};
|
||||
@@ -1,9 +1,14 @@
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import type { FieldInputTemplate } from 'features/nodes/types/field';
|
||||
import { useMemo } from 'react';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate | null => {
|
||||
export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate => {
|
||||
const template = useNodeTemplate(nodeId);
|
||||
const fieldTemplate = useMemo(() => template.inputs[fieldName] ?? null, [fieldName, template.inputs]);
|
||||
const fieldTemplate = useMemo(() => {
|
||||
const _fieldTemplate = template.inputs[fieldName];
|
||||
assert(_fieldTemplate, `Field template for field ${fieldName} not found`);
|
||||
return _fieldTemplate;
|
||||
}, [fieldName, template.inputs]);
|
||||
return fieldTemplate;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import type { FieldInputTemplate } from 'features/nodes/types/field';
|
||||
import { isSingleOrCollection } from 'features/nodes/types/field';
|
||||
import { TEMPLATE_BUILDER_MAP } from 'features/nodes/util/schema/buildFieldInputTemplate';
|
||||
import { difference, filter, keys } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const isConnectionInputField = (field: FieldInputTemplate) => {
|
||||
return (
|
||||
(field.input === 'connection' && !isSingleOrCollection(field.type)) ||
|
||||
!keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
|
||||
);
|
||||
};
|
||||
|
||||
const isAnyOrDirectInputField = (field: FieldInputTemplate) => {
|
||||
return (
|
||||
(['any', 'direct'].includes(field.input) || isSingleOrCollection(field.type)) &&
|
||||
keys(TEMPLATE_BUILDER_MAP).includes(field.type.name)
|
||||
);
|
||||
};
|
||||
|
||||
export const useFieldNames = (nodeId: string) => {
|
||||
const template = useNodeTemplate(nodeId);
|
||||
const node = useNodeData(nodeId);
|
||||
const fieldNames = useMemo(() => {
|
||||
const instanceFields = keys(node.inputs);
|
||||
const allTemplateFields = keys(template.inputs);
|
||||
const missingFields = difference(instanceFields, allTemplateFields);
|
||||
const connectionFields = filter(template.inputs, isConnectionInputField).map((f) => f.name);
|
||||
const anyOrDirectFields = filter(template.inputs, isAnyOrDirectInputField).map((f) => f.name);
|
||||
return {
|
||||
missingFields,
|
||||
connectionFields,
|
||||
anyOrDirectFields,
|
||||
};
|
||||
}, [node.inputs, template.inputs]);
|
||||
return fieldNames;
|
||||
};
|
||||
@@ -1,14 +1,14 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||
import { selectNodeData } from 'features/nodes/store/selectors';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useNodeLabel = (nodeId: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(selectNodesSlice, (nodes) => {
|
||||
return selectNodeData(nodes, nodeId)?.label ?? null;
|
||||
createSelector(selectNodesSlice, (nodesSlice) => {
|
||||
const node = nodesSlice.nodes.find((node) => node.id === nodeId);
|
||||
return node?.data.label;
|
||||
}),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useNodeTemplateTitle = (nodeId: string): string | null => {
|
||||
const template = useNodeTemplate(nodeId);
|
||||
const title = useMemo(() => template.title, [template.title]);
|
||||
const templates = useStore($templates);
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(selectNodesSlice, (nodesSlice) => {
|
||||
const node = nodesSlice.nodes.find((node) => node.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return null;
|
||||
}
|
||||
const template = templates[node.data.type];
|
||||
return template?.title ?? null;
|
||||
}),
|
||||
[nodeId, templates]
|
||||
);
|
||||
const title = useAppSelector(selector);
|
||||
return title;
|
||||
};
|
||||
|
||||
@@ -275,10 +275,9 @@ export const nodesSlice = createSlice({
|
||||
const { nodeId, label } = action.payload;
|
||||
const nodeIndex = state.nodes.findIndex((n) => n.id === nodeId);
|
||||
const node = state.nodes?.[nodeIndex];
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
if (isInvocationNode(node) || isNotesNode(node)) {
|
||||
node.data.label = label;
|
||||
}
|
||||
node.data.label = label;
|
||||
},
|
||||
nodeNotesChanged: (state, action: PayloadAction<{ nodeId: string; notes: string }>) => {
|
||||
const { nodeId, notes } = action.payload;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { InvocationNode, InvocationNodeData } from 'features/nodes/types/in
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const selectInvocationNode = (nodesSlice: NodesState, nodeId: string): InvocationNode => {
|
||||
export const selectInvocationNode = (nodesSlice: NodesState, nodeId: string): InvocationNode => {
|
||||
const node = nodesSlice.nodes.find((node) => node.id === nodeId);
|
||||
assert(isInvocationNode(node), `Node ${nodeId} is not an invocation node`);
|
||||
return node;
|
||||
|
||||
@@ -487,26 +487,18 @@ const isValidIPAdapter = (ipa: IPAdapterConfigV2, base: BaseModelType): boolean
|
||||
};
|
||||
|
||||
const isValidLayer = (layer: Layer, base: BaseModelType) => {
|
||||
if (!layer.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (isControlAdapterLayer(layer)) {
|
||||
if (!layer.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return isValidControlAdapter(layer.controlAdapter, base);
|
||||
}
|
||||
if (isIPAdapterLayer(layer)) {
|
||||
if (!layer.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return isValidIPAdapter(layer.ipAdapter, base);
|
||||
}
|
||||
if (isInitialImageLayer(layer)) {
|
||||
if (!layer.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (!layer.image) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
const hasImage = Boolean(layer.image);
|
||||
return hasImage;
|
||||
}
|
||||
if (isRegionalGuidanceLayer(layer)) {
|
||||
const hasTextPrompt = Boolean(layer.positivePrompt || layer.negativePrompt);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { img_resize, main_model_loader } from 'features/nodes/store/util/testUtils';
|
||||
import type { WorkflowV3 } from 'features/nodes/types/workflow';
|
||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||
import { get } from 'lodash-es';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('validateWorkflow', () => {
|
||||
const workflow: WorkflowV3 = {
|
||||
name: '',
|
||||
author: '',
|
||||
description: '',
|
||||
version: '',
|
||||
contact: '',
|
||||
tags: '',
|
||||
notes: '',
|
||||
exposedFields: [],
|
||||
meta: { version: '3.0.0', category: 'user' },
|
||||
nodes: [
|
||||
{
|
||||
id: '94b1d596-f2f2-4c1c-bd5b-a79c62d947ad',
|
||||
type: 'invocation',
|
||||
data: {
|
||||
id: '94b1d596-f2f2-4c1c-bd5b-a79c62d947ad',
|
||||
type: 'main_model_loader',
|
||||
version: '1.0.2',
|
||||
label: '',
|
||||
notes: '',
|
||||
isOpen: true,
|
||||
isIntermediate: true,
|
||||
useCache: true,
|
||||
inputs: {
|
||||
model: {
|
||||
name: 'model',
|
||||
label: '',
|
||||
value: {
|
||||
key: '2c85d9e7-12cd-4e59-bb94-96d4502e99d4',
|
||||
hash: 'random:aadc6641321ba17a324788ef1691f3584b382f0e7fa4a90be169f2a4ac77435c',
|
||||
name: 'Analog-Diffusion2',
|
||||
base: 'sd-1',
|
||||
type: 'main',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
position: { x: 394.62314170481613, y: -424.6962537790139 },
|
||||
},
|
||||
{
|
||||
id: 'afad11b4-bb5c-45d1-b956-6c8e2357ee11',
|
||||
type: 'invocation',
|
||||
data: {
|
||||
id: 'afad11b4-bb5c-45d1-b956-6c8e2357ee11',
|
||||
type: 'img_resize',
|
||||
version: '1.2.2',
|
||||
label: '',
|
||||
notes: '',
|
||||
isOpen: true,
|
||||
isIntermediate: true,
|
||||
useCache: true,
|
||||
inputs: {
|
||||
board: {
|
||||
name: 'board',
|
||||
label: '',
|
||||
value: { board_id: '99a08f09-8232-4b74-94a2-f8e136d62f8c' },
|
||||
},
|
||||
metadata: { name: 'metadata', label: 'Metadata' },
|
||||
image: {
|
||||
name: 'image',
|
||||
label: '',
|
||||
value: { image_name: '96c124c8-f62f-4d4f-9788-72218469f298.png' },
|
||||
},
|
||||
width: { name: 'width', label: '', value: 512 },
|
||||
height: { name: 'height', label: '', value: 512 },
|
||||
resample_mode: { name: 'resample_mode', label: '', value: 'bicubic' },
|
||||
},
|
||||
},
|
||||
position: { x: -46.428806920557236, y: -479.6641524207518 },
|
||||
},
|
||||
],
|
||||
edges: [],
|
||||
};
|
||||
const resolveTrue = async (): Promise<boolean> => new Promise((resolve) => resolve(true));
|
||||
const resolveFalse = async (): Promise<boolean> => new Promise((resolve) => resolve(false));
|
||||
it('should reset images that are inaccessible', async () => {
|
||||
const validationResult = await validateWorkflow(
|
||||
workflow,
|
||||
{ img_resize, main_model_loader },
|
||||
resolveFalse,
|
||||
resolveTrue,
|
||||
resolveTrue
|
||||
);
|
||||
expect(validationResult.warnings.length).toBe(1);
|
||||
expect(get(validationResult, 'workflow.nodes[1].data.inputs.image.value')).toBeUndefined();
|
||||
});
|
||||
it('should reset boards that are inaccessible', async () => {
|
||||
const validationResult = await validateWorkflow(
|
||||
workflow,
|
||||
{ img_resize, main_model_loader },
|
||||
resolveTrue,
|
||||
resolveFalse,
|
||||
resolveTrue
|
||||
);
|
||||
expect(validationResult.warnings.length).toBe(1);
|
||||
expect(get(validationResult, 'workflow.nodes[1].data.inputs.board.value')).toBeUndefined();
|
||||
});
|
||||
it('should reset models that are inaccessible', async () => {
|
||||
const validationResult = await validateWorkflow(
|
||||
workflow,
|
||||
{ img_resize, main_model_loader },
|
||||
resolveTrue,
|
||||
resolveTrue,
|
||||
resolveFalse
|
||||
);
|
||||
expect(validationResult.warnings.length).toBe(1);
|
||||
expect(get(validationResult, 'workflow.nodes[0].data.inputs.model.value')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { JSONObject } from 'common/types';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import type { Templates } from 'features/nodes/store/types';
|
||||
import {
|
||||
isBoardFieldInputInstance,
|
||||
isImageFieldInputInstance,
|
||||
isModelIdentifierFieldInputInstance,
|
||||
} from 'features/nodes/types/field';
|
||||
import type { WorkflowV3 } from 'features/nodes/types/workflow';
|
||||
import { isWorkflowInvocationNode } from 'features/nodes/types/workflow';
|
||||
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
|
||||
@@ -20,6 +25,18 @@ type ValidateWorkflowResult = {
|
||||
warnings: WorkflowWarning[];
|
||||
};
|
||||
|
||||
const MODEL_FIELD_TYPES = [
|
||||
'ModelIdentifier',
|
||||
'MainModelField',
|
||||
'SDXLMainModelField',
|
||||
'SDXLRefinerModelField',
|
||||
'VAEModelField',
|
||||
'LoRAModelField',
|
||||
'ControlNetModelField',
|
||||
'IPAdapterModelField',
|
||||
'T2IAdapterModelField',
|
||||
];
|
||||
|
||||
/**
|
||||
* Parses and validates a workflow:
|
||||
* - Parses the workflow schema, and migrates it to the latest version if necessary.
|
||||
@@ -27,11 +44,17 @@ type ValidateWorkflowResult = {
|
||||
* - Attempts to update nodes which have a mismatched version.
|
||||
* - Removes edges which are invalid.
|
||||
* @param workflow The raw workflow object (e.g. JSON.parse(stringifiedWorklow))
|
||||
* @param invocationTemplates The node templates to validate against.
|
||||
* @param templates The node templates to validate against.
|
||||
* @throws {WorkflowVersionError} If the workflow version is not recognized.
|
||||
* @throws {z.ZodError} If there is a validation error.
|
||||
*/
|
||||
export const validateWorkflow = (workflow: unknown, invocationTemplates: Templates): ValidateWorkflowResult => {
|
||||
export const validateWorkflow = async (
|
||||
workflow: unknown,
|
||||
templates: Templates,
|
||||
checkImageAccess: (name: string) => Promise<boolean>,
|
||||
checkBoardAccess: (id: string) => Promise<boolean>,
|
||||
checkModelAccess: (key: string) => Promise<boolean>
|
||||
): Promise<ValidateWorkflowResult> => {
|
||||
// Parse the raw workflow data & migrate it to the latest version
|
||||
const _workflow = parseAndMigrateWorkflow(workflow);
|
||||
|
||||
@@ -50,8 +73,8 @@ export const validateWorkflow = (workflow: unknown, invocationTemplates: Templat
|
||||
const invocationNodes = nodes.filter(isWorkflowInvocationNode);
|
||||
const keyedNodes = keyBy(invocationNodes, 'id');
|
||||
|
||||
invocationNodes.forEach((node) => {
|
||||
const template = invocationTemplates[node.data.type];
|
||||
for (const node of Object.values(invocationNodes)) {
|
||||
const template = templates[node.data.type];
|
||||
if (!template) {
|
||||
// This node's type template does not exist
|
||||
const message = t('nodes.missingTemplate', {
|
||||
@@ -62,7 +85,7 @@ export const validateWorkflow = (workflow: unknown, invocationTemplates: Templat
|
||||
message,
|
||||
data: parseify(node),
|
||||
});
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getNeedsUpdate(node.data, template)) {
|
||||
@@ -75,15 +98,59 @@ export const validateWorkflow = (workflow: unknown, invocationTemplates: Templat
|
||||
message,
|
||||
data: parseify({ node, nodeTemplate: template }),
|
||||
});
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
for (const input of Object.values(node.data.inputs)) {
|
||||
const fieldTemplate = template.inputs[input.name];
|
||||
|
||||
if (!fieldTemplate) {
|
||||
const message = t('nodes.missingFieldTemplate');
|
||||
warnings.push({
|
||||
message,
|
||||
data: parseify({ node, nodeTemplate: template, input }),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to confirm that all images, boards and models are accessible before loading,
|
||||
// else the workflow could end up with stale data an an error state.
|
||||
if (fieldTemplate.type.name === 'ImageField' && isImageFieldInputInstance(input) && input.value) {
|
||||
const hasAccess = await checkImageAccess(input.value.image_name);
|
||||
if (!hasAccess) {
|
||||
const message = t('nodes.imageAccessError', { image_name: input.value.image_name });
|
||||
warnings.push({ message, data: parseify({ node, nodeTemplate: template, input }) });
|
||||
input.value = undefined;
|
||||
}
|
||||
}
|
||||
if (fieldTemplate.type.name === 'BoardField' && isBoardFieldInputInstance(input) && input.value) {
|
||||
const hasAccess = await checkBoardAccess(input.value.board_id);
|
||||
if (!hasAccess) {
|
||||
const message = t('nodes.boardAccessError', { board_id: input.value.board_id });
|
||||
warnings.push({ message, data: parseify({ node, nodeTemplate: template, input }) });
|
||||
input.value = undefined;
|
||||
}
|
||||
}
|
||||
if (
|
||||
MODEL_FIELD_TYPES.includes(fieldTemplate.type.name) &&
|
||||
isModelIdentifierFieldInputInstance(input) &&
|
||||
input.value
|
||||
) {
|
||||
const hasAccess = await checkModelAccess(input.value.key);
|
||||
if (!hasAccess) {
|
||||
const message = t('nodes.modelAccessError', { key: input.value.key });
|
||||
warnings.push({ message, data: parseify({ node, nodeTemplate: template, input }) });
|
||||
input.value = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
edges.forEach((edge, i) => {
|
||||
// Validate each edge. If the edge is invalid, we must remove it to prevent runtime errors with reactflow.
|
||||
const sourceNode = keyedNodes[edge.source];
|
||||
const targetNode = keyedNodes[edge.target];
|
||||
const sourceTemplate = sourceNode ? invocationTemplates[sourceNode.data.type] : undefined;
|
||||
const targetTemplate = targetNode ? invocationTemplates[targetNode.data.type] : undefined;
|
||||
const sourceTemplate = sourceNode ? templates[sourceNode.data.type] : undefined;
|
||||
const targetTemplate = targetNode ? templates[targetNode.data.type] : undefined;
|
||||
const issues: string[] = [];
|
||||
|
||||
if (!sourceNode) {
|
||||
|
||||
@@ -39,7 +39,6 @@ export const ParamNegativePrompt = memo(() => {
|
||||
fontSize="sm"
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
|
||||
@@ -55,7 +55,6 @@ export const ParamPositivePrompt = memo(() => {
|
||||
onKeyDown={onKeyDown}
|
||||
variant="darkFilled"
|
||||
paddingRight={30}
|
||||
spellCheck={false}
|
||||
/>
|
||||
<PromptOverlayButtonWrapper>
|
||||
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import { iiLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
|
||||
import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import { t } from 'i18next';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
@@ -16,7 +16,6 @@ export const usePreselectedImage = (selectedImage?: {
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const optimalDimension = useAppSelector(selectOptimalDimension);
|
||||
const toaster = useAppToaster();
|
||||
|
||||
const { currentData: selectedImageDto } = useGetImageDTOQuery(selectedImage?.imageName ?? skipToken);
|
||||
|
||||
@@ -26,14 +25,13 @@ export const usePreselectedImage = (selectedImage?: {
|
||||
if (selectedImageDto) {
|
||||
dispatch(setInitialCanvasImage(selectedImageDto, optimalDimension));
|
||||
dispatch(setActiveTab('canvas'));
|
||||
toaster({
|
||||
toast({
|
||||
id: 'SENT_TO_CANVAS',
|
||||
title: t('toast.sentToUnifiedCanvas'),
|
||||
status: 'info',
|
||||
duration: 2500,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
}, [selectedImageDto, dispatch, optimalDimension, toaster]);
|
||||
}, [selectedImageDto, dispatch, optimalDimension]);
|
||||
|
||||
const handleSendToImg2Img = useCallback(() => {
|
||||
if (selectedImageDto) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCancelByBatchIdsMutation, useGetBatchStatusQuery } from 'services/api/endpoints/queue';
|
||||
@@ -23,7 +23,6 @@ export const useCancelBatch = (batch_id: string) => {
|
||||
const [trigger, { isLoading }] = useCancelByBatchIdsMutation({
|
||||
fixedCacheKey: 'cancelByBatchIds',
|
||||
});
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const cancelBatch = useCallback(async () => {
|
||||
if (isCanceled) {
|
||||
@@ -31,21 +30,19 @@ export const useCancelBatch = (batch_id: string) => {
|
||||
}
|
||||
try {
|
||||
await trigger({ batch_ids: [batch_id] }).unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelBatchSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_SUCCEEDED',
|
||||
title: t('queue.cancelBatchSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelBatchFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'CANCEL_BATCH_FAILED',
|
||||
title: t('queue.cancelBatchFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [batch_id, dispatch, isCanceled, t, trigger]);
|
||||
}, [batch_id, isCanceled, t, trigger]);
|
||||
|
||||
return { cancelBatch, isLoading, isCanceled, isDisabled: !isConnected };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -9,7 +9,6 @@ export const useCancelCurrentQueueItem = () => {
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const { data: queueStatus } = useGetQueueStatusQuery();
|
||||
const [trigger, { isLoading }] = useCancelQueueItemMutation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const currentQueueItemId = useMemo(() => queueStatus?.queue.item_id, [queueStatus?.queue.item_id]);
|
||||
const cancelQueueItem = useCallback(async () => {
|
||||
@@ -18,21 +17,19 @@ export const useCancelCurrentQueueItem = () => {
|
||||
}
|
||||
try {
|
||||
await trigger(currentQueueItemId).unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CANCEL_SUCCEEDED',
|
||||
title: t('queue.cancelSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CANCEL_FAILED',
|
||||
title: t('queue.cancelFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [currentQueueItemId, dispatch, t, trigger]);
|
||||
}, [currentQueueItemId, t, trigger]);
|
||||
|
||||
const isDisabled = useMemo(() => !isConnected || isNil(currentQueueItemId), [isConnected, currentQueueItemId]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
|
||||
@@ -7,26 +7,23 @@ import { useCancelQueueItemMutation } from 'services/api/endpoints/queue';
|
||||
export const useCancelQueueItem = (item_id: number) => {
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const [trigger, { isLoading }] = useCancelQueueItemMutation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const cancelQueueItem = useCallback(async () => {
|
||||
try {
|
||||
await trigger(item_id).unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CANCEL_SUCCEEDED',
|
||||
title: t('queue.cancelSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.cancelFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CANCEL_FAILED',
|
||||
title: t('queue.cancelFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [dispatch, item_id, t, trigger]);
|
||||
}, [item_id, t, trigger]);
|
||||
|
||||
return { cancelQueueItem, isLoading, isDisabled: !isConnected };
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useClearInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
|
||||
|
||||
export const useClearInvocationCache = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const [trigger, { isLoading }] = useClearInvocationCacheMutation({
|
||||
@@ -22,21 +21,19 @@ export const useClearInvocationCache = () => {
|
||||
|
||||
try {
|
||||
await trigger().unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('invocationCache.clearSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'INVOCATION_CACHE_CLEAR_SUCCEEDED',
|
||||
title: t('invocationCache.clearSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('invocationCache.clearFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'INVOCATION_CACHE_CLEAR_FAILED',
|
||||
title: t('invocationCache.clearFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [isDisabled, trigger, dispatch, t]);
|
||||
}, [isDisabled, trigger, t]);
|
||||
|
||||
return { clearInvocationCache, isLoading, cacheStatus, isDisabled };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { listCursorChanged, listPriorityChanged } from 'features/queue/store/queueSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useClearQueueMutation, useGetQueueStatusQuery } from 'services/api/endpoints/queue';
|
||||
@@ -21,21 +21,19 @@ export const useClearQueue = () => {
|
||||
|
||||
try {
|
||||
await trigger().unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.clearSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CLEAR_SUCCEEDED',
|
||||
title: t('queue.clearSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
dispatch(listCursorChanged(undefined));
|
||||
dispatch(listPriorityChanged(undefined));
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('queue.clearFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'QUEUE_CLEAR_FAILED',
|
||||
title: t('queue.clearFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [queueStatus?.queue.total, trigger, dispatch, t]);
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { toast } from 'features/toast/toast';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDisableInvocationCacheMutation, useGetInvocationCacheStatusQuery } from 'services/api/endpoints/appInfo';
|
||||
|
||||
export const useDisableInvocationCache = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: cacheStatus } = useGetInvocationCacheStatusQuery();
|
||||
const isConnected = useAppSelector((s) => s.system.isConnected);
|
||||
const [trigger, { isLoading }] = useDisableInvocationCacheMutation({
|
||||
@@ -25,21 +24,19 @@ export const useDisableInvocationCache = () => {
|
||||
|
||||
try {
|
||||
await trigger().unwrap();
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('invocationCache.disableSucceeded'),
|
||||
status: 'success',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'INVOCATION_CACHE_DISABLE_SUCCEEDED',
|
||||
title: t('invocationCache.disableSucceeded'),
|
||||
status: 'success',
|
||||
});
|
||||
} catch {
|
||||
dispatch(
|
||||
addToast({
|
||||
title: t('invocationCache.disableFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
);
|
||||
toast({
|
||||
id: 'INVOCATION_CACHE_DISABLE_FAILED',
|
||||
title: t('invocationCache.disableFailed'),
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
}, [isDisabled, trigger, dispatch, t]);
|
||||
}, [isDisabled, trigger, t]);
|
||||
|
||||
return { disableInvocationCache, isLoading, cacheStatus, isDisabled };
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user