Compare commits

..

14 Commits

Author SHA1 Message Date
psychedelicious
b65acc0137 chore(ui): add TODO for revising field type names 2023-11-20 10:29:04 +11:00
psychedelicious
0e640adc2c feat(nodes): add runtime check for custom field names
"Custom", "CustomCollection" and "CustomPolymorphic" are reserved field names.
2023-11-20 10:28:38 +11:00
psychedelicious
f280a2ecbd fix(ui): fix ts error 2023-11-19 18:09:32 +11:00
psychedelicious
e047d43111 fix(ui): rename 'nodes.currentConnectionFieldType' -> 'nodes.connectionStartFieldType'
This was confusingly named and kept tripping me up. Renamed to be consistent with the `reactflow` `ConnectionStartParams` type.
2023-11-19 18:05:25 +11:00
psychedelicious
57567d4fc3 chore(ui): remove errant console.log 2023-11-19 17:45:47 +11:00
psychedelicious
9ebffcd26b feat(ui): add validation for CustomCollection & CustomPolymorphic types
- Update connection validation for custom types
- Use simple string parsing to determine if a field is a collection or polymorphic type.
- No longer need to keep a list of collection and polymorphic types.
- Added runtime checks in `baseinvocation.py` to ensure no fields are named in such a way that it could mess up the new parsing
2023-11-19 17:44:42 +11:00
psychedelicious
e30f22ae7e feat(ui): add CustomCollection and CustomPolymorphic field types 2023-11-19 14:30:08 +11:00
psychedelicious
3ff13dc93c fix(ui): typo 2023-11-19 12:47:05 +11:00
psychedelicious
5e4b0932fd Merge remote-tracking branch 'origin/main' into feat/arbitrary-field-types 2023-11-18 10:41:36 +11:00
psychedelicious
98a0ce0f42 feat(ui): custom field types connection validation
In the initial commit, a custom field's original type was added to the *field templates* only as `originalType`. Custom fields' `type` property was `"Custom"`*. This allowed for type safety throughout the UI logic.

*Actually, it was `"Unknown"`, but I changed it to custom for clarity.

Connection validation logic, however, uses the *field instance* of the node/field. Like the templates, *field instances* with custom types have their `type` set to `"Custom"`, but they didn't have an `originalType` property. As a result, all custom fields could be connected to all other custom fields.

To resolve this, we need to add `originalType` to the *field instances*, then switch the validation logic to use this instead of `type`.

This ended up needing a bit of fanagling:

- If we make `originalType` a required property on field instances, existing workflows will break during connection validation, because they won't have this property. We'd need a new layer of logic to migrate the workflows, adding the new `originalType` property.

While this layer is probably needed anyways, typing `originalType` as optional is much simpler. Workflow migration logic can come layer.

