Compare commits

..

337 Commits

Author SHA1 Message Date
Mary Hipp
f0bfa7f0e0 add button to set default settings in parameters 2024-02-29 16:24:10 -05:00
Mary Hipp
c46b2b6fa6 Merge branch 'maryhipp/default-model-settings' of https://github.com/invoke-ai/InvokeAI into maryhipp/default-model-settings 2024-02-29 14:50:37 -05:00
Mary Hipp
058cc715d4 hook MM default model settings up to API 2024-02-29 14:50:31 -05:00
maryhipp
f69e3ee01c add default_settings to metadata 2024-02-29 14:49:37 -05:00
Mary Hipp
6e0665e3d7 UI for configuring default settings for models' 2024-02-29 13:04:53 -05:00
Mary Hipp
5a35550144 add scheduler and vaePrecision to config 2024-02-29 13:02:46 -05:00
Mary Hipp
8926a1a424 lint fix 2024-02-28 12:04:51 -05:00
Mary Hipp
8566c1c7ff cleanup 2024-02-28 11:49:56 -05:00
Mary Hipp
6eb4c1ccb6 adapt embedding popover to work for trigger phrases also 2024-02-28 11:45:34 -05:00
maryhipp
ef474a3196 what have i done 2024-02-27 15:41:03 -05:00
maryhipp
16b3718d6a cleanup 2024-02-27 15:39:34 -05:00
maryhipp
30228ce2a4 fix merge 2024-02-27 15:35:27 -05:00
Mary Hipp
efb5f2d202 UI in MM to create trigger phrases 2024-02-27 15:33:11 -05:00
chainchompa
4418c118db added add all button to scan models (#5811)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2024-02-27 09:56:23 -05:00
Jennifer Player
110b0bc8fe updated to use new import model mutation 2024-02-27 09:48:41 -05:00
chainchompa
175cfe41a4 Merge branch 'next' into chainchompa/add-all-scan 2024-02-27 09:44:35 -05:00
Jennifer Player
a12d54afb9 added add all button to scan models 2024-02-27 09:43:02 -05:00
psychedelicious
18af5348a2 fix(ui): merge conflict 2024-02-27 08:38:37 -05:00
psychedelicious
b18c8e1c96 chore(ui): bump deps
The only major version is `query-string`. The breaking change for it is dropping support for old versions of node. Not a problem for us.
2024-02-27 08:38:37 -05:00
psychedelicious
ea1e647174 ci: change frontend check to dpdm 2024-02-27 08:38:37 -05:00
psychedelicious
af059f2cff feat(ui): move from madge to dpdm for circular dependencies 2024-02-27 08:38:37 -05:00
psychedelicious
d8e21091e7 tidy(ui): fix circular dependencies in listeners 2024-02-27 08:38:37 -05:00
psychedelicious
344041fd3a tidy: remove some traces of ONNX 2024-02-27 08:38:37 -05:00
psychedelicious
588a220dd4 chore(ui): typegen, update knip config
Knip should never touch the autogenerated types
2024-02-27 08:38:37 -05:00
psychedelicious
770d4092b9 chore(ui): update pnpm-lock.yaml
Forgot to run `pnpm i` earlier after removing packages.
2024-02-27 08:38:37 -05:00
psychedelicious
33fe02bdff ci: add knip to ui check workflow 2024-02-27 08:38:37 -05:00
psychedelicious
8a353bc1e3 feat(ui): configure knip 2024-02-27 08:38:37 -05:00
psychedelicious
240f4801db tidy(ui): clean up unused code 6
unused files
2024-02-27 08:38:37 -05:00
psychedelicious
da50507b2d tidy(ui): clean up unused code 5
variables, types and schemas
2024-02-27 08:38:37 -05:00
psychedelicious
67d150ab66 tidy(ui): clean up unused code 4
variables, types and schemas
2024-02-27 08:38:37 -05:00
psychedelicious
40d70add76 tidy(ui): clean up unused code 3
variables, types and schemas
2024-02-27 08:38:37 -05:00
psychedelicious
7bd9bf3ba5 tidy(ui): clean up unused code 2
types and schemas
2024-02-27 08:38:37 -05:00
psychedelicious
c94d607089 feat(mm): add log stmt for download complete event 2024-02-27 08:38:37 -05:00
psychedelicious
ad801e54d4 fix(ui): model install progress sets total bytes correctly 2024-02-27 08:38:37 -05:00
psychedelicious
fb4db83911 chore(ui): lint 2024-02-27 08:38:37 -05:00
psychedelicious
cc229c3ea0 fix(ui): fix remaining TS issues 2024-02-27 08:38:37 -05:00
psychedelicious
ca00fabd79 fix(ui): fix up MM queries & types (wip) 2024-02-27 08:38:37 -05:00
psychedelicious
b361fabf81 tidy(api): remove non-heuristic install route 2024-02-27 08:38:37 -05:00
psychedelicious
00669200c7 tidy(mm): remove ONNX from AnyModelConfig 2024-02-27 08:38:37 -05:00
psychedelicious
fa07e82d2a tidy(ui): clean up unused code 1
- Only export when necessary
- Remove totally usused functions, variables, state, etc
- Remove unused packages
2024-02-27 08:38:37 -05:00
psychedelicious
3632c5cd57 feat(ui): add knip + minimal config
https://knip.dev/

Replaces `unimported`
2024-02-27 08:38:37 -05:00
psychedelicious
daef68d3c1 fix(ui): fix missing component import 2024-02-27 08:38:37 -05:00
blessedcoolant
ba29376fba ui: split the canvas mask blur and edge size setting 2024-02-27 07:32:13 -05:00
psychedelicious
3efd9465eb feat(ui): create metadata types for control adapters
These are the same as the existing control adapter types, but the model field is non-nullable, simplifying handling of these objects.
2024-02-26 14:49:38 -05:00
psychedelicious
a3b11c04cb fix(ui): model metadata handlers use model identifiers, not configs
Model metadata includes the main model, VAE and refiner model.

These used full model configs, as returned by the server, as their metadata type.

LoRA and control adapter metadata only use the metadata identifier.

This created a difference in handling. After parsing a model/vae/refiner, we have its name and can display it. But for LoRAs and control adapters, we only have the model key and must query for the full model config to get the name.

This change makes main model/vae/refiner metadata only have the model key, like LoRAs and control adapters.

The render function is now async so fetching can occur within it. All metadata fields with models now only contain the identifier, and fetch the model name to render their values.
2024-02-26 14:49:38 -05:00
psychedelicious
8f9e3ac795 fix(ui): CanvasPasteBack types 2024-02-26 14:49:38 -05:00
psychedelicious
2367f53367 tidy(ui): remove unused metadata schemas 2024-02-26 14:49:38 -05:00
psychedelicious
8b9f0a9551 feat(nodes): update LoRAMetadataItem model
LoRA model now at under `model` not `lora.
2024-02-26 14:49:38 -05:00
psychedelicious
ab57976e42 tidy(ui): tidy model identifier logic
- Move some files around
- Use util to extract key and base from model config
2024-02-26 14:49:38 -05:00
psychedelicious
3c103c89f3 feat(ui): optimize model query caching
When we retrieve a list of models, upsert that data into the `getModelConfig` and `getModelConfigByAttrs` query caches.

With this change, calls to those two queries are almost always going to be free, because their caches will already have all models in them. The exception is queries for models that no longer exist.
2024-02-26 14:49:38 -05:00
psychedelicious
0f19176944 fix(ui): fix lora metadata item type 2024-02-26 14:49:38 -05:00
psychedelicious
fc09a954b5 fix(ui): fix node type 2024-02-26 14:49:38 -05:00
psychedelicious
e7eee29825 feat(ui): add transformation to width/height parameter schemas to round to multiple of 8
This allows image dimensions that are not multiples of 8 to still be recalled with best effort.
2024-02-26 14:49:38 -05:00
psychedelicious
2c1ba23f61 fix(ui): fix lora metadata rendering 2024-02-26 14:49:38 -05:00
psychedelicious
58ef6dc6ce fix(ui): fix type issues related to change in LoRA type 2024-02-26 14:49:38 -05:00
psychedelicious
8faefa89fe feat(ui): migrate all metadata recall logic to new system 2024-02-26 14:49:38 -05:00
psychedelicious
02f59a3831 fix(ui): use id for component key in control adapter components 2024-02-26 14:49:38 -05:00
psychedelicious
2555be3058 feat(ui): no JSX in metadata handlers 2024-02-26 14:49:38 -05:00
psychedelicious
e174ce038f feat(ui): refactor metadata handling (again)
Add concepts for metadata handlers. Handlers include parsers, recallers and validators for different metadata types:
- Parsers parse a raw metadata object of any shape to a structured object.
- Recallers load the parsed metadata into state. Recallers are optional, as some metadata types don't need to be loaded into state.
- Validators provide an additional layer of validation before recalling the metadata. This is needed because a metadata object may be valid, but not able to be recalled due to some other requirement, like base model compatibility. Validators are optional.

Sometimes metadata is not a single object but a list of items - like LoRAs. Metadata handlers may implement an optional set of "item" handlers which operate on individual items in the list.

Parsers and validators are async to allow fetching additional data, like a model config. Recallers are synchronous.

The these handlers are composed into a public API, exported as a `handlers` object. Besides the handlers functions, a metadata handler set includes:
- A function to get the label of the metadata type.
- An optional function to render the value of the metadata type.
- An optional function to render the _item_ value of the metadata type.
2024-02-26 14:49:38 -05:00
psychedelicious
0f10faf0d4 build(ui): do not fail build on eslint error in dev mode 2024-02-26 14:49:38 -05:00
psychedelicious
393e32f8a7 chore(ui): typegen 2024-02-26 14:49:38 -05:00
psychedelicious
70412464c8 feat(api): add MM get_by_attrs route
Gets the first model that matches the given name, base and type. Raises 404 if there isn't one.

This will be used for backwards compatibility with old metadata.
2024-02-26 14:49:38 -05:00
Mary Hipp
30fdb9dbfd undo 2024-02-26 14:44:37 -05:00
Mary Hipp
66f6013436 fix literal strings in MM UI 2024-02-26 14:44:37 -05:00
Mary Hipp
49b04f7db8 fix TI appearing as key in prompt 2024-02-26 14:20:28 -05:00
Mary Hipp
253dc5d43d fix base model grouping in combobox 2024-02-26 14:20:28 -05:00
psychedelicious
3ccb4e6ff9 fix(mm): fix ModelCacheBase method name 2024-02-26 17:38:31 +11:00
psychedelicious
200a9d1801 chore: ruff 2024-02-26 17:38:31 +11:00
Lincoln Stein
b09a76ea0d recover gracefuly from GPU out of memory errors (next version) 2024-02-26 17:38:31 +11:00
Lincoln Stein
8a2030e78a clear out VRAM when an OOM occurs 2024-02-26 17:38:31 +11:00
psychedelicious
dfa5505ed8 feat(ui): bulk download click to download 2024-02-25 22:23:15 -05:00
psychedelicious
f8b731b900 fix(ui): fix node types for canvas graphs 2024-02-24 19:38:16 +11:00
psychedelicious
fd9ab0fb7d chore(ui): typegen 2024-02-24 19:38:16 +11:00
psychedelicious
f504a5c96e tidy(nodes): rename canvas paste back 2024-02-24 19:38:16 +11:00
blessedcoolant
afe6639b9c fix: outpaint result not getting pasted back correctly 2024-02-24 19:38:16 +11:00
blessedcoolant
1f1bf15099 fix: lint errors 2024-02-24 19:38:16 +11:00
blessedcoolant
8fa238f100 canvas: improve paste back (or try to) 2024-02-24 19:38:16 +11:00
blessedcoolant
30b6a0ee23 wip(ui): Replace 2 Layer Coherence pass with Gradient Mask 2024-02-24 19:38:16 +11:00
psychedelicious
784878c300 chore: ruff 2024-02-24 19:04:52 +11:00
psychedelicious
b51b163400 fix(ui): fix merge issue 2024-02-24 19:04:52 +11:00
psychedelicious
7e13224ec8 fix(ui): use new scan_folder response instead of hook to determine if models are installed already 2024-02-24 19:04:52 +11:00
psychedelicious
7bc454209c chore(ui): typegen 2024-02-24 19:04:52 +11:00
psychedelicious
cc7f6c7048 feat(mm): add logic to scan_folder route to check if a model is already installed
This was done in the frontend before but it's something the backend should handle.

The logic compares the found model paths to the path and source of all installed models. It excludes core models.
2024-02-24 19:04:52 +11:00
psychedelicious
8b8d950137 chore(ui): lint 2024-02-24 19:04:52 +11:00
psychedelicious
24fd7f41ff build(ui): restore i18n eslint rule 2024-02-24 19:04:52 +11:00
psychedelicious
7c5e458372 chore: ruff 2024-02-24 19:04:52 +11:00
psychedelicious
a5dba4b0d9 fix(ui): fix metadata route 2024-02-24 19:04:52 +11:00
psychedelicious
72fb1cefff chore(ui): typegen 2024-02-24 19:04:52 +11:00
psychedelicious
a64f1c0b20 feat(api): mm metadata route "meta" -> "metadata" 2024-02-24 19:04:52 +11:00
Mary Hipp
974658107d lint fix 2024-02-24 19:04:52 +11:00
Mary Hipp
07fb5d5c19 updated translations 2024-02-24 19:04:52 +11:00
maryhipp
20c75e7a7e fix convert endpoint logic 2024-02-24 19:04:52 +11:00
Mary Hipp
cfcb68696c clean up old model manager components and endpoints 2024-02-24 19:04:52 +11:00
Mary Hipp
7b1b6d3235 add model convert to checkpoint main models 2024-02-24 19:04:52 +11:00
Mary Hipp
aefba52a0a fix logic to see if scanned models are already installed, style tweaks 2024-02-24 19:04:52 +11:00
maryhipp
6af46f9c5f add error_reason to ModelInstallJob 2024-02-24 19:04:52 +11:00
Mary Hipp
190702d011 add error_reason to UI if import fails 2024-02-24 19:04:52 +11:00
Mary Hipp
7785e8ff79 fix types for ImportQueue, add QuickAdd for scan models 2024-02-24 19:04:52 +11:00
Jennifer Player
b3beaefa04 refactored and fixed issues with advanced import form 2024-02-24 19:04:52 +11:00
psychedelicious
98be81354a fix(ui): misc MM cleanup 2024-02-24 19:04:52 +11:00
psychedelicious
2a2a5eb775 chore(ui): temp disable eslint i18 rule 2024-02-24 19:04:52 +11:00
psychedelicious
4a42b15b42 fix(ui): fix ImportMainModelResponse type 2024-02-24 19:04:52 +11:00
psychedelicious
f24d5e5e31 fix(ui): simplify model install event listeners 2024-02-24 19:04:52 +11:00
psychedelicious
4b106bc903 fix(ui): fix model install event types 2024-02-24 19:04:52 +11:00
Jennifer Player
135ef9066f added advanced import forms, not fully working yet 2024-02-24 19:04:52 +11:00
Mary Hipp
0567f98e4a get positioning/scrolling working for scan results list 2024-02-24 19:04:52 +11:00
Mary Hipp
5b66baa3ec basic scan working and renders results 2024-02-24 19:04:52 +11:00
Mary Hipp
a022aaf258 add scan model endpoint, break add model into tabs 2024-02-24 19:04:52 +11:00
Mary Hipp
94065b090a update metadata endpoint 2024-02-24 19:04:52 +11:00
maryhipp
091bf9220b allow metadata-less models to be used for GET metadata endpoint 2024-02-24 19:04:52 +11:00
Jennifer Player
8d243b1fca added status to import queue model 2024-02-24 19:04:52 +11:00
Jennifer Player
23c412e011 delete model imports and prune all finished, update state with socket messages 2024-02-24 19:04:52 +11:00
Mary Hipp
66692f02aa fix sync model endpoint 2024-02-24 19:04:52 +11:00
Mary Hipp
38af1c3a81 form error handling 2024-02-24 19:04:52 +11:00
Mary Hipp
7b4b7e3781 finish model update 2024-02-24 19:04:52 +11:00
Jennifer Player
02a3472505 added socket listeners, added more info to ui 2024-02-24 19:04:52 +11:00
Mary Hipp
909d354a38 edit view for model, depending on type and valid values 2024-02-24 19:04:52 +11:00
Mary Hipp
7801b8c42f hook up Add Model button 2024-02-24 19:04:52 +11:00
Mary Hipp
4fd259bb89 single model view 2024-02-24 19:04:52 +11:00
Jennifer Player
b8b3ef9725 added import model form and importqueue 2024-02-24 19:04:52 +11:00
Mary Hipp
3a8d5dc349 model list, filtering, searching 2024-02-24 19:04:52 +11:00
Mary Hipp
358cac9674 workspace for mary and jenn 2024-02-24 19:04:52 +11:00
Mary Hipp
bdc2b8069b get old UI working somewhat with new endpoints 2024-02-24 19:04:52 +11:00
Brandon Rising
09295ae43b Allow passing in key on register 2024-02-23 14:47:14 -05:00
Brandon Rising
80ad14d89f Remove passing keys in on register 2024-02-23 14:33:49 -05:00
Brandon Rising
c674eb3168 Run ruff 2024-02-23 14:33:49 -05:00
Brandon Rising
63138640a7 Allow users to run model manager without cuda 2024-02-23 14:33:49 -05:00
psychedelicious
d103ff0d6e fix(ui): roll back utility-types
It's `Required` util does not distribute over unions as expected. Also we have `ts-toolbelt` already for some utils.
2024-02-23 07:53:45 +11:00
psychedelicious
94931e8ac0 feat(ui): refactor metadata handling
Refactor of metadata recall handling. This is in preparation for a backwards compatibility layer for models.

- Create helpers to fetch a model outside react (e.g. not in a hook)
- Created helpers to parse model metadata
- Renamed a lot of types that were confusing and/or had naming collisions
2024-02-23 07:53:45 +11:00
psychedelicious
b409f3aaf9 chore(ui): typegen 2024-02-23 07:53:45 +11:00
psychedelicious
f96b7f2e11 fix(nodes): make fields on ModelConfigBase required
The setup of `ModelConfigBase` means autogenerated types have critical fields flagged as nullable (like `key` and `base`). Need to manually flag them as required.
2024-02-23 07:53:45 +11:00
psychedelicious
de3be4bd30 feat(ui): replace type-fest with utility-types
- The new package has more useful types
- Only used `JsonObject` from `type-fest`; added an implementation of that type
2024-02-23 07:53:45 +11:00
Lincoln Stein
cc12f57a5a several small model install enhancements
- Support extended HF repoid syntax in TUI. This allows
  installation of subfolders and safetensors files, as in
  `XpucT/Deliberate::Deliberate_v5.safetensors`

- Add `error` and `error_traceback` properties to the install
  job objects.

- Rename the `heuristic_import` route to `heuristic_install`.

- Fix the example `config` input in the `heuristic_install` route.
2024-02-23 07:48:23 +11:00
Lincoln Stein
613f11a3ac use official Deliberate download repo 2024-02-23 07:48:04 +11:00
Lincoln Stein
a6e2d2c5e0 fix repo-id for the Deliberate v5 model
prevent lora and embedding file suffixes from being stripped during installation

apply psychedelicious patch to get compel to load proper TI embedding
2024-02-23 07:48:04 +11:00
Lincoln Stein
ae14df97d6 remove startup dependency on legacy models.yaml file 2024-02-23 07:47:39 +11:00
dunkeroni
a6e1ac6096 chore: typing 2024-02-22 10:04:33 -05:00
dunkeroni
8530635540 chore: typing fix 2024-02-22 10:04:33 -05:00
dunkeroni
b2b7aed030 feat(nodes): added gradient mask node 2024-02-22 10:04:33 -05:00
Brandon Rising
970d45f691 Run ruff 2024-02-22 09:50:02 -05:00
Mary Hipp
19b9a22d93 rename endpoint for scanning 2024-02-22 09:50:02 -05:00
Brandon Rising
c0d9990344 Create /search endpoint, update model object structure in scan model page 2024-02-22 09:50:02 -05:00
psychedelicious
4ac5e307c4 chore(ui): bump deps
Notable updates:
- Minor version of RTK includes customizable selectors for RTK Query, so we can remove the patch that was added to ensure only the LRU memoize function was used for perf reasons. Updated to use the LRU memoize function.
- Major version of react-resizable-panels. No breaking changes, works great, and you can now resize all panels when dragging at the intersection point of panels. Cool!
- Minor (?) version of nanostores. `action` API is removed, we were using it in one spot. Fixed.
- @invoke-ai/eslint-config-react has all deps bumped and now has its dependent plugins/configs listed as normal dependencies (as opposed to peer deps). This means we can remove those packages from explicit dev deps.
2024-02-22 07:27:28 +11:00
psychedelicious
2815f737fe tidy(ui): remove debugging stmt 2024-02-22 07:26:47 +11:00
psychedelicious
63e96fd1ea fix(ui): handle new model format for metadata 2024-02-22 07:26:47 +11:00
psychedelicious
66ab56246a fix(ui): use model names in badges 2024-02-22 07:26:47 +11:00
psychedelicious
20a56bc757 fix(nodes): fix TI loading 2024-02-22 07:26:47 +11:00
psychedelicious
82925e1539 fix(ui): fix package build 2024-02-21 08:31:55 -05:00
psychedelicious
0137a0db7b feat(ui): do not subscribe to bulk download sio room if baseUrl is set 2024-02-21 00:00:25 +11:00
psychedelicious
b410793684 feat(ui): revise bulk download listeners
- Use a single listener for all of the to keep them in one spot
- Use the bulk download item name as a toast id so we can update the existing toasts
- Update handling to work with other environments
- Move all bulk download handling from components to listener
2024-02-21 00:00:25 +11:00
psychedelicious
894e9f127b chore(ui): typegen 2024-02-21 00:00:25 +11:00
psychedelicious
dd9b1c8eec feat(bulk_download): update response model, messages 2024-02-21 00:00:25 +11:00
Stefan Tobler
8d9c566656 implementing download for bulk_download events 2024-02-21 00:00:25 +11:00
Stefan Tobler
9db7e073a3 setting up event listeners for bulk download socket 2024-02-21 00:00:25 +11:00
psychedelicious
5f64ed5bd5 test: clean up & fix tests
- Deduplicate the mock invocation services. This is possible now that the import order issue is resolved.
- Merge `DummyEventService` into `TestEventService` and update all tests to use `TestEventService`.
2024-02-20 23:39:30 +11:00
psychedelicious
7f75f6226b tidy(bulk_download): don't store events service separately
Using the invoker object directly leaves no ambiguity as to what `_events_bus` actually is.
2024-02-20 23:39:30 +11:00
psychedelicious
6dc819fd47 tidy(bulk_download): do not rely on pagination API to get all images for board
We can get all images for the board as a list of image names, then pass that to `_image_handler` to get the DTOs, decoupling from the pagination API.
2024-02-20 23:39:30 +11:00
psychedelicious
0cc81e5d63 tidy(bulk_download): nit - use or as a coalescing operator
Just a bit cleaner.
2024-02-20 23:39:30 +11:00
psychedelicious
daecc54153 tidy(bulk_download): use single underscore for private attrs
Double underscores are used in the app but it doesn't actually do or convey anything that single underscores don't already do. Considered unpythonic except for actual dunder/magic methods.
2024-02-20 23:39:30 +11:00
psychedelicious
4c31c7f9f1 tidy(bulk_download): remove class-level attr annotations
These can be misleading as they shadow actual assigned class attributes. This pattern is in the rest of the app but it shouldn't be.
2024-02-20 23:39:30 +11:00
psychedelicious
d709c5519f tidy(bulk_download): remove extraneous abstract methods
`start`, `stop` and `__init__` are not required in implementations of an ABC or service.
2024-02-20 23:39:30 +11:00
psychedelicious
5d84ecef49 tidy(bulk_download): clean up comments 2024-02-20 23:39:30 +11:00
Stefan Tobler
641d246213 adding bulk_download_item_name to socket events 2024-02-20 23:39:30 +11:00
Stefan Tobler
2e53aa48c9 refactoring handlers to do null check 2024-02-20 23:39:30 +11:00
Stefan Tobler
ef12631450 removing dependency on an output folder, embrace python temp folder for bulk download 2024-02-20 23:39:30 +11:00
Stefan Tobler
d9eb626b62 relocating event_service fixture due to import ordering 2024-02-20 23:39:30 +11:00
Stefan Tobler
8033589629 moving the responsibility of cleaning up board names to the service not the route 2024-02-20 23:39:30 +11:00
Stefan Tobler
124075ae7a updating imports to satisfy ruff 2024-02-20 23:39:30 +11:00
Stefan Tobler
0bde933c89 using temp directory for downloads 2024-02-20 23:39:30 +11:00
Stefan Tobler
fc5c5b6bdd returning the bulk_download_item_name on response for possible polling 2024-02-20 23:39:30 +11:00
Stefan Tobler
ff53563152 narrowing bulk_download stop service scope 2024-02-20 23:39:30 +11:00
Stefan Tobler
12b0d735e7 adding test coverage for new bulk download routes 2024-02-20 23:39:30 +11:00
Stefan Tobler
d06ee94fd3 cleaning up bulk download zip after the response is complete 2024-02-20 23:39:30 +11:00
Stefan Tobler
9dbdb6cf7c replacing import removed during rebase 2024-02-20 23:39:30 +11:00
Stefan Tobler
7c091570fe 97% test coverage on bulk_download 2024-02-20 23:39:30 +11:00
Stefan Tobler
e99f3482cc refactoring bulk_download to be better managed 2024-02-20 23:39:30 +11:00
Stefan Tobler
d999c9ffd6 refactoring dummy event service, DRY principal; adding bulk_download_event to existing invoker tests 2024-02-20 23:39:30 +11:00
Stefan Tobler
888db8ac46 refactoring bulkdownload to consider image category 2024-02-20 23:39:30 +11:00
Stefan Tobler
7deef2cb27 fixing issue where default board did not return images 2024-02-20 23:39:30 +11:00
Stefan Tobler
ada807af0c using the board name to download boards 2024-02-20 23:39:30 +11:00
Stefan Tobler
aa132fb9e3 reworking some of the logic to use a default room, adding endpoint to download file on complete 2024-02-20 23:39:30 +11:00
Stefan Tobler
98a01368b8 linted and styling 2024-02-20 23:39:30 +11:00
Stefan Tobler
fc9a62dbf5 implementation of bulkdownload background task 2024-02-20 23:39:30 +11:00
Stefan Tobler
4d8bec1605 adding socket events for bulk download 2024-02-20 23:39:30 +11:00
Stefan Tobler
cf9dad83bc groundwork for the bulk_download_service 2024-02-20 23:39:30 +11:00
psychedelicious
0d0a2a5c91 fix(ui): get workflow editor model selects working 2024-02-20 13:33:31 +11:00
psychedelicious
0cab636ab0 fix(ui): get refiner model select working 2024-02-20 13:33:31 +11:00
psychedelicious
de097ec58a fix(ui): get vae model select working 2024-02-20 13:33:31 +11:00
psychedelicious
bb6f426162 fix(ui): get embedding select working 2024-02-20 13:33:31 +11:00
psychedelicious
663f135b3c fix(ui): get lora select working 2024-02-20 13:33:31 +11:00
psychedelicious
2f2097662a chore(ui): bump @invoke-ai/ui-library 2024-02-20 13:33:31 +11:00
psychedelicious
458c29cfa5 fix(ui): fix low-hanging fruit types 2024-02-20 13:33:31 +11:00
Lincoln Stein
4bec01d6f2 Add a few convenience targets to Makefile
- "test" to run pytests
- "frontend-install" to reinstall pnpm's node modeuls
2024-02-20 10:02:46 +11:00
psychedelicious
9d79ee8dc4 chore(nodes): update TODO comment 2024-02-20 09:54:01 +11:00
psychedelicious
78dd460348 tidy(nodes): clean up profiler/stats in processor, better comments 2024-02-20 09:54:01 +11:00
psychedelicious
9d27d354cf fix(nodes): fix typing on stats service context manager 2024-02-20 09:54:01 +11:00
psychedelicious
e8725a1099 fix(nodes): fix model load events
was accessing incorrect properties in event data
2024-02-20 09:54:01 +11:00
psychedelicious
479d65b6e1 feat(nodes): making invocation class var in processor 2024-02-20 09:54:01 +11:00
psychedelicious
5d4b388dfd feat(nodes): improved error messages in processor 2024-02-20 09:54:01 +11:00
psychedelicious
4956fa282b feat(nodes): make processor thread limit and polling interval configurable 2024-02-20 09:54:01 +11:00
psychedelicious
51133522b7 tests(nodes): fix tests following removal of services 2024-02-20 09:54:01 +11:00
psychedelicious
6d5cc8b1ff chore(nodes): better comments for invocation context 2024-02-20 09:54:01 +11:00
psychedelicious
08a5bb90e2 chore(nodes): "context_data" -> "data"
Changed within InvocationContext, for brevity.
2024-02-20 09:54:01 +11:00
psychedelicious
39bdf5c4e9 refactor(nodes): move is_canceled to context.util 2024-02-20 09:54:01 +11:00
psychedelicious
f31e4205aa feat(nodes): add whole queue_item to InvocationContextData
No reason to not have the whole thing in there.
2024-02-20 09:54:01 +11:00
psychedelicious
4d05c4ff66 tidy(nodes): remove extraneous comments 2024-02-20 09:54:01 +11:00
psychedelicious
7e88d2a7f1 feat(nodes): better invocation error messages 2024-02-20 09:54:01 +11:00
psychedelicious
556f6aa174 chore(nodes): add comments for cancel state 2024-02-20 09:54:01 +11:00
psychedelicious
6a74048af8 feat(nodes): promote is_canceled to public node API 2024-02-20 09:54:01 +11:00
psychedelicious
2cb51bff11 refactor(nodes): merge processors
Consolidate graph processing logic into session processor.

With graphs as the unit of work, and the session queue distributing graphs, we no longer need the invocation queue or processor.

Instead, the session processor dequeues the next session and processes it in a simple loop, greatly simplifying the app.

- Remove `graph_execution_manager` service.
- Remove `queue` (invocation queue) service.
- Remove `processor` (invocation processor) service.
- Remove queue-related logic from `Invoker`. It now only starts and stops the services, providing them with access to other services.
- Remove unused `invocation_retrieval_error` and `session_retrieval_error` events, these are no longer needed.
- Clean up stats service now that it is less coupled to the rest of the app.
- Refactor cancellation logic - cancellations now originate from session queue (i.e. HTTP cancel endpoint) and are emitted as events. Processor gets the events and sets the canceled event. Access to this event is provided to the invocation context for e.g. the step callback.
- Remove `sessions` router; it provided access to `graph_executions` but that no longer exists.
2024-02-20 09:54:01 +11:00
psychedelicious
851e835e0e tidy(nodes): remove commented tests 2024-02-20 09:48:14 +11:00
psychedelicious
fe04f28841 chore(ui): typegen 2024-02-20 09:48:14 +11:00
psychedelicious
258fc006ec tidy(nodes): remove no-op model_config
Because we now customize the JSON Schema creation for GraphExecutionState, the model_config did nothing.
2024-02-20 09:48:14 +11:00
psychedelicious
dcb4ee47d5 tidy(nodes): remove LibraryGraphs
The workflow library supersedes this unused feature.
2024-02-20 09:48:14 +11:00
psychedelicious
1a56f5aaf9 tidy(nodes): move node tests to parent dir
Thanks to the resolution of the import vs union issue, we can put tests anywhere.
2024-02-20 09:48:14 +11:00
psychedelicious
5fc745653a tidy(nodes): remove GraphInvocation
`GraphInvocation` is a node that can contain a whole graph. It is removed for a number of reasons:

1. This feature was unused (the UI doesn't support it) and there is no plan for it to be used.

The use-case it served is known in other node execution engines as "node groups" or "blocks" - a self-contained group of nodes, which has group inputs and outputs. This is a planned feature that will be handled client-side.

2. It adds substantial complexity to the graph processing logic. It's probably not enough to have a measurable performance impact but it does make it harder to work in the graph logic.

3. It allows for graphs to be recursive, and the improved invocations union handling does not play well with it. Actually, it works fine within `graph.py` but not in the tests for some reason. I do not understand why. There's probably a workaround, but I took this as encouragement to remove `GraphInvocation` from the app since we don't use it.
2024-02-20 09:48:14 +11:00
psychedelicious
47b5a90177 fix(nodes): fix OpenAPI schema generation
The change to `Graph.nodes` and `GraphExecutionState.results` validation requires some fanagling to get the OpenAPI schema generation to work. See new comments for a details.
2024-02-20 09:48:14 +11:00
psychedelicious
81518ee1af feat(nodes): JIT graph nodes validation
We use pydantic to validate a union of valid invocations when instantiating a graph.

Previously, we constructed the union while creating the `Graph` class. This introduces a dependency on the order of imports.

For example, consider a setup where we have 3 invocations in the app:

- Python executes the module where `FirstInvocation` is defined, registering `FirstInvocation`.
- Python executes the module where `SecondInvocation` is defined, registering `SecondInvocation`.
- Python executes the module where `Graph` is defined. A union of invocations is created and used to define the `Graph.nodes` field. The union contains `FirstInvocation` and `SecondInvocation`.
- Python executes the module where `ThirdInvocation` is defined, registering `ThirdInvocation`.
- A graph is created that includes `ThirdInvocation`. Pydantic validates the graph using the union, which does not know about `ThirdInvocation`, raising a `ValidationError` about an unknown invocation type.

This scenario has been particularly problematic in tests, where we may create invocations dynamically. The test files have to be structured in such a way that the imports happen in the right order. It's a major pain.

This PR refactors the validation of graph nodes to resolve this issue:

- `BaseInvocation` gets a new method `get_typeadapter`. This builds a pydantic `TypeAdapter` for the union of all registered invocations, caching it after the first call.
- `Graph.nodes`'s type is widened to `dict[str, BaseInvocation]`. This actually is a nice bonus, because we get better type hints whenever we reference `some_graph.nodes`.
- A "plain" field validator takes over the validation logic for `Graph.nodes`. "Plain" validators totally override pydantic's own validation logic. The validator grabs the `TypeAdapter` from `BaseInvocation`, then validates each node with it. The validation is identical to the previous implementation - we get the same errors.

`BaseInvocationOutput` gets the same treatment.
2024-02-20 09:48:14 +11:00
Lincoln Stein
b06d63fb34 remove errant def that was crashing invokeai-configure 2024-02-19 17:31:53 +11:00
dunkeroni
5278a64301 one more redundant RGB convert removed 2024-02-19 17:31:08 +11:00
dunkeroni
4de4473c0f chore: ruff formatting 2024-02-19 17:31:08 +11:00
dunkeroni
2c28a850ca chore(invocations): remove redundant RGB conversions 2024-02-19 17:31:08 +11:00
dunkeroni
6dada3326d chore(invocations): use IMAGE_MODES constant literal 2024-02-19 17:31:08 +11:00
dunkeroni
2dfdc02ec8 fix: removed custom module 2024-02-19 17:31:08 +11:00
dunkeroni
1f19db4c6a fix(nodes): canny preprocessor uses RGBA again 2024-02-19 17:31:08 +11:00
dunkeroni
7c150c27f2 feat(nodes): format option for get_image method
Also default CNet preprocessors to "RGB"
2024-02-19 17:31:08 +11:00
blessedcoolant
248916c190 fix: Alpha channel causing issue with DW Processor 2024-02-19 08:17:56 +11:00
psychedelicious
be8b99eed5 final tidying before marking PR as ready for review
- Replace AnyModelLoader with ModelLoaderRegistry
- Fix type check errors in multiple files
- Remove apparently unneeded `get_model_config_enum()` method from model manager
- Remove last vestiges of old model manager
- Updated tests and documentation

resolve conflict with seamless.py
2024-02-19 08:16:56 +11:00
Lincoln Stein
2ad0752582 Tidy names and locations of modules
- Rename old "model_management" directory to "model_management_OLD" in order to catch
  dangling references to original model manager.
- Caught and fixed most dangling references (still checking)
- Rename lora, textual_inversion and model_patcher modules
- Introduce a RawModel base class to simplfy the Union returned by the
  model loaders.
- Tidy up the model manager 2-related tests. Add useful fixtures, and
  a finalizer to the queue and installer fixtures that will stop the
  services and release threads.
2024-02-19 08:16:56 +11:00
Lincoln Stein
ba1f8878dd Fix issues identified during PR review by RyanjDick and brandonrising
- ModelMetadataStoreService is now injected into ModelRecordStoreService
  (these two services are really joined at the hip, and should someday be merged)
- ModelRecordStoreService is now injected into ModelManagerService
- Reduced timeout value for the various installer and download wait*() methods
- Introduced a Mock modelmanager for testing
- Removed bare print() statement with _logger in the install helper backend.
- Removed unused code from model loader init file
- Made `locker` a private variable in the `LoadedModel` object.
- Fixed up model merge frontend (will be deprecated anyway!)
2024-02-19 08:16:56 +11:00
Brandon
bc524026f9 feat(ui): update model identifiers to use key (#5730)
## What type of PR is this? (check all applicable)

- [x] Refactor

## Description

- Update zod schemas & types to use key instead of name/base/type
- Use new `CustomSelect` component instead of `ComboBox` for main model
select and control adapter model selects (less jank, will switch to
ComboBox based on CustomSelect for v4 so you can search the select)

## QA Instructions, Screenshots, Recordings

If you hold your breath, you should be able to generate with a control
adapter.

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Merge Plan

This PR can be merged when approved. Frontend tests not passing.

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->
2024-02-16 11:17:35 -05:00
Brandon
ad7c571983 fix(nodes): fix t2i adapter model loading (#5731)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Description

Fixes t2i adapter loading

## Merge Plan

This PR can be merged when approved

<!--
A merge plan describes how this PR should be handled after it is
approved.

Example merge plans:
- "This PR can be merged when approved"
- "This must be squash-merged when approved"
- "DO NOT MERGE - I will rebase and tidy commits before merging"
- "#dev-chat on discord needs to be advised of this change when it is
merged"

A merge plan is particularly important for large PRs or PRs that touch
the
database in any way.
-->
2024-02-16 11:17:21 -05:00
psychedelicious
8559c6a392 fix(nodes): fix t2i adapter model loading 2024-02-16 22:51:47 +11:00
psychedelicious
c7904a32f4 chore(ui): lint 2024-02-16 22:42:15 +11:00
psychedelicious
17f5484f5b feat(ui): fix main model & control adapter model selects 2024-02-16 22:41:09 +11:00
psychedelicious
86a372b02f refactor(ui): url builders for each router
The MM2 router is at `api/v2/models`. URL builder utils make this a bit easier to manage.
2024-02-16 21:57:30 +11:00
psychedelicious
2e9aa9391d feat(ui): update model identifier to be key (wip)
- Update most model identifiers to be `{key: string}` instead of name/base/type. Doesn't change the model select components yet.
- Update model _parameters_, stored in redux, to be `{key: string, base: BaseModel}` - we need to store the base model to be able to check model compatibility. May want to store the whole config? Not sure...
2024-02-16 18:56:02 +11:00
psychedelicious
0c8112cf28 fix(ui): update model types 2024-02-15 22:17:16 +11:00
psychedelicious
019898c7be tests(ui): add type tests 2024-02-15 22:16:55 +11:00
psychedelicious
2b1ff8d196 tests(ui): enable vitest type testing
This is useful for the zod schemas and types we have created to match the backend.
2024-02-15 22:16:11 +11:00
psychedelicious
79fb691b4d chore(ui): typegen 2024-02-15 22:15:21 +11:00
psychedelicious
560ae17e21 feat(ui): export components type 2024-02-15 21:16:25 +11:00
psychedelicious
2bd1ab2f1c fix(ui): fix type issues 2024-02-15 20:53:41 +11:00
psychedelicious
ed43472582 chore: lint 2024-02-15 20:52:44 +11:00
psychedelicious
6e5e9176c0 chore: ruff 2024-02-15 20:50:47 +11:00
psychedelicious
4c6bcdbc18 feat(nodes): update invocation context for mm2, update nodes model usage 2024-02-15 20:43:41 +11:00
Brandon Rising
20e6d4fa3c Raise InvalidModelConfigException when unable to detect load class in ModelLoader 2024-02-15 18:00:16 +11:00
Brandon Rising
8e51392910 Update _get_hf_load_class to support clipvision models 2024-02-15 18:00:16 +11:00
Brandon Rising
0b1c2acd61 References to context.services.model_manager.store.get_model can only accept keys, remove invalid assertion 2024-02-15 18:00:16 +11:00
Brandon Rising
86ac55ab5f Remove references to model_records service, change submodel property on ModelInfo to submodel_type to support new params in model manager 2024-02-15 18:00:16 +11:00
Lincoln Stein
3e82f63c7e improve swagger documentation 2024-02-15 18:00:08 +11:00
Lincoln Stein
631f6cae19 fix a number of typechecking errors 2024-02-15 18:00:08 +11:00
Lincoln Stein
0845a0ed84 add route for model conversion from safetensors to diffusers
- Begin to add SwaggerUI documentation for AnyModelConfig and other
  discriminated Unions.
2024-02-15 18:00:08 +11:00
Lincoln Stein
46c8ce9fed add a JIT download_and_cache() call to the model installer 2024-02-15 18:00:08 +11:00
Lincoln Stein
13a9ea35b5 add back the heuristic_import() method and extend repo_ids to arbitrary file paths 2024-02-15 18:00:08 +11:00
Lincoln Stein
94e8d1b6d5 make model manager v2 ready for PR review
- Replace legacy model manager service with the v2 manager.

- Update invocations to use new load interface.

- Fixed many but not all type checking errors in the invocations. Most
  were unrelated to model manager

- Updated routes. All the new routes live under the route tag
  `model_manager_v2`. To avoid confusion with the old routes,
  they have the URL prefix `/api/v2/models`. The old routes
  have been de-registered.

- Added a pytest for the loader.

- Updated documentation in contributing/MODEL_MANAGER.md
2024-02-15 18:00:08 +11:00
Lincoln Stein
2b1dc74080 consolidate model manager parts into a single class 2024-02-15 17:57:14 +11:00
Lincoln Stein
f7e558d165 probe for required encoder for IPAdapters and add to config 2024-02-15 17:56:01 +11:00
Lincoln Stein
d959276217 fix invokeai_configure script to work with new mm; rename CLIs 2024-02-15 17:56:01 +11:00
Lincoln Stein
dfcf38be91 BREAKING CHANGES: invocations now require model key, not base/type/name
- Implement new model loader and modify invocations and embeddings

- Finish implementation loaders for all models currently supported by
  InvokeAI.

- Move lora, textual_inversion, and model patching support into
  backend/embeddings.

- Restore support for model cache statistics collection (a little ugly,
  needs work).

- Fixed up invocations that load and patch models.

- Move seamless and silencewarnings utils into better location
2024-02-15 17:56:01 +11:00
Lincoln Stein
fbded1c0f2 Multiple refinements on loaders:
- Cache stat collection enabled.
- Implemented ONNX loading.
- Add ability to specify the repo version variant in installer CLI.
- If caller asks for a repo version that doesn't exist, will fall back
  to empty version rather than raising an error.
2024-02-15 17:51:07 +11:00
Lincoln Stein
ad2926a24c added textual inversion and lora loaders 2024-02-15 17:51:07 +11:00
Lincoln Stein
34d5cad4c9 loaders for main, controlnet, ip-adapter, clipvision and t2i 2024-02-15 17:51:07 +11:00
Lincoln Stein
60aa3d4893 model loading and conversion implemented for vaes 2024-02-15 17:50:51 +11:00
Lincoln Stein
5c2884569e add ram cache module and support files 2024-02-15 17:50:31 +11:00
Lincoln Stein
a1307b9f2e add concept of repo variant 2024-02-15 17:50:31 +11:00
psychedelicious
f505ec64ba tests(ui): add parseFieldType.test.ts 2024-02-15 17:32:38 +11:00
psychedelicious
f22eb368a3 feat(ui): add more types of FieldParseError
Unfortunately you cannot test for both a specific type of error and match its message. Splitting the error classes makes it easier to test expected error conditions.
2024-02-15 17:32:38 +11:00
psychedelicious
96ae22c7e0 feat(ui): add vitest
- Add vitest.
- Consolidate vite configs into single file (easier to config everything based on env for testing)
2024-02-15 17:32:38 +11:00
psychedelicious
f5447cdc23 feat(ui): workflow schema v3 (WIP)
The changes aim to deduplicate data between workflows and node templates, decoupling workflows from internal implementation details. A good amount of data that was needlessly duplicated from the node template to the workflow is removed.

These changes substantially reduce the file size of workflows (and therefore the images with embedded workflows):

- Default T2I SD1.5 workflow JSON is reduced from 23.7kb (798 lines) to 10.9kb (407 lines).
- Default tiled upscale workflow JSON is reduced from 102.7kb (3341 lines) to 51.9kb (1774 lines).

The trade-off is that we need to reference node templates to get things like the field type and other things. In practice, this is a non-issue, because we need a node template to do anything with a node anyways.

- Field types are not included in the workflow. They are always pulled from the node templates.

The field type is now properly an internal implementation detail and we can change it as needed. Previously this would require a migration for the workflow itself. With the v3 schema, the structure of a field type is an internal implementation detail that we are free to change as we see fit.

- Workflow nodes no long have an `outputs` property and there is no longer such a thing as a `FieldOutputInstance`. These are only on the templates.

These were never referenced at a time when we didn't also have the templates available, and there'd be no reason to do so.

- Node width and height are no longer stored in the node.

These weren't used. Also, per https://reactflow.dev/api-reference/types/node, we shouldn't be programmatically changing these properties. A future enhancement can properly add node resizing.

- `nodeTemplates` slice is merged back into `nodesSlice` as `nodes.templates`. Turns out it's just a hassle having these separate in separate slices.

- Workflow migration logic updated to support the new schema. V1 workflows migrate all the way to v3 now.

- Changes throughout the nodes code to accommodate the above changes.
2024-02-15 17:32:38 +11:00
psychedelicious
c76a6bd65f chore(ui): regen types 2024-02-15 17:30:03 +11:00
psychedelicious
6c4eeaa569 feat(nodes): add more missing exports to invocation_api
Crawled through a few custom nodes to figure out what I had missed.
2024-02-15 17:30:03 +11:00
psychedelicious
1bbd13ead7 chore(nodes): "SAMPLER_NAME_VALUES" -> "SCHEDULER_NAME_VALUES"
This was named inaccurately.
2024-02-15 17:30:03 +11:00
psychedelicious
321b939d0e chore(nodes): remove deprecation logic for nodes API 2024-02-15 17:30:03 +11:00
psychedelicious
8fb77e431e chore(nodes): export model-related objects from invocation_api 2024-02-15 17:30:03 +11:00
psychedelicious
083a4f3faa chore(backend): rename ModelInfo -> LoadedModelInfo
We have two different classes named `ModelInfo` which might need to be used by API consumers. We need to export both but have to deal with this naming collision.

The `ModelInfo` I've renamed here is the one that is returned when a model is loaded. It's the object least likely to be used by API consumers.
2024-02-15 17:30:03 +11:00
psychedelicious
2005411f7e feat(nodes): use LATENT_SCALE_FACTOR in primitives.py, noise.py
- LatentsOutput.build
- NoiseOutput.build
- Noise.width, Noise.height multiple_of
2024-02-15 17:30:03 +11:00
psychedelicious
ba7b1b2665 feat(nodes): extract LATENT_SCALE_FACTOR to constants.py 2024-02-15 17:30:03 +11:00
psychedelicious
b7ffd36cc6 feat(nodes): use TemporaryDirectory to handle ephemeral storage in ObjectSerializerDisk
Replace `delete_on_startup: bool` & associated logic with `ephemeral: bool` and `TemporaryDirectory`.

The temp dir is created inside of `output_dir`. For example, if `output_dir` is `invokeai/outputs/tensors/`, then the temp dir might be `invokeai/outputs/tensors/tmpvj35ht7b/`.

The temp dir is cleaned up when the service is stopped, or when it is GC'd if not properly stopped.

In the event of a catastrophic crash where the temp files are not cleaned up, the user can delete the tempdir themselves.

This situation may not occur in normal use, but if you kill the process, python cannot clean up the temp dir itself. This includes running the app in a debugger and killing the debugger process - something I do relatively often.

Tests updated.
2024-02-15 17:30:03 +11:00
psychedelicious
199ddd6623 tests: test ObjectSerializerDisk class name extraction 2024-02-15 17:30:03 +11:00
psychedelicious
a7207ed8cf chore(nodes): update ObjectSerializerForwardCache docstring 2024-02-15 17:30:03 +11:00
psychedelicious
6bb2dda3f1 chore(nodes): fix pyright ignore 2024-02-15 17:30:03 +11:00
psychedelicious
c1e5cd5893 tidy(nodes): "latents" -> "obj" 2024-02-15 17:30:03 +11:00
psychedelicious
ff249a2315 tidy(nodes): do not store unnecessarily store invoker 2024-02-15 17:30:03 +11:00
psychedelicious
c58f8c3269 feat(nodes): make delete on startup configurable for obj serializer
- The default is to not delete on startup - feels safer.
- The two services using this class _do_ delete on startup.
- The class has "ephemeral" removed from its name.
- Tests & app updated for this change.
2024-02-15 17:30:03 +11:00
psychedelicious
ed772a7107 fix(nodes): use metadata/board_id if provided by user, overriding WithMetadata/WithBoard-provided values 2024-02-15 17:30:03 +11:00
psychedelicious
cb0b389b4b tidy(nodes): clarify comment 2024-02-15 17:30:03 +11:00
psychedelicious
8892df1d97 Revert "feat(nodes): use LATENT_SCALE_FACTOR const in tensor output builders"
This reverts commit ef18fc546560277302f3886e456da9a47e8edce0.
2024-02-15 17:30:03 +11:00
psychedelicious
bc5f356390 feat(nodes): use LATENT_SCALE_FACTOR const in tensor output builders 2024-02-15 17:30:03 +11:00
psychedelicious
bcb85e100d tests: fix broken tests 2024-02-15 17:30:03 +11:00
psychedelicious
1f27ddc07d tidy(nodes): minor spelling correction 2024-02-15 17:30:03 +11:00
psychedelicious
7a2b606001 tests: add object serializer tests
These test both object serializer and its forward cache implementation.
2024-02-15 17:30:03 +11:00
psychedelicious
83ddcc5f3a feat(nodes): allow _delete_all in obj serializer to be called at any time
`_delete_all` logged how many items it deleted, and had to be called _after_ service start bc it needed access to logger.

Move the logger call to the startup method and return the the deleted stats from `_delete_all`. This lets `_delete_all` be called at any time.
2024-02-15 17:30:03 +11:00
psychedelicious
55fa785561 tidy(nodes): remove object serializer on_saved
It's unused.
2024-02-15 17:30:03 +11:00
psychedelicious
06429028c8 revert(nodes): revert making tensors/conditioning use item storage
Turns out they are just different enough in purpose that the implementations would be rather unintuitive. I've made a separate ObjectSerializer service to handle tensors and conditioning.

Refined the class a bit too.
2024-02-15 17:30:03 +11:00
psychedelicious
8b6e322697 feat(nodes): support custom exception in ephemeral disk storage 2024-02-15 17:30:03 +11:00
psychedelicious
54a67459bf feat(nodes): support custom save and load functions in ItemStorageEphemeralDisk 2024-02-15 17:30:03 +11:00
psychedelicious
7fe5283e74 feat(nodes): create helper function to generate the item ID 2024-02-15 17:30:03 +11:00
psychedelicious
fe0391c86b feat(nodes): use ItemStorageABC for tensors and conditioning
Turns out `ItemStorageABC` was almost identical to `PickleStorageBase`. Instead of maintaining separate classes, we can use `ItemStorageABC` for both.

There's only one change needed - the `ItemStorageABC.set` method must return the newly stored item's ID. This allows us to let the service handle the responsibility of naming the item, but still create the requisite output objects during node execution.

The naming implementation is improved here. It extracts the name of the generic and appends a UUID to that string when saving items.
2024-02-15 17:30:03 +11:00
psychedelicious
25386a76ef tidy(nodes): do not refer to files as latents in PickleStorageTorch (again) 2024-02-15 17:30:03 +11:00
psychedelicious
fd30cb4d90 feat(nodes): ItemStorageABC typevar no longer bound to pydantic.BaseModel
This bound is totally unnecessary. There's no requirement for any implementation of `ItemStorageABC` to work only on pydantic models.
2024-02-15 17:30:03 +11:00
psychedelicious
0266946d3d fix(nodes): add super init to PickleStorageTorch 2024-02-15 17:30:03 +11:00
psychedelicious
a7f91b3e01 tidy(nodes): do not refer to files as latents in PickleStorageTorch 2024-02-15 17:30:03 +11:00
psychedelicious
de0b72528c feat(nodes): replace latents service with tensors and conditioning services
- New generic class `PickleStorageBase`, implements the same API as `LatentsStorageBase`, use for storing non-serializable data via pickling
- Implementation `PickleStorageTorch` uses `torch.save` and `torch.load`, same as `LatentsStorageDisk`
- Add `tensors: PickleStorageBase[torch.Tensor]` to `InvocationServices`
- Add `conditioning: PickleStorageBase[ConditioningFieldData]` to `InvocationServices`
- Remove `latents` service and all `LatentsStorage` classes
- Update `InvocationContext` and all usage of old `latents` service to use the new services/context wrapper methods
2024-02-15 17:30:03 +11:00
psychedelicious
2932652787 tidy(nodes): delete onnx.py
It doesn't work and keeping it updated to prevent the app from starting was getting tedious. Deleted.
2024-02-15 17:30:03 +11:00
psychedelicious
db6bc7305a fix(nodes): rearrange fields.py to avoid needing forward refs 2024-02-15 17:30:02 +11:00
psychedelicious
a5db204629 tidy(nodes): remove unnecessary, shadowing class attr declarations 2024-02-15 17:30:02 +11:00
psychedelicious
8e2b61e19f feat(ui): revise graphs to not use LinearUIOutputInvocation
See this comment for context: https://github.com/invoke-ai/InvokeAI/pull/5491#discussion_r1480760629

- Remove this now-unnecessary node from all graphs
- Update graphs' terminal image-outputting nodes' `is_intermediate` and `board` fields appropriately
- Add util function to prepare the `board` field, tidy the utils
- Update `socketInvocationComplete` listener to work correctly with this change

I've manually tested all graph permutations that were changed (I think this is all...) to ensure images go to the gallery as expected:
- ad-hoc upscaling
- t2i w/ sd1.5
- t2i w/ sd1.5 & hrf
- t2i w/ sdxl
- t2i w/ sdxl + refiner
- i2i w/ sd1.5
- i2i w/ sdxl
- i2i w/ sdxl + refiner
- canvas t2i w/ sd1.5
- canvas t2i w/ sdxl
- canvas t2i w/ sdxl + refiner
- canvas i2i w/ sd1.5
- canvas i2i w/ sdxl
- canvas i2i w/ sdxl + refiner
- canvas inpaint w/ sd1.5
- canvas inpaint w/ sdxl
- canvas inpaint w/ sdxl + refiner
- canvas outpaint w/ sd1.5
- canvas outpaint w/ sdxl
- canvas outpaint w/ sdxl + refiner
2024-02-15 17:30:02 +11:00
psychedelicious
a3faa3792a chore(ui): regen types 2024-02-15 17:30:02 +11:00
psychedelicious
c16eba78ab feat(nodes): add WithBoard field helper class
This class works the same way as `WithMetadata` - it simply adds a `board` field to the node. The context wrapper function is able to pull the board id from this. This allows image-outputting nodes to get a board field "for free", and have their outputs automatically saved to it.

This is a breaking change for node authors who may have a field called `board`, because it makes `board` a reserved field name. I'll look into how to avoid this - maybe by naming this invoke-managed field `_board` to avoid collisions?

Supporting changes:
- `WithBoard` is added to all image-outputting nodes, giving them the ability to save to board.
- Unused, duplicate `WithMetadata` and `WithWorkflow` classes are deleted from `baseinvocation.py`. The "real" versions are in `fields.py`.
- Remove `LinearUIOutputInvocation`. Now that all nodes that output images also have a `board` field by default, this node is no longer necessary. See comment here for context: https://github.com/invoke-ai/InvokeAI/pull/5491#discussion_r1480760629
- Without `LinearUIOutputInvocation`, the `ImagesInferface.update` method is no longer needed, and removed.

Note: This commit does not bump all node versions. I will ensure that is done correctly before merging the PR of which this commit is a part.

Note: A followup commit will implement the frontend changes to support this change.
2024-02-15 17:30:02 +11:00
psychedelicious
1a191c4655 remove unused configdict import 2024-02-15 17:30:02 +11:00
psychedelicious
e36d925bce fix(ui): remove original l2i node in HRF graph 2024-02-15 17:30:02 +11:00
psychedelicious
b1ba18b3d1 fix(nodes): do not freeze or cache config in context wrapper
- The config is already cached by the config class's `get_config()` method.
- The config mutates itself in its `root_path` property getter. Freezing the class makes any attempt to grab a path from the config error. Unfortunately this means we cannot easily freeze the class without fiddling with the inner workings of `InvokeAIAppConfig`, which is outside the scope here.
2024-02-15 17:30:02 +11:00
psychedelicious
aff46759f9 feat(nodes): context.data -> context._data 2024-02-15 17:30:02 +11:00
psychedelicious
d7b7dcc7fe feat(nodes): context.__services -> context._services 2024-02-15 17:30:02 +11:00
psychedelicious
889a26c5b6 feat(nodes): cache invocation interface config 2024-02-15 17:30:02 +11:00
psychedelicious
b4c774896a feat(nodes): do not hide services in invocation context interfaces 2024-02-15 17:30:02 +11:00
psychedelicious
afbe889d35 fix(nodes): restore missing context type annotations 2024-02-15 17:30:02 +11:00
psychedelicious
9c1e52b1ef tests(nodes): fix mock InvocationContext 2024-02-15 17:30:02 +11:00
psychedelicious
3f5ab02da9 chore(nodes): add comments for ConfigInterface 2024-02-15 17:30:02 +11:00
psychedelicious
bf48e8a03a feat(nodes): export more things from `invocation_api" 2024-02-15 17:30:02 +11:00
psychedelicious
e52434cb99 feat(nodes): add boards interface to invocation context 2024-02-15 17:30:02 +11:00
psychedelicious
483bdbcb9f fix(nodes): restore type annotations for InvocationContext 2024-02-15 17:30:02 +11:00
psychedelicious
ae421fb4ab feat(nodes): do not freeze InvocationContextData, prevents it from being subclassesd 2024-02-15 17:30:02 +11:00
psychedelicious
cc295a9f0a feat: tweak pyright config 2024-02-15 17:30:02 +11:00
psychedelicious
a7e23af9c6 feat(nodes): create invocation_api.py
This is the public API for invocations.

Everything a custom node might need should be re-exported from this file.
2024-02-15 17:30:02 +11:00
psychedelicious
3de4390711 feat(nodes): move ConditioningFieldData to conditioning_data.py 2024-02-15 17:30:02 +11:00
psychedelicious
3ceee2b2b2 tests: fix missing arg for InvocationContext 2024-02-15 17:30:02 +11:00
psychedelicious
5c7ed24aab feat(nodes): restore previous invocation context methods with deprecation warnings 2024-02-15 17:30:02 +11:00
psychedelicious
183c9c4799 chore: ruff 2024-02-15 17:30:02 +11:00
psychedelicious
8baf3f78a2 feat(nodes): tidy invocation_context.py, improve comments 2024-02-15 17:30:02 +11:00
psychedelicious
ac2eb16a65 tests: fix tests for new invocation context 2024-02-15 17:30:02 +11:00
psychedelicious
4aa7bee4b9 docs: update INVOCATIONS.md 2024-02-15 17:30:02 +11:00
psychedelicious
7e5ba2795e feat(nodes): update all invocations to use new invocation context
Update all invocations to use the new context. The changes are all fairly simple, but there are a lot of them.

Supporting minor changes:
- Patch bump for all nodes that use the context
- Update invocation processor to provide new context
- Minor change to `EventServiceBase` to accept a node's ID instead of the dict version of a node
- Minor change to `ModelManagerService` to support the new wrapped context
- Fanagling of imports to avoid circular dependencies
2024-02-15 17:30:02 +11:00
psychedelicious
97a6c6eea7 feat: add pyright config
I was having issues with mypy bother over- and under-reporting certain problems. I've added a pyright config.
2024-02-15 17:30:02 +11:00
psychedelicious
f0e60a4ba2 feat(nodes): restricts invocation context power
Creates a low-power `InvocationContext` with simplified methods and data.

See `invocation_context.py` for detailed comments.
2024-02-15 17:30:02 +11:00
psychedelicious
aa089e8108 tidy(nodes): move all field things to fields.py
Unfortunately, this is necessary to prevent circular imports at runtime.
2024-02-15 17:30:02 +11:00
80 changed files with 1758 additions and 1213 deletions

View File

@@ -32,7 +32,6 @@ To use a community workflow, download the the `.json` node graph file and load i
+ [Image to Character Art Image Nodes](#image-to-character-art-image-nodes)
+ [Image Picker](#image-picker)
+ [Image Resize Plus](#image-resize-plus)
+ [Latent Upscale](#latent-upscale)
+ [Load Video Frame](#load-video-frame)
+ [Make 3D](#make-3d)
+ [Mask Operations](#mask-operations)
@@ -291,13 +290,6 @@ View:
</br><img src="https://raw.githubusercontent.com/VeyDlin/image-resize-plus-node/master/.readme/node.png" width="500" />
--------------------------------
### Latent Upscale
**Description:** This node uses a small (~2.4mb) model to upscale the latents used in a Stable Diffusion 1.5 or Stable Diffusion XL image generation, rather than the typical interpolation method, avoiding the traditional downsides of the latent upscale technique.
**Node Link:** [https://github.com/gogurtenjoyer/latent-upscale](https://github.com/gogurtenjoyer/latent-upscale)
--------------------------------
### Load Video Frame
@@ -354,21 +346,12 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
**Description:** A set of nodes for Metadata. Collect Metadata from within an `iterate` node & extract metadata from an image.
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node
- `Metadata From Image` - Provides Metadata from an image
- `Metadata To String` - Extracts a String value of a label from metadata
- `Metadata To Integer` - Extracts an Integer value of a label from metadata
- `Metadata To Float` - Extracts a Float value of a label from metadata
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata
- `Metadata To Bool` - Extracts Bool types from metadata
- `Metadata To Model` - Extracts model types from metadata
- `Metadata To SDXL Model` - Extracts SDXL model types from metadata
- `Metadata To LoRAs` - Extracts Loras from metadata.
- `Metadata To SDXL LoRAs` - Extracts SDXL Loras from metadata
- `Metadata To ControlNets` - Extracts ControNets from metadata
- `Metadata To IP-Adapters` - Extracts IP-Adapters from metadata
- `Metadata To T2I-Adapters` - Extracts T2I-Adapters from metadata
- `Denoise Latents + Metadata` - This is an inherited version of the existing `Denoise Latents` node but with a metadata input and output.
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node.
- `Metadata From Image` - Provides Metadata from an image.
- `Metadata To String` - Extracts a String value of a label from metadata.
- `Metadata To Integer` - Extracts an Integer value of a label from metadata.
- `Metadata To Float` - Extracts a Float value of a label from metadata.
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata.
**Node Link:** https://github.com/skunkworxdark/metadata-linked-nodes

View File

@@ -5,6 +5,7 @@ import pathlib
import shutil
from hashlib import sha1
from random import randbytes
import traceback
from typing import Any, Dict, List, Optional, Set
from fastapi import Body, Path, Query, Response
@@ -14,6 +15,7 @@ from starlette.exceptions import HTTPException
from typing_extensions import Annotated
from invokeai.app.services.model_install import ModelInstallJob
from invokeai.app.services.model_metadata.metadata_store_base import ModelMetadataChanges
from invokeai.app.services.model_records import (
DuplicateModelException,
InvalidModelException,
@@ -32,6 +34,7 @@ from invokeai.backend.model_manager.config import (
)
from invokeai.backend.model_manager.merge import MergeInterpolationMethod, ModelMerger
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
from invokeai.backend.model_manager.metadata.metadata_base import BaseMetadata
from invokeai.backend.model_manager.search import ModelSearch
from ..dependencies import ApiDependencies
@@ -242,6 +245,47 @@ async def get_model_metadata(
return result
@model_manager_router.patch(
"/i/{key}/metadata",
operation_id="update_model_metadata",
responses={
201: {
"description": "The model metadata was updated successfully",
"content": {"application/json": {"example": example_model_metadata}},
},
400: {"description": "Bad request"},
},
)
async def update_model_metadata(
key: str = Path(description="Key of the model repo metadata to fetch."),
changes: ModelMetadataChanges = Body(description="The changes")
) -> Optional[AnyModelRepoMetadata]:
"""Updates or creates a model metadata object."""
record_store = ApiDependencies.invoker.services.model_manager.store
metadata_store = ApiDependencies.invoker.services.model_manager.store.metadata_store
try:
original_metadata = record_store.get_metadata(key)
if original_metadata:
if changes.trigger_phrases:
original_metadata.trigger_phrases = changes.trigger_phrases
if changes.default_settings:
original_metadata.default_settings = changes.default_settings
metadata_store.update_metadata(key, original_metadata)
else:
metadata_store.add_metadata(key, BaseMetadata(name="", author="",trigger_phrases=changes.trigger_phrases, default_settings=changes.default_settings))
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"An error occurred while updating the model metadata: {e}",
)
result: Optional[AnyModelRepoMetadata] = record_store.get_metadata(key)
return result
@model_manager_router.get(
"/tags",

View File

@@ -2,7 +2,6 @@
# which are imported/used before parse_args() is called will get the default config values instead of the
# values from the command line or config file.
import sys
from contextlib import asynccontextmanager
from invokeai.app.api.no_cache_staticfiles import NoCacheStaticFiles
from invokeai.version.invokeai_version import __version__
@@ -72,25 +71,9 @@ logger = InvokeAILogger.get_logger(config=app_config)
mimetypes.add_type("application/javascript", ".js")
mimetypes.add_type("text/css", ".css")
@asynccontextmanager
async def lifespan(app: FastAPI):
# Add startup event to load dependencies
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
yield
# Shut down threads
ApiDependencies.shutdown()
# Create the app
# TODO: create this all in a method so configuration/etc. can be passed in?
app = FastAPI(
title="Invoke - Community Edition",
docs_url=None,
redoc_url=None,
separate_input_output_schemas=False,
lifespan=lifespan,
)
app = FastAPI(title="Invoke - Community Edition", docs_url=None, redoc_url=None, separate_input_output_schemas=False)
# Add event handler
event_handler_id: int = id(app)
@@ -113,6 +96,18 @@ app.add_middleware(
app.add_middleware(GZipMiddleware, minimum_size=1000)
# Add startup event to load dependencies
@app.on_event("startup")
async def startup_event() -> None:
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
# Shut down threads
@app.on_event("shutdown")
async def shutdown_event() -> None:
ApiDependencies.shutdown()
# Include all routers
app.include_router(utilities.utilities_router, prefix="/api")
app.include_router(model_manager.model_manager_router, prefix="/api")

View File

@@ -1,15 +1,24 @@
from typing import Iterator, List, Optional, Tuple, Union, cast
from typing import Iterator, List, Optional, Tuple, Union
import torch
from compel import Compel, ReturnedEmbeddingsType
from compel.prompt_parser import Blend, Conjunction, CrossAttentionControlSubstitute, FlattenedPrompt, Fragment
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
from transformers import CLIPTokenizer
from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField, UIComponent
import invokeai.backend.util.logging as logger
from invokeai.app.invocations.fields import (
FieldDescriptions,
Input,
InputField,
OutputField,
UIComponent,
)
from invokeai.app.invocations.primitives import ConditioningOutput
from invokeai.app.services.model_records import UnknownModelException
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.app.util.ti_utils import generate_ti_list
from invokeai.app.util.ti_utils import extract_ti_triggers_from_prompt
from invokeai.backend.lora import LoRAModelRaw
from invokeai.backend.model_manager import ModelType
from invokeai.backend.model_patcher import ModelPatcher
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
BasicConditioningInfo,
@@ -17,9 +26,15 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
ExtraConditioningInfo,
SDXLConditioningInfo,
)
from invokeai.backend.textual_inversion import TextualInversionModelRaw
from invokeai.backend.util.devices import torch_dtype
from .baseinvocation import BaseInvocation, BaseInvocationOutput, invocation, invocation_output
from .baseinvocation import (
BaseInvocation,
BaseInvocationOutput,
invocation,
invocation_output,
)
from .model import ClipField
# unconditioned: Optional[torch.Tensor]
@@ -55,11 +70,7 @@ class CompelInvocation(BaseInvocation):
@torch.no_grad()
def invoke(self, context: InvocationContext) -> ConditioningOutput:
tokenizer_info = context.models.load(**self.clip.tokenizer.model_dump())
tokenizer_model = tokenizer_info.model
assert isinstance(tokenizer_model, CLIPTokenizer)
text_encoder_info = context.models.load(**self.clip.text_encoder.model_dump())
text_encoder_model = text_encoder_info.model
assert isinstance(text_encoder_model, CLIPTextModel)
def _lora_loader() -> Iterator[Tuple[LoRAModelRaw, float]]:
for lora in self.clip.loras:
@@ -71,10 +82,21 @@ class CompelInvocation(BaseInvocation):
# loras = [(context.models.get(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
ti_list = generate_ti_list(self.prompt, text_encoder_info.config.base, context)
ti_list = []
for trigger in extract_ti_triggers_from_prompt(self.prompt):
name = trigger[1:-1]
try:
loaded_model = context.models.load(key=name).model
assert isinstance(loaded_model, TextualInversionModelRaw)
ti_list.append((name, loaded_model))
except UnknownModelException:
# print(e)
# import traceback
# print(traceback.format_exc())
print(f'Warn: trigger: "{trigger}" not found')
with (
ModelPatcher.apply_ti(tokenizer_model, text_encoder_model, ti_list) as (
ModelPatcher.apply_ti(tokenizer_info.model, text_encoder_info.model, ti_list) as (
tokenizer,
ti_manager,
),
@@ -82,9 +104,8 @@ class CompelInvocation(BaseInvocation):
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
ModelPatcher.apply_lora_text_encoder(text_encoder, _lora_loader()),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder_model, self.clip.skipped_layers),
ModelPatcher.apply_clip_skip(text_encoder_info.model, self.clip.skipped_layers),
):
assert isinstance(text_encoder, CLIPTextModel)
compel = Compel(
tokenizer=tokenizer,
text_encoder=text_encoder,
@@ -134,11 +155,7 @@ class SDXLPromptInvocationBase:
zero_on_empty: bool,
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[ExtraConditioningInfo]]:
tokenizer_info = context.models.load(**clip_field.tokenizer.model_dump())
tokenizer_model = tokenizer_info.model
assert isinstance(tokenizer_model, CLIPTokenizer)
text_encoder_info = context.models.load(**clip_field.text_encoder.model_dump())
text_encoder_model = text_encoder_info.model
assert isinstance(text_encoder_model, (CLIPTextModel, CLIPTextModelWithProjection))
# return zero on empty
if prompt == "" and zero_on_empty:
@@ -172,10 +189,25 @@ class SDXLPromptInvocationBase:
# loras = [(context.models.get(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
ti_list = generate_ti_list(prompt, text_encoder_info.config.base, context)
ti_list = []
for trigger in extract_ti_triggers_from_prompt(prompt):
name = trigger[1:-1]
try:
ti_model = context.models.load_by_attrs(
model_name=name, base_model=text_encoder_info.config.base, model_type=ModelType.TextualInversion
).model
assert isinstance(ti_model, TextualInversionModelRaw)
ti_list.append((name, ti_model))
except UnknownModelException:
# print(e)
# import traceback
# print(traceback.format_exc())
logger.warning(f'trigger: "{trigger}" not found')
except ValueError:
logger.warning(f'trigger: "{trigger}" more than one similarly-named textual inversion models')
with (
ModelPatcher.apply_ti(tokenizer_model, text_encoder_model, ti_list) as (
ModelPatcher.apply_ti(tokenizer_info.model, text_encoder_info.model, ti_list) as (
tokenizer,
ti_manager,
),
@@ -183,10 +215,8 @@ class SDXLPromptInvocationBase:
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
ModelPatcher.apply_lora(text_encoder, _lora_loader(), lora_prefix),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder_model, clip_field.skipped_layers),
ModelPatcher.apply_clip_skip(text_encoder_info.model, clip_field.skipped_layers),
):
assert isinstance(text_encoder, (CLIPTextModel, CLIPTextModelWithProjection))
text_encoder = cast(CLIPTextModel, text_encoder)
compel = Compel(
tokenizer=tokenizer,
text_encoder=text_encoder,

View File

@@ -4,9 +4,27 @@ Storage for Model Metadata
"""
from abc import ABC, abstractmethod
from typing import List, Set, Tuple
from typing import List, Optional, Set, Tuple
from pydantic import Field
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
from invokeai.backend.model_manager.metadata.metadata_base import ModelDefaultSettings
class ModelMetadataChanges(BaseModelExcludeNull, extra="allow"):
"""A set of changes to apply to model metadata.
Only limited changes are valid:
- `trigger_phrases`: the list of trigger phrases for this model
- `default_settings`: the user-configured default settings for this model
"""
trigger_phrases: Optional[List[str]] = Field(default=None, description="The model's list of trigger phrases")
"""The model's list of trigger phrases"""
default_settings: Optional[ModelDefaultSettings] = Field(default=None, description="The user-configured default settings for this model")
"""The user-configured default settings for this model"""
class ModelMetadataStoreBase(ABC):

View File

@@ -115,6 +115,8 @@ class ModelMetadataStoreSQL(ModelMetadataStoreBase):
except sqlite3.Error as e:
self._db.conn.rollback()
raise e
except Exception as e:
raise e
return self.get_metadata(model_key)
@@ -179,44 +181,45 @@ class ModelMetadataStoreSQL(ModelMetadataStoreBase):
)
return {x[0] for x in self._cursor.fetchall()}
def _update_tags(self, model_key: str, tags: Set[str]) -> None:
def _update_tags(self, model_key: str, tags: Optional[Set[str]]) -> None:
"""Update tags for the model referenced by model_key."""
if tags:
# remove previous tags from this model
self._cursor.execute(
"""--sql
DELETE FROM model_tags
WHERE model_id=?;
""",
(model_key,),
)
self._cursor.execute(
"""--sql
DELETE FROM model_tags
WHERE model_id=?;
""",
(model_key,),
)
for tag in tags:
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO tags (
tag_text
)
VALUES (?);
""",
(tag,),
)
self._cursor.execute(
"""--sql
SELECT tag_id
FROM tags
WHERE tag_text = ?
LIMIT 1;
""",
(tag,),
)
tag_id = self._cursor.fetchone()[0]
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO model_tags (
model_id,
tag_id
)
VALUES (?,?);
""",
(model_key, tag_id),
)
for tag in tags:
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO tags (
tag_text
)
VALUES (?);
""",
(tag,),
)
self._cursor.execute(
"""--sql
SELECT tag_id
FROM tags
WHERE tag_text = ?
LIMIT 1;
""",
(tag,),
)
tag_id = self._cursor.fetchone()[0]
self._cursor.execute(
"""--sql
INSERT OR IGNORE INTO model_tags (
model_id,
tag_id
)
VALUES (?,?);
""",
(model_key, tag_id),
)

View File

@@ -1,47 +1,8 @@
import re
from typing import List, Tuple
import invokeai.backend.util.logging as logger
from invokeai.app.services.model_records import UnknownModelException
from invokeai.app.services.shared.invocation_context import InvocationContext
from invokeai.backend.model_manager.config import BaseModelType, ModelType
from invokeai.backend.textual_inversion import TextualInversionModelRaw
def extract_ti_triggers_from_prompt(prompt: str) -> List[str]:
ti_triggers: List[str] = []
def extract_ti_triggers_from_prompt(prompt: str) -> list[str]:
ti_triggers = []
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", prompt):
ti_triggers.append(str(trigger))
ti_triggers.append(trigger)
return ti_triggers
def generate_ti_list(
prompt: str, base: BaseModelType, context: InvocationContext
) -> List[Tuple[str, TextualInversionModelRaw]]:
ti_list: List[Tuple[str, TextualInversionModelRaw]] = []
for trigger in extract_ti_triggers_from_prompt(prompt):
name_or_key = trigger[1:-1]
try:
loaded_model = context.models.load(key=name_or_key)
model = loaded_model.model
assert isinstance(model, TextualInversionModelRaw)
assert loaded_model.config.base == base
ti_list.append((name_or_key, model))
except UnknownModelException:
try:
loaded_model = context.models.load_by_attrs(
model_name=name_or_key, base_model=base, model_type=ModelType.TextualInversion
)
model = loaded_model.model
assert isinstance(model, TextualInversionModelRaw)
assert loaded_model.config.base == base
ti_list.append((name_or_key, model))
except UnknownModelException:
pass
except ValueError:
logger.warning(f'trigger: "{trigger}" more than one similarly-named textual inversion models')
except AssertionError:
logger.warning(f'trigger: "{trigger}" not a valid textual inversion model for this graph')
except Exception:
logger.warning(f'Failed to load TI model for trigger: "{trigger}"')
return ti_list

View File

@@ -160,10 +160,11 @@ class CivitaiMetadataFetch(ModelMetadataFetchBase):
nsfw=model_json["nsfw"],
restrictions=LicenseRestrictions(
AllowNoCredit=model_json["allowNoCredit"],
AllowCommercialUse={CommercialUsage(x) for x in model_json["allowCommercialUse"]},
AllowCommercialUse=CommercialUsage(model_json["allowCommercialUse"]),
AllowDerivatives=model_json["allowDerivatives"],
AllowDifferentLicense=model_json["allowDifferentLicense"],
),
trigger_phrases=version_json["trainedWords"],
)
def from_civitai_versionid(self, version_id: int, model_id: Optional[int] = None) -> CivitaiMetadata:

View File

@@ -24,6 +24,7 @@ from pydantic import BaseModel, Field, TypeAdapter
from pydantic.networks import AnyHttpUrl
from requests.sessions import Session
from typing_extensions import Annotated
from invokeai.app.invocations.constants import SCHEDULER_NAME_VALUES
from invokeai.backend.model_manager import ModelRepoVariant
@@ -54,8 +55,8 @@ class LicenseRestrictions(BaseModel):
AllowDifferentLicense: bool = Field(
description="if true, derivatives of this model be redistributed under a different license", default=False
)
AllowCommercialUse: Optional[Set[CommercialUsage] | CommercialUsage] = Field(
description="Type of commercial use allowed if no commercial use is allowed.", default=None
AllowCommercialUse: Optional[CommercialUsage] = Field(
description="Type of commercial use allowed or 'No' if no commercial use is allowed.", default=None
)
@@ -68,12 +69,22 @@ class RemoteModelFile(BaseModel):
sha256: Optional[str] = Field(description="SHA256 hash of this model (not always available)", default=None)
class ModelDefaultSettings(BaseModel):
vae: str | None
vae_precision: str | None
scheduler: SCHEDULER_NAME_VALUES | None
steps: int | None
cfg_scale: float | None
cfg_rescale_multiplier: float | None
class ModelMetadataBase(BaseModel):
"""Base class for model metadata information."""
name: str = Field(description="model's name")
author: str = Field(description="model's author")
tags: Set[str] = Field(description="tags provided by model source")
tags: Optional[Set[str]] = Field(description="tags provided by model source", default=None)
trigger_phrases: Optional[List[str]] = Field(description="trigger phrases for this model", default=None)
default_settings: Optional[ModelDefaultSettings] = Field(description="default settings for this model", default=None)
class BaseMetadata(ModelMetadataBase):
@@ -142,10 +153,7 @@ class CivitaiMetadata(ModelMetadataWithFiles):
if self.restrictions.AllowCommercialUse is None:
return False
else:
# accommodate schema change
acu = self.restrictions.AllowCommercialUse
commercial_usage = acu if isinstance(acu, set) else {acu}
return CommercialUsage.No not in commercial_usage
return self.restrictions.AllowCommercialUse != CommercialUsage("None")
@property
def allow_derivatives(self) -> bool:

View File

@@ -28,7 +28,6 @@ from typing import Callable, Optional, Set, Union
from pydantic import BaseModel, Field
from invokeai.app.services.config import InvokeAIAppConfig
from invokeai.backend.util.logging import InvokeAILogger
default_logger: Logger = InvokeAILogger.get_logger()
@@ -118,10 +117,13 @@ class ModelSearch(ModelSearchBase):
"""
models_found: Set[Path] = Field(default_factory=set)
config: InvokeAIAppConfig = InvokeAIAppConfig.get_config()
scanned_dirs: Set[Path] = Field(default_factory=set)
pruned_paths: Set[Path] = Field(default_factory=set)
def search_started(self) -> None:
self.models_found = set()
self.scanned_dirs = set()
self.pruned_paths = set()
if self.on_search_started:
self.on_search_started(self._directory)
@@ -137,53 +139,53 @@ class ModelSearch(ModelSearchBase):
def search(self, directory: Union[Path, str]) -> Set[Path]:
self._directory = Path(directory)
if not self._directory.is_absolute():
self._directory = self.config.models_path / self._directory
self.stats = SearchStats() # zero out
self.search_started() # This will initialize _models_found to empty
self._walk_directory(self._directory)
self._walk_directory(directory)
self.search_completed()
return self.models_found
def _walk_directory(self, path: Union[Path, str], max_depth: int = 20) -> None:
absolute_path = Path(path)
if (
len(absolute_path.parts) - len(self._directory.parts) > max_depth
or not absolute_path.exists()
or absolute_path.parent in self.models_found
):
return
entries = os.scandir(absolute_path.as_posix())
entries = [entry for entry in entries if not entry.name.startswith(".")]
dirs = [entry for entry in entries if entry.is_dir()]
file_names = [entry.name for entry in entries if entry.is_file()]
if any(
x in file_names
for x in [
"config.json",
"model_index.json",
"learned_embeds.bin",
"pytorch_lora_weights.bin",
"image_encoder.txt",
]
):
try:
self.model_found(absolute_path)
return
except KeyboardInterrupt:
raise
except Exception as e:
self.logger.warning(str(e))
return
def _walk_directory(self, path: Union[Path, str]) -> None:
for root, dirs, files in os.walk(path, followlinks=True):
# don't descend into directories that start with a "."
# to avoid the Mac .DS_STORE issue.
if str(Path(root).name).startswith("."):
self.pruned_paths.add(Path(root))
if any(Path(root).is_relative_to(x) for x in self.pruned_paths):
continue
for n in file_names:
if n.endswith((".ckpt", ".bin", ".pth", ".safetensors", ".pt")):
try:
self.model_found(absolute_path / n)
except KeyboardInterrupt:
raise
except Exception as e:
self.logger.warning(str(e))
self.stats.items_scanned += len(dirs) + len(files)
for d in dirs:
path = Path(root) / d
if path.parent in self.scanned_dirs:
self.scanned_dirs.add(path)
continue
if any(
(path / x).exists()
for x in [
"config.json",
"model_index.json",
"learned_embeds.bin",
"pytorch_lora_weights.bin",
"image_encoder.txt",
]
):
self.scanned_dirs.add(path)
try:
self.model_found(path)
except KeyboardInterrupt:
raise
except Exception as e:
self.logger.warning(str(e))
for d in dirs:
self._walk_directory(absolute_path / d)
for f in files:
path = Path(root) / f
if path.parent in self.scanned_dirs:
continue
if path.suffix in {".ckpt", ".bin", ".pth", ".safetensors", ".pt"}:
try:
self.model_found(path)
except KeyboardInterrupt:
raise
except Exception as e:
self.logger.warning(str(e))

View File

@@ -4,12 +4,12 @@ from __future__ import annotations
import pickle
from contextlib import contextmanager
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union
from typing import Any, Dict, Iterator, List, Optional, Tuple
import numpy as np
import torch
from diffusers import OnnxRuntimeModel, UNet2DConditionModel
from transformers import CLIPTextModel, CLIPTextModelWithProjection, CLIPTokenizer
from transformers import CLIPTextModel, CLIPTokenizer
from invokeai.app.shared.models import FreeUConfig
from invokeai.backend.model_manager import AnyModel
@@ -168,7 +168,7 @@ class ModelPatcher:
def apply_ti(
cls,
tokenizer: CLIPTokenizer,
text_encoder: Union[CLIPTextModel, CLIPTextModelWithProjection],
text_encoder: CLIPTextModel,
ti_list: List[Tuple[str, TextualInversionModelRaw]],
) -> Iterator[Tuple[CLIPTokenizer, TextualInversionManager]]:
init_tokens_count = None
@@ -265,7 +265,7 @@ class ModelPatcher:
@contextmanager
def apply_clip_skip(
cls,
text_encoder: Union[CLIPTextModel, CLIPTextModelWithProjection],
text_encoder: CLIPTextModel,
clip_skip: int,
) -> None:
skipped_layers = []

View File

@@ -81,7 +81,7 @@
"outputs": "Ausgabe",
"data": "Daten",
"safetensors": "Safe-Tensors",
"outpaint": "Outpaint (Außen ausmalen)",
"outpaint": "Ausmalen",
"details": "Details",
"format": "Format",
"unknown": "Unbekannt",
@@ -110,18 +110,17 @@
"nextPage": "Nächste Seite",
"unknownError": "Unbekannter Fehler",
"unsaved": "Nicht gespeichert",
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Siehe hier:",
"aboutDesc": "Verwenden Sie Invoke für die Arbeit? Dann siehe hier:",
"localSystem": "Lokales System",
"orderBy": "Ordnen nach",
"saveAs": "Speichern als",
"saveAs": "Speicher als",
"updated": "Aktualisiert",
"copy": "Kopieren",
"aboutHeading": "Nutzen Sie Ihre kreative Energie",
"toResolve": "Lösen"
"aboutHeading": "Nutzen Sie Ihre kreative Energie"
},
"gallery": {
"generations": "Erzeugungen",
"showGenerations": "Zeige Ergebnisse",
"showGenerations": "Zeige Erzeugnisse",
"uploads": "Uploads",
"showUploads": "Zeige Uploads",
"galleryImageSize": "Bildgröße",
@@ -151,9 +150,9 @@
"problemDeletingImagesDesc": "Ein oder mehrere Bilder konnten nicht gelöscht werden",
"starImage": "Bild markieren",
"assets": "Ressourcen",
"unstarImage": "Markierung entfernen",
"unstarImage": "Markierung Entfernen",
"image": "Bild",
"deleteSelection": "Lösche Auswahl",
"deleteSelection": "Lösche markierte",
"dropToUpload": "$t(gallery.drop) zum hochladen",
"dropOrUpload": "$t(gallery.drop) oder hochladen",
"drop": "Ablegen",
@@ -591,21 +590,10 @@
"general": "Allgemein",
"hiresStrength": "High Res Stärke",
"hidePreview": "Verstecke Vorschau",
"showPreview": "Zeige Vorschau",
"aspect": "Seitenverhältnis",
"aspectRatio": "Seitenverhältnis",
"scheduler": "Planer",
"aspectRatioFree": "Frei",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (kann zu groß sein)",
"lockAspectRatio": "Seitenverhältnis sperren",
"swapDimensions": "Seitenverhältnis umkehren",
"setToOptimalSize": "Optimiere Größe für Modell",
"useSize": "Maße übernehmen",
"remixImage": "Remix des Bilds erstellen",
"imageActions": "Weitere Bildaktionen"
"showPreview": "Zeige Vorschau"
},
"settings": {
"displayInProgress": "Zwischenbilder anzeigen",
"displayInProgress": "Bilder in Bearbeitung anzeigen",
"saveSteps": "Speichern der Bilder alle n Schritte",
"confirmOnDelete": "Bestätigen beim Löschen",
"displayHelpIcons": "Hilfesymbole anzeigen",
@@ -618,34 +606,7 @@
"useSlidersForAll": "Schieberegler für alle Optionen verwenden",
"showAdvancedOptions": "Erweiterte Optionen anzeigen",
"alternateCanvasLayout": "Alternatives Leinwand-Layout",
"clearIntermediatesDesc1": "Das Löschen der Zwischenbilder setzt Leinwand und ControlNet zurück.",
"favoriteSchedulers": "Lieblings-Planer",
"favoriteSchedulersPlaceholder": "Keine Planer favorisiert",
"generation": "Erzeugung",
"enableInformationalPopovers": "Info-Popouts anzeigen",
"shouldLogToConsole": "Konsole loggen",
"showProgressInViewer": "Zwischenbilder im Viewer anzeigen",
"clearIntermediatesDesc3": "Ihre Bilder werden nicht gelöscht.",
"clearIntermediatesWithCount_one": "Lösche {{count}} Zwischenbilder",
"clearIntermediatesWithCount_other": "Lösche {{count}} Zwischenbilder",
"reloadingIn": "Neuladen in",
"enableNodesEditor": "Nodes Editor aktivieren",
"autoChangeDimensions": "Breite/Höhe auf Modellstandard setzen",
"experimental": "Experimentell",
"intermediatesCleared_one": "{{count}} Zwischenbilder gelöscht",
"intermediatesCleared_other": "{{count}} Zwischenbilder gelöscht",
"enableInvisibleWatermark": "Unsichtbares Wasserzeichen aktivieren",
"general": "Allgemein",
"consoleLogLevel": "Protokollierungsstufe",
"clearIntermediatesDisabled": "Warteschlange muss leer sein, um Zwischenbilder zu löschen",
"developer": "Entwickler",
"antialiasProgressImages": "Zwischenbilder mit Anti-Alias",
"beta": "Beta",
"ui": "Benutzeroberfläche",
"clearIntermediatesDesc2": "Zwischenbilder sind Nebenprodukte der Erstellung. Sie zu löschen macht Festplattenspeicher frei.",
"clearIntermediates": "Zwischenbilder löschen",
"intermediatesClearedFailed": "Problem beim Löschen der Zwischenbilder",
"enableNSFWChecker": "Auf unangemessene Inhalte prüfen"
"clearIntermediatesDesc1": "Das Löschen der Zwischenprodukte setzt Leinwand und ControlNet zurück."
},
"toast": {
"tempFoldersEmptied": "Temp-Ordner geleert",
@@ -690,9 +651,7 @@
"problemCopyingCanvas": "Problem beim Kopieren der Leinwand",
"problemCopyingCanvasDesc": "Kann Basis-Layer nicht exportieren",
"problemDownloadingCanvas": "Problem beim Herunterladen der Leinwand",
"setAsCanvasInitialImage": "Als Ausgangsbild gesetzt",
"addedToBoard": "Dem Board hinzugefügt",
"loadedWithWarnings": "Workflow mit Warnungen geladen"
"setAsCanvasInitialImage": "Als Ausgangsbild gesetzt"
},
"tooltip": {
"feature": {
@@ -774,23 +733,23 @@
"accessibility": {
"modelSelect": "Modell-Auswahl",
"uploadImage": "Bild hochladen",
"previousImage": "Vorheriges Bild",
"previousImage": "Voriges Bild",
"useThisParameter": "Benutze diesen Parameter",
"copyMetadataJson": "Kopiere JSON-Metadaten",
"copyMetadataJson": "Kopiere Metadaten JSON",
"zoomIn": "Vergrößern",
"rotateClockwise": "Im Uhrzeigersinn drehen",
"flipHorizontally": "Horizontal drehen",
"flipVertically": "Vertikal drehen",
"modifyConfig": "Optionen einstellen",
"toggleAutoscroll": "Auroscroll ein/ausschalten",
"toggleLogViewer": "Log-Betrachter ein/ausschalten",
"toggleLogViewer": "Log Betrachter ein/ausschalten",
"showOptionsPanel": "Seitenpanel anzeigen",
"reset": "Zurücksetzten",
"nextImage": "Nächstes Bild",
"zoomOut": "Verkleinern",
"rotateCounterClockwise": "Gegen den Uhrzeigersinn drehen",
"showGalleryPanel": "Galerie-Panel anzeigen",
"exitViewer": "Betrachter beenden",
"showGalleryPanel": "Galeriefenster anzeigen",
"exitViewer": "Betrachten beenden",
"menu": "Menü",
"loadMore": "Mehr laden",
"invokeProgressBar": "Invoke Fortschrittsanzeige",
@@ -800,7 +759,7 @@
"about": "Über"
},
"boards": {
"autoAddBoard": "Automatisches Hinzufügen zum Board",
"autoAddBoard": "Automatisches Hinzufügen zum Ordner",
"topMessage": "Dieser Ordner enthält Bilder die in den folgenden Funktionen verwendet werden:",
"move": "Bewegen",
"menuItemAutoAdd": "Auto-Hinzufügen zu diesem Ordner",
@@ -809,13 +768,13 @@
"noMatching": "Keine passenden Ordner",
"selectBoard": "Ordner aussuchen",
"cancel": "Abbrechen",
"addBoard": "Board hinzufügen",
"addBoard": "Ordner hinzufügen",
"uncategorized": "Ohne Kategorie",
"downloadBoard": "Ordner runterladen",
"changeBoard": "Ordner wechseln",
"loading": "Laden...",
"clearSearch": "Suche leeren",
"bottomMessage": "Löschen des Boards und seiner Bilder setzt alle Funktionen zurück, die sie gerade verwenden.",
"bottomMessage": "Durch das Löschen dieses Ordners und seiner Bilder werden alle Funktionen zurückgesetzt, die sie derzeit verwenden.",
"deleteBoardOnly": "Nur Ordner löschen",
"deleteBoard": "Löschen Ordner",
"deleteBoardAndImages": "Löschen Ordner und Bilder",
@@ -861,7 +820,7 @@
"colorMap": "Farbe",
"lowThreshold": "Niedrige Schwelle",
"highThreshold": "Hohe Schwelle",
"toggleControlNet": "Dieses ControlNet ein- oder ausschalten",
"toggleControlNet": "Schalten ControlNet um",
"delete": "Löschen",
"controlAdapter_one": "Control Adapter",
"controlAdapter_other": "Control Adapter",
@@ -906,23 +865,18 @@
"maxFaces": "Maximale Anzahl Gesichter",
"resizeSimple": "Größe ändern (einfach)",
"large": "Groß",
"modelSize": "Modellgröße",
"modelSize": "Modell Größe",
"small": "Klein",
"base": "Basis",
"depthAnything": "Depth Anything",
"depthAnythingDescription": "Erstellung einer Tiefenkarte mit der Depth-Anything-Technik",
"face": "Gesicht",
"body": "Körper",
"hands": "Hände",
"dwOpenpose": "DW Openpose",
"dwOpenposeDescription": "Posenschätzung mit DW Openpose"
"depthAnything": "Depth Anything / \"Tiefe irgendwas\"",
"depthAnythingDescription": "Erstellung einer Tiefenkarte mit der Depth Anything-Technik"
},
"queue": {
"status": "Status",
"cancelTooltip": "Aktuellen Aufgabe abbrechen",
"queueEmpty": "Warteschlange leer",
"in_progress": "In Arbeit",
"queueFront": "Am Anfang der Warteschlange einreihen",
"queueFront": "An den Anfang der Warteschlange tun",
"completed": "Fertig",
"queueBack": "In die Warteschlange",
"clearFailed": "Probleme beim leeren der Warteschlange",
@@ -950,7 +904,7 @@
"batchValues": "Stapel Werte",
"queueCountPrediction": "{{promptsCount}} Prompts × {{iterations}} Iterationen -> {{count}} Generationen",
"queuedCount": "{{pending}} wartenden Elemente",
"clearQueueAlertDialog": "\"Die Warteschlange leeren\" stoppt den aktuellen Prozess und leert die Warteschlange komplett.",
"clearQueueAlertDialog": "Die Warteschlange leeren, stoppt den aktuellen Prozess und leert die Warteschlange komplett.",
"completedIn": "Fertig in",
"cancelBatchSucceeded": "Stapel abgebrochen",
"cancelBatch": "Stapel stoppen",
@@ -959,20 +913,20 @@
"cancelBatchFailed": "Problem beim Abbruch vom Stapel",
"clearQueueAlertDialog2": "Warteschlange wirklich leeren?",
"pruneSucceeded": "{{item_count}} abgeschlossene Elemente aus der Warteschlange entfernt",
"pauseSucceeded": "Prozess angehalten",
"pauseSucceeded": "Prozessor angehalten",
"cancelFailed": "Problem beim Stornieren des Auftrags",
"pauseFailed": "Problem beim Anhalten des Prozesses",
"pauseFailed": "Problem beim Anhalten des Prozessors",
"front": "Vorne",
"pruneTooltip": "Bereinigen Sie {{item_count}} abgeschlossene Aufträge",
"resumeFailed": "Problem beim Fortsetzen des Prozesses",
"resumeFailed": "Problem beim wieder aufnehmen von Prozessor",
"pruneFailed": "Problem beim leeren der Warteschlange",
"pauseTooltip": "Prozess anhalten",
"pauseTooltip": "Pause von Prozessor",
"back": "Hinten",
"resumeSucceeded": "Prozess wird fortgesetzt",
"resumeTooltip": "Prozess wieder aufnehmen",
"resumeSucceeded": "Prozessor wieder aufgenommen",
"resumeTooltip": "Prozessor wieder aufnehmen",
"time": "Zeit",
"batchQueuedDesc_one": "{{count}} Eintrag an {{direction}} der Wartschlange hinzugefügt",
"batchQueuedDesc_other": "{{count}} Einträge an {{direction}} der Wartschlange hinzugefügt",
"batchQueuedDesc_one": "{{count}} Eintrag ans {{direction}} der Wartschlange hinzugefügt",
"batchQueuedDesc_other": "{{count}} Einträge ans {{direction}} der Wartschlange hinzugefügt",
"openQueue": "Warteschlange öffnen",
"batchFailedToQueue": "Fehler beim Einreihen in die Stapelverarbeitung",
"batchFieldValues": "Stapelverarbeitungswerte",
@@ -1007,12 +961,11 @@
"workflow": "Workflow",
"scheduler": "Planer",
"noRecallParameters": "Es wurden keine Parameter zum Abrufen gefunden",
"recallParameters": "Parameter wiederherstellen",
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
"recallParameters": "Parameter wiederherstellen"
},
"popovers": {
"noiseUseCPU": {
"heading": "Nutze CPU-Rauschen",
"heading": "Nutze Prozessor rauschen",
"paragraphs": [
"Entscheidet, ob auf der CPU oder GPU Rauschen erzeugt wird.",
"Mit aktiviertem CPU-Rauschen wird ein bestimmter Seedwert das gleiche Bild auf jeder Maschine erzeugen.",
@@ -1022,7 +975,8 @@
"paramModel": {
"heading": "Modell",
"paragraphs": [
"Modell für die Entrauschungsschritte."
"Modell für die Entrauschungsschritte.",
"Verschiedene Modelle werden in der Regel so trainiert, dass sie sich auf die Erzeugung bestimmter Ästhetik und/oder Inhalte spezialisiert."
]
},
"paramIterations": {
@@ -1130,23 +1084,12 @@
"Wie stark wird das ControlNet das generierte Bild beeinflussen wird."
],
"heading": "Einfluss"
},
"paramScheduler": {
"paragraphs": [
"\"Planer\" definiert, wie iterativ Rauschen zu einem Bild hinzugefügt wird, oder wie ein Sample bei der Ausgabe eines Modells aktualisiert wird."
],
"heading": "Planer"
},
"imageFit": {
"paragraphs": [
"Reduziert das Ausgangsbild auf die Breite und Höhe des Ausgangsbildes. Empfohlen zu aktivieren."
]
}
},
"ui": {
"lockRatio": "Verhältnis sperren",
"hideProgressImages": "Fortschrittsbilder verbergen",
"showProgressImages": "Fortschrittsbilder anzeigen",
"hideProgressImages": "Verstecke Prozess Bild",
"showProgressImages": "Zeige Prozess Bild",
"swapSizes": "Tausche Größen"
},
"invocationCache": {
@@ -1344,19 +1287,7 @@
"vaeFieldDescription": "VAE Submodell.",
"unknownInput": "Unbekannte Eingabe: {{name}}",
"unknownNodeType": "Unbekannter Knotentyp",
"float": "Kommazahlen",
"latentsPolymorphic": "Latents Polymorph",
"integerPolymorphicDescription": "Eine Sammlung von ganzen Zahlen.",
"integerPolymorphic": "Ganze Zahl Polymorph",
"ipAdapterPolymorphic": "IP-Adapter Polymorph",
"floatPolymorphic": "Fließkommazahl Polymorph",
"enumDescription": "Aufzählungen sind Werte, die eine von mehreren Optionen sein können.",
"floatCollection": "Fließkommazahl Sammlung",
"enum": "Aufzählung",
"floatPolymorphicDescription": "Eine Sammlung von Fließkommazahlen",
"fullyContainNodes": "Vollständig ausgewählte Nodes auswählen",
"editMode": "Im Workflow-Editor bearbeiten",
"floatCollectionDescription": "Eine Sammlung von Fließkommazahlen"
"float": "Kommazahlen"
},
"hrf": {
"enableHrf": "Korrektur für hohe Auflösungen",
@@ -1405,12 +1336,12 @@
},
"control": {
"title": "Kontrolle",
"controlAdaptersTab": "Kontroll-Adapter",
"ipTab": "Bild-Prompts"
"controlAdaptersTab": "Kontroll Adapter",
"ipTab": "Bild Beschreibung"
},
"compositing": {
"coherenceTab": "Kohärenzpass",
"infillTab": "Füllung / Infill",
"infillTab": "Füllung",
"title": "Compositing"
}
},
@@ -1448,15 +1379,5 @@
},
"app": {
"storeNotInitialized": "App-Store ist nicht initialisiert"
},
"sdxl": {
"concatPromptStyle": "Verknüpfen von Prompt & Stil",
"scheduler": "Planer",
"steps": "Schritte",
"useRefiner": "Refiner verwenden",
"selectAModel": "Modell auswählen"
},
"dynamicPrompts": {
"showDynamicPrompts": "Dynamische Prompts anzeigen"
}
}

View File

@@ -78,6 +78,7 @@
"aboutDesc": "Using Invoke for work? Check out:",
"aboutHeading": "Own Your Creative Power",
"accept": "Accept",
"add": "Add",
"advanced": "Advanced",
"advancedOptions": "Advanced Options",
"ai": "ai",
@@ -303,6 +304,12 @@
"method": "High Resolution Fix Method"
}
},
"prompt": {
"addPromptTrigger": "Add Prompt Trigger",
"compatibleEmbeddings": "Compatible Embeddings",
"noPromptTriggers": "No triggers available",
"noMatchingTriggers": "No matching triggers"
},
"embedding": {
"addEmbedding": "Add Embedding",
"incompatibleModel": "Incompatible base model:",
@@ -734,6 +741,8 @@
"customConfig": "Custom Config",
"customConfigFileLocation": "Custom Config File Location",
"customSaveLocation": "Custom Save Location",
"defaultSettings": "Default Settings",
"defaultSettingsSaved": "Default Settings Saved",
"delete": "Delete",
"deleteConfig": "Delete Config",
"deleteModel": "Delete Model",
@@ -768,6 +777,7 @@
"mergedModelName": "Merged Model Name",
"mergedModelSaveLocation": "Save Location",
"mergeModels": "Merge Models",
"metadata": "Metadata",
"model": "Model",
"modelAdded": "Model Added",
"modelConversionFailed": "Model Conversion Failed",
@@ -839,9 +849,12 @@
"statusConverting": "Converting",
"syncModels": "Sync Models",
"syncModelsDesc": "If your models are out of sync with the backend, you can refresh them up using this option. This is generally handy in cases where you add models to the InvokeAI root folder or autoimport directory after the application has booted.",
"triggerPhrases": "Trigger Phrases",
"typePhraseHere": "Type phrase here",
"upcastAttention": "Upcast Attention",
"updateModel": "Update Model",
"useCustomConfig": "Use Custom Config",
"useDefaultSettings": "Use Default Settings",
"v1": "v1",
"v2_768": "v2 (768px)",
"v2_base": "v2 (512px)",
@@ -1456,8 +1469,9 @@
"clipSkip": {
"heading": "CLIP Skip",
"paragraphs": [
"How many layers of the CLIP model to skip.",
"Certain models are better suited to be used with CLIP Skip."
"Choose how many layers of the CLIP model to skip.",
"Some models work better with certain CLIP Skip settings.",
"A higher value typically results in a less detailed image."
]
},
"paramNegativeConditioning": {
@@ -1477,8 +1491,7 @@
"paramScheduler": {
"heading": "Scheduler",
"paragraphs": [
"Scheduler used during the generation process.",
"Each scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
"Scheduler defines how to iteratively add noise to an image or how to update a sample based on a model's output."
]
},
"compositingMaskBlur": {
@@ -1495,15 +1508,7 @@
},
"compositingCoherenceMode": {
"heading": "Mode",
"paragraphs": ["Method used to create a coherent image with the newly generated masked area."]
},
"compositingCoherenceSteps": {
"heading": "Steps",
"paragraphs": ["Number of steps in the Coherence Pass.", "Similar to Generation Steps."]
},
"compositingStrength": {
"heading": "Strength",
"paragraphs": ["Amount of noise added for the Coherence Pass.", "Similar to Denoising Strength."]
"paragraphs": ["The mode of the Coherence Pass."]
},
"compositingCoherenceEdgeSize": {
"heading": "Edge Size",
@@ -1520,38 +1525,30 @@
"heading": "Mask Adjustments",
"paragraphs": ["Adjust the mask."]
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
"paragraphs": [
"Which steps of the denoising process will have the ControlNet applied.",
"ControlNets applied at the beginning of the process guide composition, and ControlNets applied at the end guide details."
]
},
"controlNetControlMode": {
"heading": "Control Mode",
"paragraphs": ["Lends more weight to either the prompt or ControlNet."]
},
"controlNetResizeMode": {
"heading": "Resize Mode",
"paragraphs": ["How the ControlNet image will be fit to the image output size."]
},
"controlNet": {
"heading": "ControlNet",
"paragraphs": [
"ControlNets provide guidance to the generation process, helping create images with controlled composition, structure, or style, depending on the model selected."
]
},
"controlNetBeginEnd": {
"heading": "Begin / End Step Percentage",
"paragraphs": [
"The part of the of the denoising process that will have the Control Adapter applied.",
"Generally, Control Adapters applied at the start of the process guide composition, and Control Adapters applied at the end guide details."
]
},
"controlNetControlMode": {
"heading": "Control Mode",
"paragraphs": ["Lend more weight to either the prompt or ControlNet."]
},
"controlNetProcessor": {
"heading": "Processor",
"paragraphs": [
"Method of processing the input image to guide the generation process. Different processors will providedifferent effects or styles in your generated images."
]
},
"controlNetResizeMode": {
"heading": "Resize Mode",
"paragraphs": ["Method to fit Control Adapter's input image size to the output generation size."]
},
"controlNetWeight": {
"heading": "Weight",
"paragraphs": [
"Weight of the Control Adapter. Higher weight will lead to larger impacts on the final image."
]
"paragraphs": ["How strongly the ControlNet will impact the generated image."]
},
"dynamicPrompts": {
"heading": "Dynamic Prompts",
@@ -1574,23 +1571,13 @@
"Per Image will use a unique seed for each image. This provides more variation."
]
},
"imageFit": {
"heading": "Fit Initial Image to Output Size",
"paragraphs": [
"Resizes the initial image to the width and height of the output image. Recommended to enable."
]
},
"infillMethod": {
"heading": "Infill Method",
"paragraphs": ["Method of infilling during the Outpainting or Inpainting process."]
"paragraphs": ["Method to infill the selected area."]
},
"lora": {
"heading": "LoRA",
"paragraphs": ["Lightweight models that are used in conjunction with base models."]
},
"loraWeight": {
"heading": "Weight",
"paragraphs": ["Weight of the LoRA. Higher weight will lead to larger impacts on the final image."]
"heading": "LoRA Weight",
"paragraphs": ["Higher LoRA weight will lead to larger impacts on the final image."]
},
"noiseUseCPU": {
"heading": "Use CPU Noise",
@@ -1600,25 +1587,14 @@
"There is no performance impact to enabling CPU Noise."
]
},
"paramAspect": {
"heading": "Aspect",
"paragraphs": [
"Aspect ratio of the generated image. Changing the ratio will update the Width and Height accordingly.",
"“Optimize” will set the Width and Height to optimal dimensions for the chosen model."
]
},
"paramCFGScale": {
"heading": "CFG Scale",
"paragraphs": [
"Controls how much the prompt influences the generation process.",
"High CFG Scale values can result in over-saturation and distorted generation results. "
]
"paragraphs": ["Controls how much your prompt influences the generation process."]
},
"paramCFGRescaleMultiplier": {
"heading": "CFG Rescale Multiplier",
"paragraphs": [
"Rescale multiplier for CFG guidance, used for models trained using zero-terminal SNR (ztsnr).",
"Suggested value of 0.7 for these models."
"Rescale multiplier for CFG guidance, used for models trained using zero-terminal SNR (ztsnr). Suggested value 0.7."
]
},
"paramDenoisingStrength": {
@@ -1628,16 +1604,6 @@
"0 will result in an identical image, while 1 will result in a completely new image."
]
},
"paramHeight": {
"heading": "Height",
"paragraphs": ["Height of the generated image. Must be a multiple of 8."]
},
"paramHrf": {
"heading": "Enable High Resolution Fix",
"paragraphs": [
"Generate high quality images at a larger resolution than optimal for the model. Generally used to prevent duplication in the generated image."
]
},
"paramIterations": {
"heading": "Iterations",
"paragraphs": [
@@ -1648,7 +1614,8 @@
"paramModel": {
"heading": "Model",
"paragraphs": [
"Model used for generation. Different models are trained to specialize in producing different aesthetic results and content."
"Model used for the denoising steps.",
"Different models are typically trained to specialize in producing particular aesthetic results and content."
]
},
"paramRatio": {
@@ -1662,7 +1629,7 @@
"heading": "Seed",
"paragraphs": [
"Controls the starting noise used for generation.",
"Disable the “Random” option to produce identical results with the same generation settings."
"Disable “Random Seed” to produce identical results with the same generation settings."
]
},
"paramSteps": {
@@ -1672,10 +1639,6 @@
"Higher step counts will typically create better images but will require more generation time."
]
},
"paramUpscaleMethod": {
"heading": "Upscale Method",
"paragraphs": ["Method used to upscale the image for High Resolution Fix."]
},
"paramVAE": {
"heading": "VAE",
"paragraphs": ["Model used for translating AI output into the final image."]
@@ -1683,82 +1646,14 @@
"paramVAEPrecision": {
"heading": "VAE Precision",
"paragraphs": [
"The precision used during VAE encoding and decoding.",
"Fp16/Half precision is more efficient, at the expense of minor image variations."
]
},
"paramWidth": {
"heading": "Width",
"paragraphs": ["Width of the generated image. Must be a multiple of 8."]
},
"patchmatchDownScaleSize": {
"heading": "Downscale",
"paragraphs": [
"How much downscaling occurs before infilling.",
"Higher downscaling will improve performance and reduce quality."
]
},
"refinerModel": {
"heading": "Refiner Model",
"paragraphs": [
"Model used during the refiner portion of the generation process.",
"Similar to the Generation Model."
]
},
"refinerPositiveAestheticScore": {
"heading": "Positive Aesthetic Score",
"paragraphs": [
"Weight generations to be more similar to images with a high aesthetic score, based on the training data."
]
},
"refinerNegativeAestheticScore": {
"heading": "Negative Aesthetic Score",
"paragraphs": [
"Weight generations to be more similar to images with a low aesthetic score, based on the training data."
]
},
"refinerScheduler": {
"heading": "Scheduler",
"paragraphs": [
"Scheduler used during the refiner portion of the generation process.",
"Similar to the Generation Scheduler."
]
},
"refinerStart": {
"heading": "Refiner Start",
"paragraphs": [
"Where in the generation process the refiner will start to be used.",
"0 means the refiner will be used for the entire generation process, 0.8 means the refiner will be used for the last 20% of the generation process."
]
},
"refinerSteps": {
"heading": "Steps",
"paragraphs": [
"Number of steps that will be performed during the refiner portion of the generation process.",
"Similar to the Generation Steps."
]
},
"refinerCfgScale": {
"heading": "CFG Scale",
"paragraphs": [
"Controls how much the prompt influences the generation process.",
"Similar to the Generation CFG Scale."
"The precision used during VAE encoding and decoding. FP16/half precision is more efficient, at the expense of minor image variations."
]
},
"scaleBeforeProcessing": {
"heading": "Scale Before Processing",
"paragraphs": [
"“Auto” scales the selected area to the size best suited for the model before the image generation process.",
"“Manual” allows you to choose the width and height the selected area will be scaled to before the image generation process."
"Scales the selected area to the size best suited for the model before the image generation process."
]
},
"seamlessTilingXAxis": {
"heading": "Seamless Tiling X Axis",
"paragraphs": ["Seamlessly tile an image along the horizontal axis."]
},
"seamlessTilingYAxis": {
"heading": "Seamless Tiling Y Axis",
"paragraphs": ["Seamlessly tile an image along the vertical axis."]
}
},
"ui": {

View File

@@ -47,7 +47,7 @@
"statusModelConverted": "Modello Convertito",
"statusConvertingModel": "Conversione Modello",
"loading": "Caricamento in corso",
"loadingInvokeAI": "Caricamento di Invoke AI",
"loadingInvokeAI": "Caricamento Invoke AI",
"postprocessing": "Post Elaborazione",
"txt2img": "Testo a Immagine",
"accept": "Accetta",
@@ -61,7 +61,7 @@
"imagePrompt": "Prompt Immagine",
"darkMode": "Modalità scura",
"batch": "Gestione Lotto",
"modelManager": "Gestore Modelli",
"modelManager": "Gestore modello",
"communityLabel": "Comunità",
"nodeEditor": "Editor dei nodi",
"statusProcessing": "Elaborazione in corso",
@@ -81,7 +81,7 @@
"error": "Errore",
"installed": "Installato",
"template": "Schema",
"outputs": "Risultati",
"outputs": "Uscite",
"data": "Dati",
"somethingWentWrong": "Qualcosa è andato storto",
"copyError": "$t(gallery.copy) Errore",
@@ -93,7 +93,7 @@
"created": "Creato",
"prevPage": "Pagina precedente",
"delete": "Elimina",
"orderBy": "Ordina per",
"orderBy": "Ordinato per",
"nextPage": "Pagina successiva",
"saveAs": "Salva come",
"unsaved": "Non salvato",
@@ -109,12 +109,7 @@
"green": "Verde",
"blue": "Blu",
"alpha": "Alfa",
"copy": "Copia",
"on": "Attivato",
"checkpoint": "Checkpoint",
"safetensors": "Safetensors",
"ai": "ia",
"file": "File"
"copy": "Copia"
},
"gallery": {
"generations": "Generazioni",
@@ -939,7 +934,7 @@
"executionStateCompleted": "Completato",
"boardFieldDescription": "Una bacheca della galleria",
"addNodeToolTip": "Aggiungi nodo (Shift+A, Space)",
"sDXLRefinerModelField": "Modello Affinatore",
"sDXLRefinerModelField": "Modello Refiner",
"problemReadingMetadata": "Problema durante la lettura dei metadati dall'immagine",
"colorCodeEdgesHelp": "Bordi con codice colore in base ai campi collegati",
"animatedEdges": "Bordi animati",
@@ -1143,11 +1138,7 @@
"unsupportedAnyOfLength": "unione di troppi elementi ({{count}})",
"clearWorkflowDesc": "Cancellare questo flusso di lavoro e avviarne uno nuovo?",
"clearWorkflow": "Cancella il flusso di lavoro",
"clearWorkflowDesc2": "Il tuo flusso di lavoro attuale presenta modifiche non salvate.",
"viewMode": "Utilizzare nella vista lineare",
"reorderLinearView": "Riordina la vista lineare",
"editMode": "Modifica nell'editor del flusso di lavoro",
"resetToDefaultValue": "Ripristina il valore predefinito"
"clearWorkflowDesc2": "Il tuo flusso di lavoro attuale presenta modifiche non salvate."
},
"boards": {
"autoAddBoard": "Aggiungi automaticamente bacheca",
@@ -1250,16 +1241,7 @@
"large": "Grande",
"small": "Piccolo",
"depthAnythingDescription": "Generazione di mappe di profondità utilizzando la tecnica Depth Anything",
"modelSize": "Dimensioni del modello",
"dwOpenposeDescription": "Stima della posa umana utilizzando DW Openpose",
"face": "Viso",
"body": "Corpo",
"hands": "Mani",
"lineartAnime": "Linea Anime",
"base": "Base",
"lineart": "Linea",
"controlnet": "$t(controlnet.controlAdapter_one) #{{number}} ($t(common.controlNet))",
"mediapipeFace": "Mediapipe Volto"
"modelSize": "Dimensioni del modello"
},
"queue": {
"queueFront": "Aggiungi all'inizio della coda",
@@ -1339,7 +1321,7 @@
"noModelsAvailable": "Nessun modello disponibile",
"selectModel": "Seleziona un modello",
"selectLoRA": "Seleziona un LoRA",
"noRefinerModelsInstalled": "Nessun modello affinatore SDXL installato",
"noRefinerModelsInstalled": "Nessun modello SDXL Refiner installato",
"noLoRAsInstalled": "Nessun LoRA installato",
"esrganModel": "Modello ESRGAN",
"addLora": "Aggiungi LoRA",
@@ -1389,8 +1371,7 @@
"popovers": {
"paramScheduler": {
"paragraphs": [
"Il campionatore utilizzato durante il processo di generazione.",
"Ciascun campionatore definisce come aggiungere in modo iterativo il rumore a un'immagine o come aggiornare un campione in base all'output di un modello."
"Il campionatore definisce come aggiungere in modo iterativo il rumore a un'immagine o come aggiornare un campione in base all'output di un modello."
],
"heading": "Campionatore"
},
@@ -1403,8 +1384,8 @@
"compositingCoherenceSteps": {
"heading": "Passi",
"paragraphs": [
"Numero di passi utilizzati nel Passaggio di Coerenza.",
"Simile ai passi di generazione."
"Numero di passi di riduzione del rumore utilizzati nel Passaggio di Coerenza.",
"Uguale al parametro principale Passi."
]
},
"compositingBlur": {
@@ -1416,13 +1397,14 @@
"compositingCoherenceMode": {
"heading": "Modalità",
"paragraphs": [
"Metodo utilizzato per creare un'immagine coerente con l'area mascherata appena generata."
"La modalità del Passaggio di Coerenza."
]
},
"clipSkip": {
"paragraphs": [
"Scegli quanti livelli del modello CLIP saltare.",
"Alcuni modelli funzionano meglio con determinate impostazioni di CLIP Skip."
"Alcuni modelli funzionano meglio con determinate impostazioni di CLIP Skip.",
"Un valore più alto in genere produce un'immagine meno dettagliata."
]
},
"compositingCoherencePass": {
@@ -1434,8 +1416,8 @@
"compositingStrength": {
"heading": "Forza",
"paragraphs": [
"Quantità di rumore aggiunta per il Passaggio di Coerenza.",
"Simile alla forza di riduzione del rumore."
"Intensità di riduzione del rumore per il passaggio di coerenza.",
"Uguale al parametro intensità di riduzione del rumore da immagine a immagine."
]
},
"paramNegativeConditioning": {
@@ -1461,8 +1443,8 @@
"controlNetBeginEnd": {
"heading": "Percentuale passi Inizio / Fine",
"paragraphs": [
"La parte del processo di rimozione del rumore in cui verrà applicato l'adattatore di controllo.",
"In genere, gli adattatori di controllo applicati all'inizio del processo guidano la composizione, mentre quelli applicati alla fine guidano i dettagli."
"A quali passi del processo di rimozione del rumore verrà applicato ControlNet.",
"I ControlNet applicati all'inizio del processo guidano la composizione, mentre i ControlNet applicati alla fine guidano i dettagli."
]
},
"noiseUseCPU": {
@@ -1475,8 +1457,7 @@
},
"scaleBeforeProcessing": {
"paragraphs": [
"\"Auto\" ridimensiona l'area selezionata alla dimensione più adatta al modello prima del processo di generazione dell'immagine.",
"\"Manuale\" consente di scegliere la larghezza e l'altezza a cui verrà ridimensionata l'area selezionata prima del processo di generazione dell'immagine."
"Ridimensiona l'area selezionata alla dimensione più adatta al modello prima del processo di generazione dell'immagine."
],
"heading": "Scala prima dell'elaborazione"
},
@@ -1511,21 +1492,20 @@
"paramVAEPrecision": {
"heading": "Precisione VAE",
"paragraphs": [
"La precisione utilizzata durante la codifica e decodifica VAE.",
"Fp16/Mezza precisione è più efficiente, a scapito di minori variazioni dell'immagine."
"La precisione utilizzata durante la codifica e decodifica VAE. FP16/mezza precisione è più efficiente, a scapito di minori variazioni dell'immagine."
]
},
"paramSeed": {
"paragraphs": [
"Controlla il rumore iniziale utilizzato per la generazione.",
"Disabilita l'opzione \"Casuale\" per produrre risultati identici con le stesse impostazioni di generazione."
"Disabilita seme \"Casuale\" per produrre risultati identici con le stesse impostazioni di generazione."
],
"heading": "Seme"
},
"controlNetResizeMode": {
"heading": "Modalità ridimensionamento",
"paragraphs": [
"Metodo per adattare le dimensioni dell'immagine in ingresso dell'adattatore di controllo alle dimensioni della generazione di output."
"Come l'immagine ControlNet verrà adattata alle dimensioni di output dell'immagine."
]
},
"dynamicPromptsSeedBehaviour": {
@@ -1540,7 +1520,8 @@
"paramModel": {
"heading": "Modello",
"paragraphs": [
"Modello utilizzato per la generazione. Diversi modelli vengono addestrati per specializzarsi nella produzione di risultati e contenuti estetici diversi."
"Modello utilizzato per i passaggi di riduzione del rumore.",
"Diversi modelli sono generalmente addestrati per specializzarsi nella produzione di particolari risultati e contenuti estetici."
]
},
"paramDenoisingStrength": {
@@ -1558,26 +1539,25 @@
},
"infillMethod": {
"paragraphs": [
"Metodo di riempimento durante il processo di Outpainting o Inpainting."
"Metodo per riempire l'area selezionata."
],
"heading": "Metodo di riempimento"
},
"controlNetWeight": {
"heading": "Peso",
"paragraphs": [
"Peso dell'adattatore di controllo. Un peso maggiore portea impatti maggiori sull'immagine finale."
"Quanto forte sal'impatto di ControlNet sull'immagine generata."
]
},
"paramCFGScale": {
"heading": "Scala CFG",
"paragraphs": [
"Controlla quanto il prompt influenza il processo di generazione.",
"Valori elevati della scala CFG possono provocare una saturazione eccessiva e distorsioni nei risultati della generazione. "
"Controlla quanto il tuo prompt influenza il processo di generazione."
]
},
"controlNetControlMode": {
"paragraphs": [
"Attribuisce più peso al prompt oppure a ControlNet."
"Attribuisce più peso al prompt o a ControlNet."
],
"heading": "Modalità di controllo"
},
@@ -1589,9 +1569,9 @@
]
},
"lora": {
"heading": "LoRA",
"heading": "Peso LoRA",
"paragraphs": [
"Modelli leggeri utilizzati insieme ai modelli base."
"Un peso LoRA più elevato porterà a impatti maggiori sull'immagine finale."
]
},
"controlNet": {
@@ -1603,123 +1583,7 @@
"paramCFGRescaleMultiplier": {
"heading": "Moltiplicatore di riscala CFG",
"paragraphs": [
"Moltiplicatore di riscala per la guida CFG, utilizzato per modelli addestrati utilizzando SNR a terminale zero (ztsnr).",
"Valore suggerito di 0.7 per questi modelli."
]
},
"controlNetProcessor": {
"heading": "Processore",
"paragraphs": [
"Metodo di elaborazione dell'immagine di input per guidare il processo di generazione. Processori diversi forniranno effetti o stili diversi nelle immagini generate."
]
},
"imageFit": {
"heading": "Adatta l'immagine iniziale alle dimensioni di output",
"paragraphs": [
"Ridimensiona l'immagine iniziale in base alla larghezza e all'altezza dell'immagine di output. Si consiglia di abilitarlo."
]
},
"loraWeight": {
"heading": "Peso",
"paragraphs": [
"Peso del LoRA. Un peso maggiore comporterà un impatto maggiore sull'immagine finale."
]
},
"paramAspect": {
"heading": "Aspetto",
"paragraphs": [
"Proporzioni dell'immagine generata. La modifica del rapporto aggiornerà di conseguenza la larghezza e l'altezza.",
"\"Ottimizza\" imposterà la larghezza e l'altezza alle dimensioni ottimali per il modello scelto."
]
},
"paramHeight": {
"heading": "Altezza",
"paragraphs": [
"Altezza dell'immagine generata. Deve essere un multiplo di 8."
]
},
"paramHrf": {
"heading": "Abilita correzione alta risoluzione",
"paragraphs": [
"Genera immagini di alta qualità con una risoluzione maggiore di quella ottimale per il modello. Generalmente utilizzato per impedire la duplicazione nell'immagine generata."
]
},
"paramUpscaleMethod": {
"heading": "Metodo di ampliamento",
"paragraphs": [
"Metodo utilizzato per eseguire l'ampliamento dell'immagine per la correzione ad alta risoluzione."
]
},
"patchmatchDownScaleSize": {
"heading": "Ridimensiona",
"paragraphs": [
"Quanto ridimensionamento avviene prima del riempimento.",
"Un ridimensionamento più elevato migliorerà le prestazioni e ridurrà la qualità."
]
},
"paramWidth": {
"paragraphs": [
"Larghezza dell'immagine generata. Deve essere un multiplo di 8."
],
"heading": "Larghezza"
},
"refinerModel": {
"heading": "Modello Affinatore",
"paragraphs": [
"Modello utilizzato durante la parte di affinamento del processo di generazione.",
"Simile al modello di generazione."
]
},
"refinerNegativeAestheticScore": {
"paragraphs": [
"Valuta le generazioni in modo che siano più simili alle immagini con un punteggio estetico basso, in base ai dati di addestramento."
],
"heading": "Punteggio estetico negativo"
},
"refinerScheduler": {
"paragraphs": [
"Campionatore utilizzato durante la parte di affinamento del processo di generazione.",
"Simile al campionatore di generazione."
],
"heading": "Campionatore"
},
"refinerStart": {
"heading": "Inizio affinamento",
"paragraphs": [
"A che punto nel processo di generazione inizierà ad essere utilizzato l'affinatore.",
"0 significa che l'affinatore verrà utilizzato per l'intero processo di generazione, 0.8 significa che l'affinatore verrà utilizzato per l'ultimo 20% del processo di generazione."
]
},
"refinerSteps": {
"heading": "Passi",
"paragraphs": [
"Numero di passi che verranno eseguiti durante la parte di affinamento del processo di generazione.",
"Simile ai passi di generazione."
]
},
"refinerCfgScale": {
"heading": "Scala CFG",
"paragraphs": [
"Controlla quanto il prompt influenza il processo di generazione.",
"Simile alla scala CFG di generazione."
]
},
"seamlessTilingXAxis": {
"heading": "Asse X di piastrellatura senza cuciture",
"paragraphs": [
"Affianca senza soluzione di continuità un'immagine lungo l'asse orizzontale."
]
},
"seamlessTilingYAxis": {
"heading": "Asse Y di piastrellatura senza cuciture",
"paragraphs": [
"Affianca senza soluzione di continuità un'immagine lungo l'asse verticale."
]
},
"refinerPositiveAestheticScore": {
"heading": "Punteggio estetico positivo",
"paragraphs": [
"Valuta le generazioni in modo che siano più simili alle immagini con un punteggio estetico elevato, in base ai dati di addestramento."
"Moltiplicatore di riscala per la guida CFG, utilizzato per modelli addestrati utilizzando SNR a terminale zero (ztsnr). Valore suggerito 0.7."
]
}
},
@@ -1768,8 +1632,7 @@
"steps": "Passi",
"scheduler": "Campionatore",
"recallParameters": "Richiama i parametri",
"noRecallParameters": "Nessun parametro da richiamare trovato",
"cfgRescaleMultiplier": "$t(parameters.cfgRescaleMultiplier)"
"noRecallParameters": "Nessun parametro da richiamare trovato"
},
"hrf": {
"enableHrf": "Abilita Correzione Alta Risoluzione",

View File

@@ -1217,14 +1217,16 @@
"clipSkip": {
"paragraphs": [
"Kies hoeveel CLIP-modellagen je wilt overslaan.",
"Bepaalde modellen werken beter met bepaalde Overslaan CLIP-instellingen."
"Bepaalde modellen werken beter met bepaalde Overslaan CLIP-instellingen.",
"Een hogere waarde geeft meestal een minder gedetailleerde afbeelding."
],
"heading": "Overslaan CLIP"
},
"paramModel": {
"heading": "Model",
"paragraphs": [
"Model gebruikt voor de ontruisingsstappen."
"Model gebruikt voor de ontruisingsstappen.",
"Verschillende modellen zijn meestal getraind om zich te specialiseren in het maken van bepaalde esthetische resultaten en materiaal."
]
},
"compositingCoherencePass": {

View File

@@ -108,16 +108,7 @@
"preferencesLabel": "Предпочтения",
"or": "или",
"advancedOptions": "Расширенные настройки",
"free": "Свободно",
"aboutHeading": "Владей своей творческой силой",
"red": "Красный",
"green": "Зеленый",
"blue": "Синий",
"alpha": "Альфа",
"toResolve": "Чтоб решить",
"copy": "Копировать",
"localSystem": "Локальная система",
"aboutDesc": "Используя Invoke для работы? Проверьте это:"
"free": "Свободно"
},
"gallery": {
"generations": "Генерации",
@@ -161,17 +152,17 @@
},
"hotkeys": {
"keyboardShortcuts": "Горячие клавиши",
"appHotkeys": "Приложение",
"generalHotkeys": "Общее",
"galleryHotkeys": "Галлерея",
"unifiedCanvasHotkeys": "Единый холст",
"appHotkeys": "Горячие клавиши приложения",
"generalHotkeys": "Общие горячие клавиши",
"galleryHotkeys": орячие клавиши галереи",
"unifiedCanvasHotkeys": "Горячие клавиши Единого холста",
"invoke": {
"title": "Invoke",
"desc": "Сгенерировать изображение"
},
"cancel": {
"title": "Отменить",
"desc": "Отменить текущий элемент"
"desc": "Отменить генерацию изображения"
},
"focusPrompt": {
"title": "Переключиться на ввод запроса",
@@ -361,7 +352,7 @@
"desc": "Открывает меню добавления узла",
"title": "Добавление узлов"
},
"nodesHotkeys": "Узлы",
"nodesHotkeys": "Горячие клавиши узлов",
"cancelAndClear": {
"desc": "Отмена текущего элемента очереди и очистка всех ожидающих элементов",
"title": "Отменить и очистить"
@@ -376,11 +367,7 @@
"desc": "Открытие и закрытие панели опций и галереи",
"title": "Переключить опции и галерею"
},
"clearSearch": "Очистить поиск",
"remixImage": {
"desc": "Используйте все параметры, кроме сида из текущего изображения",
"title": "Ремикс изображения"
}
"clearSearch": "Очистить поиск"
},
"modelManager": {
"modelManager": "Менеджер моделей",
@@ -525,8 +512,7 @@
"modelType": "Тип модели",
"customConfigFileLocation": "Расположение пользовательского файла конфигурации",
"vaePrecision": "Точность VAE",
"noModelSelected": "Модель не выбрана",
"configFile": "Файл конфигурации"
"noModelSelected": "Модель не выбрана"
},
"parameters": {
"images": "Изображения",
@@ -597,8 +583,8 @@
"copyImage": "Скопировать изображение",
"showPreview": "Показать предпросмотр",
"noiseSettings": "Шум",
"seamlessXAxis": "Бесшовность по оси X",
"seamlessYAxis": "Бесшовность по оси Y",
"seamlessXAxis": "Горизонтальная",
"seamlessYAxis": "Вертикальная",
"scheduler": "Планировщик",
"boundingBoxWidth": "Ширина ограничивающей рамки",
"boundingBoxHeight": "Высота ограничивающей рамки",
@@ -626,7 +612,7 @@
"noControlImageForControlAdapter": "Адаптер контроля #{{number}} не имеет изображения",
"noModelForControlAdapter": "Не выбрана модель адаптера контроля #{{number}}.",
"unableToInvoke": "Невозможно вызвать",
"incompatibleBaseModelForControlAdapter": "Адаптер контроля №{{number}} несовместим с основной моделью.",
"incompatibleBaseModelForControlAdapter": "Модель контрольного адаптера №{{number}} недействительна для основной модели.",
"systemDisconnected": "Система отключена",
"missingNodeTemplate": "Отсутствует шаблон узла",
"readyToInvoke": "Готово к вызову",
@@ -667,10 +653,7 @@
"setToOptimalSize": "Установить оптимальный для модели размер",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (может быть слишком маленьким)",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (может быть слишком большим)",
"lockAspectRatio": "Заблокировать соотношение",
"boxBlur": "Размытие прямоугольника",
"gaussianBlur": "Размытие по Гауссу",
"remixImage": "Ремикс изображения"
"lockAspectRatio": "Заблокировать соотношение"
},
"settings": {
"models": "Модели",
@@ -804,10 +787,7 @@
"canvasSavedGallery": "Холст сохранен в галерею",
"imageUploadFailed": "Не удалось загрузить изображение",
"modelAdded": "Добавлена модель: {{modelName}}",
"problemImportingMask": "Проблема с импортом маски",
"problemDownloadingImage": "Не удается скачать изображение",
"uploadInitialImage": "Загрузить начальное изображение",
"resetInitialImage": "Сбросить начальное изображение"
"problemImportingMask": "Проблема с импортом маски"
},
"tooltip": {
"feature": {
@@ -912,8 +892,7 @@
"mode": "Режим",
"loadMore": "Загрузить больше",
"resetUI": "$t(accessibility.reset) интерфейс",
"createIssue": "Сообщить о проблеме",
"about": "Об этом"
"createIssue": "Сообщить о проблеме"
},
"ui": {
"showProgressImages": "Показывать промежуточный итог",
@@ -1138,18 +1117,7 @@
"unableToParseEdge": "Невозможно разобрать край",
"unknownInput": "Неизвестный вход: {{name}}",
"oNNXModelFieldDescription": "Поле модели ONNX.",
"imageCollection": "Коллекция изображений",
"newWorkflow": "Новый рабочий процесс",
"newWorkflowDesc": "Создать новый рабочий процесс?",
"clearWorkflow": "Очистить рабочий процесс",
"newWorkflowDesc2": "Текущий рабочий процесс имеет несохраненные изменения.",
"latentsCollection": "Коллекция латентов",
"clearWorkflowDesc": "Очистить этот рабочий процесс и создать новый?",
"clearWorkflowDesc2": "Текущий рабочий процесс имеет несохраненные измерения.",
"reorderLinearView": "Изменить порядок линейного просмотра",
"viewMode": "Использовать в линейном представлении",
"editMode": "Открыть в редакторе узлов",
"resetToDefaultValue": "Сбросить к стандартному значкнию"
"imageCollection": "Коллекция изображений"
},
"controlnet": {
"amult": "a_mult",
@@ -1230,18 +1198,7 @@
"enableIPAdapter": "Включить IP Adapter",
"maxFaces": "Макс Лица",
"mlsdDescription": "Минималистичный детектор отрезков линии",
"resizeSimple": "Изменить размер (простой)",
"megaControl": "Mega контроль",
"base": "Базовый",
"depthAnything": "Глубина всего",
"depthAnythingDescription": "Создание карты глубины с использованием метода Depth Anything",
"face": "Лицо",
"dwOpenposeDescription": "Оценка позы человека с помощью DW Openpose",
"large": "Большой",
"modelSize": "Размер модели",
"small": "Маленький",
"body": "Тело",
"hands": "Руки"
"resizeSimple": "Изменить размер (простой)"
},
"boards": {
"autoAddBoard": "Авто добавление Доски",
@@ -1324,7 +1281,7 @@
"compositingCoherenceSteps": {
"heading": "Шаги",
"paragraphs": [
"Количество шагов снижения шума, используемых при прохождении когерентности.",
null,
"То же, что и основной параметр «Шаги»."
]
},
@@ -1362,10 +1319,7 @@
]
},
"compositingCoherenceMode": {
"heading": "Режим",
"paragraphs": [
"Режим прохождения когерентности."
]
"heading": "Режим"
},
"paramSeed": {
"paragraphs": [
@@ -1399,14 +1353,16 @@
"clipSkip": {
"paragraphs": [
"Выберите, сколько слоев модели CLIP нужно пропустить.",
"Некоторые модели работают лучше с определенными настройками пропуска CLIP."
"Некоторые модели работают лучше с определенными настройками пропуска CLIP.",
"Более высокое значение обычно приводит к менее детализированному изображению."
],
"heading": "CLIP пропуск"
},
"paramModel": {
"heading": "Модель",
"paragraphs": [
"Модель, используемая для шагов шумоподавления."
"Модель, используемая для шагов шумоподавления.",
"Различные модели обычно обучаются, чтобы специализироваться на достижении определенных эстетических результатов и содержания."
]
},
"compositingCoherencePass": {
@@ -1645,7 +1601,7 @@
"openWorkflow": "Открытый рабочий процесс",
"clearWorkflowSearchFilter": "Очистить фильтр поиска рабочих процессов",
"workflowLibrary": "Библиотека",
"downloadWorkflow": "Сохранить в файл",
"downloadWorkflow": "Скачать рабочий процесс",
"noRecentWorkflows": "Нет недавних рабочих процессов",
"workflowSaved": "Рабочий процесс сохранен",
"workflowIsOpen": "Рабочий процесс открыт",
@@ -1658,12 +1614,9 @@
"deleteWorkflow": "Удалить рабочий процесс",
"workflows": "Рабочие процессы",
"noDescription": "Без описания",
"uploadWorkflow": "Загрузить из файла",
"uploadWorkflow": "Загрузить рабочий процесс",
"userWorkflows": "Мои рабочие процессы",
"newWorkflowCreated": "Создан новый рабочий процесс",
"saveWorkflowToProject": "Сохранить рабочий процесс в проект",
"workflowCleared": "Рабочий процесс очищен",
"noWorkflows": "Нет рабочих процессов"
"newWorkflowCreated": "Создан новый рабочий процесс"
},
"embedding": {
"noEmbeddingsLoaded": "встраивания не загружены",

View File

@@ -1444,14 +1444,16 @@
"clipSkip": {
"paragraphs": [
"选择要跳过 CLIP 模型多少层。",
"部分模型跳过特定数值的层时效果会更好。"
"部分模型跳过特定数值的层时效果会更好。",
"较高的数值通常会导致图像细节更少。"
],
"heading": "CLIP 跳过层"
},
"paramModel": {
"heading": "模型",
"paragraphs": [
"用于去噪过程的模型。"
"用于去噪过程的模型。",
"不同的模型一般会通过接受训练来专门产生特定的美学内容和结果。"
]
},
"paramIterations": {

View File

@@ -55,6 +55,8 @@ import { addUpscaleRequestedListener } from 'app/store/middleware/listenerMiddle
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
import type { AppDispatch, RootState } from 'app/store/store';
import { addSetDefaultSettingsListener } from './listeners/setDefaultSettings';
export const listenerMiddleware = createListenerMiddleware();
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
@@ -153,3 +155,5 @@ addUpscaleRequestedListener(startAppListening);
// Dynamic prompts
addDynamicPromptsListener(startAppListening);
addSetDefaultSettingsListener(startAppListening)

View File

@@ -0,0 +1,88 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { setDefaultSettings } from 'features/parameters/store/actions';
import { setCfgRescaleMultiplier, setCfgScale, setScheduler, setSteps, vaePrecisionChanged, vaeSelected } from 'features/parameters/store/generationSlice';
import { isParameterCFGRescaleMultiplier, isParameterCFGScale, isParameterPrecision, isParameterScheduler, isParameterSteps, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next';
import { map } from 'lodash-es';
import { modelsApi } from 'services/api/endpoints/models';
export const addSetDefaultSettingsListener = (startAppListening: AppStartListening) => {
startAppListening({
actionCreator: setDefaultSettings,
effect: async (action, { dispatch, getState }) => {
const state = getState();
const currentModel = state.generation.model;
if (!currentModel) {
return
}
const metadata = await dispatch(
modelsApi.endpoints.getModelMetadata.initiate(currentModel.key)
).unwrap();
console.log({ metadata })
if (!metadata || !metadata.default_settings) {
return;
}
const { vae, vae_precision, cfg_scale, cfg_rescale_multiplier, steps, scheduler } = metadata.default_settings
if (vae) {
// we store this as "default" within default settings
// to distinguish it from no default set
if (vae === "default") {
dispatch(vaeSelected(null))
} else {
const { data } = modelsApi.endpoints.getVaeModels.select()(state)
const vaeArray = map(data?.entities)
const validVae = vaeArray.find(model => model.key === vae)
const result = zParameterVAEModel.safeParse(validVae);
if (!result.success) {
return;
}
dispatch(vaeSelected(result.data));
}
}
if (vae_precision) {
if (isParameterPrecision(vae_precision)) {
dispatch(vaePrecisionChanged(vae_precision));
}
}
if (cfg_scale) {
if (isParameterCFGScale(cfg_scale)) {
dispatch(setCfgScale(cfg_scale));
}
}
if (cfg_rescale_multiplier) {
if (isParameterCFGRescaleMultiplier(cfg_rescale_multiplier)) {
dispatch(setCfgRescaleMultiplier(cfg_rescale_multiplier));
}
}
if (steps) {
if (isParameterSteps(steps)) {
dispatch(setSteps(steps));
}
}
if (scheduler) {
if (isParameterScheduler(scheduler)) {
dispatch(setScheduler(scheduler));
}
}
dispatch(addToast(makeToast({ title: t('toast.parameterSet', { parameter: "Default settings" }) })))
},
});
};

View File

@@ -1,4 +1,5 @@
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import type { O } from 'ts-toolbelt';
@@ -82,6 +83,8 @@ export type AppConfig = {
guidance: NumericalParameterConfig;
cfgRescaleMultiplier: NumericalParameterConfig;
img2imgStrength: NumericalParameterConfig;
scheduler?: ParameterScheduler,
vaePrecision?: ParameterPrecision
// Canvas
boundingBoxHeight: NumericalParameterConfig; // initial value comes from model
boundingBoxWidth: NumericalParameterConfig; // initial value comes from model

View File

@@ -13,46 +13,28 @@ export type Feature =
| 'compositingCoherenceEdgeSize'
| 'compositingCoherenceMinDenoise'
| 'compositingMaskAdjustments'
| 'controlNet'
| 'controlNetBeginEnd'
| 'controlNetControlMode'
| 'controlNetProcessor'
| 'controlNetResizeMode'
| 'controlNet'
| 'controlNetWeight'
| 'dynamicPrompts'
| 'dynamicPromptsMaxPrompts'
| 'dynamicPromptsSeedBehaviour'
| 'imageFit'
| 'infillMethod'
| 'lora'
| 'loraWeight'
| 'noiseUseCPU'
| 'paramAspect'
| 'paramCFGScale'
| 'paramCFGRescaleMultiplier'
| 'paramDenoisingStrength'
| 'paramHeight'
| 'paramHrf'
| 'paramIterations'
| 'paramModel'
| 'paramRatio'
| 'paramSeed'
| 'paramSteps'
| 'paramUpscaleMethod'
| 'paramVAE'
| 'paramVAEPrecision'
| 'paramWidth'
| 'patchmatchDownScaleSize'
| 'refinerModel'
| 'refinerNegativeAestheticScore'
| 'refinerPositiveAestheticScore'
| 'refinerScheduler'
| 'refinerStart'
| 'refinerSteps'
| 'refinerCfgScale'
| 'scaleBeforeProcessing'
| 'seamlessTilingXAxis'
| 'seamlessTilingYAxis';
| 'scaleBeforeProcessing';
export type PopoverData = PopoverProps & {
image?: string;
@@ -64,57 +46,21 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
paramNegativeConditioning: {
placement: 'right',
},
clipSkip: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
controlNet: {
href: 'https://support.invoke.ai/support/solutions/articles/151000105880',
},
controlNetBeginEnd: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
},
controlNetWeight: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
},
lora: {
href: 'https://support.invoke.ai/support/solutions/articles/151000159072',
},
loraWeight: {
href: 'https://support.invoke.ai/support/solutions/articles/151000159072-concepts-low-rank-adaptations-loras-',
},
compositingBlur: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
},
compositingBlurMethod: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
},
compositingCoherenceMode: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
},
compositingCoherenceSteps: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
},
compositingStrength: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158838-compositing-settings',
href: 'https://support.invoke.ai/support/solutions/articles/151000158838',
},
infillMethod: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158841-infill-and-scaling',
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
},
scaleBeforeProcessing: {
href: 'https://support.invoke.ai/support/solutions/articles/151000158841',
},
paramCFGScale: {
href: 'https://www.youtube.com/watch?v=1OeHEJrsTpI',
},
paramCFGRescaleMultiplier: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
paramDenoisingStrength: {
href: 'https://support.invoke.ai/support/solutions/articles/151000094998-image-to-image',
},
paramHrf: {
href: 'https://support.invoke.ai/support/solutions/articles/151000096700-how-can-i-get-larger-images-what-does-upscaling-do-',
},
paramIterations: {
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
},
@@ -124,10 +70,7 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
},
paramScheduler: {
placement: 'right',
href: 'https://www.youtube.com/watch?v=1OeHEJrsTpI',
},
paramSeed: {
href: 'https://support.invoke.ai/support/solutions/articles/151000096684-what-is-a-seed-how-do-i-use-it-to-recreate-the-same-image-',
href: 'https://support.invoke.ai/support/solutions/articles/151000159073',
},
paramModel: {
placement: 'right',
@@ -138,53 +81,15 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
},
controlNetControlMode: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
},
controlNetProcessor: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000105880-using-controlnet',
},
controlNetResizeMode: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000178148',
},
paramVAE: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
paramVAEPrecision: {
placement: 'right',
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
paramUpscaleMethod: {
href: 'https://support.invoke.ai/support/solutions/articles/151000096700-how-can-i-get-larger-images-what-does-upscaling-do-',
},
refinerModel: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerNegativeAestheticScore: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerPositiveAestheticScore: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerScheduler: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerStart: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerSteps: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
refinerCfgScale: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178333-using-the-refiner',
},
seamlessTilingXAxis: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
seamlessTilingYAxis: {
href: 'https://support.invoke.ai/support/solutions/articles/151000178161-advanced-settings',
},
} as const;

View File

@@ -1,6 +1,5 @@
import { CompositeRangeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterBeginEndStepPct } from 'features/controlAdapters/hooks/useControlAdapterBeginEndStepPct';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import {
@@ -62,10 +61,12 @@ export const ParamControlAdapterBeginEnd = memo(({ id }: Props) => {
}
return (
<FormControl isDisabled={!isEnabled} orientation="vertical">
<InformationalPopover feature="controlNetBeginEnd">
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
</InformationalPopover>
<FormControl
isDisabled={!isEnabled}
// feature="controlNetBeginEnd"
orientation="vertical"
>
<FormLabel>{t('controlnet.beginEndStepPercent')}</FormLabel>
<CompositeRangeSlider
aria-label={ariaLabel}
value={value}

View File

@@ -2,7 +2,6 @@ import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useControlAdapterIsEnabled } from 'features/controlAdapters/hooks/useControlAdapterIsEnabled';
import { useControlAdapterProcessorNode } from 'features/controlAdapters/hooks/useControlAdapterProcessorNode';
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
@@ -59,9 +58,7 @@ const ParamControlAdapterProcessorSelect = ({ id }: Props) => {
}
return (
<FormControl isDisabled={!isEnabled}>
<InformationalPopover feature="controlNetProcessor">
<FormLabel>{t('controlnet.processor')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('controlnet.processor')}</FormLabel>
<Combobox value={value} options={options} onChange={onChange} />
</FormControl>
);

View File

@@ -1,21 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react';
import { EmbeddingSelect } from './EmbeddingSelect';
import type { EmbeddingSelectProps } from './types';
const meta: Meta<typeof EmbeddingSelect> = {
title: 'Feature/Prompt/EmbeddingSelect',
tags: ['autodocs'],
component: EmbeddingSelect,
};
export default meta;
type Story = StoryObj<typeof EmbeddingSelect>;
const Component = (props: EmbeddingSelectProps) => {
return <EmbeddingSelect {...props}>Invoke</EmbeddingSelect>;
};
export const Default: Story = {
render: Component,
};

View File

@@ -1,67 +0,0 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Combobox, FormControl } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import type { EmbeddingSelectProps } from 'features/embedding/types';
import { t } from 'i18next';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
import type { TextualInversionModelConfig } from 'services/api/types';
const noOptionsMessage = () => t('embedding.noMatchingEmbedding');
export const EmbeddingSelect = memo(({ onSelect, onClose }: EmbeddingSelectProps) => {
const { t } = useTranslation();
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
const getIsDisabled = useCallback(
(embedding: TextualInversionModelConfig): boolean => {
const isCompatible = currentBaseModel === embedding.base;
const hasMainModel = Boolean(currentBaseModel);
return !hasMainModel || !isCompatible;
},
[currentBaseModel]
);
const { data, isLoading } = useGetTextualInversionModelsQuery();
const _onChange = useCallback(
(embedding: TextualInversionModelConfig | null) => {
if (!embedding) {
return;
}
onSelect(embedding.name);
},
[onSelect]
);
const { options, onChange } = useGroupedModelCombobox({
modelEntities: data,
getIsDisabled,
onChange: _onChange,
});
return (
<FormControl>
<Combobox
placeholder={isLoading ? t('common.loading') : t('embedding.addEmbedding')}
defaultMenuIsOpen
autoFocus
value={null}
options={options}
noOptionsMessage={noOptionsMessage}
onChange={onChange}
onMenuClose={onClose}
data-testid="add-embedding"
sx={selectStyles}
/>
</FormControl>
);
});
EmbeddingSelect.displayName = 'EmbeddingSelect';
const selectStyles: ChakraProps['sx'] = {
w: 'full',
};

View File

@@ -1,7 +1,6 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setHrfMethod } from 'features/hrf/store/hrfSlice';
import { isParameterHRFMethod } from 'features/parameters/types/parameterSchemas';
import { memo, useCallback, useMemo } from 'react';
@@ -31,9 +30,7 @@ const ParamHrfMethodSelect = () => {
return (
<FormControl>
<InformationalPopover feature="paramUpscaleMethod">
<FormLabel>{t('hrf.upscaleMethod')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('hrf.upscaleMethod')}</FormLabel>
<Combobox value={value} options={options} onChange={onChange} />
</FormControl>
);

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setHrfStrength } from 'features/hrf/store/hrfSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -26,9 +25,7 @@ const ParamHrfStrength = () => {
return (
<FormControl>
<InformationalPopover feature="paramDenoisingStrength">
<FormLabel>{`${t('parameters.denoisingStrength')}`}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.denoisingStrength')}</FormLabel>
<CompositeSlider
min={sliderMin}
max={sliderMax}

View File

@@ -1,6 +1,5 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setHrfEnabled } from 'features/hrf/store/hrfSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -19,9 +18,7 @@ const ParamHrfToggle = () => {
return (
<FormControl w="full">
<InformationalPopover feature="paramHrf">
<FormLabel flexGrow={1}>{t('hrf.enableHrf')}</FormLabel>
</InformationalPopover>
<FormLabel flexGrow={1}>{t('hrf.enableHrf')}</FormLabel>
<Switch isChecked={hrfEnabled} onChange={handleHrfEnabled} />
</FormControl>
);

View File

@@ -10,7 +10,6 @@ import {
Text,
} from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import type { LoRA } from 'features/lora/store/loraSlice';
import { loraIsEnabledChanged, loraRemoved, loraWeightChanged } from 'features/lora/store/loraSlice';
import { memo, useCallback } from 'react';
@@ -60,31 +59,29 @@ export const LoRACard = memo((props: LoRACardProps) => {
</Flex>
</Flex>
</CardHeader>
<InformationalPopover feature="loraWeight">
<CardBody>
<CompositeSlider
value={lora.weight}
onChange={handleChange}
min={-1}
max={2}
step={0.01}
marks={marks}
defaultValue={0.75}
isDisabled={!lora.isEnabled}
/>
<CompositeNumberInput
value={lora.weight}
onChange={handleChange}
min={-5}
max={5}
step={0.01}
w={20}
flexShrink={0}
defaultValue={0.75}
isDisabled={!lora.isEnabled}
/>
</CardBody>
</InformationalPopover>
<CardBody>
<CompositeSlider
value={lora.weight}
onChange={handleChange}
min={-1}
max={2}
step={0.01}
marks={marks}
defaultValue={0.75}
isDisabled={!lora.isEnabled}
/>
<CompositeNumberInput
value={lora.weight}
onChange={handleChange}
min={-5}
max={5}
step={0.01}
w={20}
flexShrink={0}
defaultValue={0.75}
isDisabled={!lora.isEnabled}
/>
</CardBody>
</Card>
);
});

View File

@@ -2,7 +2,6 @@ import type { ChakraProps } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
import { loraAdded, selectLoraSlice } from 'features/lora/store/loraSlice';
import { memo, useCallback, useMemo } from 'react';
@@ -58,9 +57,7 @@ const LoRASelect = () => {
return (
<FormControl isDisabled={!options.length}>
<InformationalPopover feature="lora">
<FormLabel>{t('models.lora')} </FormLabel>
</InformationalPopover>
<FormLabel>{t('models.lora')} </FormLabel>
<Combobox
placeholder={placeholder}
value={null}

View File

@@ -8,7 +8,7 @@ export const ModelPane = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
return (
<Box layerStyle="first" p={2} borderRadius="base" w="50%" h="full">
{selectedModelKey ? <Model /> : <ImportModels />}
{selectedModelKey ? <Model key={selectedModelKey} /> : <ImportModels />}
</Box>
);
};

View File

@@ -0,0 +1,66 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import Loading from 'common/components/Loading/Loading';
import { selectConfigSlice } from 'features/system/store/configSlice';
import { isNil } from 'lodash-es';
import { useMemo } from 'react';
import { useGetModelMetadataQuery } from 'services/api/endpoints/models';
import { DefaultSettingsForm } from './DefaultSettings/DefaultSettingsForm';
const initialStatesSelector = createMemoizedSelector(selectConfigSlice, (config) => {
const { steps, guidance, scheduler, cfgRescaleMultiplier, vaePrecision } = config.sd;
return {
initialSteps: steps.initial,
initialCfg: guidance.initial,
initialScheduler: scheduler,
initialCfgRescaleMultiplier: cfgRescaleMultiplier.initial,
initialVaePrecision: vaePrecision,
};
});
export const DefaultSettings = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data, isLoading } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
const { initialSteps, initialCfg, initialScheduler, initialCfgRescaleMultiplier, initialVaePrecision } =
useAppSelector(initialStatesSelector);
const defaultSettingsDefaults = useMemo(() => {
return {
vae: { isEnabled: !isNil(data?.default_settings?.vae), value: data?.default_settings?.vae || 'default' },
vaePrecision: {
isEnabled: !isNil(data?.default_settings?.vae_precision),
value: data?.default_settings?.vae_precision || initialVaePrecision || 'fp32',
},
scheduler: {
isEnabled: !isNil(data?.default_settings?.scheduler),
value: data?.default_settings?.scheduler || initialScheduler || 'euler',
},
steps: { isEnabled: !isNil(data?.default_settings?.steps), value: data?.default_settings?.steps || initialSteps },
cfgScale: {
isEnabled: !isNil(data?.default_settings?.cfg_scale),
value: data?.default_settings?.cfg_scale || initialCfg,
},
cfgRescaleMultiplier: {
isEnabled: !isNil(data?.default_settings?.cfg_rescale_multiplier),
value: data?.default_settings?.cfg_rescale_multiplier || initialCfgRescaleMultiplier,
},
};
}, [
data?.default_settings,
initialSteps,
initialCfg,
initialScheduler,
initialCfgRescaleMultiplier,
initialVaePrecision,
]);
if (isLoading) {
return <Loading />;
}
return <DefaultSettingsForm defaultSettingsDefaults={defaultSettingsDefaults} />;
};

View File

@@ -0,0 +1,72 @@
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultCfgRescaleMultiplierType = DefaultSettingsFormData['cfgRescaleMultiplier'];
export function DefaultCfgRescaleMultiplier(props: UseControllerProps<DefaultSettingsFormData>) {
const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.cfgRescaleMultiplier.fineStep);
const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
const onChange = useCallback(
(v: number) => {
const updatedValue = {
...(field.value as DefaultCfgRescaleMultiplierType),
value: v,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(() => {
return (field.value as DefaultCfgRescaleMultiplierType).value;
}, [field.value]);
const isDisabled = useMemo(() => {
return !(field.value as DefaultCfgRescaleMultiplierType).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramCFGRescaleMultiplier">
<FormLabel>{t('parameters.cfgRescaleMultiplier')}</FormLabel>
</InformationalPopover>
<Flex w="full" gap={1}>
<CompositeSlider
value={value}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
marks={marks}
isDisabled={isDisabled}
/>
<CompositeNumberInput
value={value}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
isDisabled={isDisabled}
/>
</Flex>
</FormControl>
);
}

View File

@@ -0,0 +1,72 @@
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultCfgType = DefaultSettingsFormData['cfgScale'];
export function DefaultCfgScale(props: UseControllerProps<DefaultSettingsFormData>) {
const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.guidance.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.guidance.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.guidance.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.guidance.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.guidance.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.guidance.fineStep);
const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
const onChange = useCallback(
(v: number) => {
const updatedValue = {
...(field.value as DefaultCfgType),
value: v,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(() => {
return (field.value as DefaultCfgType).value;
}, [field.value]);
const isDisabled = useMemo(() => {
return !(field.value as DefaultCfgType).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramCFGScale">
<FormLabel>{t('parameters.cfgScale')}</FormLabel>
</InformationalPopover>
<Flex w="full" gap={1}>
<CompositeSlider
value={value}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
marks={marks}
isDisabled={isDisabled}
/>
<CompositeNumberInput
value={value}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
isDisabled={isDisabled}
/>
</Flex>
</FormControl>
);
}

View File

@@ -0,0 +1,50 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultSchedulerType = DefaultSettingsFormData['scheduler'];
export function DefaultScheduler(props: UseControllerProps<DefaultSettingsFormData>) {
const { t } = useTranslation();
const { field } = useController(props);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isParameterScheduler(v?.value)) {
return;
}
const updatedValue = {
...(field.value as DefaultSchedulerType),
value: v.value,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(
() => SCHEDULER_OPTIONS.find((o) => o.value === (field.value as DefaultSchedulerType).value),
[field]
);
const isDisabled = useMemo(() => {
return !(field.value as DefaultSchedulerType).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramScheduler">
<FormLabel>{t('parameters.scheduler')}</FormLabel>
</InformationalPopover>
<Combobox isDisabled={isDisabled} value={value} options={SCHEDULER_OPTIONS} onChange={onChange} />
</FormControl>
);
}

View File

@@ -0,0 +1,147 @@
import { Button, Flex, Heading } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast';
import { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { IoPencil } from 'react-icons/io5';
import { useUpdateModelMetadataMutation } from 'services/api/endpoints/models';
import { DefaultCfgRescaleMultiplier } from './DefaultCfgRescaleMultiplier';
import { DefaultCfgScale } from './DefaultCfgScale';
import { DefaultScheduler } from './DefaultScheduler';
import { DefaultSteps } from './DefaultSteps';
import { DefaultVae } from './DefaultVae';
import { DefaultVaePrecision } from './DefaultVaePrecision';
import { SettingToggle } from './SettingToggle';
export interface FormField<T> {
value: T;
isEnabled: boolean;
}
export type DefaultSettingsFormData = {
vae: FormField<string>;
vaePrecision: FormField<string>;
scheduler: FormField<ParameterScheduler>;
steps: FormField<number>;
cfgScale: FormField<number>;
cfgRescaleMultiplier: FormField<number>;
};
export const DefaultSettingsForm = ({
defaultSettingsDefaults,
}: {
defaultSettingsDefaults: DefaultSettingsFormData;
}) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const [editModelMetadata, { isLoading }] = useUpdateModelMetadataMutation();
const { handleSubmit, control, formState } = useForm<DefaultSettingsFormData>({
defaultValues: defaultSettingsDefaults,
});
const onSubmit = useCallback<SubmitHandler<DefaultSettingsFormData>>(
(data) => {
if (!selectedModelKey) {
return;
}
const body = {
vae: data.vae.isEnabled ? data.vae.value : null,
vae_precision: data.vaePrecision.isEnabled ? data.vaePrecision.value : null,
cfg_scale: data.cfgScale.isEnabled ? data.cfgScale.value : null,
cfg_rescale_multiplier: data.cfgRescaleMultiplier.isEnabled ? data.cfgRescaleMultiplier.value : null,
steps: data.steps.isEnabled ? data.steps.value : null,
scheduler: data.scheduler.isEnabled ? data.scheduler.value : null,
};
editModelMetadata({
key: selectedModelKey,
body: { default_settings: body },
})
.unwrap()
.then((_) => {
dispatch(
addToast(
makeToast({
title: t('modelManager.defaultSettingsSaved'),
status: 'success',
})
)
);
})
.catch((error) => {
if (error) {
dispatch(
addToast(
makeToast({
title: `${error.data.detail} `,
status: 'error',
})
)
);
}
});
},
[selectedModelKey, dispatch, editModelMetadata, t]
);
return (
<>
<Flex gap="2" justifyContent="space-between" w="full" mb={5}>
<Heading fontSize="md">{t('modelManager.defaultSettings')}</Heading>
<Button
size="sm"
leftIcon={<IoPencil />}
colorScheme="invokeYellow"
isDisabled={!formState.isDirty}
onClick={handleSubmit(onSubmit)}
type="submit"
isLoading={isLoading}
>
{t('common.save')}
</Button>
</Flex>
<Flex flexDir="column" gap={8}>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vae" />
<DefaultVae control={control} name="vae" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="vaePrecision" />
<DefaultVaePrecision control={control} name="vaePrecision" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="scheduler" />
<DefaultScheduler control={control} name="scheduler" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="steps" />
<DefaultSteps control={control} name="steps" />
</Flex>
</Flex>
<Flex gap={8}>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgScale" />
<DefaultCfgScale control={control} name="cfgScale" />
</Flex>
<Flex gap={4} w="full">
<SettingToggle control={control} name="cfgRescaleMultiplier" />
<DefaultCfgRescaleMultiplier control={control} name="cfgRescaleMultiplier" />
</Flex>
</Flex>
</Flex>
</>
);
};

View File

@@ -0,0 +1,72 @@
import { CompositeNumberInput, CompositeSlider, Flex,FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useCallback,useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultSteps = DefaultSettingsFormData['steps'];
export function DefaultSteps(props: UseControllerProps<DefaultSettingsFormData>) {
const { field } = useController(props);
const sliderMin = useAppSelector((s) => s.config.sd.steps.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.steps.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.steps.numberInputMin);
const numberInputMax = useAppSelector((s) => s.config.sd.steps.numberInputMax);
const coarseStep = useAppSelector((s) => s.config.sd.steps.coarseStep);
const fineStep = useAppSelector((s) => s.config.sd.steps.fineStep);
const { t } = useTranslation();
const marks = useMemo(() => [sliderMin, Math.floor(sliderMax / 2), sliderMax], [sliderMax, sliderMin]);
const onChange = useCallback(
(v: number) => {
const updatedValue = {
...(field.value as DefaultSteps),
value: v,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(() => {
return (field.value as DefaultSteps).value;
}, [field.value]);
const isDisabled = useMemo(() => {
return !(field.value as DefaultSteps).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramSteps">
<FormLabel>{t('parameters.steps')}</FormLabel>
</InformationalPopover>
<Flex w="full" gap={1}>
<CompositeSlider
value={value}
min={sliderMin}
max={sliderMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
marks={marks}
isDisabled={isDisabled}
/>
<CompositeNumberInput
value={value}
min={numberInputMin}
max={numberInputMax}
step={coarseStep}
fineStep={fineStep}
onChange={onChange}
isDisabled={isDisabled}
/>
</Flex>
</FormControl>
);
}

View File

@@ -0,0 +1,65 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { map } from 'lodash-es';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useGetModelConfigQuery, useGetVaeModelsQuery } from 'services/api/endpoints/models';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
type DefaultVaeType = DefaultSettingsFormData['vae'];
export function DefaultVae(props: UseControllerProps<DefaultSettingsFormData>) {
const { t } = useTranslation();
const { field } = useController(props);
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data: modelData } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
const { compatibleOptions } = useGetVaeModelsQuery(undefined, {
selectFromResult: ({ data }) => {
const modelArray = map(data?.entities);
const compatibleOptions = modelArray
.filter((vae) => vae.base === modelData?.base)
.map((vae) => ({ label: vae.name, value: vae.key }));
const defaultOption = { label: 'Default VAE', value: 'default' };
return { compatibleOptions: [defaultOption, ...compatibleOptions] };
},
});
const onChange = useCallback<ComboboxOnChange>(
(v) => {
const newValue = !v?.value ? 'default' : v.value;
const updatedValue = {
...(field.value as DefaultVaeType),
value: newValue,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(() => {
return compatibleOptions.find((vae) => vae.value === (field.value as DefaultVaeType).value);
}, [compatibleOptions, field.value]);
const isDisabled = useMemo(() => {
return !(field.value as DefaultVaeType).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramVAE">
<FormLabel>{t('modelManager.vae')}</FormLabel>
</InformationalPopover>
<Combobox isDisabled={isDisabled} value={value} options={compatibleOptions} onChange={onChange} />
</FormControl>
);
}

View File

@@ -0,0 +1,51 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { isParameterPrecision } from 'features/parameters/types/parameterSchemas';
import { useCallback, useMemo } from 'react';
import type {UseControllerProps } from 'react-hook-form';
import { useController } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { DefaultSettingsFormData } from './DefaultSettingsForm';
const options = [
{ label: 'FP16', value: 'fp16' },
{ label: 'FP32', value: 'fp32' },
];
type DefaultVaePrecisionType = DefaultSettingsFormData['vaePrecision'];
export function DefaultVaePrecision(props: UseControllerProps<DefaultSettingsFormData>) {
const { t } = useTranslation();
const { field } = useController(props);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isParameterPrecision(v?.value)) {
return;
}
const updatedValue = {
...(field.value as DefaultVaePrecisionType),
value: v.value,
};
field.onChange(updatedValue);
},
[field]
);
const value = useMemo(() => options.find((o) => o.value === (field.value as DefaultVaePrecisionType).value), [field]);
const isDisabled = useMemo(() => {
return !(field.value as DefaultVaePrecisionType).isEnabled;
}, [field.value]);
return (
<FormControl flexDir="column" gap={1} alignItems="flex-start">
<InformationalPopover feature="paramVAEPrecision">
<FormLabel>{t('modelManager.vaePrecision')}</FormLabel>
</InformationalPopover>
<Combobox isDisabled={isDisabled} value={value} options={options} onChange={onChange} />
</FormControl>
);
}

View File

@@ -0,0 +1,32 @@
import { Switch } from '@invoke-ai/ui-library';
import type { ChangeEvent } from 'react';
import { useCallback , useMemo } from 'react';
import type { UseControllerProps} from 'react-hook-form';
import { useController } from 'react-hook-form';
import type { DefaultSettingsFormData, FormField } from './DefaultSettingsForm';
interface Props<T> extends UseControllerProps<DefaultSettingsFormData> {
name: keyof DefaultSettingsFormData;
}
export function SettingToggle<T>(props: Props<T>) {
const { field } = useController(props);
const value = useMemo(() => {
return !!(field.value as FormField<T>).isEnabled;
}, [field.value]);
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const updatedValue: FormField<T> = {
...(field.value as FormField<T>),
isEnabled: e.target.checked,
};
field.onChange(updatedValue);
},
[field]
);
return <Switch isChecked={value} onChange={onChange} />;
}

View File

@@ -0,0 +1,21 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { useGetModelMetadataQuery } from 'services/api/endpoints/models';
import { TriggerPhrases } from './TriggerPhrases';
export const ModelMetadata = () => {
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
return (
<Flex flexDir="column" height="full" gap="3">
<Box layerStyle="second" borderRadius="base" p={3}>
<TriggerPhrases />
</Box>
<DataViewer label="metadata" data={metadata || {}} />
</Flex>
);
};

View File

@@ -0,0 +1,106 @@
import {
Button,
Flex,
FormControl,
FormErrorMessage,
Input,
Tag,
TagCloseButton,
TagLabel,
} from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import { ModelListHeader } from 'features/modelManagerV2/subpanels/ModelManagerPanel/ModelListHeader';
import type { ChangeEvent } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetModelMetadataQuery, useUpdateModelMetadataMutation } from 'services/api/endpoints/models';
export const TriggerPhrases = () => {
const { t } = useTranslation();
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
const [phrase, setPhrase] = useState('');
const [editModelMetadata, { isLoading }] = useUpdateModelMetadataMutation();
const handlePhraseChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setPhrase(e.target.value);
}, []);
const triggerPhrases = useMemo(() => {
return metadata?.trigger_phrases || [];
}, [metadata?.trigger_phrases]);
const errors = useMemo(() => {
const errors = [];
if (phrase.length && triggerPhrases.includes(phrase)) {
errors.push('Phrase is already in list');
}
return errors;
}, [phrase, triggerPhrases]);
const addTriggerPhrase = useCallback(async () => {
if (!selectedModelKey) {
return;
}
if (!phrase.length || triggerPhrases.includes(phrase)) {
return;
}
await editModelMetadata({
key: selectedModelKey,
body: { trigger_phrases: [...triggerPhrases, phrase] },
}).unwrap();
setPhrase('');
}, [editModelMetadata, selectedModelKey, phrase, triggerPhrases]);
const removeTriggerPhrase = useCallback(
async (phraseToRemove: string) => {
if (!selectedModelKey) {
return;
}
const filteredPhrases = triggerPhrases.filter((p) => p !== phraseToRemove);
await editModelMetadata({ key: selectedModelKey, body: { trigger_phrases: filteredPhrases } }).unwrap();
},
[editModelMetadata, selectedModelKey, triggerPhrases]
);
return (
<Flex flexDir="column" w="full" gap="5">
<ModelListHeader title={t('modelManager.triggerPhrases')} />
<form>
<FormControl w="full" isInvalid={Boolean(errors.length)}>
<Flex flexDir="column" w="full">
<Flex gap="3" alignItems="center" w="full">
<Input value={phrase} onChange={handlePhraseChange} placeholder={t('modelManager.typePhraseHere')} />
<Button
type="submit"
onClick={addTriggerPhrase}
isDisabled={Boolean(errors.length)}
isLoading={isLoading}
>
{t('common.add')}
</Button>
</Flex>
{!!errors.length && errors.map((error) => <FormErrorMessage key={error}>{error}</FormErrorMessage>)}
</Flex>
</FormControl>
</form>
<Flex gap="4" flexWrap="wrap" mt="3" mb="3">
{triggerPhrases.map((phrase, index) => (
<Tag size="md" key={index}>
<TagLabel>{phrase}</TagLabel>
<TagCloseButton onClick={removeTriggerPhrase.bind(null, phrase)} isDisabled={isLoading} />
</Tag>
))}
</Flex>
</Flex>
);
};

View File

@@ -1,9 +1,58 @@
import { Box, Flex, Heading, Tab, TabList, TabPanel, TabPanels, Tabs, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import { useTranslation } from 'react-i18next';
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
import { ModelMetadata } from './Metadata/ModelMetadata';
import { ModelAttrView } from './ModelAttrView';
import { ModelEdit } from './ModelEdit';
import { ModelView } from './ModelView';
export const Model = () => {
const { t } = useTranslation();
const selectedModelMode = useAppSelector((s) => s.modelmanagerV2.selectedModelMode);
return selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />;
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
if (isLoading) {
return <Text>{t('common.loading')}</Text>;
}
if (!data) {
return <Text>{t('common.somethingWentWrong')}</Text>;
}
return (
<>
<Flex flexDir="column" gap={1} p={2}>
<Heading as="h2" fontSize="lg">
{data.name}
</Heading>
{data.source && (
<Text variant="subtext">
{t('modelManager.source')}: {data?.source}
</Text>
)}
<Box mt="4">
<ModelAttrView label="Description" value={data.description} />
</Box>
</Flex>
<Tabs mt="4" h="100%">
<TabList>
<Tab>{t('modelManager.settings')}</Tab>
<Tab>{t('modelManager.metadata')}</Tab>
</TabList>
<TabPanels h="100%">
<TabPanel>{selectedModelMode === 'view' ? <ModelView /> : <ModelEdit />}</TabPanel>
<TabPanel h="full">
<ModelMetadata />
</TabPanel>
</TabPanels>
</Tabs>
</>
);
};

View File

@@ -1,12 +1,11 @@
import { Box, Button, Flex, Heading, Text } from '@invoke-ai/ui-library';
import { Box, Button, Flex, Text } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
import { setSelectedModelMode } from 'features/modelManagerV2/store/modelManagerV2Slice';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { IoPencil } from 'react-icons/io5';
import { useGetModelConfigQuery, useGetModelMetadataQuery } from 'services/api/endpoints/models';
import { useGetModelConfigQuery } from 'services/api/endpoints/models';
import type {
CheckpointModelConfig,
ControlNetModelConfig,
@@ -18,6 +17,7 @@ import type {
VAEModelConfig,
} from 'services/api/types';
import { DefaultSettings } from './DefaultSettings';
import { ModelAttrView } from './ModelAttrView';
import { ModelConvert } from './ModelConvert';
@@ -26,7 +26,6 @@ export const ModelView = () => {
const dispatch = useAppDispatch();
const selectedModelKey = useAppSelector((s) => s.modelmanagerV2.selectedModelKey);
const { data, isLoading } = useGetModelConfigQuery(selectedModelKey ?? skipToken);
const { data: metadata } = useGetModelMetadataQuery(selectedModelKey ?? skipToken);
const modelData = useMemo(() => {
if (!data) {
@@ -73,85 +72,56 @@ export const ModelView = () => {
return <Text>{t('common.somethingWentWrong')}</Text>;
}
return (
<Flex flexDir="column" h="full">
<Flex w="full" justifyContent="space-between">
<Flex flexDir="column" gap={1} p={2}>
<Heading as="h2" fontSize="lg">
{modelData.name}
</Heading>
{modelData.source && (
<Text variant="subtext">
{t('modelManager.source')}: {modelData.source}
</Text>
)}
</Flex>
<Flex gap={2}>
<Flex flexDir="column" h="full" gap="2">
<Box layerStyle="second" borderRadius="base" p={3}>
<Flex gap="2" justifyContent="flex-end" w="full">
<Button size="sm" leftIcon={<IoPencil />} colorScheme="invokeYellow" onClick={handleEditModel}>
{t('modelManager.edit')}
</Button>
{modelData.type === 'main' && modelData.format === 'checkpoint' && <ModelConvert model={modelData} />}
</Flex>
</Flex>
<Flex flexDir="column" p={2} gap={3}>
<Flex>
<ModelAttrView label="Description" value={modelData.description} />
</Flex>
<Heading as="h3" fontSize="md" mt="4">
{t('modelManager.modelSettings')}
</Heading>
<Box layerStyle="second" borderRadius="base" p={3}>
<Flex flexDir="column" gap={3}>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.baseModel')} value={modelData.base} />
<ModelAttrView label={t('modelManager.modelType')} value={modelData.type} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('common.format')} value={modelData.format} />
<ModelAttrView label={t('modelManager.path')} value={modelData.path} />
</Flex>
{modelData.type === 'main' && (
<>
<Flex gap={2}>
{modelData.format === 'diffusers' && (
<ModelAttrView label={t('modelManager.repoVariant')} value={modelData.repo_variant} />
)}
{modelData.format === 'checkpoint' && (
<ModelAttrView label={t('modelManager.pathToConfig')} value={modelData.config} />
)}
<ModelAttrView label={t('modelManager.variant')} value={modelData.variant} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.predictionType')} value={modelData.prediction_type} />
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${modelData.upcast_attention}`} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.ztsnrTraining')} value={`${modelData.ztsnr_training}`} />
<ModelAttrView label={t('modelManager.vae')} value={modelData.vae} />
</Flex>
</>
)}
{modelData.type === 'ip_adapter' && (
<Flex flexDir="column" gap={3}>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.baseModel')} value={modelData.base} />
<ModelAttrView label={t('modelManager.modelType')} value={modelData.type} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('common.format')} value={modelData.format} />
<ModelAttrView label={t('modelManager.path')} value={modelData.path} />
</Flex>
{modelData.type === 'main' && (
<>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={modelData.image_encoder_model_id} />
</Flex>
)}
</Flex>
</Box>
</Flex>
{modelData.format === 'diffusers' && (
<ModelAttrView label={t('modelManager.repoVariant')} value={modelData.repo_variant} />
)}
{modelData.format === 'checkpoint' && (
<ModelAttrView label={t('modelManager.pathToConfig')} value={modelData.config} />
)}
{metadata && (
<>
<Heading as="h3" fontSize="md" mt="4">
{t('modelManager.modelMetadata')}
</Heading>
<Flex h="full" w="full" p={2}>
<DataViewer label="metadata" data={metadata} />
</Flex>
</>
)}
<ModelAttrView label={t('modelManager.variant')} value={modelData.variant} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.predictionType')} value={modelData.prediction_type} />
<ModelAttrView label={t('modelManager.upcastAttention')} value={`${modelData.upcast_attention}`} />
</Flex>
<Flex gap={2}>
<ModelAttrView label={t('modelManager.ztsnrTraining')} value={`${modelData.ztsnr_training}`} />
<ModelAttrView label={t('modelManager.vae')} value={modelData.vae} />
</Flex>
</>
)}
{modelData.type === 'ip_adapter' && (
<Flex gap={2}>
<ModelAttrView label={t('modelManager.imageEncoderModelId')} value={modelData.image_encoder_model_id} />
</Flex>
)}
</Flex>
</Box>
<Box layerStyle="second" borderRadius="base" p={3}>
<DefaultSettings />
</Box>
</Flex>
);
};

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setInfillPatchmatchDownscaleSize } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -28,9 +27,7 @@ const ParamInfillPatchmatchDownscaleSize = () => {
return (
<FormControl isDisabled={infillMethod !== 'patchmatch'}>
<InformationalPopover feature="patchmatchDownScaleSize">
<FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.patchmatchDownScaleSize')}</FormLabel>
<CompositeSlider
min={sliderMin}
max={sliderMax}

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react';
@@ -28,9 +27,7 @@ export const ParamHeight = memo(() => {
return (
<FormControl>
<InformationalPopover feature="paramHeight">
<FormLabel>{t('parameters.height')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.height')}</FormLabel>
<CompositeSlider
value={ctx.height}
defaultValue={optimalDimension}

View File

@@ -1,10 +1,10 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { setNegativePrompt } from 'features/parameters/store/generationSlice';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@@ -19,19 +19,14 @@ export const ParamNegativePrompt = memo(() => {
},
[dispatch]
);
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt,
textareaRef,
onChange: _onChange,
});
return (
<EmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={onSelectEmbedding}
width={textareaRef.current?.clientWidth}
>
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
<Box pos="relative">
<Textarea
id="negativePrompt"
@@ -45,10 +40,10 @@ export const ParamNegativePrompt = memo(() => {
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
</PromptOverlayButtonWrapper>
</Box>
</EmbeddingPopover>
</PromptPopover>
);
});

View File

@@ -1,11 +1,11 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { ShowDynamicPromptsPreviewButton } from 'features/dynamicPrompts/components/ShowDynamicPromptsPreviewButton';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { setPositivePrompt } from 'features/parameters/store/generationSlice';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { SDXLConcatButton } from 'features/sdxl/components/SDXLPrompts/SDXLConcatButton';
import { memo, useCallback, useRef } from 'react';
import type { HotkeyCallback } from 'react-hotkeys-hook';
@@ -25,7 +25,7 @@ export const ParamPositivePrompt = memo(() => {
},
[dispatch]
);
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
prompt,
textareaRef: textareaRef,
onChange: handleChange,
@@ -42,12 +42,7 @@ export const ParamPositivePrompt = memo(() => {
useHotkeys('alt+a', focus, []);
return (
<EmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={onSelectEmbedding}
width={textareaRef.current?.clientWidth}
>
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
<Box pos="relative">
<Textarea
id="prompt"
@@ -61,12 +56,12 @@ export const ParamPositivePrompt = memo(() => {
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
{baseModel === 'sdxl' && <SDXLConcatButton />}
<ShowDynamicPromptsPreviewButton />
</PromptOverlayButtonWrapper>
</Box>
</EmbeddingPopover>
</PromptPopover>
);
});

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react';
@@ -28,9 +27,7 @@ export const ParamWidth = memo(() => {
return (
<FormControl>
<InformationalPopover feature="paramWidth">
<FormLabel>{t('parameters.width')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.width')}</FormLabel>
<CompositeSlider
value={ctx.width}
onChange={onChange}

View File

@@ -1,7 +1,6 @@
import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import type { SingleValue } from 'chakra-react-select';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/ImageSize/constants';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { isAspectRatioID } from 'features/parameters/components/ImageSize/types';
@@ -29,9 +28,7 @@ export const AspectRatioSelect = memo(() => {
return (
<FormControl>
<InformationalPopover feature="paramAspect">
<FormLabel>{t('parameters.aspect')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.aspect')}</FormLabel>
<Combobox value={value} onChange={onChange} options={ASPECT_RATIO_OPTIONS} sx={selectStyles} />
</FormControl>
);

View File

@@ -1,7 +1,6 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import type { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setShouldFitToWidthHeight } from 'features/parameters/store/generationSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -23,9 +22,7 @@ const ImageToImageFit = () => {
return (
<FormControl w="full">
<InformationalPopover feature="imageFit">
<FormLabel flexGrow={1}>{t('parameters.imageFit')}</FormLabel>
</InformationalPopover>
<FormLabel flexGrow={1}>{t('parameters.imageFit')}</FormLabel>
<Switch isChecked={shouldFitToWidthHeight} onChange={handleChangeFit} />
</FormControl>
);

View File

@@ -2,7 +2,6 @@ import { CustomSelect, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useModelCustomSelect } from 'common/hooks/useModelCustomSelect';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { modelSelected } from 'features/parameters/store/actions';
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react';
@@ -38,9 +37,7 @@ const ParamMainModelSelect = () => {
return (
<FormControl isDisabled={!items.length} isInvalid={!selectedItem || !items.length}>
<InformationalPopover feature="paramModel">
<FormLabel>{t('modelManager.model')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('modelManager.model')}</FormLabel>
<CustomSelect selectedItem={selectedItem} placeholder={placeholder} items={items} onChange={onChange} />
</FormControl>
);

View File

@@ -0,0 +1,28 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setDefaultSettings } from 'features/parameters/store/actions';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { RiSparklingFill } from 'react-icons/ri';
export const UseDefaultSettingsButton = () => {
const model = useAppSelector((s) => s.generation.model);
const { t } = useTranslation();
const dispatch = useAppDispatch();
const handleClickDefaultSettings = useCallback(() => {
dispatch(setDefaultSettings());
}, [dispatch]);
return (
<IconButton
icon={<RiSparklingFill />}
tooltip={t('modelManager.useDefaultSettings')}
aria-label={t('modelManager.useDefaultSettings')}
isDisabled={!model}
onClick={handleClickDefaultSettings}
size="sm"
variant="ghost"
/>
);
};

View File

@@ -1,6 +1,5 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setSeamlessXAxis } from 'features/parameters/store/generationSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -21,9 +20,7 @@ const ParamSeamlessXAxis = () => {
return (
<FormControl>
<InformationalPopover feature="seamlessTilingXAxis">
<FormLabel>{t('parameters.seamlessXAxis')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.seamlessXAxis')}</FormLabel>
<Switch isChecked={seamlessXAxis} onChange={handleChange} />
</FormControl>
);

View File

@@ -1,6 +1,5 @@
import { FormControl, FormLabel, Switch } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setSeamlessYAxis } from 'features/parameters/store/generationSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -19,9 +18,7 @@ const ParamSeamlessYAxis = () => {
return (
<FormControl>
<InformationalPopover feature="seamlessTilingYAxis">
<FormLabel>{t('parameters.seamlessYAxis')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('parameters.seamlessYAxis')}</FormLabel>
<Switch isChecked={seamlessYAxis} onChange={handleChange} />
</FormControl>
);

View File

@@ -5,3 +5,5 @@ import type { ImageDTO } from 'services/api/types';
export const initialImageSelected = createAction<ImageDTO | undefined>('generation/initialImageSelected');
export const modelSelected = createAction<ParameterModel>('generation/modelSelected');
export const setDefaultSettings = createAction('generation/setDefaultSettings');

View File

@@ -230,6 +230,12 @@ export const generationSlice = createSlice({
state.height = optimalDimension;
}
}
if (action.payload.sd?.scheduler) {
state.scheduler = action.payload.sd.scheduler;
}
if (action.payload.sd?.vaePrecision) {
state.vaePrecision = action.payload.sd.vaePrecision;
}
});
// TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling

View File

@@ -8,15 +8,15 @@ type Props = {
onOpen: () => void;
};
export const AddEmbeddingButton = memo((props: Props) => {
export const AddPromptTriggerButton = memo((props: Props) => {
const { onOpen, isOpen } = props;
const { t } = useTranslation();
return (
<Tooltip label={t('embedding.addEmbedding')}>
<Tooltip label={t('prompt.addPromptTrigger')}>
<IconButton
variant="promptOverlay"
isDisabled={isOpen}
aria-label={t('embedding.addEmbedding')}
aria-label={t('prompt.addPromptTrigger')}
icon={<PiCodeBold />}
onClick={onOpen}
/>
@@ -24,4 +24,4 @@ export const AddEmbeddingButton = memo((props: Props) => {
);
});
AddEmbeddingButton.displayName = 'AddEmbeddingButton';
AddPromptTriggerButton.displayName = 'AddPromptTriggerButton';

View File

@@ -1,9 +1,9 @@
import { Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
import { EmbeddingSelect } from 'features/embedding/EmbeddingSelect';
import type { EmbeddingPopoverProps } from 'features/embedding/types';
import { PromptTriggerSelect } from 'features/prompt/PromptTriggerSelect';
import type { PromptPopoverProps } from 'features/prompt/types';
import { memo } from 'react';
export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
export const PromptPopover = memo((props: PromptPopoverProps) => {
const { onSelect, isOpen, onClose, width, children } = props;
return (
@@ -14,7 +14,7 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
openDelay={0}
closeDelay={0}
closeOnBlur={true}
returnFocusOnClose={true}
returnFocusOnClose={false}
isLazy
>
<PopoverAnchor>{children}</PopoverAnchor>
@@ -27,11 +27,11 @@ export const EmbeddingPopover = memo((props: EmbeddingPopoverProps) => {
borderStyle="solid"
>
<PopoverBody p={0} width={`calc(${width}px - 0.25rem)`}>
<EmbeddingSelect onClose={onClose} onSelect={onSelect} />
<PromptTriggerSelect onClose={onClose} onSelect={onSelect} />
</PopoverBody>
</PopoverContent>
</Popover>
);
});
EmbeddingPopover.displayName = 'EmbeddingPopover';
PromptPopover.displayName = 'PromptPopover';

View File

@@ -0,0 +1,21 @@
import type { Meta, StoryObj } from '@storybook/react';
import { PromptTriggerSelect } from './PromptTriggerSelect';
import type { PromptTriggerSelectProps } from './types';
const meta: Meta<typeof PromptTriggerSelect> = {
title: 'Feature/Prompt/PromptTriggerSelect',
tags: ['autodocs'],
component: PromptTriggerSelect,
};
export default meta;
type Story = StoryObj<typeof PromptTriggerSelect>;
const Component = (props: PromptTriggerSelectProps) => {
return <PromptTriggerSelect {...props}>Invoke</PromptTriggerSelect>;
};
export const Default: Story = {
render: Component,
};

View File

@@ -0,0 +1,86 @@
import type { ChakraProps, ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl } from '@invoke-ai/ui-library';
import { skipToken } from '@reduxjs/toolkit/query';
import { useAppSelector } from 'app/store/storeHooks';
import type { PromptTriggerSelectProps } from 'features/prompt/types';
import { t } from 'i18next';
import { map } from 'lodash-es';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useGetModelMetadataQuery, useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
const noOptionsMessage = () => t('prompt.noMatchingTriggers');
export const PromptTriggerSelect = memo(({ onSelect, onClose }: PromptTriggerSelectProps) => {
const { t } = useTranslation();
const currentBaseModel = useAppSelector((s) => s.generation.model?.base);
const currentModelKey = useAppSelector((s) => s.generation.model?.key);
const { data, isLoading } = useGetTextualInversionModelsQuery();
const { data: metadata } = useGetModelMetadataQuery(currentModelKey ?? skipToken);
const _onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!v) {
onSelect('');
return;
}
onSelect(v.value);
},
[onSelect]
);
const embeddingOptions = useMemo(() => {
if (!data) {
return [];
}
const compatibleEmbeddingsArray = map(data.entities).filter((model) => model.base === currentBaseModel);
return [
{
label: t('prompt.compatibleEmbeddings'),
options: compatibleEmbeddingsArray.map((model) => ({ label: model.name, value: `<${model.name}>` })),
},
];
}, [data, currentBaseModel, t]);
const options = useMemo(() => {
if (!metadata || !metadata.trigger_phrases) {
return [...embeddingOptions];
}
const metadataOptions = [
{
label: t('modelManager.triggerPhrases'),
options: metadata.trigger_phrases.map((phrase) => ({ label: phrase, value: phrase })),
},
];
return [...metadataOptions, ...embeddingOptions];
}, [embeddingOptions, metadata, t]);
return (
<FormControl>
<Combobox
placeholder={isLoading ? t('common.loading') : t('prompt.addPromptTrigger')}
defaultMenuIsOpen
autoFocus
value={null}
options={options}
noOptionsMessage={noOptionsMessage}
onChange={_onChange}
onMenuClose={onClose}
data-testid="add-prompt-trigger"
sx={selectStyles}
/>
</FormControl>
);
});
PromptTriggerSelect.displayName = 'PromptTriggerSelect';
const selectStyles: ChakraProps['sx'] = {
w: 'full',
};

View File

@@ -1,12 +1,12 @@
import type { PropsWithChildren } from 'react';
export type EmbeddingSelectProps = {
export type PromptTriggerSelectProps = {
onSelect: (v: string) => void;
onClose: () => void;
};
export type EmbeddingPopoverProps = PropsWithChildren &
EmbeddingSelectProps & {
export type PromptPopoverProps = PropsWithChildren &
PromptTriggerSelectProps & {
isOpen: boolean;
width?: number | string;
};

View File

@@ -4,13 +4,13 @@ import type { ChangeEventHandler, KeyboardEventHandler, RefObject } from 'react'
import { useCallback } from 'react';
import { flushSync } from 'react-dom';
type UseInsertEmbeddingArg = {
type UseInsertTriggerArg = {
prompt: string;
textareaRef: RefObject<HTMLTextAreaElement>;
onChange: (v: string) => void;
};
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertEmbeddingArg) => {
export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInsertTriggerArg) => {
const { isOpen, onClose, onOpen } = useDisclosure();
const onChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
@@ -20,13 +20,13 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
[_onChange]
);
const insertEmbedding = useCallback(
const insertTrigger = useCallback(
(v: string) => {
if (!textareaRef.current) {
return;
}
// this is where we insert the TI trigger
// this is where we insert the trigger
const caret = textareaRef.current.selectionStart;
if (isNil(caret)) {
@@ -35,13 +35,9 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
let newPrompt = prompt.slice(0, caret);
if (newPrompt[newPrompt.length - 1] !== '<') {
newPrompt += '<';
}
newPrompt += `${v}`;
newPrompt += `${v}>`;
// we insert the cursor after the `>`
// we insert the cursor after the end of trigger
const finalCaretPos = newPrompt.length;
newPrompt += prompt.slice(caret);
@@ -51,7 +47,7 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
_onChange(newPrompt);
});
// set the caret position to just after the TI trigger
// set the cursor position to just after the trigger
textareaRef.current.selectionStart = finalCaretPos;
textareaRef.current.selectionEnd = finalCaretPos;
},
@@ -62,17 +58,17 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
textareaRef.current?.focus();
}, [textareaRef]);
const handleClose = useCallback(() => {
const handleClosePopover = useCallback(() => {
onClose();
onFocus();
}, [onFocus, onClose]);
const onSelectEmbedding = useCallback(
const onSelect = useCallback(
(v: string) => {
insertEmbedding(v);
handleClose();
insertTrigger(v);
handleClosePopover();
},
[handleClose, insertEmbedding]
[handleClosePopover, insertTrigger]
);
const onKeyDown: KeyboardEventHandler<HTMLTextAreaElement> = useCallback(
@@ -90,7 +86,7 @@ export const usePrompt = ({ prompt, textareaRef, onChange: _onChange }: UseInser
isOpen,
onClose,
onOpen,
onSelectEmbedding,
onSelect,
onKeyDown,
onFocus,
};

View File

@@ -1,9 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { setNegativeStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
@@ -20,7 +20,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
},
[dispatch]
);
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown, onFocus } = usePrompt({
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown, onFocus } = usePrompt({
prompt,
textareaRef: textareaRef,
onChange: handleChange,
@@ -29,12 +29,7 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
useHotkeys('alt+a', onFocus, []);
return (
<EmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={onSelectEmbedding}
width={textareaRef.current?.clientWidth}
>
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
<Box pos="relative">
<Textarea
id="prompt"
@@ -48,10 +43,10 @@ export const ParamSDXLNegativeStylePrompt = memo(() => {
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
</PromptOverlayButtonWrapper>
</Box>
</EmbeddingPopover>
</PromptPopover>
);
});

View File

@@ -1,9 +1,9 @@
import { Box, Textarea } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { AddEmbeddingButton } from 'features/embedding/AddEmbeddingButton';
import { EmbeddingPopover } from 'features/embedding/EmbeddingPopover';
import { usePrompt } from 'features/embedding/usePrompt';
import { PromptOverlayButtonWrapper } from 'features/parameters/components/Prompts/PromptOverlayButtonWrapper';
import { AddPromptTriggerButton } from 'features/prompt/AddPromptTriggerButton';
import { PromptPopover } from 'features/prompt/PromptPopover';
import { usePrompt } from 'features/prompt/usePrompt';
import { setPositiveStylePromptSDXL } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@@ -19,19 +19,14 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
},
[dispatch]
);
const { onChange, isOpen, onClose, onOpen, onSelectEmbedding, onKeyDown } = usePrompt({
const { onChange, isOpen, onClose, onOpen, onSelect, onKeyDown } = usePrompt({
prompt,
textareaRef: textareaRef,
onChange: handleChange,
});
return (
<EmbeddingPopover
isOpen={isOpen}
onClose={onClose}
onSelect={onSelectEmbedding}
width={textareaRef.current?.clientWidth}
>
<PromptPopover isOpen={isOpen} onClose={onClose} onSelect={onSelect} width={textareaRef.current?.clientWidth}>
<Box pos="relative">
<Textarea
id="prompt"
@@ -45,10 +40,10 @@ export const ParamSDXLPositiveStylePrompt = memo(() => {
variant="darkFilled"
/>
<PromptOverlayButtonWrapper>
<AddEmbeddingButton isOpen={isOpen} onOpen={onOpen} />
<AddPromptTriggerButton isOpen={isOpen} onOpen={onOpen} />
</PromptOverlayButtonWrapper>
</Box>
</EmbeddingPopover>
</PromptPopover>
);
});

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setRefinerCFGScale } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -22,9 +21,7 @@ const ParamSDXLRefinerCFGScale = () => {
return (
<FormControl>
<InformationalPopover feature="refinerCfgScale">
<FormLabel>{t('sdxl.cfgScale')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.cfgScale')}</FormLabel>
<CompositeSlider
value={refinerCFGScale}
defaultValue={initial}

View File

@@ -1,7 +1,6 @@
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { useModelCombobox } from 'common/hooks/useModelCombobox';
import { getModelKeyAndBase } from 'features/metadata/util/modelFetchingHelpers';
import { refinerModelChanged, selectSdxlSlice } from 'features/sdxl/store/sdxlSlice';
@@ -39,9 +38,7 @@ const ParamSDXLRefinerModelSelect = () => {
});
return (
<FormControl isDisabled={!options.length} isInvalid={!options.length}>
<InformationalPopover feature="refinerModel">
<FormLabel>{t('sdxl.refinermodel')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.refinermodel')}</FormLabel>
<Combobox
value={value}
placeholder={placeholder}

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setRefinerNegativeAestheticScore } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -15,9 +14,7 @@ const ParamSDXLRefinerNegativeAestheticScore = () => {
return (
<FormControl>
<InformationalPopover feature="refinerNegativeAestheticScore">
<FormLabel>{t('sdxl.negAestheticScore')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.negAestheticScore')}</FormLabel>
<CompositeSlider
min={1}
max={10}

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setRefinerPositiveAestheticScore } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -14,9 +13,7 @@ const ParamSDXLRefinerPositiveAestheticScore = () => {
return (
<FormControl>
<InformationalPopover feature="refinerPositiveAestheticScore">
<FormLabel>{t('sdxl.posAestheticScore')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.posAestheticScore')}</FormLabel>
<CompositeSlider
step={0.5}
min={1}

View File

@@ -1,7 +1,6 @@
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
import { setRefinerScheduler } from 'features/sdxl/store/sdxlSlice';
@@ -27,9 +26,7 @@ const ParamSDXLRefinerScheduler = () => {
return (
<FormControl>
<InformationalPopover feature="refinerScheduler">
<FormLabel>{t('sdxl.scheduler')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.scheduler')}</FormLabel>
<Combobox value={value} options={SCHEDULER_OPTIONS} onChange={onChange} />
</FormControl>
);

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setRefinerStart } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -13,9 +12,7 @@ const ParamSDXLRefinerStart = () => {
return (
<FormControl>
<InformationalPopover feature="refinerStart">
<FormLabel>{t('sdxl.refinerStart')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.refinerStart')}</FormLabel>
<CompositeSlider
step={0.01}
min={0}

View File

@@ -1,6 +1,5 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { setRefinerSteps } from 'features/sdxl/store/sdxlSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -28,9 +27,7 @@ const ParamSDXLRefinerSteps = () => {
return (
<FormControl>
<InformationalPopover feature="refinerSteps">
<FormLabel>{t('sdxl.steps')}</FormLabel>
</InformationalPopover>
<FormLabel>{t('sdxl.steps')}</FormLabel>
<CompositeSlider
value={refinerSteps}
defaultValue={initial}

View File

@@ -21,6 +21,7 @@ import ParamCFGScale from 'features/parameters/components/Core/ParamCFGScale';
import ParamScheduler from 'features/parameters/components/Core/ParamScheduler';
import ParamSteps from 'features/parameters/components/Core/ParamSteps';
import ParamMainModelSelect from 'features/parameters/components/MainModel/ParamMainModelSelect';
import { UseDefaultSettingsButton } from 'features/parameters/components/MainModel/UseDefaultSettingsButton';
import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle';
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
import { filter } from 'lodash-es';
@@ -71,7 +72,10 @@ export const GenerationSettingsAccordion = memo(() => {
<TabPanel overflow="visible" px={4} pt={4}>
<Flex gap={4} alignItems="center">
<ParamMainModelSelect />
<SyncModelsIconButton />
<Flex>
<UseDefaultSettingsButton />
<SyncModelsIconButton />
</Flex>
</Flex>
<Expander isOpen={isOpenExpander} onToggle={onToggleExpander}>
<Flex gap={4} flexDir="column" pb={4}>

View File

@@ -41,6 +41,8 @@ const initialConfigState: AppConfig = {
boundingBoxHeight: { ...baseDimensionConfig },
scaledBoundingBoxWidth: { ...baseDimensionConfig },
scaledBoundingBoxHeight: { ...baseDimensionConfig },
scheduler: "euler",
vaePrecision: "fp32",
steps: {
initial: 30,
sliderMin: 1,

View File

@@ -24,12 +24,21 @@ export type UpdateModelArg = {
body: paths['/api/v2/models/i/{key}']['patch']['requestBody']['content']['application/json'];
};
type UpdateModelMetadataArg = {
key: paths['/api/v2/models/i/{key}/metadata']['patch']['parameters']['path']['key'];
body: paths['/api/v2/models/i/{key}/metadata']['patch']['requestBody']['content']['application/json'];
};
type UpdateModelResponse = paths['/api/v2/models/i/{key}']['patch']['responses']['200']['content']['application/json'];
type UpdateModelMetadataResponse =
paths['/api/v2/models/i/{key}/metadata']['patch']['responses']['200']['content']['application/json'];
type GetModelConfigResponse = paths['/api/v2/models/i/{key}']['get']['responses']['200']['content']['application/json'];
type GetModelMetadataResponse =
paths['/api/v2/models/i/{key}/metadata']['get']['responses']['200']['content']['application/json'];
type ListModelsArg = NonNullable<paths['/api/v2/models/']['get']['parameters']['query']>;
type DeleteMainModelArg = {
@@ -108,25 +117,25 @@ const anyModelConfigAdapterSelectors = anyModelConfigAdapter.getSelectors(undefi
const buildProvidesTags =
<TEntity extends AnyModelConfig>(tagType: (typeof tagTypes)[number]) =>
(result: EntityState<TEntity, string> | undefined) => {
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
if (result) {
tags.push(
...result.ids.map((id) => ({
type: tagType,
id,
}))
);
}
(result: EntityState<TEntity, string> | undefined) => {
const tags: ApiTagDescription[] = [{ type: tagType, id: LIST_TAG }, 'Model'];
if (result) {
tags.push(
...result.ids.map((id) => ({
type: tagType,
id,
}))
);
}
return tags;
};
return tags;
};
const buildTransformResponse =
<T extends AnyModelConfig>(adapter: EntityAdapter<T, string>) =>
(response: { models: T[] }) => {
return adapter.setAll(adapter.getInitialState(), response.models);
};
(response: { models: T[] }) => {
return adapter.setAll(adapter.getInitialState(), response.models);
};
/**
* Builds an endpoint URL for the models router
@@ -172,6 +181,16 @@ export const modelsApi = api.injectEndpoints({
},
invalidatesTags: ['Model'],
}),
updateModelMetadata: build.mutation<UpdateModelMetadataResponse, UpdateModelMetadataArg>({
query: ({ key, body }) => {
return {
url: buildModelsUrl(`i/${key}/metadata`),
method: 'PATCH',
body: body,
};
},
invalidatesTags: ['Model'],
}),
installModel: build.mutation<InstallModelResponse, InstallModelArg>({
query: ({ source, config, access_token }) => {
return {
@@ -351,6 +370,7 @@ export const {
useGetModelMetadataQuery,
useDeleteModelImportMutation,
usePruneModelImportsMutation,
useUpdateModelMetadataMutation,
} = modelsApi;
const upsertModelConfigs = (

View File

@@ -55,22 +55,10 @@ const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryE
const baseUrl = $baseUrl.get();
const authToken = $authToken.get();
const projectId = $projectId.get();
const isOpenAPIRequest =
(args instanceof Object && args.url.includes('openapi.json')) ||
(typeof args === 'string' && args.includes('openapi.json'));
const fetchBaseQueryArgs: FetchBaseQueryArgs = {
baseUrl: baseUrl ? `${baseUrl}/api/v1` : `${window.location.href.replace(/\/$/, '')}/api/v1`,
};
// When fetching the openapi.json, we need to remove circular references from the JSON.
if (isOpenAPIRequest) {
fetchBaseQueryArgs.jsonReplacer = getCircularReplacer();
}
// openapi.json isn't protected by authorization, but all other requests need to include the auth token and project id.
if (!isOpenAPIRequest) {
fetchBaseQueryArgs.prepareHeaders = (headers) => {
baseUrl: baseUrl || window.location.href.replace(/\/$/, ''),
prepareHeaders: (headers) => {
if (authToken) {
headers.set('Authorization', `Bearer ${authToken}`);
}
@@ -79,7 +67,15 @@ const dynamicBaseQuery: BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryE
}
return headers;
};
},
};
// When fetching the openapi.json, we need to remove circular references from the JSON.
if (
(args instanceof Object && args.url.includes('openapi.json')) ||
(typeof args === 'string' && args.includes('openapi.json'))
) {
fetchBaseQueryArgs.jsonReplacer = getCircularReplacer();
}
const rawBaseQuery = fetchBaseQuery(fetchBaseQueryArgs);

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
__version__ = "3.7.0"
__version__ = "3.6.3"

File diff suppressed because one or more lines are too long

View File

@@ -133,7 +133,7 @@ def test_metadata_civitai_fetch(mm2_session: Session) -> None:
assert metadata.id == 215485
assert metadata.author == "test_author" # note that this is not the same as the original from Civitai
assert metadata.allow_commercial_use # changed to make sure we are reading locally not remotely
assert CommercialUsage("RentCivit") in metadata.restrictions.AllowCommercialUse
assert metadata.restrictions.AllowCommercialUse == CommercialUsage("RentCivit")
assert metadata.version_id == 242807
assert metadata.tags == {"tool", "turbo", "sdxl turbo"}