mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into feat/model_manager/search-by-format
This commit is contained in:
commit
60e3e653fa
15
.github/pull_request_template.md
vendored
15
.github/pull_request_template.md
vendored
@ -42,6 +42,21 @@ Please provide steps on how to test changes, any hardware or
|
||||
software specifications as well as any other pertinent information.
|
||||
-->
|
||||
|
||||
## Merge Plan
|
||||
|
||||
<!--
|
||||
A merge plan describes how this PR should be handled after it is approved.
|
||||
|
||||
Example merge plans:
|
||||
- "This PR can be merged when approved"
|
||||
- "This must be squash-merged when approved"
|
||||
- "DO NOT MERGE - I will rebase and tidy commits before merging"
|
||||
- "#dev-chat on discord needs to be advised of this change when it is merged"
|
||||
|
||||
A merge plan is particularly important for large PRs or PRs that touch the
|
||||
database in any way.
|
||||
-->
|
||||
|
||||
## Added/updated tests?
|
||||
|
||||
- [ ] Yes
|
||||
|
@ -293,6 +293,19 @@ manager, please follow these steps:
|
||||
|
||||
## Developer Install
|
||||
|
||||
!!! warning
|
||||
|
||||
InvokeAI uses a SQLite database. By running on `main`, you accept responsibility for your database. This
|
||||
means making regular backups (especially before pulling) and/or fixing it yourself in the event that a
|
||||
PR introduces a schema change.
|
||||
|
||||
If you don't need persistent backend storage, you can use an ephemeral in-memory database by setting
|
||||
`use_memory_db: true` under `Path:` in your `invokeai.yaml` file.
|
||||
|
||||
If this is untenable, you should run the application via the official installer or a manual install of the
|
||||
python package from pypi. These releases will not break your database.
|
||||
|
||||
|
||||
If you have an interest in how InvokeAI works, or you would like to
|
||||
add features or bugfixes, you are encouraged to install the source
|
||||
code for InvokeAI. For this to work, you will need to install the
|
||||
@ -388,3 +401,5 @@ environment variable INVOKEAI_ROOT to point to the installation directory.
|
||||
|
||||
Note that if you run into problems with the Conda installation, the InvokeAI
|
||||
staff will **not** be able to help you out. Caveat Emptor!
|
||||
|
||||
[dev-chat]: https://discord.com/channels/1020123559063990373/1049495067846524939
|
10
docs/javascripts/init_kapa_widget.js
Normal file
10
docs/javascripts/init_kapa_widget.js
Normal file
@ -0,0 +1,10 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var script = document.createElement("script");
|
||||
script.src = "https://widget.kapa.ai/kapa-widget.bundle.js";
|
||||
script.setAttribute("data-website-id", "b5973bb1-476b-451e-8cf4-98de86745a10");
|
||||
script.setAttribute("data-project-name", "Invoke.AI");
|
||||
script.setAttribute("data-project-color", "#11213C");
|
||||
script.setAttribute("data-project-logo", "https://avatars.githubusercontent.com/u/113954515?s=280&v=4");
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
});
|
@ -13,7 +13,15 @@ from invokeai.app.shared.fields import FieldDescriptions
|
||||
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
|
||||
from invokeai.backend.image_util.safety_checker import SafetyChecker
|
||||
|
||||
from .baseinvocation import BaseInvocation, Input, InputField, InvocationContext, WithMetadata, invocation
|
||||
from .baseinvocation import (
|
||||
BaseInvocation,
|
||||
Classification,
|
||||
Input,
|
||||
InputField,
|
||||
InvocationContext,
|
||||
WithMetadata,
|
||||
invocation,
|
||||
)
|
||||
|
||||
|
||||
@invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0")
|
||||
@ -421,6 +429,64 @@ class ImageBlurInvocation(BaseInvocation, WithMetadata):
|
||||
)
|
||||
|
||||
|
||||
@invocation(
|
||||
"unsharp_mask",
|
||||
title="Unsharp Mask",
|
||||
tags=["image", "unsharp_mask"],
|
||||
category="image",
|
||||
version="1.2.0",
|
||||
classification=Classification.Beta,
|
||||
)
|
||||
class UnsharpMaskInvocation(BaseInvocation, WithMetadata):
|
||||
"""Applies an unsharp mask filter to an image"""
|
||||
|
||||
image: ImageField = InputField(description="The image to use")
|
||||
radius: float = InputField(gt=0, description="Unsharp mask radius", default=2)
|
||||
strength: float = InputField(ge=0, description="Unsharp mask strength", default=50)
|
||||
|
||||
def pil_from_array(self, arr):
|
||||
return Image.fromarray((arr * 255).astype("uint8"))
|
||||
|
||||
def array_from_pil(self, img):
|
||||
return numpy.array(img) / 255
|
||||
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
image = context.services.images.get_pil_image(self.image.image_name)
|
||||
mode = image.mode
|
||||
|
||||
alpha_channel = image.getchannel("A") if mode == "RGBA" else None
|
||||
image = image.convert("RGB")
|
||||
image_blurred = self.array_from_pil(image.filter(ImageFilter.GaussianBlur(radius=self.radius)))
|
||||
|
||||
image = self.array_from_pil(image)
|
||||
image += (image - image_blurred) * (self.strength / 100.0)
|
||||
image = numpy.clip(image, 0, 1)
|
||||
image = self.pil_from_array(image)
|
||||
|
||||
image = image.convert(mode)
|
||||
|
||||
# Make the image RGBA if we had a source alpha channel
|
||||
if alpha_channel is not None:
|
||||
image.putalpha(alpha_channel)
|
||||
|
||||
image_dto = context.services.images.create(
|
||||
image=image,
|
||||
image_origin=ResourceOrigin.INTERNAL,
|
||||
image_category=ImageCategory.GENERAL,
|
||||
node_id=self.id,
|
||||
session_id=context.graph_execution_state_id,
|
||||
is_intermediate=self.is_intermediate,
|
||||
metadata=self.metadata,
|
||||
workflow=context.workflow,
|
||||
)
|
||||
|
||||
return ImageOutput(
|
||||
image=ImageField(image_name=image_dto.image_name),
|
||||
width=image.width,
|
||||
height=image.height,
|
||||
)
|
||||
|
||||
|
||||
PIL_RESAMPLING_MODES = Literal[
|
||||
"nearest",
|
||||
"box",
|
||||
|
@ -38,7 +38,14 @@ class CalculateImageTilesOutput(BaseInvocationOutput):
|
||||
tiles: list[Tile] = OutputField(description="The tiles coordinates that cover a particular image shape.")
|
||||
|
||||
|
||||
@invocation("calculate_image_tiles", title="Calculate Image Tiles", tags=["tiles"], category="tiles", version="1.0.0")
|
||||
@invocation(
|
||||
"calculate_image_tiles",
|
||||
title="Calculate Image Tiles",
|
||||
tags=["tiles"],
|
||||
category="tiles",
|
||||
version="1.0.0",
|
||||
classification=Classification.Beta,
|
||||
)
|
||||
class CalculateImageTilesInvocation(BaseInvocation):
|
||||
"""Calculate the coordinates and overlaps of tiles that cover a target image shape."""
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
import sqlite3
|
||||
from logging import Logger
|
||||
|
||||
from pydantic import ValidationError
|
||||
from tqdm import tqdm
|
||||
|
||||
from invokeai.app.services.image_files.image_files_base import ImageFileStorageBase
|
||||
from invokeai.app.services.image_files.image_files_common import ImageFileNotFoundException
|
||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||
from invokeai.app.services.workflow_records.workflow_records_common import (
|
||||
UnsafeWorkflowWithVersionValidator,
|
||||
)
|
||||
|
||||
|
||||
class Migration2Callback:
|
||||
@ -134,7 +139,7 @@ class Migration2Callback:
|
||||
This migrate callback checks each image for the presence of an embedded workflow, then updates its entry
|
||||
in the database accordingly.
|
||||
"""
|
||||
# Get the total number of images and chunk it into pages
|
||||
# Get all image names
|
||||
cursor.execute("SELECT image_name FROM images")
|
||||
image_names: list[str] = [image[0] for image in cursor.fetchall()]
|
||||
total_image_names = len(image_names)
|
||||
@ -149,8 +154,17 @@ class Migration2Callback:
|
||||
pbar = tqdm(image_names)
|
||||
for idx, image_name in enumerate(pbar):
|
||||
pbar.set_description(f"Checking image {idx + 1}/{total_image_names} for workflow")
|
||||
pil_image = self._image_files.get(image_name)
|
||||
try:
|
||||
pil_image = self._image_files.get(image_name)
|
||||
except ImageFileNotFoundException:
|
||||
self._logger.warning(f"Image {image_name} not found, skipping")
|
||||
continue
|
||||
if "invokeai_workflow" in pil_image.info:
|
||||
try:
|
||||
UnsafeWorkflowWithVersionValidator.validate_json(pil_image.info.get("invokeai_workflow", ""))
|
||||
except ValidationError:
|
||||
self._logger.warning(f"Image {image_name} has invalid embedded workflow, skipping")
|
||||
continue
|
||||
to_migrate.append((True, image_name))
|
||||
|
||||
self._logger.info(f"Adding {len(to_migrate)} embedded workflows to database")
|
||||
|
@ -65,12 +65,24 @@ class WorkflowWithoutID(BaseModel):
|
||||
nodes: list[dict[str, JsonValue]] = Field(description="The nodes of the workflow.")
|
||||
edges: list[dict[str, JsonValue]] = Field(description="The edges of the workflow.")
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
|
||||
|
||||
WorkflowWithoutIDValidator = TypeAdapter(WorkflowWithoutID)
|
||||
|
||||
|
||||
class UnsafeWorkflowWithVersion(BaseModel):
|
||||
"""
|
||||
This utility model only requires a workflow to have a valid version string.
|
||||
It is used to validate a workflow version without having to validate the entire workflow.
|
||||
"""
|
||||
|
||||
meta: WorkflowMeta = Field(description="The meta of the workflow.")
|
||||
|
||||
|
||||
UnsafeWorkflowWithVersionValidator = TypeAdapter(UnsafeWorkflowWithVersion)
|
||||
|
||||
|
||||
class Workflow(WorkflowWithoutID):
|
||||
id: str = Field(description="The id of the workflow.")
|
||||
|
||||
|
@ -950,9 +950,9 @@
|
||||
"problemSettingTitle": "Problem Setting Title",
|
||||
"reloadNodeTemplates": "Reload Node Templates",
|
||||
"removeLinearView": "Remove from Linear View",
|
||||
"resetWorkflow": "Reset Workflow Editor",
|
||||
"resetWorkflowDesc": "Are you sure you want to reset the Workflow Editor?",
|
||||
"resetWorkflowDesc2": "Resetting the Workflow Editor will clear all nodes, edges and workflow details. Saved workflows will not be affected.",
|
||||
"newWorkflow": "New Workflow",
|
||||
"newWorkflowDesc": "Create a new workflow?",
|
||||
"newWorkflowDesc2": "Your current workflow has unsaved changes.",
|
||||
"scheduler": "Scheduler",
|
||||
"schedulerDescription": "TODO",
|
||||
"sDXLMainModelField": "SDXL Model",
|
||||
@ -1634,10 +1634,10 @@
|
||||
"userWorkflows": "My Workflows",
|
||||
"defaultWorkflows": "Default Workflows",
|
||||
"openWorkflow": "Open Workflow",
|
||||
"uploadWorkflow": "Upload Workflow",
|
||||
"uploadWorkflow": "Load from File",
|
||||
"deleteWorkflow": "Delete Workflow",
|
||||
"unnamedWorkflow": "Unnamed Workflow",
|
||||
"downloadWorkflow": "Download Workflow",
|
||||
"downloadWorkflow": "Save to File",
|
||||
"saveWorkflow": "Save Workflow",
|
||||
"saveWorkflowAs": "Save Workflow As",
|
||||
"savingWorkflow": "Saving Workflow...",
|
||||
@ -1652,7 +1652,7 @@
|
||||
"searchWorkflows": "Search Workflows",
|
||||
"clearWorkflowSearchFilter": "Clear Workflow Search Filter",
|
||||
"workflowName": "Workflow Name",
|
||||
"workflowEditorReset": "Workflow Editor Reset",
|
||||
"newWorkflowCreated": "New Workflow Created",
|
||||
"workflowEditorMenu": "Workflow Editor Menu",
|
||||
"workflowIsOpen": "Workflow is Open"
|
||||
},
|
||||
|
@ -104,7 +104,16 @@
|
||||
"copyError": "$t(gallery.copy) Errore",
|
||||
"input": "Ingresso",
|
||||
"notInstalled": "Non $t(common.installed)",
|
||||
"unknownError": "Errore sconosciuto"
|
||||
"unknownError": "Errore sconosciuto",
|
||||
"updated": "Aggiornato",
|
||||
"save": "Salva",
|
||||
"created": "Creato",
|
||||
"prevPage": "Pagina precedente",
|
||||
"delete": "Elimina",
|
||||
"orderBy": "Ordinato per",
|
||||
"nextPage": "Pagina successiva",
|
||||
"saveAs": "Salva come",
|
||||
"unsaved": "Non salvato"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generazioni",
|
||||
@ -763,7 +772,10 @@
|
||||
"setIPAdapterImage": "Imposta come immagine per l'Adattatore IP",
|
||||
"problemSavingMaskDesc": "Impossibile salvare la maschera",
|
||||
"setAsCanvasInitialImage": "Imposta come immagine iniziale della tela",
|
||||
"invalidUpload": "Caricamento non valido"
|
||||
"invalidUpload": "Caricamento non valido",
|
||||
"problemDeletingWorkflow": "Problema durante l'eliminazione del flusso di lavoro",
|
||||
"workflowDeleted": "Flusso di lavoro eliminato",
|
||||
"problemRetrievingWorkflow": "Problema nel recupero del flusso di lavoro"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@ -886,11 +898,11 @@
|
||||
"zoomInNodes": "Ingrandire",
|
||||
"fitViewportNodes": "Adatta vista",
|
||||
"showGraphNodes": "Mostra sovrapposizione grafico",
|
||||
"resetWorkflowDesc2": "Reimpostare il flusso di lavoro cancellerà tutti i nodi, i bordi e i dettagli del flusso di lavoro.",
|
||||
"resetWorkflowDesc2": "Il ripristino dell'editor del flusso di lavoro cancellerà tutti i nodi, le connessioni e i dettagli del flusso di lavoro. I flussi di lavoro salvati non saranno interessati.",
|
||||
"reloadNodeTemplates": "Ricarica i modelli di nodo",
|
||||
"loadWorkflow": "Importa flusso di lavoro JSON",
|
||||
"resetWorkflow": "Reimposta flusso di lavoro",
|
||||
"resetWorkflowDesc": "Sei sicuro di voler reimpostare questo flusso di lavoro?",
|
||||
"resetWorkflow": "Reimposta l'editor del flusso di lavoro",
|
||||
"resetWorkflowDesc": "Sei sicuro di voler reimpostare l'editor del flusso di lavoro?",
|
||||
"downloadWorkflow": "Esporta flusso di lavoro JSON",
|
||||
"scheduler": "Campionatore",
|
||||
"addNode": "Aggiungi nodo",
|
||||
@ -1080,25 +1092,27 @@
|
||||
"collectionOrScalarFieldType": "{{name}} Raccolta|Scalare",
|
||||
"nodeVersion": "Versione Nodo",
|
||||
"inputFieldTypeParseError": "Impossibile analizzare il tipo di campo di input {{node}}.{{field}} ({{message}})",
|
||||
"unsupportedArrayItemType": "tipo di elemento dell'array non supportato \"{{type}}\"",
|
||||
"unsupportedArrayItemType": "Tipo di elemento dell'array non supportato \"{{type}}\"",
|
||||
"targetNodeFieldDoesNotExist": "Connessione non valida: il campo di destinazione/input {{node}}.{{field}} non esiste",
|
||||
"unsupportedMismatchedUnion": "tipo CollectionOrScalar non corrispondente con tipi di base {{firstType}} e {{secondType}}",
|
||||
"allNodesUpdated": "Tutti i nodi sono aggiornati",
|
||||
"sourceNodeDoesNotExist": "Connessione non valida: il nodo di origine/output {{node}} non esiste",
|
||||
"unableToExtractEnumOptions": "impossibile estrarre le opzioni enum",
|
||||
"unableToParseFieldType": "impossibile analizzare il tipo di campo",
|
||||
"unableToExtractEnumOptions": "Impossibile estrarre le opzioni enum",
|
||||
"unableToParseFieldType": "Impossibile analizzare il tipo di campo",
|
||||
"unrecognizedWorkflowVersion": "Versione dello schema del flusso di lavoro non riconosciuta {{version}}",
|
||||
"outputFieldTypeParseError": "Impossibile analizzare il tipo di campo di output {{node}}.{{field}} ({{message}})",
|
||||
"sourceNodeFieldDoesNotExist": "Connessione non valida: il campo di origine/output {{node}}.{{field}} non esiste",
|
||||
"unableToGetWorkflowVersion": "Impossibile ottenere la versione dello schema del flusso di lavoro",
|
||||
"nodePack": "Pacchetto di nodi",
|
||||
"unableToExtractSchemaNameFromRef": "impossibile estrarre il nome dello schema dal riferimento",
|
||||
"unableToExtractSchemaNameFromRef": "Impossibile estrarre il nome dello schema dal riferimento",
|
||||
"unknownOutput": "Output sconosciuto: {{name}}",
|
||||
"unknownNodeType": "Tipo di nodo sconosciuto",
|
||||
"targetNodeDoesNotExist": "Connessione non valida: il nodo di destinazione/input {{node}} non esiste",
|
||||
"unknownFieldType": "$t(nodes.unknownField) tipo: {{type}}",
|
||||
"deletedInvalidEdge": "Eliminata connessione non valida {{source}} -> {{target}}",
|
||||
"unknownInput": "Input sconosciuto: {{name}}"
|
||||
"unknownInput": "Input sconosciuto: {{name}}",
|
||||
"prototypeDesc": "Questa invocazione è un prototipo. Potrebbe subire modifiche sostanziali durante gli aggiornamenti dell'app e potrebbe essere rimossa in qualsiasi momento.",
|
||||
"betaDesc": "Questa invocazione è in versione beta. Fino a quando non sarà stabile, potrebbe subire modifiche importanti durante gli aggiornamenti dell'app. Abbiamo intenzione di supportare questa invocazione a lungo termine."
|
||||
},
|
||||
"boards": {
|
||||
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
||||
@ -1594,5 +1608,34 @@
|
||||
"hrf": "Correzione Alta Risoluzione",
|
||||
"hrfStrength": "Forza della Correzione Alta Risoluzione",
|
||||
"strengthTooltip": "Valori più bassi comportano meno dettagli, il che può ridurre potenziali artefatti."
|
||||
},
|
||||
"workflows": {
|
||||
"saveWorkflowAs": "Salva flusso di lavoro come",
|
||||
"workflowEditorMenu": "Menu dell'editor del flusso di lavoro",
|
||||
"noSystemWorkflows": "Nessun flusso di lavoro del sistema",
|
||||
"workflowName": "Nome del flusso di lavoro",
|
||||
"noUserWorkflows": "Nessun flusso di lavoro utente",
|
||||
"defaultWorkflows": "Flussi di lavoro predefiniti",
|
||||
"saveWorkflow": "Salva flusso di lavoro",
|
||||
"openWorkflow": "Apri flusso di lavoro",
|
||||
"clearWorkflowSearchFilter": "Cancella il filtro di ricerca del flusso di lavoro",
|
||||
"workflowEditorReset": "Reimpostazione dell'editor del flusso di lavoro",
|
||||
"workflowLibrary": "Libreria",
|
||||
"noRecentWorkflows": "Nessun flusso di lavoro recente",
|
||||
"workflowSaved": "Flusso di lavoro salvato",
|
||||
"workflowIsOpen": "Il flusso di lavoro è aperto",
|
||||
"unnamedWorkflow": "Flusso di lavoro senza nome",
|
||||
"savingWorkflow": "Salvataggio del flusso di lavoro...",
|
||||
"problemLoading": "Problema durante il caricamento dei flussi di lavoro",
|
||||
"loading": "Caricamento dei flussi di lavoro",
|
||||
"searchWorkflows": "Cerca flussi di lavoro",
|
||||
"problemSavingWorkflow": "Problema durante il salvataggio del flusso di lavoro",
|
||||
"deleteWorkflow": "Elimina flusso di lavoro",
|
||||
"workflows": "Flussi di lavoro",
|
||||
"noDescription": "Nessuna descrizione",
|
||||
"userWorkflows": "I miei flussi di lavoro"
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "Il negozio non è inizializzato"
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -110,7 +110,17 @@
|
||||
"copyError": "$t(gallery.copy) 错误",
|
||||
"input": "输入",
|
||||
"notInstalled": "非 $t(common.installed)",
|
||||
"delete": "删除"
|
||||
"delete": "删除",
|
||||
"updated": "已上传",
|
||||
"save": "保存",
|
||||
"created": "已创建",
|
||||
"prevPage": "上一页",
|
||||
"unknownError": "未知错误",
|
||||
"direction": "指向",
|
||||
"orderBy": "排序方式:",
|
||||
"nextPage": "下一页",
|
||||
"saveAs": "保存为",
|
||||
"unsaved": "未保存"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "生成的图像",
|
||||
@ -146,7 +156,11 @@
|
||||
"image": "图像",
|
||||
"drop": "弃用",
|
||||
"dropOrUpload": "$t(gallery.drop) 或上传",
|
||||
"dropToUpload": "$t(gallery.drop) 以上传"
|
||||
"dropToUpload": "$t(gallery.drop) 以上传",
|
||||
"problemDeletingImagesDesc": "有一张或多张图像无法被删除",
|
||||
"problemDeletingImages": "删除图像时出现问题",
|
||||
"unstarImage": "取消收藏图像",
|
||||
"starImage": "收藏图像"
|
||||
},
|
||||
"hotkeys": {
|
||||
"keyboardShortcuts": "键盘快捷键",
|
||||
@ -724,7 +738,7 @@
|
||||
"nodesUnrecognizedTypes": "无法加载。节点图有无法识别的节点类型",
|
||||
"nodesNotValidJSON": "无效的 JSON",
|
||||
"nodesNotValidGraph": "无效的 InvokeAi 节点图",
|
||||
"nodesLoadedFailed": "节点图加载失败",
|
||||
"nodesLoadedFailed": "节点加载失败",
|
||||
"modelAddedSimple": "已添加模型",
|
||||
"modelAdded": "已添加模型: {{modelName}}",
|
||||
"imageSavingFailed": "图像保存失败",
|
||||
@ -760,7 +774,10 @@
|
||||
"problemImportingMask": "导入遮罩时出现问题",
|
||||
"baseModelChangedCleared_other": "基础模型已更改, 已清除或禁用 {{count}} 个不兼容的子模型",
|
||||
"setAsCanvasInitialImage": "设为画布初始图像",
|
||||
"invalidUpload": "无效的上传"
|
||||
"invalidUpload": "无效的上传",
|
||||
"problemDeletingWorkflow": "删除工作流时出现问题",
|
||||
"workflowDeleted": "已删除工作流",
|
||||
"problemRetrievingWorkflow": "检索工作流时发生问题"
|
||||
},
|
||||
"unifiedCanvas": {
|
||||
"layer": "图层",
|
||||
@ -875,11 +892,11 @@
|
||||
},
|
||||
"nodes": {
|
||||
"zoomInNodes": "放大",
|
||||
"resetWorkflowDesc": "是否确定要清空节点图?",
|
||||
"resetWorkflow": "清空节点图",
|
||||
"loadWorkflow": "读取节点图",
|
||||
"resetWorkflowDesc": "是否确定要重置工作流编辑器?",
|
||||
"resetWorkflow": "重置工作流编辑器",
|
||||
"loadWorkflow": "加载工作流",
|
||||
"zoomOutNodes": "缩小",
|
||||
"resetWorkflowDesc2": "重置节点图将清除所有节点、边际和节点图详情.",
|
||||
"resetWorkflowDesc2": "重置工作流编辑器将清除所有节点、边际和节点图详情。不影响已保存的工作流。",
|
||||
"reloadNodeTemplates": "重载节点模板",
|
||||
"hideGraphNodes": "隐藏节点图信息",
|
||||
"fitViewportNodes": "自适应视图",
|
||||
@ -888,7 +905,7 @@
|
||||
"showLegendNodes": "显示字段类型图例",
|
||||
"hideLegendNodes": "隐藏字段类型图例",
|
||||
"showGraphNodes": "显示节点图信息",
|
||||
"downloadWorkflow": "下载节点图 JSON",
|
||||
"downloadWorkflow": "下载工作流 JSON",
|
||||
"workflowDescription": "简述",
|
||||
"versionUnknown": " 未知版本",
|
||||
"noNodeSelected": "无选中的节点",
|
||||
@ -1103,7 +1120,9 @@
|
||||
"collectionOrScalarFieldType": "{{name}} 合集 | 标量",
|
||||
"nodeVersion": "节点版本",
|
||||
"deletedInvalidEdge": "已删除无效的边缘 {{source}} -> {{target}}",
|
||||
"unknownInput": "未知输入:{{name}}"
|
||||
"unknownInput": "未知输入:{{name}}",
|
||||
"prototypeDesc": "此调用是一个原型 (prototype)。它可能会在本项目更新期间发生破坏性更改,并且随时可能被删除。",
|
||||
"betaDesc": "此调用尚处于测试阶段。在稳定之前,它可能会在项目更新期间发生破坏性更改。本项目计划长期支持这种调用。"
|
||||
},
|
||||
"controlnet": {
|
||||
"resize": "直接缩放",
|
||||
@ -1607,5 +1626,36 @@
|
||||
"hrf": "高分辨率修复",
|
||||
"hrfStrength": "高分辨率修复强度",
|
||||
"strengthTooltip": "值越低细节越少,但可以减少部分潜在的伪影。"
|
||||
},
|
||||
"workflows": {
|
||||
"saveWorkflowAs": "保存工作流为",
|
||||
"workflowEditorMenu": "工作流编辑器菜单",
|
||||
"noSystemWorkflows": "无系统工作流",
|
||||
"workflowName": "工作流名称",
|
||||
"noUserWorkflows": "无用户工作流",
|
||||
"defaultWorkflows": "默认工作流",
|
||||
"saveWorkflow": "保存工作流",
|
||||
"openWorkflow": "打开工作流",
|
||||
"clearWorkflowSearchFilter": "清除工作流检索过滤器",
|
||||
"workflowEditorReset": "工作流编辑器重置",
|
||||
"workflowLibrary": "工作流库",
|
||||
"downloadWorkflow": "下载工作流",
|
||||
"noRecentWorkflows": "无最近工作流",
|
||||
"workflowSaved": "已保存工作流",
|
||||
"workflowIsOpen": "工作流已打开",
|
||||
"unnamedWorkflow": "未命名的工作流",
|
||||
"savingWorkflow": "保存工作流中...",
|
||||
"problemLoading": "加载工作流时出现问题",
|
||||
"loading": "加载工作流中",
|
||||
"searchWorkflows": "检索工作流",
|
||||
"problemSavingWorkflow": "保存工作流时出现问题",
|
||||
"deleteWorkflow": "删除工作流",
|
||||
"workflows": "工作流",
|
||||
"noDescription": "无描述",
|
||||
"uploadWorkflow": "上传工作流",
|
||||
"userWorkflows": "我的工作流"
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "商店尚未初始化"
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,10 @@ const migrateV1toV2 = (workflowToMigrate: WorkflowV1): WorkflowV2 => {
|
||||
const nodePack = invocationTemplate
|
||||
? invocationTemplate.nodePack
|
||||
: t('common.unknown');
|
||||
|
||||
(node.data as unknown as InvocationNodeData).nodePack = nodePack;
|
||||
// Fallback to 1.0.0 if not specified - this matches the behavior of the backend
|
||||
node.data.version ||= '1.0.0';
|
||||
}
|
||||
});
|
||||
// Bump version
|
||||
|
@ -11,44 +11,48 @@ import {
|
||||
Text,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
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 { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaTrash } from 'react-icons/fa';
|
||||
import { FaCircleNodes } from 'react-icons/fa6';
|
||||
|
||||
const ResetWorkflowEditorMenuItem = () => {
|
||||
const NewWorkflowMenuItem = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||
const isTouched = useAppSelector((state) => state.workflow.isTouched);
|
||||
|
||||
const handleConfirmClear = useCallback(() => {
|
||||
const handleNewWorkflow = useCallback(() => {
|
||||
dispatch(nodeEditorReset());
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('workflows.workflowEditorReset'),
|
||||
title: t('workflows.newWorkflowCreated'),
|
||||
status: 'success',
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onClose();
|
||||
}, [dispatch, t, onClose]);
|
||||
}, [dispatch, onClose, t]);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (!isTouched) {
|
||||
handleNewWorkflow();
|
||||
return;
|
||||
}
|
||||
onOpen();
|
||||
}, [handleNewWorkflow, isTouched, onOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
as="button"
|
||||
icon={<FaTrash />}
|
||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{t('nodes.resetWorkflow')}
|
||||
<MenuItem as="button" icon={<FaCircleNodes />} onClick={onClick}>
|
||||
{t('nodes.newWorkflow')}
|
||||
</MenuItem>
|
||||
|
||||
<AlertDialog
|
||||
@ -61,13 +65,13 @@ const ResetWorkflowEditorMenuItem = () => {
|
||||
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||
{t('nodes.resetWorkflow')}
|
||||
{t('nodes.newWorkflow')}
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogBody py={4}>
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<Text>{t('nodes.resetWorkflowDesc')}</Text>
|
||||
<Text variant="subtext">{t('nodes.resetWorkflowDesc2')}</Text>
|
||||
<Text>{t('nodes.newWorkflowDesc')}</Text>
|
||||
<Text variant="subtext">{t('nodes.newWorkflowDesc2')}</Text>
|
||||
</Flex>
|
||||
</AlertDialogBody>
|
||||
|
||||
@ -75,7 +79,7 @@ const ResetWorkflowEditorMenuItem = () => {
|
||||
<Button ref={cancelRef} onClick={onClose}>
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button colorScheme="error" ml={3} onClick={handleConfirmClear}>
|
||||
<Button colorScheme="error" ml={3} onClick={handleNewWorkflow}>
|
||||
{t('common.accept')}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
@ -85,4 +89,4 @@ const ResetWorkflowEditorMenuItem = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ResetWorkflowEditorMenuItem);
|
||||
export default memo(NewWorkflowMenuItem);
|
@ -9,7 +9,7 @@ import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useGlobalMenuCloseTrigger } from 'common/hooks/useGlobalMenuCloseTrigger';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import DownloadWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/DownloadWorkflowMenuItem';
|
||||
import ResetWorkflowEditorMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/ResetWorkflowEditorMenuItem';
|
||||
import NewWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/NewWorkflowMenuItem';
|
||||
import SaveWorkflowAsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowAsMenuItem';
|
||||
import SaveWorkflowMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SaveWorkflowMenuItem';
|
||||
import SettingsMenuItem from 'features/workflowLibrary/components/WorkflowLibraryMenu/SettingsMenuItem';
|
||||
@ -39,7 +39,7 @@ const WorkflowLibraryMenu = () => {
|
||||
{isWorkflowLibraryEnabled && <SaveWorkflowAsMenuItem />}
|
||||
<DownloadWorkflowMenuItem />
|
||||
<UploadWorkflowMenuItem />
|
||||
<ResetWorkflowEditorMenuItem />
|
||||
<NewWorkflowMenuItem />
|
||||
<MenuDivider />
|
||||
<SettingsMenuItem />
|
||||
</MenuList>
|
||||
|
@ -101,6 +101,8 @@ plugins:
|
||||
extra_javascript:
|
||||
- https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js
|
||||
- javascripts/tablesort.js
|
||||
- https://widget.kapa.ai/kapa-widget.bundle.js
|
||||
- javascript/init_kapa_widget.js
|
||||
|
||||
extra:
|
||||
analytics:
|
||||
|
Loading…
Reference in New Issue
Block a user