(Technically, we could remove all references to field types from the workflow files, and let the templates hold all this information. This feels like a significant change and I'm reluctant to do it now.)

- Because `originalType` is optional, anywhere we care about the type of a field, we need to use it over `type`. So there are a number of `field.originalType ?? field.type` expressions. This is a bit of a gotcha, we'll need to remember this in the future.

- We use `Array.prototype.includes()` often in the workflow editor, e.g. `COLLECTION_TYPES.includes(type)`. In these cases, the const array is of type `FieldType[]`, and `type` is is `FieldType`.

Because we now support custom types, the arg `type` is now widened from `FieldType` to `string`.

This causes a TS error. This behaviour is somewhat controversial (see https://github.com/microsoft/TypeScript/issues/14520). These expressions are now rewritten as `COLLECTION_TYPES.some((t) => t === type)` to satisfy TS. It's logically equivalent.
2023-11-18 10:40:19 +11:00
psychedelicious
7b93b5e928 Merge branch 'main' into feat/arbitrary-field-types 2023-11-17 15:00:24 +11:00
psychedelicious
dc44debbab fix(ui): fix ts error with custom fields 2023-11-17 12:09:15 +11:00
psychedelicious
5ce2dc3a58 feat(ui): fix tooltips for custom types
We need to hold onto the original type of the field so they don't all just show up as "Unknown".
2023-11-17 12:01:39 +11:00
psychedelicious
27fd9071ba feat(ui): add support for custom field types
Node authors may now create their own arbitrary/custom field types. Any pydantic model is supported.

Two notes:
1. Your field type's class name must be unique.

Suggest prefixing fields with something related to the node pack as a kind of namespace.

2. Custom field types function as connection-only fields.

For example, if your custom field has string attributes, you will not get a text input for that attribute when you give a node a field with your custom type.

This is the same behaviour as other complex fields that don't have custom UIs in the workflow editor - like, say, a string collection.
2023-11-17 11:32:35 +11:00
79 changed files with 880 additions and 911 deletions

View File

@@ -7,12 +7,12 @@ To use them, right click on your desired workflow, follow the link to GitHub and
If you're interested in finding more workflows, checkout the [#share-your-workflows](https://discord.com/channels/1020123559063990373/1130291608097661000) channel in the InvokeAI Discord.
* [SD1.5 / SD2 Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Text_to_Image.json)
* [SDXL Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/SDXL_Text_to_Image.json)
* [SDXL Text to Image with Refiner](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/SDXL_w_Refiner_Text_to_Image.json)
* [Multi ControlNet (Canny & Depth)](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Multi_ControlNet_Canny_and_Depth.json)
* [SDXL Text to Image](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/SDXL_Text_to_Image.json)
* [SDXL Text to Image with Refiner](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/SDXL_w_Refiner_Text_to_Image.json)
* [Multi ControlNet (Canny & Depth)](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Multi_ControlNet_Canny_and_Depth.json)
* [Tiled Upscaling with ControlNet](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/ESRGAN_img2img_upscale_w_Canny_ControlNet.json)
* [Prompt From File](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Prompt_from_File.json)
* [Face Detailer with IP-Adapter & ControlNet](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Face_Detailer_with_IP-Adapter_and_Canny.json)
* [Prompt From File](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Prompt_from_File.json)
* [Face Detailer with IP-Adapter & ControlNet](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Face_Detailer_with_IP-Adapter_and_Canny.json.json)
* [FaceMask](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceMask.json)
* [FaceOff with 2x Face Scaling](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceOff_FaceScale2x.json)
* [QR Code Monster](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/QR_Code_Monster.json)
* [QR Code Monster](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/QR_Code_Monster.json)

View File

@@ -8,7 +8,7 @@ from abc import ABC, abstractmethod
from enum import Enum
from inspect import signature
from types import UnionType
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Literal, Optional, Type, TypeVar, Union
from typing import TYPE_CHECKING, Any, Callable, ClassVar, ForwardRef, Iterable, Literal, Optional, Type, TypeVar, Union
import semver
from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter, create_model
@@ -648,17 +648,40 @@ class _Model(BaseModel):
# Get all pydantic model attrs, methods, etc
RESERVED_PYDANTIC_FIELD_NAMES = {m[0] for m in inspect.getmembers(_Model())}
RESERVED_INVOKEAI_FIELD_NAMES = {"Custom", "CustomCollection", "CustomPolymorphic"}
def validate_fields(model_fields: dict[str, FieldInfo], model_type: str) -> None:
"""
Validates the fields of an invocation or invocation output:
- must not override any pydantic reserved fields
- must not end with "Collection" or "Polymorphic" as these are reserved for internal use
- must be created via `InputField`, `OutputField`, or be an internal field defined in this file
"""
for name, field in model_fields.items():
if name in RESERVED_PYDANTIC_FIELD_NAMES:
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved by pydantic)')
if not field.annotation:
raise InvalidFieldError(f'Invalid field type "{name}" on "{model_type}" (missing annotation)')
annotation_name = (
field.annotation.__forward_arg__ if isinstance(field.annotation, ForwardRef) else field.annotation.__name__
)
if annotation_name.endswith("Polymorphic"):
raise InvalidFieldError(
f'Invalid field type "{annotation_name}" for "{name}" on "{model_type}" (must not end in "Polymorphic")'
)
if annotation_name.endswith("Collection"):
raise InvalidFieldError(
f'Invalid field type "{annotation_name}" for "{name}" on "{model_type}" (must not end in "Collection")'
)
if annotation_name in RESERVED_INVOKEAI_FIELD_NAMES:
raise InvalidFieldError(f'Invalid field type "{annotation_name}" for "{name}" on "{model_type}" (reserved)')
field_kind = (
# _field_kind is defined via InputField(), OutputField() or by one of the internal fields defined in this file
field.json_schema_extra.get("_field_kind", None) if field.json_schema_extra else None

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{I as s,ie as T,v as l,$ as A,ig as R,aa as V,ih as z,ii as j,ij as D,ik as F,il as G,im as W,io as K,az as H,ip as U,iq as Y}from"./index-f820e2e3.js";import{M as Z}from"./MantineProvider-a6a1d85c.js";var P=String.raw,E=P`
import{w as s,ie as T,v as l,_ as I,ig as R,aa as V,ih as z,ii as j,ij as D,ik as F,il as G,im as W,io as K,az as H,ip as U,iq as Y}from"./index-c553e366.js";import{M as Z}from"./MantineProvider-094ba0de.js";var P=String.raw,E=P`
:root,
:host {
--chakra-vh: 100vh;
@@ -277,4 +277,4 @@ import{I as s,ie as T,v as l,$ as A,ig as R,aa as V,ih as z,ii as j,ij as D,ik a
}
${E}
`}),g={light:"chakra-ui-light",dark:"chakra-ui-dark"};function Q(e={}){const{preventTransition:o=!0}=e,n={setDataset:r=>{const t=o?n.preventTransition():void 0;document.documentElement.dataset.theme=r,document.documentElement.style.colorScheme=r,t==null||t()},setClassName(r){document.body.classList.add(r?g.dark:g.light),document.body.classList.remove(r?g.light:g.dark)},query(){return window.matchMedia("(prefers-color-scheme: dark)")},getSystemTheme(r){var t;return((t=n.query().matches)!=null?t:r==="dark")?"dark":"light"},addListener(r){const t=n.query(),i=a=>{r(a.matches?"dark":"light")};return typeof t.addListener=="function"?t.addListener(i):t.addEventListener("change",i),()=>{typeof t.removeListener=="function"?t.removeListener(i):t.removeEventListener("change",i)}},preventTransition(){const r=document.createElement("style");return r.appendChild(document.createTextNode("*{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(r),()=>{window.getComputedStyle(document.body),requestAnimationFrame(()=>{requestAnimationFrame(()=>{document.head.removeChild(r)})})}}};return n}var X="chakra-ui-color-mode";function L(e){return{ssr:!1,type:"localStorage",get(o){if(!(globalThis!=null&&globalThis.document))return o;let n;try{n=localStorage.getItem(e)||o}catch{}return n||o},set(o){try{localStorage.setItem(e,o)}catch{}}}}var ee=L(X),M=()=>{};function S(e,o){return e.type==="cookie"&&e.ssr?e.get(o):o}function O(e){const{value:o,children:n,options:{useSystemColorMode:r,initialColorMode:t,disableTransitionOnChange:i}={},colorModeManager:a=ee}=e,d=t==="dark"?"dark":"light",[u,p]=l.useState(()=>S(a,d)),[y,b]=l.useState(()=>S(a)),{getSystemTheme:w,setClassName:k,setDataset:x,addListener:$}=l.useMemo(()=>Q({preventTransition:i}),[i]),v=t==="system"&&!u?y:u,c=l.useCallback(m=>{const f=m==="system"?w():m;p(f),k(f==="dark"),x(f),a.set(f)},[a,w,k,x]);A(()=>{t==="system"&&b(w())},[]),l.useEffect(()=>{const m=a.get();if(m){c(m);return}if(t==="system"){c("system");return}c(d)},[a,d,t,c]);const C=l.useCallback(()=>{c(v==="dark"?"light":"dark")},[v,c]);l.useEffect(()=>{if(r)return $(c)},[r,$,c]);const N=l.useMemo(()=>({colorMode:o??v,toggleColorMode:o?M:C,setColorMode:o?M:c,forced:o!==void 0}),[v,C,c,o]);return s.jsx(R.Provider,{value:N,children:n})}O.displayName="ColorModeProvider";var te=["borders","breakpoints","colors","components","config","direction","fonts","fontSizes","fontWeights","letterSpacings","lineHeights","radii","shadows","sizes","space","styles","transition","zIndices"];function re(e){return V(e)?te.every(o=>Object.prototype.hasOwnProperty.call(e,o)):!1}function h(e){return typeof e=="function"}function oe(...e){return o=>e.reduce((n,r)=>r(n),o)}var ne=e=>function(...n){let r=[...n],t=n[n.length-1];return re(t)&&r.length>1?r=r.slice(0,r.length-1):t=e,oe(...r.map(i=>a=>h(i)?i(a):ae(a,i)))(t)},ie=ne(j);function ae(...e){return z({},...e,_)}function _(e,o,n,r){if((h(e)||h(o))&&Object.prototype.hasOwnProperty.call(r,n))return(...t)=>{const i=h(e)?e(...t):e,a=h(o)?o(...t):o;return z({},i,a,_)}}var q=l.createContext({getDocument(){return document},getWindow(){return window}});q.displayName="EnvironmentContext";function I(e){const{children:o,environment:n,disabled:r}=e,t=l.useRef(null),i=l.useMemo(()=>n||{getDocument:()=>{var d,u;return(u=(d=t.current)==null?void 0:d.ownerDocument)!=null?u:document},getWindow:()=>{var d,u;return(u=(d=t.current)==null?void 0:d.ownerDocument.defaultView)!=null?u:window}},[n]),a=!r||!n;return s.jsxs(q.Provider,{value:i,children:[o,a&&s.jsx("span",{id:"__chakra_env",hidden:!0,ref:t})]})}I.displayName="EnvironmentProvider";var se=e=>{const{children:o,colorModeManager:n,portalZIndex:r,resetScope:t,resetCSS:i=!0,theme:a={},environment:d,cssVarsRoot:u,disableEnvironment:p,disableGlobalStyle:y}=e,b=s.jsx(I,{environment:d,disabled:p,children:o});return s.jsx(D,{theme:a,cssVarsRoot:u,children:s.jsxs(O,{colorModeManager:n,options:a.config,children:[i?s.jsx(J,{scope:t}):s.jsx(B,{}),!y&&s.jsx(F,{}),r?s.jsx(G,{zIndex:r,children:b}):b]})})},le=e=>function({children:n,theme:r=e,toastOptions:t,...i}){return s.jsxs(se,{theme:r,...i,children:[s.jsx(W,{value:t==null?void 0:t.defaultOptions,children:n}),s.jsx(K,{...t})]})},de=le(j);const ue=()=>l.useMemo(()=>({colorScheme:"dark",fontFamily:"'Inter Variable', sans-serif",components:{ScrollArea:{defaultProps:{scrollbarSize:10},styles:{scrollbar:{"&:hover":{backgroundColor:"var(--invokeai-colors-baseAlpha-300)"}},thumb:{backgroundColor:"var(--invokeai-colors-baseAlpha-300)"}}}}}),[]),ce=L("@@invokeai-color-mode");function me({children:e}){const{i18n:o}=H(),n=o.dir(),r=l.useMemo(()=>ie({...U,direction:n}),[n]);l.useEffect(()=>{document.body.dir=n},[n]);const t=ue();return s.jsx(Z,{theme:t,children:s.jsx(de,{theme:r,colorModeManager:ce,toastOptions:Y,children:e})})}const ve=l.memo(me);export{ve as default};
`}),g={light:"chakra-ui-light",dark:"chakra-ui-dark"};function Q(e={}){const{preventTransition:o=!0}=e,n={setDataset:r=>{const t=o?n.preventTransition():void 0;document.documentElement.dataset.theme=r,document.documentElement.style.colorScheme=r,t==null||t()},setClassName(r){document.body.classList.add(r?g.dark:g.light),document.body.classList.remove(r?g.light:g.dark)},query(){return window.matchMedia("(prefers-color-scheme: dark)")},getSystemTheme(r){var t;return((t=n.query().matches)!=null?t:r==="dark")?"dark":"light"},addListener(r){const t=n.query(),i=a=>{r(a.matches?"dark":"light")};return typeof t.addListener=="function"?t.addListener(i):t.addEventListener("change",i),()=>{typeof t.removeListener=="function"?t.removeListener(i):t.removeEventListener("change",i)}},preventTransition(){const r=document.createElement("style");return r.appendChild(document.createTextNode("*{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),document.head.appendChild(r),()=>{window.getComputedStyle(document.body),requestAnimationFrame(()=>{requestAnimationFrame(()=>{document.head.removeChild(r)})})}}};return n}var X="chakra-ui-color-mode";function L(e){return{ssr:!1,type:"localStorage",get(o){if(!(globalThis!=null&&globalThis.document))return o;let n;try{n=localStorage.getItem(e)||o}catch{}return n||o},set(o){try{localStorage.setItem(e,o)}catch{}}}}var ee=L(X),M=()=>{};function S(e,o){return e.type==="cookie"&&e.ssr?e.get(o):o}function O(e){const{value:o,children:n,options:{useSystemColorMode:r,initialColorMode:t,disableTransitionOnChange:i}={},colorModeManager:a=ee}=e,d=t==="dark"?"dark":"light",[u,p]=l.useState(()=>S(a,d)),[y,b]=l.useState(()=>S(a)),{getSystemTheme:w,setClassName:k,setDataset:x,addListener:$}=l.useMemo(()=>Q({preventTransition:i}),[i]),v=t==="system"&&!u?y:u,c=l.useCallback(m=>{const f=m==="system"?w():m;p(f),k(f==="dark"),x(f),a.set(f)},[a,w,k,x]);I(()=>{t==="system"&&b(w())},[]),l.useEffect(()=>{const m=a.get();if(m){c(m);return}if(t==="system"){c("system");return}c(d)},[a,d,t,c]);const C=l.useCallback(()=>{c(v==="dark"?"light":"dark")},[v,c]);l.useEffect(()=>{if(r)return $(c)},[r,$,c]);const A=l.useMemo(()=>({colorMode:o??v,toggleColorMode:o?M:C,setColorMode:o?M:c,forced:o!==void 0}),[v,C,c,o]);return s.jsx(R.Provider,{value:A,children:n})}O.displayName="ColorModeProvider";var te=["borders","breakpoints","colors","components","config","direction","fonts","fontSizes","fontWeights","letterSpacings","lineHeights","radii","shadows","sizes","space","styles","transition","zIndices"];function re(e){return V(e)?te.every(o=>Object.prototype.hasOwnProperty.call(e,o)):!1}function h(e){return typeof e=="function"}function oe(...e){return o=>e.reduce((n,r)=>r(n),o)}var ne=e=>function(...n){let r=[...n],t=n[n.length-1];return re(t)&&r.length>1?r=r.slice(0,r.length-1):t=e,oe(...r.map(i=>a=>h(i)?i(a):ae(a,i)))(t)},ie=ne(j);function ae(...e){return z({},...e,_)}function _(e,o,n,r){if((h(e)||h(o))&&Object.prototype.hasOwnProperty.call(r,n))return(...t)=>{const i=h(e)?e(...t):e,a=h(o)?o(...t):o;return z({},i,a,_)}}var q=l.createContext({getDocument(){return document},getWindow(){return window}});q.displayName="EnvironmentContext";function N(e){const{children:o,environment:n,disabled:r}=e,t=l.useRef(null),i=l.useMemo(()=>n||{getDocument:()=>{var d,u;return(u=(d=t.current)==null?void 0:d.ownerDocument)!=null?u:document},getWindow:()=>{var d,u;return(u=(d=t.current)==null?void 0:d.ownerDocument.defaultView)!=null?u:window}},[n]),a=!r||!n;return s.jsxs(q.Provider,{value:i,children:[o,a&&s.jsx("span",{id:"__chakra_env",hidden:!0,ref:t})]})}N.displayName="EnvironmentProvider";var se=e=>{const{children:o,colorModeManager:n,portalZIndex:r,resetScope:t,resetCSS:i=!0,theme:a={},environment:d,cssVarsRoot:u,disableEnvironment:p,disableGlobalStyle:y}=e,b=s.jsx(N,{environment:d,disabled:p,children:o});return s.jsx(D,{theme:a,cssVarsRoot:u,children:s.jsxs(O,{colorModeManager:n,options:a.config,children:[i?s.jsx(J,{scope:t}):s.jsx(B,{}),!y&&s.jsx(F,{}),r?s.jsx(G,{zIndex:r,children:b}):b]})})},le=e=>function({children:n,theme:r=e,toastOptions:t,...i}){return s.jsxs(se,{theme:r,...i,children:[s.jsx(W,{value:t==null?void 0:t.defaultOptions,children:n}),s.jsx(K,{...t})]})},de=le(j);const ue=()=>l.useMemo(()=>({colorScheme:"dark",fontFamily:"'Inter Variable', sans-serif",components:{ScrollArea:{defaultProps:{scrollbarSize:10},styles:{scrollbar:{"&:hover":{backgroundColor:"var(--invokeai-colors-baseAlpha-300)"}},thumb:{backgroundColor:"var(--invokeai-colors-baseAlpha-300)"}}}}}),[]),ce=L("@@invokeai-color-mode");function me({children:e}){const{i18n:o}=H(),n=o.dir(),r=l.useMemo(()=>ie({...U,direction:n}),[n]);l.useEffect(()=>{document.body.dir=n},[n]);const t=ue();return s.jsx(Z,{theme:t,children:s.jsx(de,{theme:r,colorModeManager:ce,toastOptions:Y,children:e})})}const ve=l.memo(me);export{ve as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@
margin: 0;
}
</style>
<script type="module" crossorigin src="./assets/index-f820e2e3.js"></script>
<script type="module" crossorigin src="./assets/index-c553e366.js"></script>
</head>
<body dir="ltr">

View File

@@ -113,14 +113,7 @@
"images": "Bilder",
"copy": "Kopieren",
"download": "Runterladen",
"setCurrentImage": "Setze aktuelle Bild",
"featuresWillReset": "Wenn Sie dieses Bild löschen, werden diese Funktionen sofort zurückgesetzt.",
"deleteImageBin": "Gelöschte Bilder werden an den Papierkorb Ihres Betriebssystems gesendet.",
"unableToLoad": "Galerie kann nicht geladen werden",
"downloadSelection": "Auswahl herunterladen",
"currentlyInUse": "Dieses Bild wird derzeit in den folgenden Funktionen verwendet:",
"deleteImagePermanent": "Gelöschte Bilder können nicht wiederhergestellt werden.",
"autoAssignBoardOnClick": "Board per Klick automatisch zuweisen"
"setCurrentImage": "Setze aktuelle Bild"
},
"hotkeys": {
"keyboardShortcuts": "Tastenkürzel",
@@ -330,8 +323,7 @@
},
"nodesHotkeys": "Knoten Tastenkürzel",
"addNodes": {
"title": "Knotenpunkt hinzufügen",
"desc": "Öffnet das Menü zum Hinzufügen von Knoten"
"title": "Knotenpunkt hinzufügen"
}
},
"modelManager": {
@@ -437,43 +429,7 @@
"customConfigFileLocation": "Benutzerdefinierte Konfiguration Datei Speicherort",
"baseModel": "Basis Modell",
"convertToDiffusers": "Konvertiere zu Diffusers",
"diffusersModels": "Diffusers",
"noCustomLocationProvided": "Kein benutzerdefinierter Standort angegeben",
"onnxModels": "Onnx",
"vaeRepoID": "VAE-Repo-ID",
"weightedSum": "Gewichtete Summe",
"syncModelsDesc": "Wenn Ihre Modelle nicht mit dem Backend synchronisiert sind, können Sie sie mit dieser Option aktualisieren. Dies ist im Allgemeinen praktisch, wenn Sie Ihre models.yaml-Datei manuell aktualisieren oder Modelle zum InvokeAI-Stammordner hinzufügen, nachdem die Anwendung gestartet wurde.",
"vae": "VAE",
"noModels": "Keine Modelle gefunden",
"statusConverting": "Konvertieren",
"sigmoid": "Sigmoid",
"predictionType": "Vorhersagetyp (für Stable Diffusion 2.x-Modelle und gelegentliche Stable Diffusion 1.x-Modelle)",
"selectModel": "Wählen Sie Modell aus",
"repo_id": "Repo-ID",
"modelSyncFailed": "Modellsynchronisierung fehlgeschlagen",
"quickAdd": "Schnell hinzufügen",
"simpleModelDesc": "Geben Sie einen Pfad zu einem lokalen Diffusers-Modell, einem lokalen Checkpoint-/Safetensors-Modell, einer HuggingFace-Repo-ID oder einer Checkpoint-/Diffusers-Modell-URL an.",
"modelDeleted": "Modell gelöscht",
"inpainting": "v1 Ausmalen",
"modelUpdateFailed": "Modellaktualisierung fehlgeschlagen",
"useCustomConfig": "Benutzerdefinierte Konfiguration verwenden",
"settings": "Einstellungen",
"modelConversionFailed": "Modellkonvertierung fehlgeschlagen",
"syncModels": "Modelle synchronisieren",
"mergedModelSaveLocation": "Speicherort",
"modelType": "Modelltyp",
"modelsMerged": "Modelle zusammengeführt",
"modelsMergeFailed": "Modellzusammenführung fehlgeschlagen",
"convertToDiffusersHelpText1": "Dieses Modell wird in das 🧨 Diffusers-Format konvertiert.",
"modelsSynced": "Modelle synchronisiert",
"vaePrecision": "VAE-Präzision",
"mergeModels": "Modelle zusammenführen",
"interpolationType": "Interpolationstyp",
"oliveModels": "Olives",
"variant": "Variante",
"loraModels": "LoRAs",
"modelDeleteFailed": "Modell konnte nicht gelöscht werden",
"mergedModelName": "Zusammengeführter Modellname"
"diffusersModels": "Diffusers"
},
"parameters": {
"images": "Bilder",
@@ -760,33 +716,7 @@
"saveControlImage": "Speichere Referenz Bild",
"safe": "Speichern",
"ipAdapterImageFallback": "Kein IP Adapter Bild ausgewählt",
"resetIPAdapterImage": "Zurücksetzen vom IP Adapter Bild",
"pidi": "PIDI",
"normalBae": "Normales BAE",
"mlsdDescription": "Minimalistischer Liniensegmentdetektor",
"openPoseDescription": "Schätzung der menschlichen Pose mit Openpose",
"control": "Kontrolle",
"coarse": "Coarse",
"crop": "Zuschneiden",
"pidiDescription": "PIDI-Bildverarbeitung",
"mediapipeFace": "Mediapipe Gesichter",
"mlsd": "M-LSD",
"controlMode": "Steuermodus",
"cannyDescription": "Canny Ecken Erkennung",
"lineart": "Lineart",
"lineartAnimeDescription": "Lineart-Verarbeitung im Anime-Stil",
"minConfidence": "Minimales Vertrauen",
"megaControl": "Mega-Kontrolle",
"autoConfigure": "Prozessor automatisch konfigurieren",
"normalBaeDescription": "Normale BAE-Verarbeitung",
"noneDescription": "Es wurde keine Verarbeitung angewendet",
"openPose": "Openpose",
"lineartAnime": "Lineart Anime",
"mediapipeFaceDescription": "Gesichtserkennung mit Mediapipe",
"canny": "Canny",
"hedDescription": "Ganzheitlich verschachtelte Kantenerkennung",
"scribble": "Scribble",
"maxFaces": "Maximal Anzahl Gesichter"
"resetIPAdapterImage": "Zurücksetzen vom IP Adapter Bild"
},
"queue": {
"status": "Status",
@@ -828,19 +758,7 @@
"enqueueing": "Stapel in der Warteschlange",
"queueMaxExceeded": "Maximum von {{max_queue_size}} Elementen erreicht, würde {{skip}} Elemente überspringen",
"cancelBatchFailed": "Problem beim Abbruch vom Stapel",
"clearQueueAlertDialog2": "bist du sicher die Warteschlange zu leeren?",
"pruneSucceeded": "{{item_count}} abgeschlossene Elemente aus der Warteschlange entfernt",
"pauseSucceeded": "Prozessor angehalten",
"cancelFailed": "Problem beim Stornieren des Auftrags",
"pauseFailed": "Problem beim Anhalten des Prozessors",
"front": "Vorne",
"pruneTooltip": "Bereinigen Sie {{item_count}} abgeschlossene Aufträge",
"resumeFailed": "Problem beim wieder aufnehmen von Prozessor",
"pruneFailed": "Problem beim leeren der Warteschlange",
"pauseTooltip": "Pause von Prozessor",
"back": "Hinten",
"resumeSucceeded": "Prozessor wieder aufgenommen",
"resumeTooltip": "Prozessor wieder aufnehmen"
"clearQueueAlertDialog2": "bist du sicher die Warteschlange zu leeren?"
},
"metadata": {
"negativePrompt": "Negativ Beschreibung",
@@ -855,20 +773,7 @@
"noMetaData": "Keine Meta-Data gefunden",
"width": "Breite",
"createdBy": "Erstellt von",
"steps": "Schritte",
"seamless": "Nahtlos",
"positivePrompt": "Positiver Prompt",
"generationMode": "Generierungsmodus",
"Threshold": "Noise Schwelle",
"seed": "Samen",
"perlin": "Perlin Noise",
"hiresFix": "Optimierung für hohe Auflösungen",
"initImage": "Erstes Bild",
"variations": "Samengewichtspaare",
"vae": "VAE",
"workflow": "Arbeitsablauf",
"scheduler": "Scheduler",
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden"
"steps": "Schritte"
},
"popovers": {
"noiseUseCPU": {
@@ -906,68 +811,11 @@
"misses": "Cache Nötig",
"hits": "Cache Treffer",
"enable": "Aktivieren",
"clear": "Leeren",
"maxCacheSize": "Maximale Cache Größe",
"cacheSize": "Cache Größe"
"clear": "Leeren"
},
"embedding": {
"noMatchingEmbedding": "Keine passenden Embeddings",
"addEmbedding": "Embedding hinzufügen",
"incompatibleModel": "Inkompatibles Basismodell:"
},
"nodes": {
"booleanPolymorphicDescription": "Eine Sammlung boolescher Werte.",
"colorFieldDescription": "Eine RGBA-Farbe.",
"conditioningCollection": "Konditionierungssammlung",
"addNode": "Knoten hinzufügen",
"conditioningCollectionDescription": "Konditionierung kann zwischen Knoten weitergegeben werden.",
"colorPolymorphic": "Farbpolymorph",
"colorCodeEdgesHelp": "Farbkodieren Sie Kanten entsprechend ihren verbundenen Feldern",
"animatedEdges": "Animierte Kanten",
"booleanCollectionDescription": "Eine Sammlung boolescher Werte.",
"colorField": "Farbe",
"collectionItem": "Objekt in Sammlung",
"animatedEdgesHelp": "Animieren Sie ausgewählte Kanten und Kanten, die mit ausgewählten Knoten verbunden sind",
"cannotDuplicateConnection": "Es können keine doppelten Verbindungen erstellt werden",
"booleanPolymorphic": "Boolesche Polymorphie",
"colorPolymorphicDescription": "Eine Sammlung von Farben.",
"clipFieldDescription": "Tokenizer- und text_encoder-Untermodelle.",
"clipField": "Clip",
"colorCollection": "Eine Sammlung von Farben.",
"boolean": "Boolesche Werte",
"currentImage": "Aktuelles Bild",
"booleanDescription": "Boolesche Werte sind wahr oder falsch.",
"collection": "Sammlung",
"cannotConnectInputToInput": "Eingang kann nicht mit Eingang verbunden werden",
"conditioningField": "Konditionierung",
"cannotConnectOutputToOutput": "Ausgang kann nicht mit Ausgang verbunden werden",
"booleanCollection": "Boolesche Werte Sammlung",
"cannotConnectToSelf": "Es kann keine Verbindung zu sich selbst hergestellt werden",
"colorCodeEdges": "Farbkodierte Kanten",
"addNodeToolTip": "Knoten hinzufügen (Umschalt+A, Leertaste)"
},
"hrf": {
"enableHrf": "Aktivieren Sie die Korrektur für hohe Auflösungen",
"upscaleMethod": "Vergrößerungsmethoden",
"enableHrfTooltip": "Generieren Sie mit einer niedrigeren Anfangsauflösung, skalieren Sie auf die Basisauflösung hoch und führen Sie dann Image-to-Image aus.",
"metadata": {
"strength": "Hochauflösender Fix Stärke",
"enabled": "Hochauflösender Fix aktiviert",
"method": "Hochauflösender Fix Methode"
},
"hrf": "Hochauflösender Fix",
"hrfStrength": "Hochauflösende Fix Stärke",
"strengthTooltip": "Niedrigere Werte führen zu weniger Details, wodurch potenzielle Artefakte reduziert werden können."
},
"models": {
"noMatchingModels": "Keine passenden Modelle",
"loading": "lade",
"noMatchingLoRAs": "Keine passenden LoRAs",
"noLoRAsAvailable": "Keine LoRAs verfügbar",
"noModelsAvailable": "Keine Modelle verfügbar",
"selectModel": "Wählen ein Modell aus",
"noRefinerModelsInstalled": "Keine SDXL Refiner-Modelle installiert",
"noLoRAsInstalled": "Keine LoRAs installiert",
"selectLoRA": "Wählen ein LoRA aus"
}
}

View File

@@ -6,7 +6,6 @@
"flipVertically": "Flip Vertically",
"invokeProgressBar": "Invoke progress bar",
"menu": "Menu",
"mode": "Mode",
"modelSelect": "Model Select",
"modifyConfig": "Modify Config",
"nextImage": "Next Image",
@@ -31,10 +30,6 @@
"cancel": "Cancel",
"changeBoard": "Change Board",
"clearSearch": "Clear Search",
"deleteBoard": "Delete Board",
"deleteBoardAndImages": "Delete Board and Images",
"deleteBoardOnly": "Delete Board Only",
"deletedBoardsCannotbeRestored": "Deleted boards cannot be restored",
"loading": "Loading...",
"menuItemAutoAdd": "Auto-add to this Board",
"move": "Move",
@@ -56,12 +51,9 @@
"cancel": "Cancel",
"close": "Close",
"on": "On",
"checkpoint": "Checkpoint",
"communityLabel": "Community",
"controlNet": "ControlNet",
"controlAdapter": "Control Adapter",
"data": "Data",
"details": "Details",
"ipAdapter": "IP Adapter",
"t2iAdapter": "T2I Adapter",
"darkMode": "Dark Mode",
@@ -73,7 +65,6 @@
"imagePrompt": "Image Prompt",
"imageFailedToLoad": "Unable to Load Image",
"img2img": "Image To Image",
"inpaint": "inpaint",
"langArabic": "العربية",
"langBrPortuguese": "Português do Brasil",
"langDutch": "Nederlands",
@@ -102,8 +93,6 @@
"nodes": "Workflow Editor",
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
"openInNewTab": "Open in New Tab",
"outpaint": "outpaint",
"outputs": "Outputs",
"postProcessDesc1": "Invoke AI offers a wide variety of post processing features. Image Upscaling and Face Restoration are already available in the WebUI. You can access them from the Advanced Options menu of the Text To Image and Image To Image tabs. You can also process images directly, using the image action buttons above the current image display or in the viewer.",
"postProcessDesc2": "A dedicated UI will be released soon to facilitate more advanced post processing workflows.",
"postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.",
@@ -111,9 +100,7 @@
"postProcessing": "Post Processing",
"random": "Random",
"reportBugLabel": "Report Bug",
"safetensors": "Safetensors",
"settingsLabel": "Settings",
"simple": "Simple",
"statusConnected": "Connected",
"statusConvertingModel": "Converting Model",
"statusDisconnected": "Disconnected",
@@ -140,7 +127,6 @@
"statusSavingImage": "Saving Image",
"statusUpscaling": "Upscaling",
"statusUpscalingESRGAN": "Upscaling (ESRGAN)",
"template": "Template",
"training": "Training",
"trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.",
"trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.",
@@ -228,7 +214,6 @@
"setControlImageDimensions": "Set Control Image Dimensions To W/H",
"showAdvanced": "Show Advanced",
"toggleControlNet": "Toggle this ControlNet",
"unstarImage": "Unstar Image",
"w": "W",
"weight": "Weight",
"enableIPAdapter": "Enable IP Adapter",
@@ -294,7 +279,6 @@
"next": "Next",
"status": "Status",
"total": "Total",
"time": "Time",
"pending": "Pending",
"in_progress": "In Progress",
"completed": "Completed",
@@ -302,7 +286,6 @@
"canceled": "Canceled",
"completedIn": "Completed in",
"batch": "Batch",
"batchFieldValues": "Batch Field Values",
"item": "Item",
"session": "Session",
"batchValues": "Batch Values",
@@ -352,7 +335,6 @@
"loading": "Loading",
"loadMore": "Load More",
"maintainAspectRatio": "Maintain Aspect Ratio",
"noImageSelected": "No Image Selected",
"noImagesInGallery": "No Images to Display",
"setCurrentImage": "Set as Current Image",
"showGenerations": "Show Generations",
@@ -601,7 +583,7 @@
"strength": "Image to image strength",
"Threshold": "Noise Threshold",
"variations": "Seed-weight pairs",
"vae": "VAE",
"vae": "VAE",
"width": "Width",
"workflow": "Workflow"
},
@@ -624,7 +606,6 @@
"cannotUseSpaces": "Cannot Use Spaces",
"checkpointFolder": "Checkpoint Folder",
"checkpointModels": "Checkpoints",
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
"clearCheckpointFolder": "Clear Checkpoint Folder",
"closeAdvanced": "Close Advanced",
"config": "Config",
@@ -704,7 +685,6 @@
"nameValidationMsg": "Enter a name for your model",
"noCustomLocationProvided": "No Custom Location Provided",
"noModels": "No Models Found",
"noModelSelected": "No Model Selected",
"noModelsFound": "No Models Found",
"none": "none",
"notLoaded": "not loaded",
@@ -750,8 +730,6 @@
"widthValidationMsg": "Default width of your model."
},
"models": {
"addLora": "Add LoRA",
"esrganModel": "ESRGAN Model",
"loading": "loading",
"noLoRAsAvailable": "No LoRAs available",
"noMatchingLoRAs": "No matching LoRAs",
@@ -1032,7 +1010,6 @@
"maskAdjustmentsHeader": "Mask Adjustments",
"maskBlur": "Blur",
"maskBlurMethod": "Blur Method",
"maskEdge": "Mask Edge",
"negativePromptPlaceholder": "Negative Prompt",
"noiseSettings": "Noise",
"noiseThreshold": "Noise Threshold",
@@ -1080,7 +1057,6 @@
"upscale": "Upscale (Shift + U)",
"upscaleImage": "Upscale Image",
"upscaling": "Upscaling",
"unmasked": "Unmasked",
"useAll": "Use All",
"useCpuNoise": "Use CPU Noise",
"cpuNoise": "CPU Noise",
@@ -1102,7 +1078,6 @@
"dynamicPrompts": "Dynamic Prompts",
"enableDynamicPrompts": "Enable Dynamic Prompts",
"maxPrompts": "Max Prompts",
"promptsPreview": "Prompts Preview",
"promptsWithCount_one": "{{count}} Prompt",
"promptsWithCount_other": "{{count}} Prompts",
"seedBehaviour": {
@@ -1142,10 +1117,7 @@
"displayHelpIcons": "Display Help Icons",
"displayInProgress": "Display Progress Images",
"enableImageDebugging": "Enable Image Debugging",
"enableInformationalPopovers": "Enable Informational Popovers",
"enableInvisibleWatermark": "Enable Invisible Watermark",
"enableNodesEditor": "Enable Nodes Editor",
"enableNSFWChecker": "Enable NSFW Checker",
"experimental": "Experimental",
"favoriteSchedulers": "Favorite Schedulers",
"favoriteSchedulersPlaceholder": "No schedulers favorited",
@@ -1245,8 +1217,7 @@
"sentToImageToImage": "Sent To Image To Image",
"sentToUnifiedCanvas": "Sent to Unified Canvas",
"serverError": "Server Error",
"setAsCanvasInitialImage": "Set as canvas initial image",
"setCanvasInitialImage": "Set canvas initial image",
"setCanvasInitialImage": "Set as canvas initial image",
"setControlImage": "Set as control image",
"setIPAdapterImage": "Set as IP Adapter Image",
"setInitialImage": "Set as initial image",
@@ -1304,15 +1275,11 @@
},
"compositingBlur": {
"heading": "Blur",
"paragraphs": [
"The blur radius of the mask."
]
"paragraphs": ["The blur radius of the mask."]
},
"compositingBlurMethod": {
"heading": "Blur Method",
"paragraphs": [
"The method of blur applied to the masked area."
]
"paragraphs": ["The method of blur applied to the masked area."]
},
"compositingCoherencePass": {
"heading": "Coherence Pass",
@@ -1322,9 +1289,7 @@
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraphs": [
"The mode of the Coherence Pass."
]
"paragraphs": ["The mode of the Coherence Pass."]
},
"compositingCoherenceSteps": {
"heading": "Steps",
@@ -1342,9 +1307,7 @@
},
"compositingMaskAdjustments": {
"heading": "Mask Adjustments",
"paragraphs": [
"Adjust the mask."
]
"paragraphs": ["Adjust the mask."]
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
@@ -1402,9 +1365,7 @@
},
"infillMethod": {
"heading": "Infill Method",
"paragraphs": [
"Method to infill the selected area."
]
"paragraphs": ["Method to infill the selected area."]
},
"lora": {
"heading": "LoRA Weight",

View File

@@ -6,7 +6,6 @@
"flipVertically": "Flip Vertically",
"invokeProgressBar": "Invoke progress bar",
"menu": "Menu",
"mode": "Mode",
"modelSelect": "Model Select",
"modifyConfig": "Modify Config",
"nextImage": "Next Image",
@@ -31,10 +30,6 @@
"cancel": "Cancel",
"changeBoard": "Change Board",
"clearSearch": "Clear Search",
"deleteBoard": "Delete Board",
"deleteBoardAndImages": "Delete Board and Images",
"deleteBoardOnly": "Delete Board Only",
"deletedBoardsCannotbeRestored": "Deleted boards cannot be restored",
"loading": "Loading...",
"menuItemAutoAdd": "Auto-add to this Board",
"move": "Move",
@@ -56,12 +51,9 @@
"cancel": "Cancel",
"close": "Close",
"on": "On",
"checkpoint": "Checkpoint",
"communityLabel": "Community",
"controlNet": "ControlNet",
"controlAdapter": "Control Adapter",
"data": "Data",
"details": "Details",
"ipAdapter": "IP Adapter",
"t2iAdapter": "T2I Adapter",
"darkMode": "Dark Mode",
@@ -73,7 +65,6 @@
"imagePrompt": "Image Prompt",
"imageFailedToLoad": "Unable to Load Image",
"img2img": "Image To Image",
"inpaint": "inpaint",
"langArabic": "العربية",
"langBrPortuguese": "Português do Brasil",
"langDutch": "Nederlands",
@@ -102,8 +93,6 @@
"nodes": "Workflow Editor",
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
"openInNewTab": "Open in New Tab",
"outpaint": "outpaint",
"outputs": "Outputs",
"postProcessDesc1": "Invoke AI offers a wide variety of post processing features. Image Upscaling and Face Restoration are already available in the WebUI. You can access them from the Advanced Options menu of the Text To Image and Image To Image tabs. You can also process images directly, using the image action buttons above the current image display or in the viewer.",
"postProcessDesc2": "A dedicated UI will be released soon to facilitate more advanced post processing workflows.",
"postProcessDesc3": "The Invoke AI Command Line Interface offers various other features including Embiggen.",
@@ -111,9 +100,7 @@
"postProcessing": "Post Processing",
"random": "Random",
"reportBugLabel": "Report Bug",
"safetensors": "Safetensors",
"settingsLabel": "Settings",
"simple": "Simple",
"statusConnected": "Connected",
"statusConvertingModel": "Converting Model",
"statusDisconnected": "Disconnected",
@@ -140,7 +127,6 @@
"statusSavingImage": "Saving Image",
"statusUpscaling": "Upscaling",
"statusUpscalingESRGAN": "Upscaling (ESRGAN)",
"template": "Template",
"training": "Training",
"trainingDesc1": "A dedicated workflow for training your own embeddings and checkpoints using Textual Inversion and Dreambooth from the web interface.",
"trainingDesc2": "InvokeAI already supports training custom embeddourings using Textual Inversion using the main script.",
@@ -228,7 +214,6 @@
"setControlImageDimensions": "Set Control Image Dimensions To W/H",
"showAdvanced": "Show Advanced",
"toggleControlNet": "Toggle this ControlNet",
"unstarImage": "Unstar Image",
"w": "W",
"weight": "Weight",
"enableIPAdapter": "Enable IP Adapter",
@@ -294,7 +279,6 @@
"next": "Next",
"status": "Status",
"total": "Total",
"time": "Time",
"pending": "Pending",
"in_progress": "In Progress",
"completed": "Completed",
@@ -302,7 +286,6 @@
"canceled": "Canceled",
"completedIn": "Completed in",
"batch": "Batch",
"batchFieldValues": "Batch Field Values",
"item": "Item",
"session": "Session",
"batchValues": "Batch Values",
@@ -352,7 +335,6 @@
"loading": "Loading",
"loadMore": "Load More",
"maintainAspectRatio": "Maintain Aspect Ratio",
"noImageSelected": "No Image Selected",
"noImagesInGallery": "No Images to Display",
"setCurrentImage": "Set as Current Image",
"showGenerations": "Show Generations",
@@ -601,7 +583,7 @@
"strength": "Image to image strength",
"Threshold": "Noise Threshold",
"variations": "Seed-weight pairs",
"vae": "VAE",
"vae": "VAE",
"width": "Width",
"workflow": "Workflow"
},
@@ -624,7 +606,6 @@
"cannotUseSpaces": "Cannot Use Spaces",
"checkpointFolder": "Checkpoint Folder",
"checkpointModels": "Checkpoints",
"checkpointOrSafetensors": "$t(common.checkpoint) / $t(common.safetensors)",
"clearCheckpointFolder": "Clear Checkpoint Folder",
"closeAdvanced": "Close Advanced",
"config": "Config",
@@ -704,7 +685,6 @@
"nameValidationMsg": "Enter a name for your model",
"noCustomLocationProvided": "No Custom Location Provided",
"noModels": "No Models Found",
"noModelSelected": "No Model Selected",
"noModelsFound": "No Models Found",
"none": "none",
"notLoaded": "not loaded",
@@ -750,8 +730,6 @@
"widthValidationMsg": "Default width of your model."
},
"models": {
"addLora": "Add LoRA",
"esrganModel": "ESRGAN Model",
"loading": "loading",
"noLoRAsAvailable": "No LoRAs available",
"noMatchingLoRAs": "No matching LoRAs",
@@ -1032,7 +1010,6 @@
"maskAdjustmentsHeader": "Mask Adjustments",
"maskBlur": "Blur",
"maskBlurMethod": "Blur Method",
"maskEdge": "Mask Edge",
"negativePromptPlaceholder": "Negative Prompt",
"noiseSettings": "Noise",
"noiseThreshold": "Noise Threshold",
@@ -1080,7 +1057,6 @@
"upscale": "Upscale (Shift + U)",
"upscaleImage": "Upscale Image",
"upscaling": "Upscaling",
"unmasked": "Unmasked",
"useAll": "Use All",
"useCpuNoise": "Use CPU Noise",
"cpuNoise": "CPU Noise",
@@ -1102,7 +1078,6 @@
"dynamicPrompts": "Dynamic Prompts",
"enableDynamicPrompts": "Enable Dynamic Prompts",
"maxPrompts": "Max Prompts",
"promptsPreview": "Prompts Preview",
"promptsWithCount_one": "{{count}} Prompt",
"promptsWithCount_other": "{{count}} Prompts",
"seedBehaviour": {
@@ -1142,10 +1117,7 @@
"displayHelpIcons": "Display Help Icons",
"displayInProgress": "Display Progress Images",
"enableImageDebugging": "Enable Image Debugging",
"enableInformationalPopovers": "Enable Informational Popovers",
"enableInvisibleWatermark": "Enable Invisible Watermark",
"enableNodesEditor": "Enable Nodes Editor",
"enableNSFWChecker": "Enable NSFW Checker",
"experimental": "Experimental",
"favoriteSchedulers": "Favorite Schedulers",
"favoriteSchedulersPlaceholder": "No schedulers favorited",
@@ -1245,8 +1217,7 @@
"sentToImageToImage": "Sent To Image To Image",
"sentToUnifiedCanvas": "Sent to Unified Canvas",
"serverError": "Server Error",
"setAsCanvasInitialImage": "Set as canvas initial image",
"setCanvasInitialImage": "Set canvas initial image",
"setCanvasInitialImage": "Set as canvas initial image",
"setControlImage": "Set as control image",
"setIPAdapterImage": "Set as IP Adapter Image",
"setInitialImage": "Set as initial image",
@@ -1304,15 +1275,11 @@
},
"compositingBlur": {
"heading": "Blur",
"paragraphs": [
"The blur radius of the mask."
]
"paragraphs": ["The blur radius of the mask."]
},
"compositingBlurMethod": {
"heading": "Blur Method",
"paragraphs": [
"The method of blur applied to the masked area."
]
"paragraphs": ["The method of blur applied to the masked area."]
},
"compositingCoherencePass": {
"heading": "Coherence Pass",
@@ -1322,9 +1289,7 @@
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraphs": [
"The mode of the Coherence Pass."
]
"paragraphs": ["The mode of the Coherence Pass."]
},
"compositingCoherenceSteps": {
"heading": "Steps",
@@ -1342,9 +1307,7 @@
},
"compositingMaskAdjustments": {
"heading": "Mask Adjustments",
"paragraphs": [
"Adjust the mask."
]
"paragraphs": ["Adjust the mask."]
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
@@ -1402,9 +1365,7 @@
},
"infillMethod": {
"heading": "Infill Method",
"paragraphs": [
"Method to infill the selected area."
]
"paragraphs": ["Method to infill the selected area."]
},
"lora": {
"heading": "LoRA Weight",

View File

@@ -79,7 +79,7 @@ export const addImageUploadedFulfilledListener = () => {
dispatch(
addToast({
...DEFAULT_UPLOADED_TOAST,
description: t('toast.setAsCanvasInitialImage'),
description: t('toast.setCanvasInitialImage'),
})
);
return;

View File

@@ -17,7 +17,6 @@ import IAIInformationalPopover from 'common/components/IAIInformationalPopover/I
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
import { memo } from 'react';
import { FaCircleExclamation } from 'react-icons/fa6';
import { useTranslation } from 'react-i18next';
const selector = createSelector(
stateSelector,
@@ -39,7 +38,6 @@ const listItemStyles: ChakraProps['sx'] = {
};
const ParamDynamicPromptsPreview = () => {
const { t } = useTranslation();
const { prompts, parsingError, isLoading, isError } =
useAppSelector(selector);
@@ -71,7 +69,7 @@ const ParamDynamicPromptsPreview = () => {
overflow="hidden"
textOverflow="ellipsis"
>
{t('dynamicPrompts.promptsPreview')} ({prompts.length})
Prompts Preview ({prompts.length})
{parsingError && ` - ${parsingError}`}
</FormLabel>
<Flex

View File

@@ -115,7 +115,7 @@ const DeleteBoardModal = (props: Props) => {
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{t('controlnet.delete')} {boardToDelete.board_name}
Delete {boardToDelete.board_name}
</AlertDialogHeader>
<AlertDialogBody>
@@ -136,7 +136,7 @@ const DeleteBoardModal = (props: Props) => {
bottomMessage={t('boards.bottomMessage')}
/>
)}
<Text>{t('boards.deletedBoardsCannotbeRestored')}</Text>
<Text>Deleted boards cannot be restored.</Text>
<Text>
{canRestoreDeletedImagesFromBin
? t('gallery.deleteImageBin')
@@ -149,21 +149,21 @@ const DeleteBoardModal = (props: Props) => {
sx={{ justifyContent: 'space-between', width: 'full', gap: 2 }}
>
<IAIButton ref={cancelRef} onClick={handleClose}>
{t('boards.cancel')}
Cancel
</IAIButton>
<IAIButton
colorScheme="warning"
isLoading={isLoading}
onClick={handleDeleteBoardOnly}
>
{t('boards.deleteBoardOnly')}
Delete Board Only
</IAIButton>
<IAIButton
colorScheme="error"
isLoading={isLoading}
onClick={handleDeleteBoardAndImages}
>
{t('boards.deleteBoardAndImages')}
Delete Board and Images
</IAIButton>
</Flex>
</AlertDialogFooter>

View File

@@ -2,14 +2,13 @@ import { MenuItem } from '@chakra-ui/react';
import { memo, useCallback } from 'react';
import { FaTrash } from 'react-icons/fa';
import { BoardDTO } from 'services/api/types';
import { useTranslation } from 'react-i18next';
type Props = {
board: BoardDTO;
setBoardToDelete?: (board?: BoardDTO) => void;
};
const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
const { t } = useTranslation();
const handleDelete = useCallback(() => {
if (!setBoardToDelete) {
return;
@@ -35,7 +34,7 @@ const GalleryBoardContextMenuItems = ({ board, setBoardToDelete }: Props) => {
icon={<FaTrash />}
onClick={handleDelete}
>
{t('boards.deleteBoard')}
Delete Board
</MenuItem>
</>
);

View File

@@ -170,10 +170,7 @@ const CurrentImagePreview = () => {
useThumbailFallback
dropLabel={t('gallery.setCurrentImage')}
noContentFallback={
<IAINoContentFallback
icon={FaImage}
label={t('gallery.noImageSelected')}
/>
<IAINoContentFallback icon={FaImage} label="No image selected" />
}
dataTestId="image-preview"
/>

View File

@@ -104,7 +104,7 @@ const MultipleSelectionMenuItems = () => {
</MenuItem>
)}
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
{t('boards.changeBoard')}
Change Board
</MenuItem>
<MenuItem
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}

View File

@@ -224,14 +224,14 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
</MenuItem>
)}
<MenuItem icon={<FaFolder />} onClickCapture={handleChangeBoard}>
{t('boards.changeBoard')}
Change Board
</MenuItem>
{imageDTO.starred ? (
<MenuItem
icon={customStarUi ? customStarUi.off.icon : <MdStar />}
onClickCapture={handleUnstarImage}
>
{customStarUi ? customStarUi.off.text : t('controlnet.unstarImage')}
{customStarUi ? customStarUi.off.text : `Unstar Image`}
</MenuItem>
) : (
<MenuItem

View File

@@ -157,8 +157,6 @@ const ImageMetadataActions = (props: Props) => {
return null;
}
console.log(metadata);
return (
<>
{metadata.created_by && (

View File

@@ -95,7 +95,7 @@ const ParamLoRASelect = () => {
return (
<IAIMantineSearchableSelect
placeholder={data.length === 0 ? 'All LoRAs added' : t('models.addLora')}
placeholder={data.length === 0 ? 'All LoRAs added' : 'Add LoRA'}
value={null}
data={data}
nothingFound="No matching LoRAs"

View File

@@ -3,10 +3,8 @@ import IAIButton from 'common/components/IAIButton';
import { useCallback, useState } from 'react';
import AdvancedAddModels from './AdvancedAddModels';
import SimpleAddModels from './SimpleAddModels';
import { useTranslation } from 'react-i18next';
export default function AddModels() {
const { t } = useTranslation();
const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>(
'simple'
);
@@ -29,14 +27,14 @@ export default function AddModels() {
isChecked={addModelMode == 'simple'}
onClick={handleAddModelSimple}
>
{t('common.simple')}
Simple
</IAIButton>
<IAIButton
size="sm"
isChecked={addModelMode == 'advanced'}
onClick={handleAddModelAdvanced}
>
{t('common.advanced')}
Advanced
</IAIButton>
</ButtonGroup>
<Flex

View File

@@ -1,11 +1,16 @@
import { Flex } from '@chakra-ui/react';
import { SelectItem } from '@mantine/core';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState } from 'react';
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
import AdvancedAddDiffusers from './AdvancedAddDiffusers';
import { useTranslation } from 'react-i18next';
export const advancedAddModeData: SelectItem[] = [
{ label: 'Diffusers', value: 'diffusers' },
{ label: 'Checkpoint / Safetensors', value: 'checkpoint' },
];
export type ManualAddMode = 'diffusers' | 'checkpoint';
export default function AdvancedAddModels() {
@@ -20,14 +25,6 @@ export default function AdvancedAddModels() {
setAdvancedAddMode(v as ManualAddMode);
}, []);
const advancedAddModeData: SelectItem[] = useMemo(
() => [
{ label: t('modelManager.diffusersModels'), value: 'diffusers' },
{ label: t('modelManager.checkpointOrSafetensors'), value: 'checkpoint' },
],
[t]
);
return (
<Flex flexDirection="column" gap={4} width="100%">
<IAIMantineSelect

View File

@@ -4,14 +4,13 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { motion } from 'framer-motion';
import { useCallback, useEffect, useState, useMemo } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { FaTimes } from 'react-icons/fa';
import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
import AdvancedAddDiffusers from './AdvancedAddDiffusers';
import { ManualAddMode } from './AdvancedAddModels';
import { ManualAddMode, advancedAddModeData } from './AdvancedAddModels';
import { useTranslation } from 'react-i18next';
import { SelectItem } from '@mantine/core';
export default function ScanAdvancedAddModels() {
const advancedAddScanModel = useAppSelector(
@@ -20,14 +19,6 @@ export default function ScanAdvancedAddModels() {
const { t } = useTranslation();
const advancedAddModeData: SelectItem[] = useMemo(
() => [
{ label: t('modelManager.diffusersModels'), value: 'diffusers' },
{ label: t('modelManager.checkpointOrSafetensors'), value: 'checkpoint' },
],
[t]
);
const [advancedAddMode, setAdvancedAddMode] =
useState<ManualAddMode>('diffusers');

View File

@@ -227,7 +227,7 @@ export default function MergeModelsPanel() {
<Flex columnGap={4}>
<IAIMantineSelect
label={t('modelManager.modelType')}
label="Model Type"
w="100%"
data={baseModelTypeSelectData}
value={baseModel}

View File

@@ -13,7 +13,6 @@ import DiffusersModelEdit from './ModelManagerPanel/DiffusersModelEdit';
import LoRAModelEdit from './ModelManagerPanel/LoRAModelEdit';
import ModelList from './ModelManagerPanel/ModelList';
import { ALL_BASE_MODELS } from 'services/api/constants';
import { useTranslation } from 'react-i18next';
export default function ModelManagerPanel() {
const [selectedModelId, setSelectedModelId] = useState<string>();
@@ -46,7 +45,6 @@ type ModelEditProps = {
};
const ModelEdit = (props: ModelEditProps) => {
const { t } = useTranslation();
const { model } = props;
if (model?.model_format === 'checkpoint') {
@@ -77,7 +75,7 @@ const ModelEdit = (props: ModelEditProps) => {
userSelect: 'none',
}}
>
<Text variant="subtext">{t('modelManager.noModelSelected')}</Text>
<Text variant="subtext">No Model Selected</Text>
</Flex>
);
};

View File

@@ -54,7 +54,7 @@ export default function SyncModelsButton(props: SyncModelsButtonProps) {
minW="max-content"
{...rest}
>
{t('modelManager.syncModels')}
Sync Models
</IAIButton>
) : (
<IAIIconButton

View File

@@ -57,7 +57,7 @@ const AddNodePopover = () => {
const { t } = useTranslation();
const fieldFilter = useAppSelector(
(state) => state.nodes.currentConnectionFieldType
(state) => state.nodes.connectionStartFieldType
);
const handleFilter = useAppSelector(
(state) => state.nodes.connectionStartParams?.handleType
@@ -74,9 +74,13 @@ const AddNodePopover = () => {
return some(handles, (handle) => {
const sourceType =
handleFilter == 'source' ? fieldFilter : handle.type;
handleFilter == 'source'
? fieldFilter
: handle.originalType ?? handle.type;
const targetType =
handleFilter == 'target' ? fieldFilter : handle.type;
handleFilter == 'target'
? fieldFilter
: handle.originalType ?? handle.type;
return validateSourceAndTargetTypes(sourceType, targetType);
});
@@ -111,7 +115,7 @@ const AddNodePopover = () => {
data.sort((a, b) => a.label.localeCompare(b.label));
return { data, t };
return { data };
},
defaultSelectorOptions
);

View File

@@ -2,18 +2,17 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { FIELDS } from 'features/nodes/types/constants';
import { memo } from 'react';
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
import { getFieldColor } from '../edges/util/getEdgeColor';
const selector = createSelector(stateSelector, ({ nodes }) => {
const { shouldAnimateEdges, currentConnectionFieldType, shouldColorEdges } =
const { shouldAnimateEdges, connectionStartFieldType, shouldColorEdges } =
nodes;
const stroke =
currentConnectionFieldType && shouldColorEdges
? colorTokenToCssVar(FIELDS[currentConnectionFieldType].color)
: colorTokenToCssVar('base.500');
const stroke = shouldColorEdges
? getFieldColor(connectionStartFieldType)
: colorTokenToCssVar('base.500');
let className = 'react-flow__custom_connection-path';

View File

@@ -0,0 +1,12 @@
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { FIELDS } from 'features/nodes/types/constants';
import { FieldType } from 'features/nodes/types/types';
export const getFieldColor = (fieldType: FieldType | string | null): string => {
if (!fieldType) {
return colorTokenToCssVar('base.500');
}
const color = FIELDS[fieldType]?.color;
return color ? colorTokenToCssVar(color) : colorTokenToCssVar('base.500');
};

View File

@@ -2,8 +2,8 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { FIELDS } from 'features/nodes/types/constants';
import { isInvocationNode } from 'features/nodes/types/types';
import { getFieldColor } from './getEdgeColor';
export const makeEdgeSelector = (
source: string,
@@ -29,7 +29,7 @@ export const makeEdgeSelector = (
const stroke =
sourceType && nodes.shouldColorEdges
? colorTokenToCssVar(FIELDS[sourceType].color)
? getFieldColor(sourceType)
: colorTokenToCssVar('base.500');
return {

View File

@@ -1,11 +1,12 @@
import { Tooltip } from '@chakra-ui/react';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import {
COLLECTION_TYPES,
FIELDS,
getIsCollection,
getIsPolymorphic,
} from 'features/nodes/store/util/parseFieldType';
import {
HANDLE_TOOLTIP_OPEN_DELAY,
MODEL_TYPES,
POLYMORPHIC_TYPES,
} from 'features/nodes/types/constants';
import {
InputFieldTemplate,
@@ -13,6 +14,7 @@ import {
} from 'features/nodes/types/types';
import { CSSProperties, memo, useMemo } from 'react';
import { Handle, HandleType, Position } from 'reactflow';
import { getFieldColor } from '../../../edges/util/getEdgeColor';
export const handleBaseStyles: CSSProperties = {
position: 'absolute',
@@ -47,23 +49,21 @@ const FieldHandle = (props: FieldHandleProps) => {
isConnectionStartField,
connectionError,
} = props;
const { name, type } = fieldTemplate;
const { color: typeColor, title } = FIELDS[type];
const { name } = fieldTemplate;
const type = fieldTemplate.originalType ?? fieldTemplate.type;
const styles: CSSProperties = useMemo(() => {
const isCollectionType = COLLECTION_TYPES.includes(type);
const isPolymorphicType = POLYMORPHIC_TYPES.includes(type);
const isModelType = MODEL_TYPES.includes(type);
const color = colorTokenToCssVar(typeColor);
const isCollection = getIsCollection(fieldTemplate.type);
const isPolymorphic = getIsPolymorphic(fieldTemplate.type);
const isModelType = MODEL_TYPES.some((t) => t === type);
const color = getFieldColor(type);
const s: CSSProperties = {
backgroundColor:
isCollectionType || isPolymorphicType
? 'var(--invokeai-colors-base-900)'
: color,
isCollection || isPolymorphic ? colorTokenToCssVar('base.900') : color,
position: 'absolute',
width: '1rem',
height: '1rem',
borderWidth: isCollectionType || isPolymorphicType ? 4 : 0,
borderWidth: isCollection || isPolymorphic ? 4 : 0,
borderStyle: 'solid',
borderColor: color,
borderRadius: isModelType ? 4 : '100%',
@@ -93,22 +93,19 @@ const FieldHandle = (props: FieldHandleProps) => {
return s;
}, [
connectionError,
fieldTemplate.type,
handleType,
isConnectionInProgress,
isConnectionStartField,
type,
typeColor,
]);
const tooltip = useMemo(() => {
if (isConnectionInProgress && isConnectionStartField) {
return title;
}
if (isConnectionInProgress && connectionError) {
return connectionError ?? title;
return connectionError;
}
return title;
}, [connectionError, isConnectionInProgress, isConnectionStartField, title]);
return type;
}, [connectionError, isConnectionInProgress, type]);
return (
<Tooltip

View File

@@ -1,7 +1,6 @@
import { Flex, Text } from '@chakra-ui/react';
import { useFieldData } from 'features/nodes/hooks/useFieldData';
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
import { FIELDS } from 'features/nodes/types/constants';
import {
isInputFieldTemplate,
isInputFieldValue,
@@ -9,7 +8,6 @@ import {
import { startCase } from 'lodash-es';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
interface Props {
nodeId: string;
fieldName: string;
@@ -49,7 +47,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
{fieldTemplate.description}
</Text>
)}
{fieldTemplate && <Text>Type: {FIELDS[fieldTemplate.type].title}</Text>}
{fieldTemplate && <Text>Type: {fieldTemplate.originalType}</Text>}
{isInputTemplate && <Text>Input: {startCase(fieldTemplate.input)}</Text>}
</Flex>
);

View File

@@ -127,7 +127,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
py: 4,
}}
>
<Heading size="sm">{t('parameters.general')}</Heading>
<Heading size="sm">General</Heading>
<IAISwitch
formLabelProps={formLabelProps}
onChange={handleChangeShouldAnimate}
@@ -159,7 +159,7 @@ const WorkflowEditorSettings = forwardRef((_, ref) => {
helperText={t('nodes.fullyContainNodesHelp')}
/>
<Heading size="sm" pt={4}>
{t('common.advanced')}
Advanced
</Heading>
<IAISwitch
formLabelProps={formLabelProps}

View File

@@ -10,11 +10,9 @@ import { memo } from 'react';
import InspectorDataTab from './InspectorDataTab';
import InspectorOutputsTab from './InspectorOutputsTab';
import InspectorTemplateTab from './InspectorTemplateTab';
import { useTranslation } from 'react-i18next';
import InspectorDetailsTab from './InspectorDetailsTab';
const InspectorPanel = () => {
const { t } = useTranslation();
return (
<Flex
layerStyle="first"
@@ -32,10 +30,10 @@ const InspectorPanel = () => {
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
>
<TabList>
<Tab>{t('common.details')}</Tab>
<Tab>{t('common.outputs')}</Tab>
<Tab>{t('common.data')}</Tab>
<Tab>{t('common.template')}</Tab>
<Tab>Details</Tab>
<Tab>Outputs</Tab>
<Tab>Data</Tab>
<Tab>Template</Tab>
</TabList>
<TabPanels>

View File

@@ -10,10 +10,8 @@ import { memo } from 'react';
import WorkflowGeneralTab from './WorkflowGeneralTab';
import WorkflowJSONTab from './WorkflowJSONTab';
import WorkflowLinearTab from './WorkflowLinearTab';
import { useTranslation } from 'react-i18next';
const WorkflowPanel = () => {
const { t } = useTranslation();
return (
<Flex
layerStyle="first"
@@ -31,8 +29,8 @@ const WorkflowPanel = () => {
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
>
<TabList>
<Tab>{t('common.linear')}</Tab>
<Tab>{t('common.details')}</Tab>
<Tab>Linear</Tab>
<Tab>Details</Tab>
<Tab>JSON</Tab>
</TabList>

View File

@@ -4,11 +4,9 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { getIsPolymorphic } from '../store/util/parseFieldType';
import { TYPES_WITH_INPUT_COMPONENTS } from '../types/constants';
import { isInvocationNode } from '../types/types';
import {
POLYMORPHIC_TYPES,
TYPES_WITH_INPUT_COMPONENTS,
} from '../types/constants';
import { getSortedFilteredFieldNames } from '../util/getSortedFilteredFieldNames';
export const useAnyOrDirectInputFieldNames = (nodeId: string) => {
@@ -28,7 +26,7 @@ export const useAnyOrDirectInputFieldNames = (nodeId: string) => {
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(['any', 'direct'].includes(field.input) ||
POLYMORPHIC_TYPES.includes(field.type)) &&
getIsPolymorphic(field.type)) &&
TYPES_WITH_INPUT_COMPONENTS.includes(field.type)
);
return getSortedFilteredFieldNames(fields);

View File

@@ -4,10 +4,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import {
POLYMORPHIC_TYPES,
TYPES_WITH_INPUT_COMPONENTS,
} from '../types/constants';
import { getIsPolymorphic } from '../store/util/parseFieldType';
import { TYPES_WITH_INPUT_COMPONENTS } from '../types/constants';
import { isInvocationNode } from '../types/types';
import { getSortedFilteredFieldNames } from '../util/getSortedFilteredFieldNames';
@@ -29,8 +27,7 @@ export const useConnectionInputFieldNames = (nodeId: string) => {
// get the visible fields
const fields = map(nodeTemplate.inputs).filter(
(field) =>
(field.input === 'connection' &&
!POLYMORPHIC_TYPES.includes(field.type)) ||
(field.input === 'connection' && !getIsPolymorphic(field.type)) ||
!TYPES_WITH_INPUT_COMPONENTS.includes(field.type)
);

View File

@@ -8,7 +8,7 @@ import { useFieldType } from './useFieldType.ts';
const selectIsConnectionInProgress = createSelector(
stateSelector,
({ nodes }) =>
nodes.currentConnectionFieldType !== null &&
nodes.connectionStartFieldType !== null &&
nodes.connectionStartParams !== null
);

View File

@@ -20,7 +20,8 @@ export const useFieldType = (
if (!isInvocationNode(node)) {
return;
}
return node?.data[KIND_MAP[kind]][fieldName]?.type;
const field = node.data[KIND_MAP[kind]][fieldName];
return field?.originalType ?? field?.type;
},
defaultSelectorOptions
),

View File

@@ -34,10 +34,10 @@ export const useIsValidConnection = () => {
return false;
}
const sourceType = sourceNode.data.outputs[sourceHandle]?.type;
const targetType = targetNode.data.inputs[targetHandle]?.type;
const sourceField = sourceNode.data.outputs[sourceHandle];
const targetField = targetNode.data.inputs[targetHandle];
if (!sourceType || !targetType) {
if (!sourceField || !targetField) {
// something has gone terribly awry
return false;
}
@@ -70,12 +70,18 @@ export const useIsValidConnection = () => {
return edge.target === target && edge.targetHandle === targetHandle;
}) &&
// except CollectionItem inputs can have multiples
targetType !== 'CollectionItem'
targetField.type !== 'CollectionItem'
) {
return false;
}
if (!validateSourceAndTargetTypes(sourceType, targetType)) {
// Must use the originalType here if it exists
if (
!validateSourceAndTargetTypes(
sourceField?.originalType ?? sourceField.type,
targetField?.originalType ?? targetField.type
)
) {
return false;
}

View File

@@ -6,7 +6,7 @@ import { NodesState } from './types';
export const nodesPersistDenylist: (keyof NodesState)[] = [
'nodeTemplates',
'connectionStartParams',
'currentConnectionFieldType',
'connectionStartFieldType',
'selectedNodes',
'selectedEdges',
'isReady',

View File

@@ -93,7 +93,7 @@ export const initialNodesState: NodesState = {
nodeTemplates: {},
isReady: false,
connectionStartParams: null,
currentConnectionFieldType: null,
connectionStartFieldType: null,
connectionMade: false,
modifyingEdge: false,
addNewNodePosition: null,
@@ -203,7 +203,7 @@ const nodesSlice = createSlice({
nodeId &&
handleId &&
handleType &&
state.currentConnectionFieldType
state.connectionStartFieldType
) {
const newConnection = findConnectionToValidHandle(
node,
@@ -212,7 +212,7 @@ const nodesSlice = createSlice({
nodeId,
handleId,
handleType,
state.currentConnectionFieldType
state.connectionStartFieldType
);
if (newConnection) {
state.edges = addEdge(
@@ -224,7 +224,7 @@ const nodesSlice = createSlice({
}
state.connectionStartParams = null;
state.currentConnectionFieldType = null;
state.connectionStartFieldType = null;
},
edgeChangeStarted: (state) => {
state.modifyingEdge = true;
@@ -258,10 +258,11 @@ const nodesSlice = createSlice({
handleType === 'source'
? node.data.outputs[handleId]
: node.data.inputs[handleId];
state.currentConnectionFieldType = field?.type ?? null;
state.connectionStartFieldType =
field?.originalType ?? field?.type ?? null;
},
connectionMade: (state, action: PayloadAction<Connection>) => {
const fieldType = state.currentConnectionFieldType;
const fieldType = state.connectionStartFieldType;
if (!fieldType) {
return;
}
@@ -286,7 +287,7 @@ const nodesSlice = createSlice({
nodeId &&
handleId &&
handleType &&
state.currentConnectionFieldType
state.connectionStartFieldType
) {
const newConnection = findConnectionToValidHandle(
mouseOverNode,
@@ -295,7 +296,7 @@ const nodesSlice = createSlice({
nodeId,
handleId,
handleType,
state.currentConnectionFieldType
state.connectionStartFieldType
);
if (newConnection) {
state.edges = addEdge(
@@ -306,14 +307,14 @@ const nodesSlice = createSlice({
}
}
state.connectionStartParams = null;
state.currentConnectionFieldType = null;
state.connectionStartFieldType = null;
} else {
state.addNewNodePosition = action.payload.cursorPosition;
state.isAddNodePopoverOpen = true;
}
} else {
state.connectionStartParams = null;
state.currentConnectionFieldType = null;
state.connectionStartFieldType = null;
}
state.modifyingEdge = false;
},
@@ -942,7 +943,7 @@ const nodesSlice = createSlice({
//Make sure these get reset if we close the popover and haven't selected a node
state.connectionStartParams = null;
state.currentConnectionFieldType = null;
state.connectionStartFieldType = null;
},
addNodePopoverToggled: (state) => {
state.isAddNodePopoverOpen = !state.isAddNodePopoverOpen;

View File

@@ -21,7 +21,7 @@ export type NodesState = {
edges: Edge<InvocationEdgeExtra>[];
nodeTemplates: Record<string, InvocationTemplate>;
connectionStartParams: OnConnectStartParams | null;
currentConnectionFieldType: FieldType | null;
connectionStartFieldType: FieldType | string | null;
connectionMade: boolean;
modifyingEdge: boolean;
shouldShowFieldTypeLegend: boolean;

View File

@@ -94,6 +94,7 @@ export const buildNodeData = (
name: outputName,
type: outputTemplate.type,
fieldKind: 'output',
originalType: outputTemplate.originalType,
};
outputsAccumulator[outputName] = outputFieldValue;

View File

@@ -12,7 +12,7 @@ import { getIsGraphAcyclic } from './getIsGraphAcyclic';
const isValidConnection = (
edges: Edge[],
handleCurrentType: HandleType,
handleCurrentFieldType: FieldType,
handleCurrentFieldType: FieldType | string,
node: Node,
handle: InputFieldValue | OutputFieldValue
) => {
@@ -35,7 +35,12 @@ const isValidConnection = (
}
}
if (!validateSourceAndTargetTypes(handleCurrentFieldType, handle.type)) {
if (
!validateSourceAndTargetTypes(
handleCurrentFieldType,
handle.originalType ?? handle.type
)
) {
isValidConnection = false;
}
@@ -49,7 +54,7 @@ export const findConnectionToValidHandle = (
handleCurrentNodeId: string,
handleCurrentName: string,
handleCurrentType: HandleType,
handleCurrentFieldType: FieldType
handleCurrentFieldType: FieldType | string
): Connection | null => {
if (node.id === handleCurrentNodeId) {
return null;

View File

@@ -1,9 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { getIsGraphAcyclic } from './getIsGraphAcyclic';
import { FieldType } from 'features/nodes/types/types';
import i18n from 'i18next';
import { HandleType } from 'reactflow';
import { getIsGraphAcyclic } from './getIsGraphAcyclic';
import { validateSourceAndTargetTypes } from './validateSourceAndTargetTypes';
/**
@@ -15,17 +15,17 @@ export const makeConnectionErrorSelector = (
nodeId: string,
fieldName: string,
handleType: HandleType,
fieldType?: FieldType
fieldType?: FieldType | string
) => {
return createSelector(stateSelector, (state) => {
if (!fieldType) {
return i18n.t('nodes.noFieldType');
}
const { currentConnectionFieldType, connectionStartParams, nodes, edges } =
const { connectionStartFieldType, connectionStartParams, nodes, edges } =
state.nodes;
if (!connectionStartParams || !currentConnectionFieldType) {
if (!connectionStartParams || !connectionStartFieldType) {
return i18n.t('nodes.noConnectionInProgress');
}
@@ -40,9 +40,9 @@ export const makeConnectionErrorSelector = (
}
const targetType =
handleType === 'target' ? fieldType : currentConnectionFieldType;
handleType === 'target' ? fieldType : connectionStartFieldType;
const sourceType =
handleType === 'source' ? fieldType : currentConnectionFieldType;
handleType === 'source' ? fieldType : connectionStartFieldType;
if (nodeId === connectionNodeId) {
return i18n.t('nodes.cannotConnectToSelf');

View File

@@ -0,0 +1,14 @@
import { FieldType } from 'features/nodes/types/types';
export const getIsPolymorphic = (type: FieldType | string): boolean =>
type.endsWith('Polymorphic');
export const getIsCollection = (type: FieldType | string): boolean =>
type.endsWith('Collection');
export const getBaseType = (type: FieldType | string): FieldType | string =>
getIsPolymorphic(type)
? type.replace(/Polymorphic$/, '')
: getIsCollection(type)
? type.replace(/Collection$/, '')
: type;

View File

@@ -1,18 +1,32 @@
import {
COLLECTION_MAP,
COLLECTION_TYPES,
POLYMORPHIC_TO_SINGLE_MAP,
POLYMORPHIC_TYPES,
} from 'features/nodes/types/constants';
import { FieldType } from 'features/nodes/types/types';
import {
getBaseType,
getIsCollection,
getIsPolymorphic,
} from './parseFieldType';
/**
* Validates that the source and target types are compatible for a connection.
* @param sourceType The type of the source field. Must be the originalType if it exists.
* @param targetType The type of the target field. Must be the originalType if it exists.
* @returns True if the connection is valid, false otherwise.
*/
export const validateSourceAndTargetTypes = (
sourceType: FieldType,
targetType: FieldType
sourceType: FieldType | string,
targetType: FieldType | string
) => {
const isSourcePolymorphic = getIsPolymorphic(sourceType);
const isSourceCollection = getIsCollection(sourceType);
const sourceBaseType = getBaseType(sourceType);
const isTargetPolymorphic = getIsPolymorphic(targetType);
const isTargetCollection = getIsCollection(targetType);
const targetBaseType = getBaseType(targetType);
// TODO: There's a bug with Collect -> Iterate nodes:
// https://github.com/invoke-ai/InvokeAI/issues/3956
// Once this is resolved, we can remove this check.
// Note that 'Collection' here is a field type, not node type.
if (sourceType === 'Collection' && targetType === 'Collection') {
return false;
}
@@ -31,37 +45,21 @@ export const validateSourceAndTargetTypes = (
*/
const isCollectionItemToNonCollection =
sourceType === 'CollectionItem' && !COLLECTION_TYPES.includes(targetType);
sourceType === 'CollectionItem' && !isTargetCollection;
const isNonCollectionToCollectionItem =
targetType === 'CollectionItem' &&
!COLLECTION_TYPES.includes(sourceType) &&
!POLYMORPHIC_TYPES.includes(sourceType);
!isSourceCollection &&
!isSourcePolymorphic;
const isAnythingToPolymorphicOfSameBaseType =
POLYMORPHIC_TYPES.includes(targetType) &&
(() => {
if (!POLYMORPHIC_TYPES.includes(targetType)) {
return false;
}
const baseType =
POLYMORPHIC_TO_SINGLE_MAP[
targetType as keyof typeof POLYMORPHIC_TO_SINGLE_MAP
];
const collectionType =
COLLECTION_MAP[baseType as keyof typeof COLLECTION_MAP];
return sourceType === baseType || sourceType === collectionType;
})();
isTargetPolymorphic && sourceBaseType === targetBaseType;
const isGenericCollectionToAnyCollectionOrPolymorphic =
sourceType === 'Collection' &&
(COLLECTION_TYPES.includes(targetType) ||
POLYMORPHIC_TYPES.includes(targetType));
sourceType === 'Collection' && (isTargetCollection || isTargetPolymorphic);
const isCollectionToGenericCollection =
targetType === 'Collection' && COLLECTION_TYPES.includes(sourceType);
targetType === 'Collection' && isSourceCollection;
const isIntToFloat = sourceType === 'integer' && targetType === 'float';
@@ -71,6 +69,7 @@ export const validateSourceAndTargetTypes = (
const isTargetAnyType = targetType === 'Any';
// One of these must be true for the connection to be valid
return (
isCollectionItemToNonCollection ||
isNonCollectionToCollectionItem ||

View File

@@ -20,38 +20,6 @@ export const KIND_MAP = {
output: 'outputs' as const,
};
export const COLLECTION_TYPES: FieldType[] = [
'Collection',
'IntegerCollection',
'BooleanCollection',
'FloatCollection',
'StringCollection',
'ImageCollection',
'LatentsCollection',
'ConditioningCollection',
'ControlCollection',
'ColorCollection',
'T2IAdapterCollection',
'IPAdapterCollection',
'MetadataItemCollection',
'MetadataCollection',
];
export const POLYMORPHIC_TYPES: FieldType[] = [
'IntegerPolymorphic',
'BooleanPolymorphic',
'FloatPolymorphic',
'StringPolymorphic',
'ImagePolymorphic',
'LatentsPolymorphic',
'ConditioningPolymorphic',
'ControlPolymorphic',
'ColorPolymorphic',
'T2IAdapterPolymorphic',
'IPAdapterPolymorphic',
'MetadataItemPolymorphic',
];
export const MODEL_TYPES: FieldType[] = [
'IPAdapterModelField',
'ControlNetModelField',
@@ -68,6 +36,26 @@ export const MODEL_TYPES: FieldType[] = [
'IPAdapterModelField',
];
/**
* TODO: Revise the field type naming scheme
*
* Unfortunately, due to inconsistent naming of types, we need to keep the below map objects/callbacks.
*
* Problems:
* - some types do not use the word "Field" in their name, e.g. "Scheduler"
* - primitive types use all-lowercase names, e.g. "integer"
* - collection and polymorphic types do not use the word "Field"
*
* If these inconsistencies were resolved, we could remove these mappings and use simple string
* parsing/manipulation to handle field types.
*
* It would make some of the parsing logic simpler and reduce the maintenance overhead of adding new
* "official" field types.
*
* This will require migration logic for workflows to update their field types. Workflows *do* have a
* version attached to them, so this shouldn't be too difficult.
*/
export const COLLECTION_MAP: FieldTypeMapWithNumber = {
integer: 'IntegerCollection',
boolean: 'BooleanCollection',
@@ -83,6 +71,7 @@ export const COLLECTION_MAP: FieldTypeMapWithNumber = {
IPAdapterField: 'IPAdapterCollection',
MetadataItemField: 'MetadataItemCollection',
MetadataField: 'MetadataCollection',
Custom: 'CustomCollection',
};
export const isCollectionItemType = (
itemType: string | undefined
@@ -103,6 +92,7 @@ export const SINGLE_TO_POLYMORPHIC_MAP: FieldTypeMapWithNumber = {
T2IAdapterField: 'T2IAdapterPolymorphic',
IPAdapterField: 'IPAdapterPolymorphic',
MetadataItemField: 'MetadataItemPolymorphic',
Custom: 'CustomPolymorphic',
};
export const POLYMORPHIC_TO_SINGLE_MAP: FieldTypeMap = {
@@ -118,6 +108,7 @@ export const POLYMORPHIC_TO_SINGLE_MAP: FieldTypeMap = {
T2IAdapterPolymorphic: 'T2IAdapterField',
IPAdapterPolymorphic: 'IPAdapterField',
MetadataItemPolymorphic: 'MetadataItemField',
CustomPolymorphic: 'Custom',
};
export const TYPES_WITH_INPUT_COMPONENTS: FieldType[] = [
@@ -150,12 +141,27 @@ export const isPolymorphicItemType = (
): itemType is keyof typeof SINGLE_TO_POLYMORPHIC_MAP =>
Boolean(itemType && itemType in SINGLE_TO_POLYMORPHIC_MAP);
export const FIELDS: Record<FieldType, FieldUIConfig> = {
export const FIELDS: Record<FieldType | string, FieldUIConfig> = {
Any: {
color: 'gray.500',
description: 'Any field type is accepted.',
title: 'Any',
},
Custom: {
color: 'gray.500',
description: 'A custom field, provided by an external node.',
title: 'Custom',
},
CustomCollection: {
color: 'gray.500',
description: 'A custom field collection, provided by an external node.',
title: 'Custom Collection',
},
CustomPolymorphic: {
color: 'gray.500',
description: 'A custom field polymorphic, provided by an external node.',
title: 'Custom Polymorphic',
},
MetadataField: {
color: 'gray.500',
description: 'A metadata dict.',

View File

@@ -133,6 +133,9 @@ export const zFieldType = z.enum([
'UNetField',
'VaeField',
'VaeModelField',
'Custom',
'CustomCollection',
'CustomPolymorphic',
]);
export type FieldType = z.infer<typeof zFieldType>;
@@ -143,7 +146,7 @@ export type FieldTypeMapWithNumber = {
export const zReservedFieldType = z.enum([
'WorkflowField',
'IsIntermediate',
'IsIntermediate', // this is technically a reserved field type!
'MetadataField',
]);
@@ -163,6 +166,7 @@ export const zFieldValueBase = z.object({
id: z.string().trim().min(1),
name: z.string().trim().min(1),
type: zFieldType,
originalType: z.string().optional(),
});
export type FieldValueBase = z.infer<typeof zFieldValueBase>;
@@ -190,6 +194,7 @@ export type OutputFieldTemplate = {
type: FieldType;
title: string;
description: string;
originalType?: string; // used for custom types
} & _OutputField;
export const zInputFieldValueBase = zFieldValueBase.extend({
@@ -789,6 +794,21 @@ export const zAnyInputFieldValue = zInputFieldValueBase.extend({
value: z.any().optional(),
});
export const zCustomInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('Custom'),
value: z.any().optional(),
});
export const zCustomCollectionInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('CustomCollection'),
value: z.array(z.any()).optional(),
});
export const zCustomPolymorphicInputFieldValue = zInputFieldValueBase.extend({
type: z.literal('CustomPolymorphic'),
value: z.union([z.any(), z.array(z.any())]).optional(),
});
export const zInputFieldValue = z.discriminatedUnion('type', [
zAnyInputFieldValue,
zBoardInputFieldValue,
@@ -846,6 +866,9 @@ export const zInputFieldValue = z.discriminatedUnion('type', [
zMetadataItemPolymorphicInputFieldValue,
zMetadataInputFieldValue,
zMetadataCollectionInputFieldValue,
zCustomInputFieldValue,
zCustomCollectionInputFieldValue,
zCustomPolymorphicInputFieldValue,
]);
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
@@ -856,6 +879,7 @@ export type InputFieldTemplateBase = {
description: string;
required: boolean;
fieldKind: 'input';
originalType?: string; // used for custom types
} & _InputField;
export type AnyInputFieldTemplate = InputFieldTemplateBase & {
@@ -863,6 +887,21 @@ export type AnyInputFieldTemplate = InputFieldTemplateBase & {
default: undefined;
};
export type CustomInputFieldTemplate = InputFieldTemplateBase & {
type: 'Custom';
default: undefined;
};
export type CustomCollectionInputFieldTemplate = InputFieldTemplateBase & {
type: 'CustomCollection';
default: [];
};
export type CustomPolymorphicInputFieldTemplate = InputFieldTemplateBase & {
type: 'CustomPolymorphic';
default: undefined;
};
export type IntegerInputFieldTemplate = InputFieldTemplateBase & {
type: 'integer';
default: number;
@@ -1259,7 +1298,10 @@ export type InputFieldTemplate =
| MetadataItemCollectionInputFieldTemplate
| MetadataInputFieldTemplate
| MetadataItemPolymorphicInputFieldTemplate
| MetadataCollectionInputFieldTemplate;
| MetadataCollectionInputFieldTemplate
| CustomInputFieldTemplate
| CustomCollectionInputFieldTemplate
| CustomPolymorphicInputFieldTemplate;
export const isInputFieldValue = (
field?: InputFieldValue | OutputFieldValue

View File

@@ -8,9 +8,9 @@ import {
} from 'lodash-es';
import { OpenAPIV3_1 } from 'openapi-types';
import { ControlField } from 'services/api/types';
import { getIsPolymorphic } from '../store/util/parseFieldType';
import {
COLLECTION_MAP,
POLYMORPHIC_TYPES,
SINGLE_TO_POLYMORPHIC_MAP,
isCollectionItemType,
isPolymorphicItemType,
@@ -35,6 +35,9 @@ import {
ControlInputFieldTemplate,
ControlNetModelInputFieldTemplate,
ControlPolymorphicInputFieldTemplate,
CustomCollectionInputFieldTemplate,
CustomInputFieldTemplate,
CustomPolymorphicInputFieldTemplate,
DenoiseMaskInputFieldTemplate,
EnumInputFieldTemplate,
FieldType,
@@ -84,6 +87,7 @@ import {
VaeInputFieldTemplate,
VaeModelInputFieldTemplate,
isArraySchemaObject,
isFieldType,
isNonArraySchemaObject,
isRefObject,
isSchemaObject,
@@ -981,9 +985,45 @@ const buildSchedulerInputFieldTemplate = ({
return template;
};
const buildCustomCollectionInputFieldTemplate = ({
baseField,
}: BuildInputFieldArg): CustomCollectionInputFieldTemplate => {
const template: CustomCollectionInputFieldTemplate = {
...baseField,
type: 'CustomCollection',
default: [],
};
return template;
};
const buildCustomPolymorphicInputFieldTemplate = ({
baseField,
}: BuildInputFieldArg): CustomPolymorphicInputFieldTemplate => {
const template: CustomPolymorphicInputFieldTemplate = {
...baseField,
type: 'CustomPolymorphic',
default: undefined,
};
return template;
};
const buildCustomInputFieldTemplate = ({
baseField,
}: BuildInputFieldArg): CustomInputFieldTemplate => {
const template: CustomInputFieldTemplate = {
...baseField,
type: 'Custom',
default: undefined,
};
return template;
};
export const getFieldType = (
schemaObject: OpenAPIV3_1SchemaOrRef
): string | undefined => {
): { type: string; originalType: string } | undefined => {
if (isSchemaObject(schemaObject)) {
if (!schemaObject.type) {
// if schemaObject has no type, then it should have one of allOf, anyOf, oneOf
@@ -991,7 +1031,17 @@ export const getFieldType = (
if (schemaObject.allOf) {
const allOf = schemaObject.allOf;
if (allOf && allOf[0] && isRefObject(allOf[0])) {
return refObjectToSchemaName(allOf[0]);
// This is a single ref type
const originalType = refObjectToSchemaName(allOf[0]);
if (!originalType) {
// something has gone terribly awry
return;
}
return {
type: isFieldType(originalType) ? originalType : 'Custom',
originalType,
};
}
} else if (schemaObject.anyOf) {
// ignore null types
@@ -1004,8 +1054,17 @@ export const getFieldType = (
return true;
});
if (anyOf.length === 1) {
// This is a single ref type
if (isRefObject(anyOf[0])) {
return refObjectToSchemaName(anyOf[0]);
const originalType = refObjectToSchemaName(anyOf[0]);
if (!originalType) {
return;
}
return {
type: isFieldType(originalType) ? originalType : 'Custom',
originalType,
};
} else if (isSchemaObject(anyOf[0])) {
return getFieldType(anyOf[0]);
}
@@ -1051,16 +1110,29 @@ export const getFieldType = (
secondType = second.type;
}
}
if (firstType === secondType && isPolymorphicItemType(firstType)) {
return SINGLE_TO_POLYMORPHIC_MAP[firstType];
if (firstType === secondType) {
if (isPolymorphicItemType(firstType)) {
// Known polymorphic field type
const originalType = SINGLE_TO_POLYMORPHIC_MAP[firstType];
if (!originalType) {
return;
}
return { type: originalType, originalType };
}
// else custom polymorphic
return {
type: 'CustomPolymorphic',
originalType: `${firstType}Polymorphic`,
};
}
}
} else if (schemaObject.enum) {
return 'enum';
return { type: 'enum', originalType: 'enum' };
} else if (schemaObject.type) {
if (schemaObject.type === 'number') {
// floats are "number" in OpenAPI, while ints are "integer" - we need to distinguish them
return 'float';
return { type: 'float', originalType: 'float' };
} else if (schemaObject.type === 'array') {
const itemType = isSchemaObject(schemaObject.items)
? schemaObject.items.type
@@ -1072,16 +1144,39 @@ export const getFieldType = (
}
if (isCollectionItemType(itemType)) {
return COLLECTION_MAP[itemType];
// known collection field type
const originalType = COLLECTION_MAP[itemType];
if (!originalType) {
return;
}
return { type: originalType, originalType };
}
return;
} else if (!isArray(schemaObject.type)) {
return schemaObject.type;
return {
type: 'CustomCollection',
originalType: `${itemType}Collection`,
};
} else if (
!isArray(schemaObject.type) &&
schemaObject.type !== 'null' && // 'null' is not valid
schemaObject.type !== 'object' // 'object' is not valid
) {
const originalType = schemaObject.type;
return { type: originalType, originalType };
}
// else ignore
return;
}
} else if (isRefObject(schemaObject)) {
return refObjectToSchemaName(schemaObject);
const originalType = refObjectToSchemaName(schemaObject);
if (!originalType) {
return;
}
return {
type: isFieldType(originalType) ? originalType : 'Custom',
originalType,
};
}
return;
};
@@ -1145,13 +1240,11 @@ const TEMPLATE_BUILDER_MAP: {
UNetField: buildUNetInputFieldTemplate,
VaeField: buildVaeInputFieldTemplate,
VaeModelField: buildVaeModelInputFieldTemplate,
Custom: buildCustomInputFieldTemplate,
CustomCollection: buildCustomCollectionInputFieldTemplate,
CustomPolymorphic: buildCustomPolymorphicInputFieldTemplate,
};
const isTemplatedFieldType = (
fieldType: string | undefined
): fieldType is keyof typeof TEMPLATE_BUILDER_MAP =>
Boolean(fieldType && fieldType in TEMPLATE_BUILDER_MAP);
/**
* Builds an input field from an invocation schema property.
* @param fieldSchema The schema object
@@ -1161,7 +1254,8 @@ export const buildInputFieldTemplate = (
nodeSchema: InvocationSchemaObject,
fieldSchema: InvocationFieldSchema,
name: string,
fieldType: FieldType
fieldType: FieldType,
originalType: string
) => {
const {
input,
@@ -1175,7 +1269,7 @@ export const buildInputFieldTemplate = (
const extra = {
// TODO: Can we support polymorphic inputs in the UI?
input: POLYMORPHIC_TYPES.includes(fieldType) ? 'connection' : input,
input: getIsPolymorphic(fieldType) ? 'connection' : input,
ui_hidden,
ui_component,
ui_type,
@@ -1183,6 +1277,7 @@ export const buildInputFieldTemplate = (
ui_order,
ui_choice_labels,
item_default,
originalType,
};
const baseField = {
@@ -1193,10 +1288,6 @@ export const buildInputFieldTemplate = (
...extra,
};
if (!isTemplatedFieldType(fieldType)) {
return;
}
const builder = TEMPLATE_BUILDER_MAP[fieldType];
if (!builder) {

View File

@@ -60,6 +60,9 @@ const FIELD_VALUE_FALLBACK_MAP: {
UNetField: undefined,
VaeField: undefined,
VaeModelField: undefined,
Custom: undefined,
CustomCollection: [],
CustomPolymorphic: undefined,
};
export const buildInputFieldValue = (
@@ -76,10 +79,9 @@ export const buildInputFieldValue = (
type: template.type,
label: '',
fieldKind: 'input',
originalType: template.originalType,
value: template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type],
} as InputFieldValue;
fieldValue.value =
template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type];
return fieldValue;
};

View File

@@ -103,14 +103,15 @@ export const parseSchema = (
return inputsAccumulator;
}
const fieldType = property.ui_type ?? getFieldType(property);
const fieldTypeResult = property.ui_type
? { type: property.ui_type, originalType: property.ui_type }
: getFieldType(property);
if (!fieldType) {
if (!fieldTypeResult) {
logger('nodes').warn(
{
node: type,
fieldName: propertyName,
fieldType,
field: parseify(property),
},
'Missing input field type'
@@ -118,6 +119,9 @@ export const parseSchema = (
return inputsAccumulator;
}
// stash this for custom types
const { type: fieldType, originalType } = fieldTypeResult;
if (fieldType === 'WorkflowField') {
withWorkflow = true;
return inputsAccumulator;
@@ -136,6 +140,18 @@ export const parseSchema = (
return inputsAccumulator;
}
if (!isFieldType(originalType)) {
logger('nodes').debug(
{
node: type,
fieldName: propertyName,
fieldType,
field: parseify(property),
},
`Fallback handling for unknown input field type: ${fieldType}`
);
}
if (!isFieldType(fieldType)) {
logger('nodes').warn(
{
@@ -144,7 +160,7 @@ export const parseSchema = (
fieldType,
field: parseify(property),
},
`Skipping unknown input field type: ${fieldType}`
`Unable to parse field type: ${fieldType}`
);
return inputsAccumulator;
}
@@ -153,7 +169,8 @@ export const parseSchema = (
schema,
property,
propertyName,
fieldType
fieldType,
originalType
);
if (!field) {
@@ -162,6 +179,7 @@ export const parseSchema = (
node: type,
fieldName: propertyName,
fieldType,
originalType,
field: parseify(property),
},
'Skipping input field with no template'
@@ -220,12 +238,46 @@ export const parseSchema = (
return outputsAccumulator;
}
const fieldType = property.ui_type ?? getFieldType(property);
const fieldTypeResult = property.ui_type
? { type: property.ui_type, originalType: property.ui_type }
: getFieldType(property);
if (!fieldTypeResult) {
logger('nodes').warn(
{
node: type,
fieldName: propertyName,
field: parseify(property),
},
'Missing output field type'
);
return outputsAccumulator;
}
const { type: fieldType, originalType } = fieldTypeResult;
if (!isFieldType(fieldType)) {
logger('nodes').debug(
{
node: type,
fieldName: propertyName,
fieldType,
originalType,
field: parseify(property),
},
`Fallback handling for unknown output field type: ${fieldType}`
);
}
if (!isFieldType(fieldType)) {
logger('nodes').warn(
{ fieldName: propertyName, fieldType, field: parseify(property) },
'Skipping unknown output field type'
{
node: type,
fieldName: propertyName,
fieldType,
field: parseify(property),
},
`Unable to parse field type: ${fieldType}`
);
return outputsAccumulator;
}
@@ -240,6 +292,7 @@ export const parseSchema = (
ui_hidden: property.ui_hidden ?? false,
ui_type: property.ui_type,
ui_order: property.ui_order,
originalType,
};
return outputsAccumulator;

View File

@@ -5,9 +5,16 @@ import { IAISelectDataType } from 'common/components/IAIMantineSearchableSelect'
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
import { CanvasCoherenceModeParam } from 'features/parameters/types/parameterSchemas';
import { memo, useCallback, useMemo } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
const coherenceModeSelectData: IAISelectDataType[] = [
{ label: 'Unmasked', value: 'unmasked' },
{ label: 'Mask', value: 'mask' },
{ label: 'Mask Edge', value: 'edge' },
];
const ParamCanvasCoherenceMode = () => {
const dispatch = useAppDispatch();
const canvasCoherenceMode = useAppSelector(
@@ -15,15 +22,6 @@ const ParamCanvasCoherenceMode = () => {
);
const { t } = useTranslation();
const coherenceModeSelectData: IAISelectDataType[] = useMemo(
() => [
{ label: t('parameters.unmasked'), value: 'unmasked' },
{ label: t('unifiedCanvas.mask'), value: 'mask' },
{ label: t('parameters.maskEdge'), value: 'edge' },
],
[t]
);
const handleCoherenceModeChange = useCallback(
(v: string | null) => {
if (!v) {

View File

@@ -1,26 +1,20 @@
import { Box } from '@chakra-ui/react';
import { useCanvasGenerationMode } from 'features/canvas/hooks/useCanvasGenerationMode';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { memo } from 'react';
const GENERATION_MODE_NAME_MAP = {
txt2img: 'Text to Image',
img2img: 'Image to Image',
inpaint: 'Inpaint',
outpaint: 'Inpaint',
};
const GenerationModeStatusText = () => {
const generationMode = useCanvasGenerationMode();
const { t } = useTranslation();
const GENERATION_MODE_NAME_MAP = useMemo(
() => ({
txt2img: t('common.txt2img'),
img2img: t('common.img2img'),
inpaint: t('common.inpaint'),
outpaint: t('common.outpaint'),
}),
[t]
);
return (
<Box>
{t('accessibility.mode')}:{' '}
{generationMode ? GENERATION_MODE_NAME_MAP[generationMode] : '...'}
Mode: {generationMode ? GENERATION_MODE_NAME_MAP[generationMode] : '...'}
</Box>
);
};

View File

@@ -7,7 +7,6 @@ import {
ESRGANModelName,
esrganModelNameChanged,
} from 'features/parameters/store/postprocessingSlice';
import { useTranslation } from 'react-i18next';
import { useCallback } from 'react';
export const ESRGAN_MODEL_NAMES: SelectItem[] = [
@@ -38,8 +37,6 @@ export const ESRGAN_MODEL_NAMES: SelectItem[] = [
];
export default function ParamESRGANModel() {
const { t } = useTranslation();
const esrganModelName = useAppSelector(
(state: RootState) => state.postprocessing.esrganModelName
);
@@ -53,7 +50,7 @@ export default function ParamESRGANModel() {
return (
<IAIMantineSelect
label={t('models.esrganModel')}
label="ESRGAN Model"
value={esrganModelName}
itemComponent={IAIMantineSelectItemWithTooltip}
onChange={handleChange}

View File

@@ -1,9 +1,8 @@
import { Flex, Text } from '@chakra-ui/react';
import { memo } from 'react';
import { COLUMN_WIDTHS } from './constants';
import { useTranslation } from 'react-i18next';
const QueueListHeader = () => {
const { t } = useTranslation();
return (
<Flex
alignItems="center"
@@ -23,16 +22,16 @@ const QueueListHeader = () => {
<Text variant="subtext">#</Text>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.statusBadge} alignItems="center">
<Text variant="subtext">{t('queue.status')}</Text>
<Text variant="subtext">status</Text>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.time} alignItems="center">
<Text variant="subtext">{t('queue.time')}</Text>
<Text variant="subtext">time</Text>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.batchId} alignItems="center">
<Text variant="subtext">{t('queue.batch')}</Text>
<Text variant="subtext">batch</Text>
</Flex>
<Flex ps={0.5} w={COLUMN_WIDTHS.fieldValues} alignItems="center">
<Text variant="subtext">{t('queue.batchFieldValues')}</Text>
<Text variant="subtext">batch field values</Text>
</Flex>
</Flex>
);

View File

@@ -298,13 +298,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
<Heading size="sm">{t('settings.generation')}</Heading>
<SettingsSchedulers />
<SettingSwitch
label={t('settings.enableNSFWChecker')}
label="Enable NSFW Checker"
isDisabled={!isNSFWCheckerAvailable}
isChecked={shouldUseNSFWChecker}
onChange={handleChangeShouldUseNSFWChecker}
/>
<SettingSwitch
label={t('settings.enableInvisibleWatermark')}
label="Enable Invisible Watermark"
isDisabled={!isWatermarkerAvailable}
isChecked={shouldUseWatermarker}
onChange={handleChangeShouldUseWatermarker}
@@ -351,7 +351,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
/>
)}
<SettingSwitch
label={t('settings.enableInformationalPopovers')}
label="Enable informational popovers"
isChecked={shouldEnableInformationalPopovers}
onChange={handleChangeShouldEnableInformationalPopovers}
/>

View File

@@ -1,6 +1,6 @@
import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
import { ReactNode, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import i18n from 'i18n';
import { ReactNode, memo } from 'react';
import ImportModelsPanel from '../../../../modelManager/subpanels/ImportModelsPanel';
import MergeModelsPanel from '../../../../modelManager/subpanels/MergeModelsPanel';
import ModelManagerPanel from '../../../../modelManager/subpanels/ModelManagerPanel';
@@ -18,34 +18,30 @@ type ModelManagerTabInfo = {
content: ReactNode;
};
const ModelManagerTab = () => {
const { t } = useTranslation();
const tabs: ModelManagerTabInfo[] = [
{
id: 'modelManager',
label: i18n.t('modelManager.modelManager'),
content: <ModelManagerPanel />,
},
{
id: 'importModels',
label: i18n.t('modelManager.importModels'),
content: <ImportModelsPanel />,
},
{
id: 'mergeModels',
label: i18n.t('modelManager.mergeModels'),
content: <MergeModelsPanel />,
},
{
id: 'settings',
label: i18n.t('modelManager.settings'),
content: <ModelManagerSettingsPanel />,
},
];
const tabs: ModelManagerTabInfo[] = useMemo(
() => [
{
id: 'modelManager',
label: t('modelManager.modelManager'),
content: <ModelManagerPanel />,
},
{
id: 'importModels',
label: t('modelManager.importModels'),
content: <ImportModelsPanel />,
},
{
id: 'mergeModels',
label: t('modelManager.mergeModels'),
content: <MergeModelsPanel />,
},
{
id: 'settings',
label: t('modelManager.settings'),
content: <ModelManagerSettingsPanel />,
},
],
[t]
);
const ModelManagerTab = () => {
return (
<Tabs
isLazy

View File

@@ -6,7 +6,6 @@ import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
import { CanvasInitialImageDropData } from 'features/dnd/types';
import { isValidDrop } from 'features/dnd/util/isValidDrop';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
const droppableData: CanvasInitialImageDropData = {
id: 'canvas-intial-image',
@@ -14,7 +13,6 @@ const droppableData: CanvasInitialImageDropData = {
};
const UnifiedCanvasContent = () => {
const { t } = useTranslation();
const {
isOver,
setNodeRef: setDroppableRef,
@@ -42,10 +40,7 @@ const UnifiedCanvasContent = () => {
<IAICanvasToolbar />
<IAICanvas />
{isValidDrop(droppableData, active) && (
<IAIDropOverlay
isOver={isOver}
label={t('toast.setCanvasInitialImage')}
/>
<IAIDropOverlay isOver={isOver} label="Set Canvas Initial Image" />
)}
</Flex>
);

View File

@@ -1 +1 @@
__version__ = "3.4.0post2"
__version__ = "3.4.0post1"