diff --git a/invokeai/app/services/shared/graph.py b/invokeai/app/services/shared/graph.py index fd31448ea4..3ea67a5291 100644 --- a/invokeai/app/services/shared/graph.py +++ b/invokeai/app/services/shared/graph.py @@ -123,6 +123,23 @@ def is_any(t: Any) -> bool: return t == Any or Any in get_args(t) +def extract_collection_item_types(t: Any) -> set[Any]: + """Extracts list item types from a collection annotation, including unions containing list branches.""" + if is_any(t): + return {Any} + + if get_origin(t) is list: + return {arg for arg in get_args(t) if arg != NoneType} + + item_types: set[Any] = set() + for arg in get_args(t): + if is_any(arg): + item_types.add(Any) + elif get_origin(arg) is list: + item_types.update(item_arg for item_arg in get_args(arg) if item_arg != NoneType) + return item_types + + def are_connection_types_compatible(from_type: Any, to_type: Any) -> bool: if not from_type or not to_type: return False @@ -280,7 +297,7 @@ class CollectInvocationOutput(BaseInvocationOutput): ) -@invocation("collect", version="1.0.0") +@invocation("collect", version="1.1.0") class CollectInvocation(BaseInvocation): """Collects values into a collection""" @@ -292,7 +309,10 @@ class CollectInvocation(BaseInvocation): input=Input.Connection, ) collection: list[Any] = InputField( - description="The collection, will be provided on execution", default=[], ui_hidden=True + description="An optional collection to append to", + default=[], + ui_type=UIType._Collection, + input=Input.Connection, ) def invoke(self, context: InvocationContext) -> CollectInvocationOutput: @@ -520,7 +540,9 @@ class Graph(BaseModel): # Validate that an edge to this node+field doesn't already exist input_edges = self._get_input_edges(edge.destination.node_id, edge.destination.field) - if len(input_edges) > 0 and not isinstance(to_node, CollectInvocation): + if len(input_edges) > 0 and ( + not isinstance(to_node, CollectInvocation) or edge.destination.field != ITEM_FIELD + ): raise InvalidEdgeError(f"Edge already exists ({edge})") # Validate that no cycles would be created @@ -546,8 +568,10 @@ class Graph(BaseModel): raise InvalidEdgeError(f"Iterator output type does not match iterator input type ({edge}): {err}") # Validate if collector input type matches output type (if this edge results in both being set) - if isinstance(to_node, CollectInvocation) and edge.destination.field == ITEM_FIELD: - err = self._is_collector_connection_valid(edge.destination.node_id, new_input=edge.source) + if isinstance(to_node, CollectInvocation) and edge.destination.field in (ITEM_FIELD, COLLECTION_FIELD): + err = self._is_collector_connection_valid( + edge.destination.node_id, new_input=edge.source, new_input_field=edge.destination.field + ) if err is not None: raise InvalidEdgeError(f"Collector output type does not match collector input type ({edge}): {err}") @@ -676,76 +700,152 @@ class Graph(BaseModel): # Collector input type must match all iterator output types if isinstance(input_node, CollectInvocation): - collector_inputs = self._get_input_edges(input_node.id, ITEM_FIELD) - if len(collector_inputs) == 0: - return "Iterator input collector must have at least one item input edge" - - # Traverse the graph to find the first collector input edge. Collectors validate that their collection - # inputs are all of the same type, so we can use the first input edge to determine the collector's type - first_collector_input_edge = collector_inputs[0] - first_collector_input_type = get_output_field_type( - self.get_node(first_collector_input_edge.source.node_id), first_collector_input_edge.source.field - ) - resolved_collector_type = ( - first_collector_input_type - if get_origin(first_collector_input_type) is None - else get_args(first_collector_input_type) - ) - if not all((are_connection_types_compatible(resolved_collector_type, t) for t in output_field_types)): + input_root_type = self._get_collector_input_root_type(input_node.id) + if input_root_type is None: + return "Iterator input collector must have at least one item or collection input edge" + if not all((are_connection_types_compatible(input_root_type, t) for t in output_field_types)): return "Iterator collection type must match all iterator output types" return None + def _resolve_collector_input_types(self, node_id: str, visited: Optional[set[str]] = None) -> set[Any]: + """Resolves possible item types for a collector's inputs, recursively following chained collectors.""" + visited = visited or set() + if node_id in visited: + return set() + visited.add(node_id) + + input_types: set[Any] = set() + + for edge in self._get_input_edges(node_id, ITEM_FIELD): + input_field_type = get_output_field_type(self.get_node(edge.source.node_id), edge.source.field) + resolved_types = [input_field_type] if get_origin(input_field_type) is None else get_args(input_field_type) + input_types.update(t for t in resolved_types if t != NoneType) + + for edge in self._get_input_edges(node_id, COLLECTION_FIELD): + source_node = self.get_node(edge.source.node_id) + if isinstance(source_node, CollectInvocation) and edge.source.field == COLLECTION_FIELD: + input_types.update(self._resolve_collector_input_types(source_node.id, visited.copy())) + continue + + input_field_type = get_output_field_type(source_node, edge.source.field) + input_types.update(extract_collection_item_types(input_field_type)) + + return input_types + + def _get_collector_input_root_type(self, node_id: str) -> Any | None: + input_types = self._resolve_collector_input_types(node_id) + non_any_input_types = {t for t in input_types if t != Any} + if len(non_any_input_types) == 0 and Any in input_types: + return Any + if len(non_any_input_types) == 0: + return None + + type_tree = nx.DiGraph() + type_tree.add_nodes_from(non_any_input_types) + type_tree.add_edges_from([e for e in itertools.permutations(non_any_input_types, 2) if issubclass(e[1], e[0])]) + type_degrees = type_tree.in_degree(type_tree.nodes) + root_types = [t[0] for t in type_degrees if t[1] == 0] # type: ignore + if len(root_types) != 1: + return Any + return root_types[0] + def _is_collector_connection_valid( self, node_id: str, new_input: Optional[EdgeConnection] = None, + new_input_field: Optional[str] = None, new_output: Optional[EdgeConnection] = None, ) -> str | None: - inputs = [e.source for e in self._get_input_edges(node_id, ITEM_FIELD)] + item_inputs = [e.source for e in self._get_input_edges(node_id, ITEM_FIELD)] + collection_inputs = [e.source for e in self._get_input_edges(node_id, COLLECTION_FIELD)] outputs = [e.destination for e in self._get_output_edges(node_id, COLLECTION_FIELD)] if new_input is not None: - inputs.append(new_input) + field = new_input_field or ITEM_FIELD + if field == ITEM_FIELD: + item_inputs.append(new_input) + elif field == COLLECTION_FIELD: + collection_inputs.append(new_input) if new_output is not None: outputs.append(new_output) - # Get input and output fields (the fields linked to the iterator's input/output) - input_field_types = [get_output_field_type(self.get_node(e.node_id), e.field) for e in inputs] + if len(item_inputs) == 0 and len(collection_inputs) == 0: + return "Collector must have at least one item or collection input edge" + + # Get input and output fields (the fields linked to the collector's input/output) + item_input_field_types = [get_output_field_type(self.get_node(e.node_id), e.field) for e in item_inputs] + collection_input_field_types = [ + get_output_field_type(self.get_node(e.node_id), e.field) for e in collection_inputs + ] output_field_types = [get_input_field_type(self.get_node(e.node_id), e.field) for e in outputs] + if not all((is_list_or_contains_list(t) or is_any(t) for t in collection_input_field_types)): + return "Collector collection input must be a collection" + # Validate that all inputs are derived from or match a single type input_field_types = { resolved_type - for input_field_type in input_field_types + for input_field_type in item_input_field_types for resolved_type in ( [input_field_type] if get_origin(input_field_type) is None else get_args(input_field_type) ) if resolved_type != NoneType } # Get unique types + + for input_conn, input_field_type in zip(collection_inputs, collection_input_field_types, strict=False): + source_node = self.get_node(input_conn.node_id) + if isinstance(source_node, CollectInvocation) and input_conn.field == COLLECTION_FIELD: + input_field_types.update(self._resolve_collector_input_types(source_node.id)) + continue + input_field_types.update(extract_collection_item_types(input_field_type)) + + non_any_input_field_types = {t for t in input_field_types if t != Any} type_tree = nx.DiGraph() - type_tree.add_nodes_from(input_field_types) - type_tree.add_edges_from([e for e in itertools.permutations(input_field_types, 2) if issubclass(e[1], e[0])]) + type_tree.add_nodes_from(non_any_input_field_types) + type_tree.add_edges_from( + [e for e in itertools.permutations(non_any_input_field_types, 2) if issubclass(e[1], e[0])] + ) type_degrees = type_tree.in_degree(type_tree.nodes) - if sum((t[1] == 0 for t in type_degrees)) != 1: # type: ignore + root_types = [t[0] for t in type_degrees if t[1] == 0] # type: ignore + if len(root_types) > 1: return "Collector input collection items must be of a single type" - # Get the input root type - input_root_type = next(t[0] for t in type_degrees if t[1] == 0) # type: ignore + # Get the input root type (if known) + input_root_type = root_types[0] if len(root_types) == 1 else None # Verify that all outputs are lists if not all(is_list_or_contains_list(t) or is_any(t) for t in output_field_types): return "Collector output must connect to a collection input" # Verify that all outputs match the input type (are a base class or the same class) - if not all( - is_any(t) - or is_union_subtype(input_root_type, get_args(t)[0]) - or issubclass(input_root_type, get_args(t)[0]) - for t in output_field_types - ): + if input_root_type is not None: + if not all( + is_any(t) + or is_union_subtype(input_root_type, get_args(t)[0]) + or issubclass(input_root_type, get_args(t)[0]) + for t in output_field_types + ): + return "Collector outputs must connect to a collection input with a matching type" + elif any(not is_any(t) and get_args(t)[0] != Any for t in output_field_types): return "Collector outputs must connect to a collection input with a matching type" + # If this collector outputs to another collector's collection input, validate against the downstream + # collector's resolved input type (if available). + for output in outputs: + output_node = self.get_node(output.node_id) + if not isinstance(output_node, CollectInvocation) or output.field != COLLECTION_FIELD: + continue + output_root_type = self._get_collector_input_root_type(output_node.id) + if output_root_type is None: + continue + if input_root_type is None: + if output_root_type != Any: + return "Collector outputs must connect to a collection input with a matching type" + continue + if not are_connection_types_compatible(input_root_type, output_root_type): + return "Collector outputs must connect to a collection input with a matching type" + return None def nx_graph(self) -> nx.DiGraph: @@ -1211,8 +1311,19 @@ class GraphExecutionState(BaseModel): if isinstance(node, CollectInvocation): item_edges = [e for e in input_edges if e.destination.field == ITEM_FIELD] item_edges.sort(key=lambda e: (self._get_iteration_path(e.source.node_id), e.source.node_id)) + collection_edges = [e for e in input_edges if e.destination.field == COLLECTION_FIELD] + collection_edges.sort(key=lambda e: (self._get_iteration_path(e.source.node_id), e.source.node_id)) - output_collection = [copydeep(getattr(self.results[e.source.node_id], e.source.field)) for e in item_edges] + output_collection = [] + for edge in collection_edges: + source_value = copydeep(getattr(self.results[edge.source.node_id], edge.source.field)) + if isinstance(source_value, list): + output_collection.extend(source_value) + else: + output_collection.append(source_value) + output_collection.extend( + copydeep(getattr(self.results[e.source.node_id], e.source.field)) for e in item_edges + ) node.collection = output_collection else: for edge in input_edges: diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 3891f4874e..64713a0d84 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -3108,9 +3108,9 @@ "whatsNew": { "whatsNewInInvoke": "What's New in Invoke", "items": [ - "FLUX.2 Klein Support: InvokeAI now supports the new FLUX.2 Klein models (4B and 9B variants) with GGUF, FP8, and Diffusers formats. Features include txt2img, img2img, inpainting, and outpainting. See 'Starter Models' to get started.", - "DyPE support for FLUX models improves high-resolution (>1536 px up to 4K) images. Go to the 'Advanced Options' section to activate.", - "Z-Image Turbo diversity: Active 'Seed Variance Enhancer' under 'Advanced Options' to add diversitiy to your ZiT gens." + "Multi-user mode supports multiple isolated users on the same server.", + "Enhanced support for Z-Image and FLUX.2 Models.", + "Multiple user interface enhancements and new canvas features." ], "takeUserSurvey": "📣 Let us know how you like InvokeAI. Take our User Experience Survey!", "readReleaseNotes": "Read Release Notes", diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index 7a6dafe4c7..cafd91d2a4 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -2639,7 +2639,7 @@ "desc": "Seleziona un singolo oggetto di destinazione. Una volta completata la selezione, fai clic su Applica per eliminare tutto ciò che si trova al di fuori dell'area selezionata, oppure salva la selezione come nuovo livello.", "visualModeDesc": "La modalità visiva utilizza input di tipo riquadro e punto per selezionare un oggetto.", "visualMode1": "Fai clic e trascina per disegnare un riquadro attorno all'oggetto che desideri selezionare. Puoi ottenere risultati migliori disegnando il riquadro un po' più grande o più piccolo dell'oggetto.", - "visualMode2": "Fare clic per aggiungere un punto di iinclusionei verde oppure fare clic tenendo premuto Maiusc per aggiungere un punto di iesclusionei rosso per indicare al modello cosa includere o escludere.", + "visualMode2": "Fai clic per aggiungere un punto verde includi oppure fai clic tenendo premuto il tasto Maiusc per aggiungere un punto rosso escludi per indicare al modello cosa includere o escludere.", "visualMode3": "I punti possono essere utilizzati per perfezionare una selezione di caselle oppure in modo indipendente.", "promptModeDesc": "La modalità Prompt utilizza l'input di testo per selezionare un oggetto.", "promptMode1": "Digitare una breve descrizione dell'oggetto che si desidera selezionare.", @@ -3089,7 +3089,8 @@ "passwordsDoNotMatch": "Le password non corrispondono", "createAccount": "Crea un account amministratore", "creatingAccount": "Impostazione in corso...", - "setupFailed": "Installazione non riuscita. Riprova." + "setupFailed": "Installazione non riuscita. Riprova.", + "passwordHelperRelaxed": "Inserisci una password qualsiasi (verrà visualizzata la sua robustezza)" }, "userMenu": "Menu utente", "logout": "Esci", diff --git a/invokeai/frontend/web/public/locales/ja.json b/invokeai/frontend/web/public/locales/ja.json index 291b34cafa..7acf6c98ca 100644 --- a/invokeai/frontend/web/public/locales/ja.json +++ b/invokeai/frontend/web/public/locales/ja.json @@ -8,7 +8,7 @@ "back": "戻る", "statusDisconnected": "切断枈", "cancel": "キャンセル", - "accept": "同意", + "accept": "確定", "img2img": "img2img", "loading": "ロヌド䞭", "githubLabel": "Github", @@ -33,9 +33,9 @@ "batch": "バッチマネヌゞャヌ", "advanced": "高床", "created": "䜜成枈", - "green": "緑", - "blue": "青", - "alpha": "アルファ", + "green": "G", + "blue": "B", + "alpha": "α", "outpaint": "outpaint", "unknown": "䞍明", "updated": "曎新枈", @@ -44,7 +44,7 @@ "copyError": "$t(gallery.copy) ゚ラヌ", "data": "デヌタ", "template": "テンプレヌト", - "red": "èµ€", + "red": "R", "or": "たたは", "checkpoint": "Checkpoint", "direction": "方向", @@ -194,7 +194,7 @@ "assets": "アセット", "useForPromptGeneration": "プロンプト生成に䜿甚する", "jump": "ゞャンプ", - "noImagesInGallery": "ディスプレむに画像がありたせん", + "noImagesInGallery": "衚瀺する画像がありたせん", "unableToLoad": "ギャラリヌを読み蟌めたせん", "selectAnImageToCompare": "比范する画像を遞択", "openViewer": "ビュヌアヌを開く", @@ -211,7 +211,7 @@ }, "useSize": { "title": "サむズを䜿甚", - "desc": "珟画像のサむズをbboxサむズずしお䜿甚する." + "desc": "珟画像のサむズをバりンディングボックスのサむズずしお䜿甚する." }, "recallPrompts": { "title": "プロンプトを再䜿甚", @@ -366,8 +366,8 @@ "desc": "矩圢ツヌルを遞択したす。" }, "settings": { - "behavior": "行動", - "display": "ディスプレむ", + "behavior": "挙動", + "display": "衚瀺", "grid": "グリッド", "debug": "デバッグ" }, @@ -388,25 +388,25 @@ "desc": "遞択したむンペむント マスクを反転し、反察の透明床を持぀新しいマスクを䜜成したす。" }, "fitBboxToLayers": { - "title": "Bboxをレむダヌに合わせる", - "desc": "衚瀺レむダヌに合わせお生成境界ボックスを自動的に調敎したす" + "title": "バりンディングボックスをレむダヌ矀に合わせる", + "desc": "衚瀺されおいるレむダヌに合わせお生成バりンディングボックスを自動的に調敎したす" }, "fitBboxToMasks": { - "title": "Bboxをマスクにフィットさせる", - "desc": "目に芋えるむンペむントマスクに合わせお生成境界ボックスを自動的に調敎したす" + "title": "バりンディングボックスをマスクにフィットさせる", + "desc": "可芖のむンペむントマスクに合わせお生成バりンディングボックスを自動的に調敎したす" }, "toggleBbox": { - "title": "Bboxの衚瀺/非衚瀺を切り替える", - "desc": "生成境界ボックスを非衚瀺たたは衚瀺する" + "title": "バりンディングボックスの衚瀺/非衚瀺を切り替える", + "desc": "生成バりンディングボックスを非衚瀺たたは衚瀺する" }, "applySegmentAnything": { - "title": "䜕でもセグメント化を適甚する", - "desc": "珟圚の「䜕でもセグメント」マスクを適甚したす。", + "title": "Segment Anythingを適甚する", + "desc": "珟圚のSegment Anythingマスクを適甚したす。", "key": "入力" }, "cancelSegmentAnything": { "title": "セグメントをキャンセル", - "desc": "珟圚の「䜕でもセグメント」操䜜をキャンセルしたす。", + "desc": "珟圚のSegment Anything操䜜をキャンセルしたす。", "key": "゚スケヌプ" } }, @@ -468,8 +468,8 @@ "title": "キャンバスタブを遞択" }, "selectUpscalingTab": { - "desc": "アップスケヌリングタブを遞択したす。", - "title": "アップスケヌリングタブを遞択" + "desc": "アップスケヌルタブを遞択したす。", + "title": "アップスケヌルタブを遞択" }, "toggleRightPanel": { "desc": "右パネルを衚瀺たたは非衚瀺。", @@ -504,7 +504,7 @@ "desc": "カヌ゜ルをポゞティブプロンプト欄に移動したす。" }, "promptHistoryPrev": { - "title": "履歎の前のプロンプト", + "title": "ヒストリヌの以前のプロンプト", "desc": "プロンプトにフォヌカスがある堎合は、履歎内の前の叀いプロンプトに移動したす。" }, "promptHistoryNext": { @@ -636,9 +636,9 @@ "controlLora": "コントロヌルLoRA", "triggerPhrases": "トリガヌフレヌズ", "t5Encoder": "T5゚ンコヌダヌ", - "textualInversions": "テキスト反転", + "textualInversions": "Textual Inversions", "fluxRedux": "FLUX リダックス", - "installQueue": "キュヌをむンストヌル", + "installQueue": "むンストヌル進捗状況", "noMatchingModels": "マッチするモデルがありたせん", "noDefaultSettings": "このモデルには構成されたデフォルト蚭定がありたせん.デフォルト蚭定を远加するためにモデルマネヌゞャヌにアクセスしおください.", "usingDefaultSettings": "モデルのデフォルト蚭定を䜿甚する", @@ -651,7 +651,7 @@ "main": "メむン", "defaultSettings": "デフォルト蚭定", "deleteModelImage": "モデル画像を削陀", - "hfTokenInvalid": "ハギングフェむストヌクンが無効たたは芋぀かりたせん", + "hfTokenInvalid": "HuggingFaceトヌクンが無効たたは芋぀かりたせん", "hfForbiddenErrorMessage": "リポゞトリにアクセスするこずを勧めたす.所有者はダりンロヌドにあたり利甚芏玄ぞの同意を芁求する堎合がありたす.", "noModelsInstalled": "むンストヌルされおいるモデルがありたせん", "pathToConfig": "蚭定ぞのパス", @@ -665,8 +665,8 @@ "installRepo": "リポゞトリをむンストヌル", "localOnly": "ロヌカルのみ", "huggingFaceHelper": "いく぀かのモデルがこのリポゞトリで芋぀かった堎合1぀を遞択しおむンストヌルするように求められたす.", - "hfTokenInvalidErrorMessage": "ハギングフェむストヌクンが無効たたは芋぀かりたせん.", - "hfTokenRequired": "有効なハギングフェむストヌクンが必芁なモデルをダりンロヌドしようずしおいたす.", + "hfTokenInvalidErrorMessage": "HuggingFaceトヌクンが無効たたは芋぀かりたせん。", + "hfTokenRequired": "有効なHuggingFaceトヌクンが必芁なモデルをダりンロヌドしようずしおいたす。", "hfTokenInvalidErrorMessage2": "曎新しおください ", "modelImageDeleted": "モデル画像削陀", "repoVariant": "リポゞトリバリアント", @@ -679,17 +679,17 @@ "urlOrLocalPath": "URLかロヌカルパス", "clipLEmbed": "クリップ-L 埋め蟌み", "defaultSettingsSaved": "デフォルト蚭定を保存したした", - "hfTokenUnableToVerify": "ハギングフェむストヌクンを確認できたせん", - "hfForbidden": "このハギングフェむスモデルにアクセスできたせん", - "hfTokenLabel": "ハギングフェむストヌクン(いく぀かのモデルに必芁)", + "hfTokenUnableToVerify": "HuggingFaceトヌクンを確認できたせん", + "hfForbidden": "このHuggingFaceモデルにアクセスできたせん", + "hfTokenLabel": "HuggingFaceトヌクン(いく぀かのモデルに必芁)", "noModelSelected": "モデルが遞択されおいたせん", "prune": "陀去", - "hfTokenHelperText": "いく぀かのモデルにハギングフェむストヌクンが必芁です.ここをクリックしおあなたのトヌクンを䜜成しおください.", + "hfTokenHelperText": "いく぀かのモデルにHuggingFaceトヌクンが必芁です。ここをクリックしおあなたのトヌクンを䜜成しおください。", "starterBundleHelpText": "メむンモデルコントロヌルネットIPアダプタヌなどベヌスモデルから始めるのに必芁なすべおのモデルを簡単にむンストヌルできたす.バンドルを遞択するずすでにむンストヌルされおいるモデルはスキップされたす.", "inplaceInstallDesc": "ファむルを移動せずにモデルをむンストヌルしたす.このモデルを䜿ったずき、元の堎所からロヌドされたす.利甚できない堎合、モデルファむルはInvoke管理モデルディレクトリにむンストヌルしおいる間に移動されたす。", - "hfTokenUnableToVerifyErrorMessage": "ハギングフェむストヌクンを確認できたせん.ネットワヌクによる゚ラヌの可胜性がありたす.埌ほどトラむしおください.", + "hfTokenUnableToVerifyErrorMessage": "HuggingFaceトヌクンを確認できたせん。ネットワヌクによる゚ラヌの可胜性がありたす。埌ほどトラむしおください。", "restoreDefaultSettings": "クリックするずモデルのデフォルト蚭定が䜿甚されたす.", - "hfTokenSaved": "ハギングフェむストヌクンを保存したした", + "hfTokenSaved": "HuggingFaceトヌクンを保存したした", "imageEncoderModelId": "画像゚ンコヌダヌモデルID", "includesNModels": "{{n}}個のモデルずこれらの䟝存関係を含みたす。", "learnMoreAboutSupportedModels": "私たちのサポヌトしおいるモデルに぀いお曎に孊ぶ", @@ -711,7 +711,7 @@ "modelPickerFallbackNoModelsInstalled2": "モデルマネヌゞャヌ にアクセスしおモデルをむンストヌルしおください.", "modelPickerFallbackNoModelsInstalled": "モデルがむンストヌルされおいたせん.", "manageModels": "モデル管理", - "hfTokenReset": "ハギングフェむストヌクンリセット", + "hfTokenReset": "HuggingFaceトヌクンをリセット", "relatedModels": "関連のあるモデル", "installedModelsCount": "{{total}} モデルのうち {{installed}} 個がむンストヌルされおいたす。", "allNModelsInstalled": "{{count}} 個のモデルがすべおむンストヌルされおいたす", @@ -719,7 +719,7 @@ "nAlreadyInstalled": "{{count}} 個すでにむンストヌルされおいたす", "bundleAlreadyInstalled": "バンドルがすでにむンストヌルされおいたす", "bundleAlreadyInstalledDesc": "{{bundleName}} バンドル内のすべおのモデルはすでにむンストヌルされおいたす。", - "launchpadTab": "ランチパッド", + "launchpadTab": "ロヌンチパッド", "launchpad": { "welcome": "モデルマネゞメントぞようこそ", "description": "Invoke プラットフォヌムのほずんどの機胜を利甚するには、モデルのむンストヌルが必芁です。手動むンストヌルオプションから遞択するか、厳遞されたスタヌタヌモデルをご芧ください。", @@ -742,7 +742,10 @@ "installBundleMsg2": "このバンドルでは、次の {{count}} モデルがむンストヌルされたす:", "ipAdapters": "IPアダプタヌ", "showOnlyRelatedModels": "関連しおいる", - "starterModelsInModelManager": "スタヌタヌモデルはモデルマネヌゞャヌにありたす" + "starterModelsInModelManager": "スタヌタヌモデルはモデルマネヌゞャヌにありたす", + "actions": "䞀括操䜜", + "selectAll": "党お遞択", + "deselectAll": "党お遞択解陀" }, "parameters": { "images": "画像", @@ -752,7 +755,7 @@ "seed": "シヌド倀", "shuffle": "シャッフル", "strength": "匷床", - "upscaling": "アップスケヌリング", + "upscaling": "アップスケヌル", "scale": "スケヌル", "scaleBeforeProcessing": "凊理前のスケヌル", "scaledWidth": "幅のスケヌル", @@ -794,10 +797,10 @@ "systemDisconnected": "システムが切断されたした", "canvasIsTransforming": "キャンバスがビゞヌ状態(倉換)", "canvasIsRasterizing": "キャンバスがビゞヌ状態(ラスタラむズ)", - "modelIncompatibleBboxHeight": "Bboxの高さは{{height}}ですが{{model}}は{{multiple}}の倍数が必芁です", - "modelIncompatibleScaledBboxHeight": "bboxの高さは{{height}}ですが{{model}}は{{multiple}}の倍数を必芁です", - "modelIncompatibleBboxWidth": "Bboxの幅は{{width}}ですが, {{model}}は{{multiple}}の倍数が必芁です", - "modelIncompatibleScaledBboxWidth": "bboxの幅は{{width}}ですが{{model}}は{{multiple}}の倍数が必芁です", + "modelIncompatibleBboxHeight": "バりンディングボックスの高さは{{height}}ですが{{model}}は{{multiple}}の倍数が必芁です", + "modelIncompatibleScaledBboxHeight": "バりンディングボックスの高さは{{height}}ですが{{model}}は{{multiple}}の倍数を必芁です", + "modelIncompatibleBboxWidth": "バりンディングボックスの幅は{{width}}ですが, {{model}}は{{multiple}}の倍数が必芁です", + "modelIncompatibleScaledBboxWidth": "バりンディングボックスの幅は{{width}}ですが{{model}}は{{multiple}}の倍数が必芁です", "canvasIsSelectingObject": "キャンバスがビゞヌ状態(オブゞェクトの遞択)", "noFLUXVAEModelSelected": "FLUX生成にVAEモデルが遞択されおいたせん", "noT5EncoderModelSelected": "FLUX生成にT5゚ンコヌダモデルが遞択されおいたせん", @@ -806,10 +809,10 @@ "promptExpansionResultPending": "プロンプト拡匵結果を受け入れるか砎棄しおください", "emptyBatches": "空のバッチ", "noStartingFrameImage": "開始フレヌム画像がありたせん", - "fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、bboxの幅は{{width}}です", - "fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、bboxの高さは{{height}}です", - "fluxModelIncompatibleScaledBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、スケヌルされたbboxの幅は{{width}}です", - "fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、スケヌルされた bbox の高さは {{height}} です", + "fluxModelIncompatibleBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、バりンディングボックスの幅は{{width}}です", + "fluxModelIncompatibleBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、バりンディングボックスの高さは{{height}}です", + "fluxModelIncompatibleScaledBboxWidth": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、スケヌルされたバりンディングボックスの幅は{{width}}です", + "fluxModelIncompatibleScaledBboxHeight": "$t(parameters.invoke.fluxRequiresDimensionsToBeMultipleOf16)、スケヌルされたバりンディングボックスの高さは {{height}} です", "incompatibleLoRAs": "互換性のない LoRA が远加されたした" }, "aspect": "瞊暪比", @@ -818,7 +821,7 @@ "sendToUpscale": "アップスケヌラヌに転送", "useSize": "サむズを䜿甚", "postProcessing": "ポストプロセス (Shift + U)", - "denoisingStrength": "ノむズ陀去匷床", + "denoisingStrength": "陀去ノむズ匷床", "recallMetadata": "メタデヌタを再䜿甚", "copyImage": "画像をコピヌ", "positivePromptPlaceholder": "ポゞティブプロンプト", @@ -834,7 +837,7 @@ "imageFit": "初期画像を出力サむズに合わせる", "setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (おそらく倧きすぎたす)", "coherenceEdgeSize": "゚ッゞサむズ", - "swapDimensions": "スワップ次元", + "swapDimensions": "瞊暪サむズを入れ替え", "controlNetControlMode": "制埡モヌド", "infillColorValue": "塗り぀ぶし色", "coherenceMinDenoise": "最小ノむズ陀去", @@ -845,7 +848,7 @@ "infillMethod": "充填法", "patchmatchDownScaleSize": "ダりンスケヌル", "boxBlur": "ボックスがかし", - "remixImage": "リミックス画像", + "remixImage": "画像をリミックス", "processImage": "プロセス画像", "useCpuNoise": "CPUノむズの䜿甚", "staged": "ステヌゞ", @@ -997,8 +1000,8 @@ "noVisibleMasksDesc": "少なくずも1぀のむンペむントマスクを䜜成たたは有効にしお反転したす", "noInpaintMaskSelected": "むンペむントマスクが遞択されおいたせん", "noInpaintMaskSelectedDesc": "反転するむンペむントマスクを遞択", - "invalidBbox": "無効な境界ボックス", - "invalidBboxDesc": "境界ボックスに有効な寞法がありたせん" + "invalidBbox": "無効なバりンディングボックス", + "invalidBboxDesc": "バりンディングボックスの寞法が有効ではありたせん" }, "accessibility": { "invokeProgressBar": "進捗バヌ", @@ -1179,7 +1182,7 @@ "cannotConnectInputToInput": "入力から入力には接続できたせん", "cannotConnectOutputToOutput": "出力から出力には接続できたせん", "cannotConnectToSelf": "自身のノヌドには接続できたせん", - "colorCodeEdges": "カラヌコヌド゚ッゞ", + "colorCodeEdges": "゚ッゞのカラヌ化", "loadingNodes": "ノヌドを読み蟌み䞭...", "scheduler": "スケゞュヌラヌ", "version": "バヌゞョン", @@ -1197,7 +1200,7 @@ "enum": "Enum", "arithmeticSequence": "等差数列", "linearDistribution": "線圢分垃", - "animatedEdges": "アニメヌション゚ッゞ", + "animatedEdges": "゚ッゞのアニメヌション", "uniformRandomDistribution": "䞀様ランダム分垃", "noBatchGroup": "グルヌプなし", "parseString": "文字列の解析", @@ -1232,7 +1235,7 @@ "unableToUpdateNode": "ノヌドアップロヌド倱敗:ノヌド {{node}} のタむプ {{type}} (削陀か再生成が必芁かもしれたせん)", "deletedInvalidEdge": "無効な゚ッゞを削陀したした{{source}} -> {{target}}", "collectionFieldType": "{{name}} (コレクション)", - "colorCodeEdgesHelp": "接続されたフィヌルドによるカラヌコヌド゚ッゞ", + "colorCodeEdgesHelp": "接続されたフィヌルド皮ごずに゚ッゞをカラヌ化", "showEdgeLabelsHelp": "゚ッゞのラベルを衚瀺接続されおいるノヌドを瀺す", "sourceNodeFieldDoesNotExist": "無効な゚ッゞ:゜ヌス/アりトプットフィヌルド{{node}}.{{field}}が存圚したせん", "deletedMissingNodeFieldFormElement": "䞍足しおいるフォヌムフィヌルドを削陀したした: ノヌド {{nodeId}} フィヌルド {{fieldName}}", @@ -1602,13 +1605,13 @@ "compositingMaskAdjustments": { "heading": "マスク調敎", "paragraphs": [ - "マスクを調敎する." + "マスクを調敎する" ] }, "compositingCoherenceMinDenoise": { "paragraphs": [ - "コヒヌレンスモヌドの最小ノむズ陀去匷床", - "むンペむンティングたたはアりトペむンティング時のコヒヌレンス領域の最小ノむズ陀去匷床" + "コヒヌレンスモヌドの最小陀去ノむズ匷床", + "むンペむント・アりトペむント時のコヒヌレンス領域の最小陀去ノむズ匷床" ], "heading": "最小ノむズ陀去" }, @@ -1691,7 +1694,7 @@ "たずえば, プロンプトが 5 ぀ある堎合, 各画像は同じシヌドを䜿甚したす.", "「画像ごず」では, 画像ごずに固有のシヌド倀が䜿甚されたす. これにより、より倚くのバリ゚ヌションが埗られたす." ], - "heading": "シヌド行動" + "heading": "シヌドの挙動" }, "imageFit": { "paragraphs": [ @@ -1730,7 +1733,7 @@ "optimizedDenoising": { "heading": "むメヌゞtoむメヌゞの最適化", "paragraphs": [ - "「むメヌゞtoむメヌゞを最適化」を有効にするず、Fluxモデルを甚いた画像間倉換およびむンペむンティング倉換においお、より段階的なノむズ陀去匷床スケヌルが適甚されたす。この蚭定により、画像に適甚される倉化量を制埡する胜力が向䞊したすが、暙準のノむズ陀去匷床スケヌルを䜿甚したい堎合はオフにするこずができたす。この蚭定は珟圚調敎䞭で、ベヌタ版です。" + "「むメヌゞtoむメヌゞを最適化」を有効にするず、Fluxモデルを甚いた画像間倉換およびむンペむント倉換においお、より段階的な陀去ノむズ匷床スケヌルが適甚されたす。この蚭定により、画像に適甚される倉化量を制埡する胜力が向䞊したすが、暙準の陀去ノむズ匷床スケヌルを䜿甚したい堎合はオフにするこずができたす。この蚭定は珟圚調敎䞭で、ベヌタ版です。" ] }, "refinerPositiveAestheticScore": { @@ -1756,8 +1759,8 @@ "refinerModel": { "heading": "リファむナヌモデル", "paragraphs": [ - "生成プロセスの粟補郚分で䜿甚されるモデル。", - "䞖代モデルに䌌おいたす。" + "生成プロセスのリファむナヌ郚分で䜿甚されるモデル。", + "生成モデルに䌌おいたす。" ] }, "refinerCfgScale": { @@ -1833,7 +1836,7 @@ "tileOverlap": { "heading": "タむルオヌバヌラップ", "paragraphs": [ - "アップスケヌリング時の隣接するタむルの重なり具合を制埡したす。重なり具合の倀を倧きくするずタむル間の継ぎ目が芋えにくくなりたすが、メモリ䜿甚量は増加したす。", + "アップスケヌル時の隣接するタむルの重なり具合を制埡したす。重なり具合の倀を倧きくするずタむル間の継ぎ目が芋えにくくなりたすが、メモリ䜿甚量は増加したす。", "デフォルト倀の 128 はほずんどの堎合に適しおいたすが、特定のニヌズやメモリの制玄に基づいお調敎できたす。" ] } @@ -1881,8 +1884,8 @@ "resultTitle": "プロンプト拡匵完了", "resultSubtitle": "拡匵プロンプトの凊理方法を遞択したす:", "insert": "挿入", - "noPromptHistory": "プロンプト履歎が蚘録されおいたせん。", - "noMatchingPrompts": "履歎にマッチするプロンプトがありたせん。", + "noPromptHistory": "プロンプトヒストリヌが蚘録されおいたせん。", + "noMatchingPrompts": "マッチするプロンプトがヒストリヌにありたせん。", "toSwitchBetweenPrompts": "プロンプトを切り替えたす。" }, "ui": { @@ -1894,7 +1897,7 @@ "gallery": "ギャラリヌ", "workflowsTab": "$t(ui.tabs.workflows) $t(common.tab)", "modelsTab": "$t(ui.tabs.models) $t(common.tab)", - "upscaling": "アップスケヌリング", + "upscaling": "アップスケヌル", "upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)", "generate": "生成" }, @@ -1904,7 +1907,7 @@ "scale": "スケヌル", "helpText": { "promptAdvice": "アップスケヌルする際は、媒䜓ずスタむルを説明するプロンプトを䜿甚しおください。画像内の具䜓的なコンテンツの詳现を説明するこずは避けおください。", - "styleAdvice": "アップスケヌリングは、画像の党䜓的なスタむルに最適です。" + "styleAdvice": "アップスケヌルは、画像の党䜓的なスタむルに最適です。" }, "uploadImage": { "title": "アップスケヌル甚の画像をアップロヌドする", @@ -1957,19 +1960,19 @@ "browseAndLoadWorkflows": "既存のワヌクフロヌを参照しお読み蟌む", "addStyleRef": { "title": "スタむル参照を远加する", - "description": "画像を远加しお倖芳を転送したす。" + "description": "倖芳を参照するための画像を远加したしょう。" }, "editImage": { "title": "画像を線集", - "description": "絞り蟌むために画像を远加したす。" + "description": "リファむンする画像を远加したしょう。" }, "generateFromText": { "title": "テキストから生成", - "description": "プロンプトを入力しお呌び出したす。" + "description": "プロンプトを入力しお生成したしょう。" }, "useALayoutImage": { "title": "レむアりト画像を䜿甚", - "description": "構成を制埡するために画像を远加したす。" + "description": "構図を制埡するための画像を远加したしょう。" }, "generate": { "canvasCalloutTitle": "画像をさらに现かく制埡、線集、反埩したいですか?", @@ -1997,13 +2000,13 @@ "canvasGroup": "キャンバス", "saveToGalleryGroup": "ギャラリヌに保存", "saveCanvasToGallery": "キャンバスをギャラリヌに保存", - "saveBboxToGallery": "Bボックスをギャラリヌに保存", + "saveBboxToGallery": "バりンディングボックスをギャラリヌに保存", "newControlLayer": "新芏コントロヌルレむダヌ", "newRasterLayer": "新芏ラスタヌレむダヌ", "newInpaintMask": "新芏むンペむントマスク", "copyToClipboard": "クリップボヌドにコピヌ", "copyCanvasToClipboard": "キャンバスをクリップボヌドにコピヌ", - "copyBboxToClipboard": "Bボックスをクリップボヌドにコピヌ", + "copyBboxToClipboard": "バりンディングボックスをクリップボヌドにコピヌ", "newResizedControlLayer": "新しくサむズ倉曎されたコントロヌルレむダヌ" }, "regionalGuidance": "領域ガむダンス", @@ -2030,7 +2033,7 @@ "rectangle": "矩圢", "move": "移動", "eraser": "消しゎム", - "bbox": "Bbox", + "bbox": "バりンディングボックス", "view": "ビュヌ" }, "saveCanvasToGallery": "キャンバスをギャラリヌに保存", @@ -2064,7 +2067,7 @@ "label": "グリッドにスナップ" }, "preserveMask": { - "label": "マスクされた領域を保持", + "label": "マスクされた領域を保護", "alert": "マスクされた領域の保存" }, "isolatedStagingPreview": "分離されたステヌゞングプレビュヌ", @@ -2072,10 +2075,10 @@ "isolatedLayerPreview": "分離されたレむダヌのプレビュヌ", "isolatedLayerPreviewDesc": "フィルタリングや倉換などの操䜜を実行するずきに、このレむダヌのみを衚瀺するかどうか。", "invertBrushSizeScrollDirection": "ブラシサむズのスクロヌル反転", - "pressureSensitivity": "圧力感床", + "pressureSensitivity": "筆圧怜知", "saveAllImagesToGallery": { - "label": "ギャラリヌに新しい生成を送る", - "alert": "キャンバスを経由せず、ギャラリヌに新しい生成を送り蟌む" + "label": "ギャラリヌに新しい生成画像を送る", + "alert": "キャンバスを経由せず、ギャラリヌに新しい生成を送る" } }, "filter": { @@ -2093,14 +2096,14 @@ "cancel": "キャンセル", "filters": "フィルタヌ", "filterType": "フィルタヌタむプ", - "autoProcess": "オヌトプロセス", + "autoProcess": "自動で実行", "process": "プロセス", - "advanced": "アドバンスド", + "advanced": "詳现蚭定", "processingLayerWith": "{{type}} フィルタヌを䜿甚した凊理レむダヌ。", "forMoreControl": "さらに现かく制埡するには、以䞋の「詳现蚭定」をクリックしおください。", "canny_edge_detection": { - "label": "キャニヌ゚ッゞ怜出", - "description": "Canny ゚ッゞ怜出アルゎリズムを䜿甚しお、遞択したレむダヌから゚ッゞ マップを生成したす。", + "label": "゚ッゞ怜出Canny", + "description": "Canny ゚ッゞ怜出アルゎリズムを䜿甚しお、遞択したレむダヌから線画を生成したす。", "low_threshold": "䜎閟倀", "high_threshold": "高閟倀" }, @@ -2115,8 +2118,8 @@ "scale_factor": "スケヌル係数" }, "depth_anything_depth_estimation": { - "label": "デプス゚ニシング", - "description": "デプス゚ニシングモデルを䜿甚しお、遞択したレむダヌから深床マップを生成したす。", + "label": "深床抜出Depth Anything", + "description": "Depth Anthingモデルを䜿甚しお、遞択したレむダヌから深床マップを生成したす。", "model_size": "モデルサむズ", "model_size_small": "スモヌル", "model_size_small_v2": "スモヌルv2", @@ -2124,50 +2127,50 @@ "model_size_large": "ラヌゞ" }, "dw_openpose_detection": { - "label": "DW オヌプンポヌズ怜出", + "label": "ポヌズ怜出DW Openpose", "description": "DW Openpose モデルを䜿甚しお、遞択したレむダヌ内の人間のポヌズを怜出したす。", "draw_hands": "手を描く", "draw_face": "顔を描く", "draw_body": "䜓を描く" }, "hed_edge_detection": { - "label": "HED゚ッゞ怜出", - "description": "HED ゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから゚ッゞ マップを生成したす。", + "label": "゚ッゞ怜出HED", + "description": "HED ゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから線画を生成したす。", "scribble": "萜曞き" }, "lineart_anime_edge_detection": { - "label": "線画アニメの゚ッゞ怜出", - "description": "線画アニメ゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから゚ッゞ マップを生成したす。" + "label": "゚ッゞ怜出Lineart Anime", + "description": "Lineart Anime゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから線画を生成したす。" }, "lineart_edge_detection": { - "label": "線画゚ッゞ怜出", - "description": "線画゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから゚ッゞ マップを生成したす。", - "coarse": "粗い" + "label": "゚ッゞ怜出Lineart", + "description": "Linart゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから線画を生成したす。", + "coarse": "粗く" }, "mediapipe_face_detection": { - "label": "メディアパむプ顔怜出", - "description": "メディアパむプ顔怜出モデルを䜿甚しお、遞択したレむダヌ内の顔を怜出したす。", - "max_faces": "マックスフェむス", + "label": "顔怜出MediaPipe", + "description": "MediaPipe顔怜出モデルを䜿甚しお、遞択したレむダヌ内の顔を怜出したす。", + "max_faces": "最倧顔数", "min_confidence": "最小信頌床" }, "mlsd_detection": { - "label": "線分怜出", - "description": "MLSD 線分怜出モデルを䜿甚しお、遞択したレむダヌから線分マップを生成したす。", + "label": "盎線怜出MLSD", + "description": "MLSD 線分怜出モデルを䜿甚しお、遞択したレむダヌから盎線郚分を抜出したす。", "score_threshold": "スコア閟倀", "distance_threshold": "距離閟倀" }, "normal_map": { - "label": "ノヌマルマップ", + "label": "ノヌマルマップ掚定", "description": "遞択したレむダヌからノヌマルマップを生成したす。" }, "pidi_edge_detection": { - "label": "PiDiNet゚ッゞ怜出", - "description": "PiDiNet ゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから゚ッゞ マップを生成したす。", + "label": "゚ッゞ怜出PiDiNet", + "description": "PiDiNet ゚ッゞ怜出モデルを䜿甚しお、遞択したレむダヌから線画を生成したす。", "scribble": "萜曞き", "quantize_edges": "゚ッゞを量子化する" }, "img_blur": { - "label": "画像をがかす", + "label": "がかし", "description": "遞択したレむダヌをがかしたす。", "blur_type": "がかしの皮類", "blur_radius": "半埄", @@ -2175,7 +2178,7 @@ "box_type": "ボックス" }, "img_noise": { - "label": "ノむズ画像", + "label": "ノむズ", "description": "遞択したレむダヌにノむズを远加したす。", "noise_type": "ノむズの皮類", "noise_amount": "総蚈", @@ -2219,26 +2222,26 @@ "newGlobalReferenceImageError": "グロヌバル参照むメヌゞの䜜成䞭に問題が発生したした", "newRegionalReferenceImageOk": "地域参照画像の䜜成", "newRegionalReferenceImageError": "地域参照画像の䜜成䞭に問題が発生したした", - "newControlLayerOk": "制埡レむダヌの䜜成", + "newControlLayerOk": "䜜成されたコントロヌルレむダヌ", "newControlLayerError": "制埡局の䜜成䞭に問題が発生したした", "newRasterLayerOk": "ラスタヌレむダヌを䜜成したした", "newRasterLayerError": "ラスタヌレむダヌの䜜成䞭に問題が発生したした", - "pullBboxIntoLayerOk": "Bbox をレむダヌにプル", - "pullBboxIntoLayerError": "BBox をレむダヌにプルする際に問題が発生したした", - "pullBboxIntoReferenceImageOk": "Bbox が ReferenceImage にプルされたした", - "pullBboxIntoReferenceImageError": "BBox を ReferenceImage にプルする際に問題が発生したした", + "pullBboxIntoLayerOk": "バりンディングボックスをレむダヌに", + "pullBboxIntoLayerError": "バりンディングボックスをレむダヌにする際に問題が発生したした", + "pullBboxIntoReferenceImageOk": "バりンディングボックスが参照画像にされたした", + "pullBboxIntoReferenceImageError": "バりンディングボックスを参照画像にする際に問題が発生したした", "regionIsEmpty": "遞択した領域は空です", "mergeVisible": "マヌゞを可芖化", "mergeVisibleOk": "マヌゞされたレむダヌ", "mergeVisibleError": "レむダヌの結合゚ラヌ", "mergingLayers": "レむダヌのマヌゞ", "clearHistory": "履歎をクリア", - "bboxOverlay": "Bboxオヌバヌレむを衚瀺", + "bboxOverlay": "バりンディングボックスのオヌバヌレむを衚瀺", "ruleOfThirds": "䞉分割法を衚瀺", "newSession": "新しいセッション", "clearCaches": "キャッシュをクリア", "recalculateRects": "長方圢を再蚈算する", - "clipToBbox": "ストロヌクをBboxにクリップ", + "clipToBbox": "ストロヌクをバりンディングボックス内に制限", "outputOnlyMaskedRegions": "生成された領域のみを出力する", "width": "幅", "autoNegative": "オヌトネガティブ", @@ -2284,13 +2287,13 @@ "pasteTo": "貌り付け先", "pasteToAssets": "アセット", "pasteToAssetsDesc": "アセットに貌り付け", - "pasteToBbox": "Bボックス", - "pasteToBboxDesc": "新しいレむダヌBbox内", + "pasteToBbox": "バりンディングボックス", + "pasteToBboxDesc": "新しいレむダヌバりンディングボックス内", "pasteToCanvas": "キャンバス", "pasteToCanvasDesc": "新しいレむダヌキャンバス内", - "transparency": "透明性", - "enableTransparencyEffect": "透明効果を有効にする", - "disableTransparencyEffect": "透明効果を無効にする", + "transparency": "透過衚瀺", + "enableTransparencyEffect": "透過衚瀺を有効にする", + "disableTransparencyEffect": "透過衚瀺を無効にする", "hidingType": "{{type}} を非衚瀺", "showingType": "{{type}}を衚瀺", "showNonRasterLayers": "非ラスタヌレむダヌを衚瀺 (Shift+H)", @@ -2301,24 +2304,24 @@ "unlocked": "ロック解陀", "deleteSelected": "遞択項目を削陀", "replaceLayer": "レむダヌの眮き換え", - "pullBboxIntoLayer": "Bboxをレむダヌに匕き蟌む", - "pullBboxIntoReferenceImage": "Bboxを参照画像に取り蟌む", + "pullBboxIntoLayer": "バりンディングボックスをレむダヌに", + "pullBboxIntoReferenceImage": "バりンディングボックスを参照画像に", "showProgressOnCanvas": "キャンバスに進捗状況を衚瀺", "useImage": "画像を䜿う", "negativePrompt": "ネガティブプロンプト", "beginEndStepPercentShort": "開始/終了 %", - "resetCanvasLayers": "キャンバスレむダヌをリセット", + "resetCanvasLayers": "キャンバスずレむダヌをリセット", "resetGenerationSettings": "生成蚭定をリセット", - "controlLayerEmptyState": "画像をアップロヌド、ギャラリヌからこのレむダヌに画像をドラッグ、境界ボックスをこのレむダヌにプル、たたはキャンバスに描画しお開始したす。", - "referenceImageEmptyStateWithCanvasOptions": "開始するには、画像をアップロヌドするか、ギャラリヌからこの参照画像に画像をドラッグするか、境界ボックスをこの参照画像に匕き蟌みたす。", + "controlLayerEmptyState": "画像をアップロヌド、ギャラリヌからこのレむダヌに画像をドラッグ、バりンディングボックスをこのレむダヌにする、たたはキャンバスに描画しお開始したす。", + "referenceImageEmptyStateWithCanvasOptions": "開始するには、画像をアップロヌドするか、ギャラリヌからこの参照画像に画像をドラッグするか、バりンディングボックスをこの参照画像にしたす。", "referenceImageEmptyState": "開始するには、画像をアップロヌドするか、ギャラリヌからこの参照画像に画像をドラッグしたす。", "imageNoise": "画像ノむズ", "denoiseLimit": "ノむズ陀去制限", "warnings": { "problemsFound": "問題が芋぀かりたした", "unsupportedModel": "遞択したベヌスモデルではレむダヌがサポヌトされおいたせん", - "controlAdapterNoModelSelected": "制埡レむダヌモデルが遞択されおいたせん", - "controlAdapterIncompatibleBaseModel": "互換性のない制埡レむダヌベヌスモデル", + "controlAdapterNoModelSelected": "コントロヌルレむダヌのモデルが遞択されおいたせん", + "controlAdapterIncompatibleBaseModel": "コントロヌルレむダヌのベヌスモデルに互換性がありたせん", "controlAdapterNoControl": "コントロヌルが遞択/描画されおいたせん", "ipAdapterNoModelSelected": "参照画像モデルが遞択されおいたせん", "ipAdapterIncompatibleBaseModel": "互換性のない参照画像ベヌスモデル", @@ -2329,7 +2332,7 @@ "rgAutoNegativeNotSupported": "遞択したベヌスモデルでは自動吊定はサポヌトされおいたせん", "rgNoRegion": "領域が描画されおいたせん", "fluxFillIncompatibleWithControlLoRA": "コントロヌルLoRAはFLUX Fillず互換性がありたせん", - "bboxHidden": "境界ボックスは非衚瀺ですShift+O で切り替えたす" + "bboxHidden": "バりンディングボックスは非衚瀺ですShift+O で切り替え" }, "errors": { "unableToFindImage": "画像が芋぀かりたせん", @@ -2370,7 +2373,7 @@ }, "selectObject": { "selectObject": "オブゞェクトを遞択", - "pointType": "ポむントタむプ", + "pointType": "点タむプ", "invertSelection": "遞択範囲を反転", "include": "含む", "exclude": "陀倖", @@ -2384,7 +2387,7 @@ "dragToMove": "ポむントをドラッグしお移動したす", "clickToRemove": "ポむントをクリックしお削陀したす", "desc": "察象オブゞェクトを1぀遞択したす。遞択が完了したら、適甚 をクリックしお遞択範囲倖のすべおを削陀するか、遞択範囲を新しいレむダヌずしお保存したす。", - "visualModeDesc": "ビゞュアル モヌドでは、ボックスずポむントの入力を䜿甚しおオブゞェクトを遞択したす。", + "visualModeDesc": "ビゞュアル モヌドでは、ボックスず点の入力を䜿甚しおオブゞェクトを遞択したす。", "visualMode1": "クリックドラッグしお、遞択したいオブゞェクトの呚囲にボックスを描きたす。オブゞェクトより少し倧きいか小さいボックスを描くず、より良い結果が埗られる堎合がありたす。", "visualMode2": "クリックしお緑の include ポむントを远加するか、Shift キヌを抌しながらクリックしお赀の exclude ポむントを远加し、モデルに含める内容ず陀倖する内容を指瀺したす。", "visualMode3": "ポむントは、ボックスの遞択を絞り蟌むために䜿甚するこずも、独立しお䜿甚するこずもできたす。", @@ -2392,13 +2395,13 @@ "promptMode1": "遞択するオブゞェクトの簡単な説明を入力したす。", "promptMode2": "耇雑な説明や耇数のオブゞェクトを避け、簡単な蚀葉を䜿甚しおください。", "model": "モデル", - "segmentAnything1": "䜕でもセグメント1", - "segmentAnything2": "䜕でもセグメント2", + "segmentAnything1": "Segment Anything 1", + "segmentAnything2": "Segment Anything 2", "prompt": "プロンプト遞択" }, "HUD": { - "bbox": "Bボックス", - "scaledBbox": "スケヌルされたBボックス", + "bbox": "バりンディングボックス", + "scaledBbox": "スケヌルされたバりンディングボックス", "entityStatus": { "isFiltering": "{{title}} はフィルタリング䞭です", "isTransforming": "{{title}}は倉化しおいたす", @@ -2418,20 +2421,20 @@ "showResultsOn": "結果を衚瀺", "showResultsOff": "結果を隠す" }, - "fitBboxToMasks": "Bboxをマスクにフィットさせる", + "fitBboxToMasks": "バりンディングボックスをマスクにフィットさせる", "addAdjustments": "調敎を远加", "removeAdjustments": "調敎を削陀", "adjustments": { "simple": "シンプル", - "curves": "曲線", + "curves": "カヌブ", "heading": "調敎", "expand": "調敎を拡匵", "collapse": "折りたたみ調敎", "brightness": "茝床", "contrast": "コントラスト", - "saturation": "飜和", - "temperature": "枩床", - "tint": "色合い", + "saturation": "圩床", + "temperature": "色枩床", + "tint": "色盞", "sharpness": "シャヌプネス", "finish": "終了", "reset": "リセット", @@ -2475,7 +2478,8 @@ "off": "オフ", "switchOnStart": "開始時", "switchOnFinish": "終了時" - } + }, + "extractRegion": "領域を抜出" }, "stylePresets": { "clearTemplateSelection": "遞択したテンプレヌトをクリア", @@ -2541,18 +2545,18 @@ "missingUpscaleInitialImage": "アップスケヌル甚の初期画像がありたせん", "missingUpscaleModel": "アップスケヌルモデルがありたせん", "missingTileControlNetModel": "有効なタむル コントロヌルネットモデルがむンストヌルされおいたせん", - "incompatibleBaseModel": "アップスケヌリングにサポヌトされおいないメむンモデルアヌキテクチャです", - "incompatibleBaseModelDesc": "アップスケヌリングはSD1.5およびSDXLアヌキテクチャモデルでのみサポヌトされおいたす。アップスケヌリングを有効にするには、メむンモデルを倉曎しおください。", + "incompatibleBaseModel": "アップスケヌルにサポヌトされおいないメむンモデルアヌキテクチャです", + "incompatibleBaseModelDesc": "アップスケヌルはSD1.5およびSDXLアヌキテクチャモデルでのみサポヌトされおいたす。アップスケヌルを有効にするには、メむンモデルを倉曎しおください。", "tileControl": "タむルコントロヌル", "tileSize": "タむルサむズ", "tileOverlap": "タむルオヌバヌラップ" }, "sdxl": { - "denoisingStrength": "ノむズ陀去匷床", + "denoisingStrength": "陀去ノむズ匷床", "scheduler": "スケゞュヌラヌ", "loading": "ロヌド䞭...", "steps": "ステップ", - "refiner": "Refiner", + "refiner": "リファむナヌ", "noModelsAvailable": "利甚できるモデルがありたせん", "cfgScale": "CFGスケヌル", "posAestheticScore": "ポゞティブ矎的スコア", @@ -2594,7 +2598,7 @@ "builder": "フォヌムビルダヌ", "text": "テキスト", "row": "行", - "multiLine": "マルチラむン", + "multiLine": "テキスト(耇数行)", "resetAllNodeFields": "すべおのノヌドフィヌルドをリセット", "slider": "スラむダヌ", "layout": "レむアりト", @@ -2604,7 +2608,7 @@ "component": "コンポヌネント", "textPlaceholder": "空のテキスト", "addOption": "オプションを远加", - "singleLine": "単線", + "singleLine": "テキスト", "numberInput": "数倀入力", "column": "列", "container": "コンテナ", @@ -2682,7 +2686,7 @@ "delete": "削陀", "loadMore": "もっず読み蟌む", "saveWorkflowToProject": "ワヌクフロヌをプロゞェクトに保存", - "created": "䜜成されたした", + "created": "䜜成順", "workflowEditorMenu": "ワヌクフロヌ゚ディタヌメニュヌ", "recentlyOpened": "最近開いた", "opened": "オヌプン", @@ -2736,9 +2740,9 @@ "seedBehaviour": { "label": "シヌドの挙動", "perPromptLabel": "画像ごずのシヌド", - "perIterationLabel": "いおレヌションごずのシヌド", + "perIterationLabel": "むテレヌションごずのシヌド", "perPromptDesc": "それぞれの画像に足しお別のシヌドを䜿う", - "perIterationDesc": "それぞれのいおレヌションに別のシヌドを䜿う" + "perIterationDesc": "それぞれのむテレヌションに別のシヌドを䜿う" }, "showDynamicPrompts": "ダむナミックプロンプトを衚瀺する", "dynamicPrompts": "ダむナミックプロンプト", @@ -2758,7 +2762,7 @@ "whatsNewInInvoke": "Invokeの新機胜", "items": [ "オブゞェクトの遞択 v2: ポむントおよびボックス入力たたはテキスト プロンプトによるオブゞェクト遞択が改善されたした。", - "ラスタヌ レむダヌの調敎: レむダヌの明るさ、コントラスト、圩床、曲線などを簡単に調敎できたす。" + "ラスタヌ レむダヌの調敎: レむダヌの明床、コントラスト、圩床、カヌブなどを簡単に調敎できたす。" ], "readReleaseNotes": "リリヌスノヌトを読む", "watchRecentReleaseVideos": "最近のリリヌスビデオを芋る", diff --git a/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.test.ts b/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.test.ts index 8adc013ab9..fb4d7ee48c 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.test.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.test.ts @@ -41,4 +41,14 @@ describe(getCollectItemType.name, () => { const result = getCollectItemType({ add: addWithoutOutputValue, collect }, [n2, n1], [e1], n1.id); expect(result).toBeNull(); }); + + it('should return the upstream collect item type for chained collects', () => { + const n1 = buildNode(collect); + const n2 = buildNode(collect); + const n3 = buildNode(add); + const e1 = buildEdge(n3.id, 'value', n1.id, 'item'); + const e2 = buildEdge(n1.id, 'collection', n2.id, 'collection'); + const result = getCollectItemType(templates, [n1, n2, n3], [e1, e2], n2.id); + expect(result).toEqual({ name: 'IntegerField', cardinality: 'SINGLE', batch: false }); + }); }); diff --git a/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.ts b/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.ts index 9fb2795ae8..35ec20220e 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/getCollectItemType.ts @@ -2,6 +2,16 @@ import type { Templates } from 'features/nodes/store/types'; import type { FieldType } from 'features/nodes/types/field'; import type { AnyEdge, AnyNode } from 'features/nodes/types/invocation'; +const toItemType = (fieldType: FieldType): FieldType | null => { + if (fieldType.name === 'CollectionField') { + return null; + } + if (fieldType.cardinality === 'COLLECTION' || fieldType.cardinality === 'SINGLE_OR_COLLECTION') { + return { ...fieldType, cardinality: 'SINGLE' }; + } + return fieldType; +}; + /** * Given a collect node, return the type of the items it collects. The graph is traversed to find the first node and * field connected to the collector's `item` input. The field type of that field is returned, else null if there is no @@ -18,21 +28,56 @@ export const getCollectItemType = ( edges: AnyEdge[], nodeId: string ): FieldType | null => { - const firstEdgeToCollect = edges.find((edge) => edge.target === nodeId && edge.targetHandle === 'item'); - if (!firstEdgeToCollect?.sourceHandle) { + const getCollectItemTypeInternal = (currentNodeId: string, visited: Set): FieldType | null => { + if (visited.has(currentNodeId)) { + return null; + } + visited.add(currentNodeId); + + const firstItemEdgeToCollect = edges.find((edge) => edge.target === currentNodeId && edge.targetHandle === 'item'); + if (firstItemEdgeToCollect?.sourceHandle) { + const node = nodes.find((n) => n.id === firstItemEdgeToCollect.source); + if (!node) { + return null; + } + const template = templates[node.data.type]; + if (!template) { + return null; + } + const fieldTemplate = template.outputs[firstItemEdgeToCollect.sourceHandle]; + if (!fieldTemplate) { + return null; + } + return toItemType(fieldTemplate.type); + } + + const firstCollectionEdgeToCollect = edges.find( + (edge) => edge.target === currentNodeId && edge.targetHandle === 'collection' + ); + if (!firstCollectionEdgeToCollect?.sourceHandle) { + return null; + } + const sourceNode = nodes.find((n) => n.id === firstCollectionEdgeToCollect.source); + if (!sourceNode) { + return null; + } + if (sourceNode.data.type === 'collect' && firstCollectionEdgeToCollect.sourceHandle === 'collection') { + return getCollectItemTypeInternal(sourceNode.id, visited); + } + const sourceTemplate = templates[sourceNode.data.type]; + if (!sourceTemplate) { + return null; + } + const sourceFieldTemplate = sourceTemplate.outputs[firstCollectionEdgeToCollect.sourceHandle]; + if (!sourceFieldTemplate) { + return null; + } + return toItemType(sourceFieldTemplate.type); + }; + + const itemType = getCollectItemTypeInternal(nodeId, new Set()); + if (!itemType) { return null; } - const node = nodes.find((n) => n.id === firstEdgeToCollect.source); - if (!node) { - return null; - } - const template = templates[node.data.type]; - if (!template) { - return null; - } - const fieldTemplate = template.outputs[firstEdgeToCollect.sourceHandle]; - if (!fieldTemplate) { - return null; - } - return fieldTemplate.type; + return itemType; }; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/testUtils.ts b/invokeai/frontend/web/src/features/nodes/store/util/testUtils.ts index 7442561984..1eb445beaf 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/testUtils.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/testUtils.ts @@ -133,11 +133,27 @@ export const sub: InvocationTemplate = { export const collect: InvocationTemplate = { title: 'Collect', type: 'collect', - version: '1.0.0', + version: '1.1.0', tags: [], description: 'Collects values into a collection', outputType: 'collect_output', inputs: { + collection: { + name: 'collection', + title: 'Collection', + required: false, + default: undefined, + description: 'An optional collection to append to', + fieldKind: 'input', + input: 'connection', + ui_hidden: false, + ui_type: 'CollectionField' as const, + type: { + name: 'CollectionField' as const, + cardinality: 'COLLECTION', + batch: false, + }, + }, item: { name: 'item', title: 'Collection Item', @@ -1162,13 +1178,12 @@ export const schema = { items: {}, type: 'array', title: 'Collection', - description: 'The collection, will be provided on execution', - default: [], + description: 'An optional collection to append to', field_kind: 'input', - input: 'any', - orig_default: [], + input: 'connection', orig_required: false, - ui_hidden: true, + ui_hidden: false, + ui_type: 'CollectionField', }, type: { type: 'string', @@ -1185,7 +1200,7 @@ export const schema = { node_pack: 'invokeai', description: 'Collects values into a collection', classification: 'stable', - version: '1.0.0', + version: '1.1.0', output: { $ref: '#/components/schemas/CollectInvocationOutput', }, diff --git a/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.test.ts b/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.test.ts index 4108f57c07..947d8745f0 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.test.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.test.ts @@ -122,6 +122,52 @@ describe(validateConnection.name, () => { expect(r).toEqual(null); }); + it('should accept chaining collect collection output to collect collection input', () => { + const n1 = buildNode(collect); + const n2 = buildNode(collect); + const nodes = [n1, n2]; + const c = { source: n1.id, sourceHandle: 'collection', target: n2.id, targetHandle: 'collection' }; + const r = validateConnection(c, nodes, [], templates, null); + expect(r).toEqual(null); + }); + + it('should reject multiple connections to collect collection input', () => { + const n1 = buildNode(collect); + const n2 = buildNode(collect); + const n3 = buildNode(collect); + const nodes = [n1, n2, n3]; + const e1 = buildEdge(n1.id, 'collection', n2.id, 'collection'); + const c = { source: n3.id, sourceHandle: 'collection', target: n2.id, targetHandle: 'collection' }; + const r = validateConnection(c, nodes, [e1], templates, null); + expect(r).toEqual('nodes.inputMayOnlyHaveOneConnection'); + }); + + it('should reject mismatched item connection when collect is typed via chained collection', () => { + const n1 = buildNode(add); + const n2 = buildNode(collect); + const n3 = buildNode(collect); + const n4 = buildNode(main_model_loader); + const nodes = [n1, n2, n3, n4]; + const e1 = buildEdge(n1.id, 'value', n2.id, 'item'); + const e2 = buildEdge(n2.id, 'collection', n3.id, 'collection'); + const c = { source: n4.id, sourceHandle: 'vae', target: n3.id, targetHandle: 'item' }; + const r = validateConnection(c, nodes, [e1, e2], templates, null); + expect(r).toEqual('nodes.cannotMixAndMatchCollectionItemTypes'); + }); + + it('should reject chaining collection-to-collection for differently typed collects', () => { + const n1 = buildNode(add); + const n2 = buildNode(img_resize); + const n3 = buildNode(collect); + const n4 = buildNode(collect); + const nodes = [n1, n2, n3, n4]; + const e1 = buildEdge(n1.id, 'value', n3.id, 'item'); + const e2 = buildEdge(n2.id, 'image', n4.id, 'item'); + const c = { source: n3.id, sourceHandle: 'collection', target: n4.id, targetHandle: 'collection' }; + const r = validateConnection(c, nodes, [e1, e2], templates, null); + expect(r).toEqual('nodes.cannotMixAndMatchCollectionItemTypes'); + }); + it('should reject connections to target field that is already connected', () => { const n1 = buildNode(add); const n2 = buildNode(add); diff --git a/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.ts b/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.ts index aaeb10edfd..9024a16f42 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/validateConnection.ts @@ -108,6 +108,24 @@ export const validateConnection: ValidateConnectionFunc = ( } } + if ( + sourceNode.data.type === 'collect' && + c.sourceHandle === 'collection' && + targetNode.data.type === 'collect' && + c.targetHandle === 'collection' + ) { + // Chained collect nodes should preserve a single item type when both ends are already typed. + const sourceCollectItemType = getCollectItemType(templates, nodes, edges, sourceNode.id); + const targetCollectItemType = getCollectItemType(templates, nodes, edges, targetNode.id); + if ( + sourceCollectItemType && + targetCollectItemType && + !areTypesEqual(sourceCollectItemType, targetCollectItemType) + ) { + return 'nodes.cannotMixAndMatchCollectionItemTypes'; + } + } + if (filteredEdges.find(getTargetEqualityPredicate(c))) { // CollectionItemField inputs can have multiple input connections if (targetFieldTemplate.type.name !== 'CollectionItemField') { diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts index db7cba5961..ba27e5dbf6 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildFLUXGraph.ts @@ -271,10 +271,7 @@ export const buildFLUXGraph = async (arg: GraphBuilderArg): Promise getGlobalReferenceImageWarnings(entity, model).length === 0); if (validFlux2RefImageConfigs.length > 0) { - const flux2KontextCollect = g.addNode({ - type: 'collect', - id: getPrefixedId('flux2_kontext_collect'), - }); + let prevCollect: Invocation<'collect'> | null = null; for (const { config } of validFlux2RefImageConfigs) { // FLUX.2 uses the same flux_kontext node - it just packages the image const kontextConditioning = g.addNode({ @@ -282,9 +279,18 @@ export const buildFLUXGraph = async (arg: GraphBuilderArg): Promise(value: T): T => JSON.parse(JSON.stringify(value)) as T; + describe('parseSchema', () => { it('should parse the schema', () => { const parsed = parseSchema(schema); - expect(parsed).toEqual(templates); + expect(stripUndefinedDeep(parsed)).toEqual(stripUndefinedDeep(templates)); }); it('should omit denied nodes', () => { const parsed = parseSchema(schema, undefined, ['add']); - expect(parsed).toEqual(omit(templates, 'add')); + expect(stripUndefinedDeep(parsed)).toEqual(stripUndefinedDeep(omit(templates, 'add'))); }); it('should include only allowed nodes', () => { const parsed = parseSchema(schema, ['add']); - expect(parsed).toEqual(pick(templates, 'add')); + expect(stripUndefinedDeep(parsed)).toEqual(stripUndefinedDeep(pick(templates, 'add'))); }); }); diff --git a/invokeai/frontend/web/src/features/nodes/util/schema/parseSchema.ts b/invokeai/frontend/web/src/features/nodes/util/schema/parseSchema.ts index 1371db1568..57cd9943c5 100644 --- a/invokeai/frontend/web/src/features/nodes/util/schema/parseSchema.ts +++ b/invokeai/frontend/web/src/features/nodes/util/schema/parseSchema.ts @@ -39,9 +39,6 @@ const isReservedInputField = (nodeType: string, fieldName: string) => { if (RESERVED_INPUT_FIELD_NAMES.includes(fieldName)) { return true; } - if (nodeType === 'collect' && fieldName === 'collection') { - return true; - } if (nodeType === 'iterate' && fieldName === 'index') { return true; } diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index fc6506ce22..895bc6f36a 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -4988,7 +4988,7 @@ export type components = { item?: unknown | null; /** * Collection - * @description The collection, will be provided on execution + * @description An optional collection to append to * @default [] */ collection?: unknown[]; diff --git a/invokeai/version/invokeai_version.py b/invokeai/version/invokeai_version.py index 0f32252c28..9bf32e7d0b 100644 --- a/invokeai/version/invokeai_version.py +++ b/invokeai/version/invokeai_version.py @@ -1 +1 @@ -__version__ = "6.11.1.post1" +__version__ = "6.12.0.post1" diff --git a/tests/test_node_graph.py b/tests/test_node_graph.py index 160dc96d85..4f3b262204 100644 --- a/tests/test_node_graph.py +++ b/tests/test_node_graph.py @@ -40,6 +40,7 @@ from tests.test_nodes import ( PromptTestInvocation, PromptTestInvocationOutput, TextToImageTestInvocation, + UnionCollectionTestInvocation, get_single_output_from_session, run_session_with_mock_context, ) @@ -337,6 +338,100 @@ def test_graph_collector_invalid_with_non_list_output(): g.add_edge(e3) +def test_graph_collector_can_chain_collection_input(): + g = Graph() + n1 = PromptCollectionTestInvocation(id="1", collection=["Banana", "Sushi"]) + n2 = PromptTestInvocation(id="2", prompt="Ramen") + n3 = CollectInvocation(id="3") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + + g.add_edge(create_edge("1", "collection", "3", "collection")) + g.add_edge(create_edge("2", "prompt", "3", "item")) + + session = GraphExecutionState(graph=g) + run_session_with_mock_context(session) + output = get_single_output_from_session(session, n3.id) + + assert isinstance(output, CollectInvocationOutput) + assert output.collection == ["Banana", "Sushi", "Ramen"] + + +def test_graph_collector_chain_rejects_mismatched_item_type(): + g = Graph() + n1 = PromptCollectionTestInvocation(id="1", collection=["Banana", "Sushi"]) + n2 = IntegerInvocation(id="2", value=7) + n3 = CollectInvocation(id="3") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + + g.add_edge(create_edge("1", "collection", "3", "collection")) + with pytest.raises(InvalidEdgeError): + g.add_edge(create_edge("2", "value", "3", "item")) + + +def test_graph_iterator_accepts_collector_chained_collection_input(): + g = Graph() + n1 = PromptTestInvocation(id="1", prompt="Banana") + n2 = CollectInvocation(id="2") + n3 = CollectInvocation(id="3") + n4 = IterateInvocation(id="4") + n5 = PromptTestInvocation(id="5") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + g.add_node(n4) + g.add_node(n5) + + g.add_edge(create_edge("1", "prompt", "2", "item")) + g.add_edge(create_edge("2", "collection", "3", "collection")) + g.add_edge(create_edge("3", "collection", "4", "collection")) + g.add_edge(create_edge("4", "item", "5", "prompt")) + + session = GraphExecutionState(graph=g) + run_session_with_mock_context(session) + + output = get_single_output_from_session(session, n5.id) + assert isinstance(output, PromptTestInvocationOutput) + assert output.prompt == "Banana" + + +def test_graph_collector_chain_rejects_upstream_mismatch_added_late(): + g = Graph() + n1 = CollectInvocation(id="1") + n2 = CollectInvocation(id="2") + n3 = PromptTestInvocation(id="3", prompt="typed-as-string") + n4 = ColorInvocation(id="4") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + g.add_node(n4) + + # Connect chain first while n1 is still untyped. + g.add_edge(create_edge("1", "collection", "2", "collection")) + # Constrain downstream collector to strings. + g.add_edge(create_edge("3", "prompt", "2", "item")) + # Now adding an incompatible type to the upstream collector must fail. + with pytest.raises(InvalidEdgeError): + g.add_edge(create_edge("4", "color", "1", "item")) + + +def test_graph_collector_rejects_mismatched_item_with_union_collection_input(): + g = Graph() + n1 = UnionCollectionTestInvocation(id="1") + n2 = CollectInvocation(id="2") + n3 = ColorInvocation(id="3") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + + g.add_edge(create_edge("1", "value", "2", "collection")) + with pytest.raises(InvalidEdgeError): + g.add_edge(create_edge("3", "color", "2", "item")) + + def test_graph_connects_iterator(): g = Graph() n1 = ListPassThroughInvocation(id="1") @@ -712,6 +807,24 @@ def test_iterate_accepts_collection(): g.add_edge(e3) +def test_iterate_accepts_collection_from_any_only_collector(): + g = Graph() + n1 = AnyTypeTestInvocation(id="1") + n2 = CollectInvocation(id="2") + n3 = IterateInvocation(id="3") + n4 = AnyTypeTestInvocation(id="4") + g.add_node(n1) + g.add_node(n2) + g.add_node(n3) + g.add_node(n4) + e1 = create_edge(n1.id, "value", n2.id, "item") + e2 = create_edge(n2.id, "collection", n3.id, "collection") + e3 = create_edge(n3.id, "item", n4.id, "value") + g.add_edge(e1) + g.add_edge(e2) + g.add_edge(e3) + + def test_iterate_validates_collection_inputs_against_iterator_outputs(): g = Graph() n1 = IntegerInvocation(id="1", value=1) diff --git a/tests/test_nodes.py b/tests/test_nodes.py index 04ea5126f0..6e8d25a603 100644 --- a/tests/test_nodes.py +++ b/tests/test_nodes.py @@ -107,6 +107,19 @@ class PolymorphicStringTestInvocation(BaseInvocation): return PromptCollectionTestInvocationOutput(collection=self.value) +@invocation_output("test_union_collection_output") +class UnionCollectionTestInvocationOutput(BaseInvocationOutput): + value: Union[str, list[str], None] = OutputField(default=None) + + +@invocation("test_union_collection", version="1.0.0") +class UnionCollectionTestInvocation(BaseInvocation): + value: Union[str, list[str], None] = InputField(default=None) + + def invoke(self, context: InvocationContext) -> UnionCollectionTestInvocationOutput: + return UnionCollectionTestInvocationOutput(value=self.value) + + # Importing these must happen after test invocations are defined or they won't register from invokeai.app.services.events.events_base import EventServiceBase # noqa: E402 from invokeai.app.services.shared.graph import Edge, EdgeConnection, GraphExecutionState # noqa: E402