mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
325 Commits
optimizati
...
next-fix-t
Author | SHA1 | Date | |
---|---|---|---|
4d6047e05f | |||
4418c118db | |||
110b0bc8fe | |||
175cfe41a4 | |||
a12d54afb9 | |||
18af5348a2 | |||
b18c8e1c96 | |||
ea1e647174 | |||
af059f2cff | |||
d8e21091e7 | |||
344041fd3a | |||
588a220dd4 | |||
770d4092b9 | |||
33fe02bdff | |||
8a353bc1e3 | |||
240f4801db | |||
da50507b2d | |||
67d150ab66 | |||
40d70add76 | |||
7bd9bf3ba5 | |||
c94d607089 | |||
ad801e54d4 | |||
fb4db83911 | |||
cc229c3ea0 | |||
ca00fabd79 | |||
b361fabf81 | |||
00669200c7 | |||
fa07e82d2a | |||
3632c5cd57 | |||
daef68d3c1 | |||
ba29376fba | |||
3efd9465eb | |||
a3b11c04cb | |||
8f9e3ac795 | |||
2367f53367 | |||
8b9f0a9551 | |||
ab57976e42 | |||
3c103c89f3 | |||
0f19176944 | |||
fc09a954b5 | |||
e7eee29825 | |||
2c1ba23f61 | |||
58ef6dc6ce | |||
8faefa89fe | |||
02f59a3831 | |||
2555be3058 | |||
e174ce038f | |||
0f10faf0d4 | |||
393e32f8a7 | |||
70412464c8 | |||
30fdb9dbfd | |||
66f6013436 | |||
49b04f7db8 | |||
253dc5d43d | |||
3ccb4e6ff9 | |||
200a9d1801 | |||
b09a76ea0d | |||
8a2030e78a | |||
dfa5505ed8 | |||
f8b731b900 | |||
fd9ab0fb7d | |||
f504a5c96e | |||
afe6639b9c | |||
1f1bf15099 | |||
8fa238f100 | |||
30b6a0ee23 | |||
784878c300 | |||
b51b163400 | |||
7e13224ec8 | |||
7bc454209c | |||
cc7f6c7048 | |||
8b8d950137 | |||
24fd7f41ff | |||
7c5e458372 | |||
a5dba4b0d9 | |||
72fb1cefff | |||
a64f1c0b20 | |||
974658107d | |||
07fb5d5c19 | |||
20c75e7a7e | |||
cfcb68696c | |||
7b1b6d3235 | |||
aefba52a0a | |||
6af46f9c5f | |||
190702d011 | |||
7785e8ff79 | |||
b3beaefa04 | |||
98be81354a | |||
2a2a5eb775 | |||
4a42b15b42 | |||
f24d5e5e31 | |||
4b106bc903 | |||
135ef9066f | |||
0567f98e4a | |||
5b66baa3ec | |||
a022aaf258 | |||
94065b090a | |||
091bf9220b | |||
8d243b1fca | |||
23c412e011 | |||
66692f02aa | |||
38af1c3a81 | |||
7b4b7e3781 | |||
02a3472505 | |||
909d354a38 | |||
7801b8c42f | |||
4fd259bb89 | |||
b8b3ef9725 | |||
3a8d5dc349 | |||
358cac9674 | |||
bdc2b8069b | |||
09295ae43b | |||
80ad14d89f | |||
c674eb3168 | |||
63138640a7 | |||
d103ff0d6e | |||
94931e8ac0 | |||
b409f3aaf9 | |||
f96b7f2e11 | |||
de3be4bd30 | |||
cc12f57a5a | |||
613f11a3ac | |||
a6e2d2c5e0 | |||
ae14df97d6 | |||
a6e1ac6096 | |||
8530635540 | |||
b2b7aed030 | |||
970d45f691 | |||
19b9a22d93 | |||
c0d9990344 | |||
4ac5e307c4 | |||
2815f737fe | |||
63e96fd1ea | |||
66ab56246a | |||
20a56bc757 | |||
82925e1539 | |||
0137a0db7b | |||
b410793684 | |||
894e9f127b | |||
dd9b1c8eec | |||
8d9c566656 | |||
9db7e073a3 | |||
5f64ed5bd5 | |||
7f75f6226b | |||
6dc819fd47 | |||
0cc81e5d63 | |||
daecc54153 | |||
4c31c7f9f1 | |||
d709c5519f | |||
5d84ecef49 | |||
641d246213 | |||
2e53aa48c9 | |||
ef12631450 | |||
d9eb626b62 | |||
8033589629 | |||
124075ae7a | |||
0bde933c89 | |||
fc5c5b6bdd | |||
ff53563152 | |||
12b0d735e7 | |||
d06ee94fd3 | |||
9dbdb6cf7c | |||
7c091570fe | |||
e99f3482cc | |||
d999c9ffd6 | |||
888db8ac46 | |||
7deef2cb27 | |||
ada807af0c | |||
aa132fb9e3 | |||
98a01368b8 | |||
fc9a62dbf5 | |||
4d8bec1605 | |||
cf9dad83bc | |||
0d0a2a5c91 | |||
0cab636ab0 | |||
de097ec58a | |||
bb6f426162 | |||
663f135b3c | |||
2f2097662a | |||
458c29cfa5 | |||
4bec01d6f2 | |||
9d79ee8dc4 | |||
78dd460348 | |||
9d27d354cf | |||
e8725a1099 | |||
479d65b6e1 | |||
5d4b388dfd | |||
4956fa282b | |||
51133522b7 | |||
6d5cc8b1ff | |||
08a5bb90e2 | |||
39bdf5c4e9 | |||
f31e4205aa | |||
4d05c4ff66 | |||
7e88d2a7f1 | |||
556f6aa174 | |||
6a74048af8 | |||
2cb51bff11 | |||
851e835e0e | |||
fe04f28841 | |||
258fc006ec | |||
dcb4ee47d5 | |||
1a56f5aaf9 | |||
5fc745653a | |||
47b5a90177 | |||
81518ee1af | |||
b06d63fb34 | |||
5278a64301 | |||
4de4473c0f | |||
2c28a850ca | |||
6dada3326d | |||
2dfdc02ec8 | |||
1f19db4c6a | |||
7c150c27f2 | |||
248916c190 | |||
be8b99eed5 | |||
2ad0752582 | |||
ba1f8878dd | |||
bc524026f9 | |||
ad7c571983 | |||
8559c6a392 | |||
c7904a32f4 | |||
17f5484f5b | |||
86a372b02f | |||
2e9aa9391d | |||
0c8112cf28 | |||
019898c7be | |||
2b1ff8d196 | |||
79fb691b4d | |||
560ae17e21 | |||
2bd1ab2f1c | |||
ed43472582 | |||
6e5e9176c0 | |||
4c6bcdbc18 | |||
20e6d4fa3c | |||
8e51392910 | |||
0b1c2acd61 | |||
86ac55ab5f | |||
3e82f63c7e | |||
631f6cae19 | |||
0845a0ed84 | |||
46c8ce9fed | |||
13a9ea35b5 | |||
94e8d1b6d5 | |||
2b1dc74080 | |||
f7e558d165 | |||
d959276217 | |||
dfcf38be91 | |||
fbded1c0f2 | |||
ad2926a24c | |||
34d5cad4c9 | |||
60aa3d4893 | |||
5c2884569e | |||
a1307b9f2e | |||
f505ec64ba | |||
f22eb368a3 | |||
96ae22c7e0 | |||
f5447cdc23 | |||
c76a6bd65f | |||
6c4eeaa569 | |||
1bbd13ead7 | |||
321b939d0e | |||
8fb77e431e | |||
083a4f3faa | |||
2005411f7e | |||
ba7b1b2665 | |||
b7ffd36cc6 | |||
199ddd6623 | |||
a7207ed8cf | |||
6bb2dda3f1 | |||
c1e5cd5893 | |||
ff249a2315 | |||
c58f8c3269 | |||
ed772a7107 | |||
cb0b389b4b | |||
8892df1d97 | |||
bc5f356390 | |||
bcb85e100d | |||
1f27ddc07d | |||
7a2b606001 | |||
83ddcc5f3a | |||
55fa785561 | |||
06429028c8 | |||
8b6e322697 | |||
54a67459bf | |||
7fe5283e74 | |||
fe0391c86b | |||
25386a76ef | |||
fd30cb4d90 | |||
0266946d3d | |||
a7f91b3e01 | |||
de0b72528c | |||
2932652787 | |||
db6bc7305a | |||
a5db204629 | |||
8e2b61e19f | |||
a3faa3792a | |||
c16eba78ab | |||
1a191c4655 | |||
e36d925bce | |||
b1ba18b3d1 | |||
aff46759f9 | |||
d7b7dcc7fe | |||
889a26c5b6 | |||
b4c774896a | |||
afbe889d35 | |||
9c1e52b1ef | |||
3f5ab02da9 | |||
bf48e8a03a | |||
e52434cb99 | |||
483bdbcb9f | |||
ae421fb4ab | |||
cc295a9f0a | |||
a7e23af9c6 | |||
3de4390711 | |||
3ceee2b2b2 | |||
5c7ed24aab | |||
183c9c4799 | |||
8baf3f78a2 | |||
ac2eb16a65 | |||
4aa7bee4b9 | |||
7e5ba2795e | |||
97a6c6eea7 | |||
f0e60a4ba2 | |||
aa089e8108 |
33
.github/actions/install-frontend-deps/action.yml
vendored
33
.github/actions/install-frontend-deps/action.yml
vendored
@ -1,33 +0,0 @@
|
||||
name: install frontend dependencies
|
||||
description: Installs frontend dependencies with pnpm, with caching
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: setup node 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: setup cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: pnpm install --prefer-frozen-lockfile
|
||||
shell: bash
|
||||
working-directory: invokeai/frontend/web
|
28
.github/pr_labels.yml
vendored
28
.github/pr_labels.yml
vendored
@ -1,59 +1,59 @@
|
||||
root:
|
||||
Root:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: '*'
|
||||
|
||||
python-deps:
|
||||
PythonDeps:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'pyproject.toml'
|
||||
|
||||
python:
|
||||
Python:
|
||||
- changed-files:
|
||||
- all-globs-to-any-file:
|
||||
- 'invokeai/**'
|
||||
- '!invokeai/frontend/web/**'
|
||||
|
||||
python-tests:
|
||||
PythonTests:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'tests/**'
|
||||
|
||||
ci-cd:
|
||||
CICD:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: .github/**
|
||||
|
||||
docker:
|
||||
Docker:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docker/**
|
||||
|
||||
installer:
|
||||
Installer:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: installer/**
|
||||
|
||||
docs:
|
||||
Documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: docs/**
|
||||
|
||||
invocations:
|
||||
Invocations:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/invocations/**'
|
||||
|
||||
backend:
|
||||
Backend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/backend/**'
|
||||
|
||||
api:
|
||||
Api:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/api/**'
|
||||
|
||||
services:
|
||||
Services:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/app/services/**'
|
||||
|
||||
frontend-deps:
|
||||
FrontendDeps:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- '**/*/package.json'
|
||||
- '**/*/pnpm-lock.yaml'
|
||||
|
||||
frontend:
|
||||
Frontend:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'invokeai/frontend/web/**'
|
||||
|
2
.github/workflows/build-container.yml
vendored
2
.github/workflows/build-container.yml
vendored
@ -11,7 +11,7 @@ on:
|
||||
- 'docker/docker-entrypoint.sh'
|
||||
- 'workflows/build-container.yml'
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
45
.github/workflows/build-installer.yml
vendored
45
.github/workflows/build-installer.yml
vendored
@ -1,45 +0,0 @@
|
||||
# Builds and uploads the installer and python build artifacts.
|
||||
|
||||
name: build installer
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
build-installer:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5 # expected run time: <2 min
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: setup python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: install pypa/build
|
||||
run: pip install --upgrade build
|
||||
|
||||
- name: setup frontend
|
||||
uses: ./.github/actions/install-frontend-deps
|
||||
|
||||
- name: create installer
|
||||
id: create_installer
|
||||
run: ./create_installer.sh
|
||||
working-directory: installer
|
||||
|
||||
- name: upload python distribution artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: ${{ steps.create_installer.outputs.DIST_PATH }}
|
||||
|
||||
- name: upload installer artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.create_installer.outputs.INSTALLER_FILENAME }}
|
||||
path: ${{ steps.create_installer.outputs.INSTALLER_PATH }}
|
68
.github/workflows/frontend-checks.yml
vendored
68
.github/workflows/frontend-checks.yml
vendored
@ -1,68 +0,0 @@
|
||||
# Runs frontend code quality checks.
|
||||
#
|
||||
# Checks for changes to frontend files before running the checks.
|
||||
# When manually triggered or when called from another workflow, always runs the checks.
|
||||
|
||||
name: 'frontend checks'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: invokeai/frontend/web
|
||||
|
||||
jobs:
|
||||
frontend-checks:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10 # expected run time: <2 min
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: check for changed frontend files
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' }}
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v42
|
||||
with:
|
||||
files_yaml: |
|
||||
frontend:
|
||||
- 'invokeai/frontend/web/**'
|
||||
|
||||
- name: install dependencies
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
uses: ./.github/actions/install-frontend-deps
|
||||
|
||||
- name: tsc
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm lint:tsc'
|
||||
shell: bash
|
||||
|
||||
- name: dpdm
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm lint:dpdm'
|
||||
shell: bash
|
||||
|
||||
- name: eslint
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm lint:eslint'
|
||||
shell: bash
|
||||
|
||||
- name: prettier
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm lint:prettier'
|
||||
shell: bash
|
||||
|
||||
- name: knip
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm lint:knip'
|
||||
shell: bash
|
48
.github/workflows/frontend-tests.yml
vendored
48
.github/workflows/frontend-tests.yml
vendored
@ -1,48 +0,0 @@
|
||||
# Runs frontend tests.
|
||||
#
|
||||
# Checks for changes to frontend files before running the tests.
|
||||
# When manually triggered or called from another workflow, always runs the tests.
|
||||
|
||||
name: 'frontend tests'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: invokeai/frontend/web
|
||||
|
||||
jobs:
|
||||
frontend-tests:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10 # expected run time: <2 min
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: check for changed frontend files
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' }}
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v42
|
||||
with:
|
||||
files_yaml: |
|
||||
frontend:
|
||||
- 'invokeai/frontend/web/**'
|
||||
|
||||
- name: install dependencies
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
uses: ./.github/actions/install-frontend-deps
|
||||
|
||||
- name: vitest
|
||||
if: ${{ steps.changed-files.outputs.frontend_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: 'pnpm test:no-watch'
|
||||
shell: bash
|
12
.github/workflows/label-pr.yml
vendored
12
.github/workflows/label-pr.yml
vendored
@ -1,6 +1,6 @@
|
||||
name: 'label PRs'
|
||||
name: "Pull Request Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
@ -9,10 +9,8 @@ jobs:
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: label PRs
|
||||
uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v5
|
||||
with:
|
||||
configuration-path: .github/pr_labels.yml
|
||||
configuration-path: .github/pr_labels.yml
|
45
.github/workflows/lint-frontend.yml
vendored
Normal file
45
.github/workflows/lint-frontend.yml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Lint frontend
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: invokeai/frontend/web
|
||||
|
||||
jobs:
|
||||
lint-frontend:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Setup Node 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: '8.12.1'
|
||||
- name: Install dependencies
|
||||
run: 'pnpm install --prefer-frozen-lockfile'
|
||||
- name: Typescript
|
||||
run: 'pnpm run lint:tsc'
|
||||
- name: Madge
|
||||
run: 'pnpm run lint:dpdm'
|
||||
- name: ESLint
|
||||
run: 'pnpm run lint:eslint'
|
||||
- name: Prettier
|
||||
run: 'pnpm run lint:prettier'
|
||||
- name: Knip
|
||||
run: 'pnpm run lint:knip'
|
54
.github/workflows/mkdocs-material.yml
vendored
54
.github/workflows/mkdocs-material.yml
vendored
@ -1,49 +1,51 @@
|
||||
# This is a mostly a copy-paste from https://github.com/squidfunk/mkdocs-material/blob/master/docs/publishing-your-site.md
|
||||
|
||||
name: mkdocs
|
||||
|
||||
name: mkdocs-material
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
- 'refs/heads/main'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
mkdocs-material:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REPO_URL: '${{ github.server_url }}/${{ github.repository }}'
|
||||
REPO_NAME: '${{ github.repository }}'
|
||||
SITE_URL: 'https://${{ github.repository_owner }}.github.io/InvokeAI'
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: checkout sources
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: setup python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: set cache id
|
||||
run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
- name: install requirements
|
||||
env:
|
||||
PIP_USE_PEP517: 1
|
||||
run: |
|
||||
python -m \
|
||||
pip install ".[docs]"
|
||||
|
||||
- name: use cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
restore-keys: |
|
||||
mkdocs-material-
|
||||
- name: confirm buildability
|
||||
run: |
|
||||
python -m \
|
||||
mkdocs build \
|
||||
--clean \
|
||||
--verbose
|
||||
|
||||
- name: install dependencies
|
||||
run: python -m pip install ".[docs]"
|
||||
|
||||
- name: build & deploy
|
||||
run: mkdocs gh-deploy --force
|
||||
- name: deploy to gh-pages
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
run: |
|
||||
python -m \
|
||||
mkdocs gh-deploy \
|
||||
--clean \
|
||||
--force
|
||||
|
67
.github/workflows/pypi-release.yml
vendored
Normal file
67
.github/workflows/pypi-release.yml
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
name: PyPI Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_package:
|
||||
description: 'Publish build on PyPi? [true/false]'
|
||||
required: true
|
||||
default: 'false'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
if: github.repository == 'invoke-ai/InvokeAI'
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||
TWINE_NON_INTERACTIVE: 1
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node 18
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: '8.12.1'
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install --prefer-frozen-lockfile
|
||||
working-directory: invokeai/frontend/web
|
||||
|
||||
- name: Build frontend
|
||||
run: pnpm run build
|
||||
working-directory: invokeai/frontend/web
|
||||
|
||||
- name: Install python dependencies
|
||||
run: pip install --upgrade build twine
|
||||
|
||||
- name: Build python package
|
||||
run: python3 -m build
|
||||
|
||||
- name: Upload build as workflow artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Check distribution
|
||||
run: twine check dist/*
|
||||
|
||||
- name: Check PyPI versions
|
||||
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')
|
||||
run: |
|
||||
pip install --upgrade requests
|
||||
python -c "\
|
||||
import scripts.pypi_helper; \
|
||||
EXISTS=scripts.pypi_helper.local_on_pypi(); \
|
||||
print(f'PACKAGE_EXISTS={EXISTS}')" >> $GITHUB_ENV
|
||||
|
||||
- name: Publish build on PyPi
|
||||
if: env.PACKAGE_EXISTS == 'False' && env.TWINE_PASSWORD != '' && github.event.inputs.publish_package == 'true'
|
||||
run: twine upload dist/*
|
64
.github/workflows/python-checks.yml
vendored
64
.github/workflows/python-checks.yml
vendored
@ -1,64 +0,0 @@
|
||||
# Runs python code quality checks.
|
||||
#
|
||||
# Checks for changes to python files before running the checks.
|
||||
# When manually triggered or called from another workflow, always runs the tests.
|
||||
#
|
||||
# TODO: Add mypy or pyright to the checks.
|
||||
|
||||
name: 'python checks'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
python-checks:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5 # expected run time: <1 min
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: check for changed python files
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' }}
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v42
|
||||
with:
|
||||
files_yaml: |
|
||||
python:
|
||||
- 'pyproject.toml'
|
||||
- 'invokeai/**'
|
||||
- '!invokeai/frontend/web/**'
|
||||
- 'tests/**'
|
||||
|
||||
- name: setup python
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: install ruff
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: pip install ruff
|
||||
shell: bash
|
||||
|
||||
- name: ruff check
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: ruff check --output-format=github .
|
||||
shell: bash
|
||||
|
||||
- name: ruff format
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: ruff format --check .
|
||||
shell: bash
|
94
.github/workflows/python-tests.yml
vendored
94
.github/workflows/python-tests.yml
vendored
@ -1,94 +0,0 @@
|
||||
# Runs python tests on a matrix of python versions and platforms.
|
||||
#
|
||||
# Checks for changes to python files before running the tests.
|
||||
# When manually triggered or called from another workflow, always runs the tests.
|
||||
|
||||
name: 'python tests'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
- '3.10'
|
||||
- '3.11'
|
||||
platform:
|
||||
- linux-cuda-11_7
|
||||
- linux-rocm-5_2
|
||||
- linux-cpu
|
||||
- macos-default
|
||||
- windows-cpu
|
||||
include:
|
||||
- platform: linux-cuda-11_7
|
||||
os: ubuntu-22.04
|
||||
github-env: $GITHUB_ENV
|
||||
- platform: linux-rocm-5_2
|
||||
os: ubuntu-22.04
|
||||
extra-index-url: 'https://download.pytorch.org/whl/rocm5.2'
|
||||
github-env: $GITHUB_ENV
|
||||
- platform: linux-cpu
|
||||
os: ubuntu-22.04
|
||||
extra-index-url: 'https://download.pytorch.org/whl/cpu'
|
||||
github-env: $GITHUB_ENV
|
||||
- platform: macos-default
|
||||
os: macOS-12
|
||||
github-env: $GITHUB_ENV
|
||||
- platform: windows-cpu
|
||||
os: windows-2022
|
||||
github-env: $env:GITHUB_ENV
|
||||
name: 'py${{ matrix.python-version }}: ${{ matrix.platform }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 15 # expected run time: 2-6 min, depending on platform
|
||||
env:
|
||||
PIP_USE_PEP517: '1'
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: check for changed python files
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' }}
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v42
|
||||
with:
|
||||
files_yaml: |
|
||||
python:
|
||||
- 'pyproject.toml'
|
||||
- 'invokeai/**'
|
||||
- '!invokeai/frontend/web/**'
|
||||
- 'tests/**'
|
||||
|
||||
- name: setup python
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: install dependencies
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: ${{ matrix.extra-index-url }}
|
||||
run: >
|
||||
pip3 install --editable=".[test]"
|
||||
|
||||
- name: run pytest
|
||||
if: ${{ steps.changed-files.outputs.python_any_changed == 'true' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call' }}
|
||||
run: pytest
|
96
.github/workflows/release.yml
vendored
96
.github/workflows/release.yml
vendored
@ -1,96 +0,0 @@
|
||||
# Main release workflow. Triggered on tag push or manual trigger.
|
||||
#
|
||||
# - Runs all code checks and tests
|
||||
# - Verifies the app version matches the tag version.
|
||||
# - Builds the installer and build, uploading them as artifacts.
|
||||
# - Publishes to TestPyPI and PyPI. Both are conditional on the previous steps passing and require a manual approval.
|
||||
#
|
||||
# See docs/RELEASE.md for more information on the release process.
|
||||
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: check python version
|
||||
uses: samuelcolvin/check-python-version@v4
|
||||
id: check-python-version
|
||||
with:
|
||||
version_file_path: invokeai/version/invokeai_version.py
|
||||
|
||||
frontend-checks:
|
||||
uses: ./.github/workflows/frontend-checks.yml
|
||||
|
||||
frontend-tests:
|
||||
uses: ./.github/workflows/frontend-tests.yml
|
||||
|
||||
python-checks:
|
||||
uses: ./.github/workflows/python-checks.yml
|
||||
|
||||
python-tests:
|
||||
uses: ./.github/workflows/python-tests.yml
|
||||
|
||||
build:
|
||||
uses: ./.github/workflows/build-installer.yml
|
||||
|
||||
publish-testpypi:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5 # expected run time: <1 min
|
||||
needs:
|
||||
[
|
||||
check-version,
|
||||
frontend-checks,
|
||||
frontend-tests,
|
||||
python-checks,
|
||||
python-tests,
|
||||
build,
|
||||
]
|
||||
environment:
|
||||
name: testpypi
|
||||
url: https://test.pypi.org/p/invokeai
|
||||
steps:
|
||||
- name: download distribution from build job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- name: publish distribution to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
|
||||
publish-pypi:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5 # expected run time: <1 min
|
||||
needs:
|
||||
[
|
||||
check-version,
|
||||
frontend-checks,
|
||||
frontend-tests,
|
||||
python-checks,
|
||||
python-tests,
|
||||
build,
|
||||
]
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/invokeai
|
||||
steps:
|
||||
- name: download distribution from build job
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
|
||||
- name: publish distribution to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
24
.github/workflows/style-checks.yml
vendored
Normal file
24
.github/workflows/style-checks.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: style checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies with pip
|
||||
run: |
|
||||
pip install ruff
|
||||
|
||||
- run: ruff check --output-format=github .
|
||||
- run: ruff format --check .
|
129
.github/workflows/test-invoke-pip.yml
vendored
Normal file
129
.github/workflows/test-invoke-pip.yml
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
name: Test invoke.py pip
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
pull_request:
|
||||
types:
|
||||
- 'ready_for_review'
|
||||
- 'opened'
|
||||
- 'synchronize'
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
if: github.event.pull_request.draft == false
|
||||
strategy:
|
||||
matrix:
|
||||
python-version:
|
||||
# - '3.9'
|
||||
- '3.10'
|
||||
pytorch:
|
||||
- linux-cuda-11_7
|
||||
- linux-rocm-5_2
|
||||
- linux-cpu
|
||||
- macos-default
|
||||
- windows-cpu
|
||||
include:
|
||||
- pytorch: linux-cuda-11_7
|
||||
os: ubuntu-22.04
|
||||
github-env: $GITHUB_ENV
|
||||
- pytorch: linux-rocm-5_2
|
||||
os: ubuntu-22.04
|
||||
extra-index-url: 'https://download.pytorch.org/whl/rocm5.2'
|
||||
github-env: $GITHUB_ENV
|
||||
- pytorch: linux-cpu
|
||||
os: ubuntu-22.04
|
||||
extra-index-url: 'https://download.pytorch.org/whl/cpu'
|
||||
github-env: $GITHUB_ENV
|
||||
- pytorch: macos-default
|
||||
os: macOS-12
|
||||
github-env: $GITHUB_ENV
|
||||
- pytorch: windows-cpu
|
||||
os: windows-2022
|
||||
github-env: $env:GITHUB_ENV
|
||||
name: ${{ matrix.pytorch }} on ${{ matrix.python-version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
PIP_USE_PEP517: '1'
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
id: checkout-sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Check for changed python files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files_yaml: |
|
||||
python:
|
||||
- 'pyproject.toml'
|
||||
- 'invokeai/**'
|
||||
- '!invokeai/frontend/web/**'
|
||||
- 'tests/**'
|
||||
|
||||
- name: set test prompt to main branch validation
|
||||
if: steps.changed-files.outputs.python_any_changed == 'true'
|
||||
run: echo "TEST_PROMPTS=tests/validate_pr_prompt.txt" >> ${{ matrix.github-env }}
|
||||
|
||||
- name: setup python
|
||||
if: steps.changed-files.outputs.python_any_changed == 'true'
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
cache-dependency-path: pyproject.toml
|
||||
|
||||
- name: install invokeai
|
||||
if: steps.changed-files.outputs.python_any_changed == 'true'
|
||||
env:
|
||||
PIP_EXTRA_INDEX_URL: ${{ matrix.extra-index-url }}
|
||||
run: >
|
||||
pip3 install
|
||||
--editable=".[test]"
|
||||
|
||||
- name: run pytest
|
||||
if: steps.changed-files.outputs.python_any_changed == 'true'
|
||||
id: run-pytest
|
||||
run: pytest
|
||||
|
||||
# - name: run invokeai-configure
|
||||
# env:
|
||||
# HUGGING_FACE_HUB_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
|
||||
# run: >
|
||||
# invokeai-configure
|
||||
# --yes
|
||||
# --default_only
|
||||
# --full-precision
|
||||
# # can't use fp16 weights without a GPU
|
||||
|
||||
# - name: run invokeai
|
||||
# id: run-invokeai
|
||||
# env:
|
||||
# # Set offline mode to make sure configure preloaded successfully.
|
||||
# HF_HUB_OFFLINE: 1
|
||||
# HF_DATASETS_OFFLINE: 1
|
||||
# TRANSFORMERS_OFFLINE: 1
|
||||
# INVOKEAI_OUTDIR: ${{ github.workspace }}/results
|
||||
# run: >
|
||||
# invokeai
|
||||
# --no-patchmatch
|
||||
# --no-nsfw_checker
|
||||
# --precision=float32
|
||||
# --always_use_cpu
|
||||
# --use_memory_db
|
||||
# --outdir ${{ env.INVOKEAI_OUTDIR }}/${{ matrix.python-version }}/${{ matrix.pytorch }}
|
||||
# --from_file ${{ env.TEST_PROMPTS }}
|
||||
|
||||
# - name: Archive results
|
||||
# env:
|
||||
# INVOKEAI_OUTDIR: ${{ github.workspace }}/results
|
||||
# uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: results
|
||||
# path: ${{ env.INVOKEAI_OUTDIR }}
|
@ -7,7 +7,7 @@ embeddedLanguageFormatting: auto
|
||||
overrides:
|
||||
- files: '*.md'
|
||||
options:
|
||||
proseWrap: preserve
|
||||
proseWrap: always
|
||||
printWidth: 80
|
||||
parser: markdown
|
||||
cursorOffset: -1
|
||||
|
142
docs/RELEASE.md
142
docs/RELEASE.md
@ -1,142 +0,0 @@
|
||||
# Release Process
|
||||
|
||||
The app is published in twice, in different build formats.
|
||||
|
||||
- A [PyPI] distribution. This includes both a source distribution and built distribution (a wheel). Users install with `pip install invokeai`. The updater uses this build.
|
||||
- An installer on the [InvokeAI Releases Page]. This is a zip file with install scripts and a wheel. This is only used for new installs.
|
||||
|
||||
## General Prep
|
||||
|
||||
Make a developer call-out for PRs to merge. Merge and test things out.
|
||||
|
||||
While the release workflow does not include end-to-end tests, it does pause before publishing so you can download and test the final build.
|
||||
|
||||
## Release Workflow
|
||||
|
||||
The `release.yml` workflow runs a number of jobs to handle code checks, tests, build and publish on PyPI.
|
||||
|
||||
It is triggered on **tag push**, when the tag matches `v*`. It doesn't matter if you've prepped a release branch like `release/v3.5.0` or are releasing from `main` - it works the same.
|
||||
|
||||
> Because commits are reference-counted, it is safe to create a release branch, tag it, let the workflow run, then delete the branch. So long as the tag exists, that commit will exist.
|
||||
|
||||
### Triggering the Workflow
|
||||
|
||||
Run `make tag-release` to tag the current commit and kick off the workflow.
|
||||
|
||||
The release may also be dispatched [manually].
|
||||
|
||||
### Workflow Jobs and Process
|
||||
|
||||
The workflow consists of a number of concurrently-run jobs, and two final publish jobs.
|
||||
|
||||
The publish jobs require manual approval and are only run if the other jobs succeed.
|
||||
|
||||
#### `check-version` Job
|
||||
|
||||
This job checks that the git ref matches the app version. It matches the ref against the `__version__` variable in `invokeai/version/invokeai_version.py`.
|
||||
|
||||
When the workflow is triggered by tag push, the ref is the tag. If the workflow is run manually, the ref is the target selected from the **Use workflow from** dropdown.
|
||||
|
||||
This job uses [samuelcolvin/check-python-version].
|
||||
|
||||
> Any valid [version specifier] works, so long as the tag matches the version. The release workflow works exactly the same for `RC`, `post`, `dev`, etc.
|
||||
|
||||
#### Check and Test Jobs
|
||||
|
||||
- **`python-tests`**: runs `pytest` on matrix of platforms
|
||||
- **`python-checks`**: runs `ruff` (format and lint)
|
||||
- **`frontend-tests`**: runs `vitest`
|
||||
- **`frontend-checks`**: runs `prettier` (format), `eslint` (lint), `dpdm` (circular refs), `tsc` (static type check) and `knip` (unused imports)
|
||||
|
||||
> **TODO** We should add `mypy` or `pyright` to the **`check-python`** job.
|
||||
|
||||
> **TODO** We should add an end-to-end test job that generates an image.
|
||||
|
||||
#### `build-installer` Job
|
||||
|
||||
This sets up both python and frontend dependencies and builds the python package. Internally, this runs `installer/create_installer.sh` and uploads two artifacts:
|
||||
|
||||
- **`dist`**: the python distribution, to be published on PyPI
|
||||
- **`InvokeAI-installer-${VERSION}.zip`**: the installer to be included in the GitHub release
|
||||
|
||||
#### Sanity Check & Smoke Test
|
||||
|
||||
At this point, the release workflow pauses as the remaining publish jobs require approval.
|
||||
|
||||
A maintainer should go to the **Summary** tab of the workflow, download the installer and test it. Ensure the app loads and generates.
|
||||
|
||||
> The same wheel file is bundled in the installer and in the `dist` artifact, which is uploaded to PyPI. You should end up with the exactly the same installation of the `invokeai` package from any of these methods.
|
||||
|
||||
#### PyPI Publish Jobs
|
||||
|
||||
The publish jobs will run if any of the previous jobs fail.
|
||||
|
||||
They use [GitHub environments], which are configured as [trusted publishers] on PyPI.
|
||||
|
||||
Both jobs require a maintainer to approve them from the workflow's **Summary** tab.
|
||||
|
||||
- Click the **Review deployments** button
|
||||
- Select the environment (either `testpypi` or `pypi`)
|
||||
- Click **Approve and deploy**
|
||||
|
||||
> **If the version already exists on PyPI, the publish jobs will fail.** PyPI only allows a given version to be published once - you cannot change it. If version published on PyPI has a problem, you'll need to "fail forward" by bumping the app version and publishing a followup release.
|
||||
|
||||
#### `publish-testpypi` Job
|
||||
|
||||
Publishes the distribution on the [Test PyPI] index, using the `testpypi` GitHub environment.
|
||||
|
||||
This job is not required for the production PyPI publish, but included just in case you want to test the PyPI release.
|
||||
|
||||
If approved and successful, you could try out the test release like this:
|
||||
|
||||
```sh
|
||||
# Create a new virtual environment
|
||||
python -m venv ~/.test-invokeai-dist --prompt test-invokeai-dist
|
||||
# Install the distribution from Test PyPI
|
||||
pip install --index-url https://test.pypi.org/simple/ invokeai
|
||||
# Run and test the app
|
||||
invokeai-web
|
||||
# Cleanup
|
||||
deactivate
|
||||
rm -rf ~/.test-invokeai-dist
|
||||
```
|
||||
|
||||
#### `publish-pypi` Job
|
||||
|
||||
Publishes the distribution on the production PyPI index, using the `pypi` GitHub environment.
|
||||
|
||||
## Publish the GitHub Release with installer
|
||||
|
||||
Once the release is published to PyPI, it's time to publish the GitHub release.
|
||||
|
||||
1. [Draft a new release] on GitHub, choosing the tag that triggered the release.
|
||||
2. Write the release notes, describing important changes. The **Generate release notes** button automatically inserts the changelog and new contributors, and you can copy/paste the intro from previous releases.
|
||||
3. Upload the zip file created in **`build`** job into the Assets section of the release notes. You can also upload the zip into the body of the release notes, since it can be hard for users to find the Assets section.
|
||||
4. Check the **Set as a pre-release** and **Create a discussion for this release** checkboxes at the bottom of the release page.
|
||||
5. Publish the pre-release.
|
||||
6. Announce the pre-release in Discord.
|
||||
|
||||
> **TODO** Workflows can create a GitHub release from a template and upload release assets. One popular action to handle this is [ncipollo/release-action]. A future enhancement to the release process could set this up.
|
||||
|
||||
## Manual Build
|
||||
|
||||
The `build installer` workflow can be dispatched manually. This is useful to test the installer for a given branch or tag.
|
||||
|
||||
No checks are run, it just builds.
|
||||
|
||||
## Manual Release
|
||||
|
||||
The `release` workflow can be dispatched manually. You must dispatch the workflow from the right tag, else it will fail the version check.
|
||||
|
||||
This functionality is available as a fallback in case something goes wonky. Typically, releases should be triggered via tag push as described above.
|
||||
|
||||
[InvokeAI Releases Page]: https://github.com/invoke-ai/InvokeAI/releases
|
||||
[PyPI]: https://pypi.org/
|
||||
[Draft a new release]: https://github.com/invoke-ai/InvokeAI/releases/new
|
||||
[Test PyPI]: https://test.pypi.org/
|
||||
[version specifier]: https://packaging.python.org/en/latest/specifications/version-specifiers/
|
||||
[ncipollo/release-action]: https://github.com/ncipollo/release-action
|
||||
[GitHub environments]: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment
|
||||
[trusted publishers]: https://docs.pypi.org/trusted-publishers/
|
||||
[samuelcolvin/check-python-version]: https://github.com/samuelcolvin/check-python-version
|
||||
[manually]: #manual-release
|
@ -1,45 +0,0 @@
|
||||
# Invocation API
|
||||
|
||||
Each invocation's `invoke` method is provided a single arg - the Invocation
|
||||
Context.
|
||||
|
||||
This object provides access to various methods, used to interact with the
|
||||
application. Loading and saving images, logging messages, etc.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
This API may shift slightly until the release of v4.0.0 as we work through a few final updates to the Model Manager.
|
||||
|
||||
```py
|
||||
class MyInvocation(BaseInvocation):
|
||||
...
|
||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||
image_pil = context.images.get_pil(image_name)
|
||||
# Do something to the image
|
||||
image_dto = context.images.save(image_pil)
|
||||
# Log a message
|
||||
context.logger.info(f"Did something cool, image saved!")
|
||||
...
|
||||
```
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
::: invokeai.app.services.shared.invocation_context.InvocationContext
|
||||
options:
|
||||
members: false
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.ImagesInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.TensorsInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.ConditioningInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.ModelsInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.LoggerInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.ConfigInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.UtilInterface
|
||||
|
||||
::: invokeai.app.services.shared.invocation_context.BoardsInterface
|
||||
<!-- prettier-ignore-end -->
|
@ -1,148 +0,0 @@
|
||||
# Invoke v4.0.0 Nodes API Migration guide
|
||||
|
||||
Invoke v4.0.0 is versioned as such due to breaking changes to the API utilized
|
||||
by nodes, both core and custom.
|
||||
|
||||
## Motivation
|
||||
|
||||
Prior to v4.0.0, the `invokeai` python package has not be set up to be utilized
|
||||
as a library. That is to say, it didn't have any explicitly public API, and node
|
||||
authors had to work with the unstable internal application API.
|
||||
|
||||
v4.0.0 introduces a stable public API for nodes.
|
||||
|
||||
## Changes
|
||||
|
||||
There are two node-author-facing changes:
|
||||
|
||||
1. Import Paths
|
||||
1. Invocation Context API
|
||||
|
||||
### Import Paths
|
||||
|
||||
All public objects are now exported from `invokeai.invocation_api`:
|
||||
|
||||
```py
|
||||
# Old
|
||||
from invokeai.app.invocations.baseinvocation import (
|
||||
BaseInvocation,
|
||||
InputField,
|
||||
InvocationContext,
|
||||
invocation,
|
||||
)
|
||||
from invokeai.app.invocations.primitives import ImageField
|
||||
|
||||
# New
|
||||
from invokeai.invocation_api import (
|
||||
BaseInvocation,
|
||||
ImageField,
|
||||
InputField,
|
||||
InvocationContext,
|
||||
invocation,
|
||||
)
|
||||
```
|
||||
|
||||
It's possible that we've missed some classes you need in your node. Please let
|
||||
us know if that's the case.
|
||||
|
||||
### Invocation Context API
|
||||
|
||||
Most nodes utilize the Invocation Context, an object that is passed to the
|
||||
`invoke` that provides access to data and services a node may need.
|
||||
|
||||
Until now, that object and the services it exposed were internal. Exposing them
|
||||
to nodes means that changes to our internal implementation could break nodes.
|
||||
The methods on the services are also often fairly complicated and allowed nodes
|
||||
to footgun.
|
||||
|
||||
In v4.0.0, this object has been refactored to be much simpler.
|
||||
|
||||
See [INVOCATION_API](./INVOCATION_API.md) for full details of the API.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
This API may shift slightly until the release of v4.0.0 as we work through a few final updates to the Model Manager.
|
||||
|
||||
#### Improved Service Methods
|
||||
|
||||
The biggest offender was the image save method:
|
||||
|
||||
```py
|
||||
# Old
|
||||
image_dto = context.services.images.create(
|
||||
image=image,
|
||||
image_origin=ResourceOrigin.INTERNAL,
|
||||
image_category=ImageCategory.GENERAL,
|
||||
node_id=self.id,
|
||||
session_id=context.graph_execution_state_id,
|
||||
is_intermediate=self.is_intermediate,
|
||||
metadata=self.metadata,
|
||||
workflow=context.workflow,
|
||||
)
|
||||
|
||||
# New
|
||||
image_dto = context.images.save(image=image)
|
||||
```
|
||||
|
||||
Other methods are simplified, or enhanced with additional functionality:
|
||||
|
||||
```py
|
||||
# Old
|
||||
image = context.services.images.get_pil_image(image_name)
|
||||
|
||||
# New
|
||||
image = context.images.get_pil(image_name)
|
||||
image_cmyk = context.images.get_pil(image_name, "CMYK")
|
||||
```
|
||||
|
||||
We also had some typing issues around tensors:
|
||||
|
||||
```py
|
||||
# Old
|
||||
# `latents` typed as `torch.Tensor`, but could be `ConditioningFieldData`
|
||||
latents = context.services.latents.get(self.latents.latents_name)
|
||||
# `data` typed as `torch.Tenssor,` but could be `ConditioningFieldData`
|
||||
context.services.latents.save(latents_name, data)
|
||||
|
||||
# New - separate methods for tensors and conditioning data w/ correct typing
|
||||
# Also, the service generates the names
|
||||
tensor_name = context.tensors.save(tensor)
|
||||
tensor = context.tensors.load(tensor_name)
|
||||
# For conditioning
|
||||
cond_name = context.conditioning.save(cond_data)
|
||||
cond_data = context.conditioning.load(cond_name)
|
||||
```
|
||||
|
||||
#### Output Construction
|
||||
|
||||
Core Outputs have builder functions right on them - no need to manually
|
||||
construct these objects, or use an extra utility:
|
||||
|
||||
```py
|
||||
# Old
|
||||
image_output = ImageOutput(
|
||||
image=ImageField(image_name=image_dto.image_name),
|
||||
width=image_dto.width,
|
||||
height=image_dto.height,
|
||||
)
|
||||
latents_output = build_latents_output(latents_name=name, latents=latents, seed=None)
|
||||
noise_output = NoiseOutput(
|
||||
noise=LatentsField(latents_name=latents_name, seed=seed),
|
||||
width=latents.size()[3] * 8,
|
||||
height=latents.size()[2] * 8,
|
||||
)
|
||||
cond_output = ConditioningOutput(
|
||||
conditioning=ConditioningField(
|
||||
conditioning_name=conditioning_name,
|
||||
),
|
||||
)
|
||||
|
||||
# New
|
||||
image_output = ImageOutput.build(image_dto)
|
||||
latents_output = LatentsOutput.build(latents_name=name, latents=noise, seed=self.seed)
|
||||
noise_output = NoiseOutput.build(latents_name=name, latents=noise, seed=self.seed)
|
||||
cond_output = ConditioningOutput.build(conditioning_name)
|
||||
```
|
||||
|
||||
You can still create the objects using constructors if you want, but we suggest
|
||||
using the builder methods.
|
@ -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
|
||||
|
||||
|
5
docs/requirements-mkdocs.txt
Normal file
5
docs/requirements-mkdocs.txt
Normal file
@ -0,0 +1,5 @@
|
||||
mkdocs
|
||||
mkdocs-material>=8, <9
|
||||
mkdocs-git-revision-date-localized-plugin
|
||||
mkdocs-redirects==1.2.0
|
||||
|
5
docs/stylesheets/extra.css
Normal file
5
docs/stylesheets/extra.css
Normal file
@ -0,0 +1,5 @@
|
||||
:root {
|
||||
--md-primary-fg-color: #35A4DB;
|
||||
--md-primary-fg-color--light: #35A4DB;
|
||||
--md-primary-fg-color--dark: #35A4DB;
|
||||
}
|
@ -2,18 +2,22 @@
|
||||
|
||||
set -e
|
||||
|
||||
BCYAN="\033[1;36m"
|
||||
BYELLOW="\033[1;33m"
|
||||
BGREEN="\033[1;32m"
|
||||
BRED="\033[1;31m"
|
||||
RED="\033[31m"
|
||||
RESET="\033[0m"
|
||||
BCYAN="\e[1;36m"
|
||||
BYELLOW="\e[1;33m"
|
||||
BGREEN="\e[1;32m"
|
||||
BRED="\e[1;31m"
|
||||
RED="\e[31m"
|
||||
RESET="\e[0m"
|
||||
|
||||
function is_bin_in_path {
|
||||
builtin type -P "$1" &>/dev/null
|
||||
}
|
||||
|
||||
function git_show {
|
||||
git show -s --format=oneline --abbrev-commit "$1" | cat
|
||||
}
|
||||
|
||||
if [[ ! -z "${VIRTUAL_ENV}" ]]; then
|
||||
if [[ -v "VIRTUAL_ENV" ]]; then
|
||||
# we can't just call 'deactivate' because this function is not exported
|
||||
# to the environment of this script from the bash process that runs the script
|
||||
echo -e "${BRED}A virtual environment is activated. Please deactivate it before proceeding.${RESET}"
|
||||
@ -22,63 +26,31 @@ fi
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo
|
||||
echo -e "${BYELLOW}This script must be run from the installer directory!${RESET}"
|
||||
echo "The current working directory is $(pwd)"
|
||||
read -p "If that looks right, press any key to proceed, or CTRL-C to exit..."
|
||||
echo
|
||||
|
||||
# Some machines only have `python3` in PATH, others have `python` - make an alias.
|
||||
# We can use a function to approximate an alias within a non-interactive shell.
|
||||
if ! is_bin_in_path python && is_bin_in_path python3; then
|
||||
function python {
|
||||
python3 "$@"
|
||||
}
|
||||
fi
|
||||
|
||||
VERSION=$(
|
||||
cd ..
|
||||
python3 -c "from invokeai.version import __version__ as version; print(version)"
|
||||
python -c "from invokeai.version import __version__ as version; print(version)"
|
||||
)
|
||||
VERSION="v${VERSION}"
|
||||
|
||||
if [[ ! -z ${CI} ]]; then
|
||||
echo
|
||||
echo -e "${BCYAN}CI environment detected${RESET}"
|
||||
echo
|
||||
else
|
||||
echo
|
||||
echo -e "${BYELLOW}This script must be run from the installer directory!${RESET}"
|
||||
echo "The current working directory is $(pwd)"
|
||||
read -p "If that looks right, press any key to proceed, or CTRL-C to exit..."
|
||||
echo
|
||||
fi
|
||||
PATCH=""
|
||||
VERSION="v${VERSION}${PATCH}"
|
||||
|
||||
echo -e "${BGREEN}HEAD${RESET}:"
|
||||
git_show HEAD
|
||||
echo
|
||||
|
||||
# ---------------------- FRONTEND ----------------------
|
||||
|
||||
pushd ../invokeai/frontend/web >/dev/null
|
||||
echo "Installing frontend dependencies..."
|
||||
echo
|
||||
pnpm i --frozen-lockfile
|
||||
echo
|
||||
if [[ ! -z ${CI} ]]; then
|
||||
echo "Building frontend without checks..."
|
||||
# In CI, we have already done the frontend checks and can just build
|
||||
pnpm vite build
|
||||
else
|
||||
echo "Running checks and building frontend..."
|
||||
# This runs all the frontend checks and builds
|
||||
pnpm build
|
||||
fi
|
||||
echo
|
||||
popd
|
||||
|
||||
# ---------------------- BACKEND ----------------------
|
||||
|
||||
echo
|
||||
echo "Building wheel..."
|
||||
echo
|
||||
|
||||
# install the 'build' package in the user site packages, if needed
|
||||
# could be improved by using a temporary venv, but it's tiny and harmless
|
||||
if [[ $(python3 -c 'from importlib.util import find_spec; print(find_spec("build") is None)') == "True" ]]; then
|
||||
pip install --user build
|
||||
fi
|
||||
|
||||
rm -rf ../build
|
||||
|
||||
python3 -m build --outdir dist/ ../.
|
||||
|
||||
# ----------------------
|
||||
|
||||
echo
|
||||
@ -106,28 +78,10 @@ chmod a+x InvokeAI-Installer/install.sh
|
||||
cp install.bat.in InvokeAI-Installer/install.bat
|
||||
cp WinLongPathsEnabled.reg InvokeAI-Installer/
|
||||
|
||||
FILENAME=InvokeAI-installer-$VERSION.zip
|
||||
|
||||
# Zip everything up
|
||||
zip -r ${FILENAME} InvokeAI-Installer
|
||||
zip -r InvokeAI-installer-$VERSION.zip InvokeAI-Installer
|
||||
|
||||
echo
|
||||
echo -e "${BGREEN}Built installer: ./${FILENAME}${RESET}"
|
||||
echo -e "${BGREEN}Built PyPi distribution: ./dist${RESET}"
|
||||
|
||||
# clean up, but only if we are not in a github action
|
||||
if [[ -z ${CI} ]]; then
|
||||
echo
|
||||
echo "Cleaning up intermediate build files..."
|
||||
rm -rf InvokeAI-Installer tmp ../invokeai/frontend/web/dist/
|
||||
fi
|
||||
|
||||
if [[ ! -z ${CI} ]]; then
|
||||
echo
|
||||
echo "Setting GitHub action outputs..."
|
||||
echo "INSTALLER_FILENAME=${FILENAME}" >>$GITHUB_OUTPUT
|
||||
echo "INSTALLER_PATH=installer/${FILENAME}" >>$GITHUB_OUTPUT
|
||||
echo "DIST_PATH=installer/dist/" >>$GITHUB_OUTPUT
|
||||
fi
|
||||
# clean up
|
||||
rm -rf InvokeAI-Installer tmp dist ../invokeai/frontend/web/dist/
|
||||
|
||||
exit 0
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
set -e
|
||||
|
||||
BCYAN="\033[1;36m"
|
||||
BYELLOW="\033[1;33m"
|
||||
BGREEN="\033[1;32m"
|
||||
BRED="\033[1;31m"
|
||||
RED="\033[31m"
|
||||
RESET="\033[0m"
|
||||
BCYAN="\e[1;36m"
|
||||
BYELLOW="\e[1;33m"
|
||||
BGREEN="\e[1;32m"
|
||||
BRED="\e[1;31m"
|
||||
RED="\e[31m"
|
||||
RESET="\e[0m"
|
||||
|
||||
function does_tag_exist {
|
||||
git rev-parse --quiet --verify "refs/tags/$1" >/dev/null
|
||||
@ -23,40 +23,49 @@ function git_show {
|
||||
|
||||
VERSION=$(
|
||||
cd ..
|
||||
python3 -c "from invokeai.version import __version__ as version; print(version)"
|
||||
python -c "from invokeai.version import __version__ as version; print(version)"
|
||||
)
|
||||
PATCH=""
|
||||
MAJOR_VERSION=$(echo $VERSION | sed 's/\..*$//')
|
||||
VERSION="v${VERSION}${PATCH}"
|
||||
LATEST_TAG="v${MAJOR_VERSION}-latest"
|
||||
|
||||
if does_tag_exist $VERSION; then
|
||||
echo -e "${BCYAN}${VERSION}${RESET} already exists:"
|
||||
git_show_ref tags/$VERSION
|
||||
echo
|
||||
fi
|
||||
if does_tag_exist $LATEST_TAG; then
|
||||
echo -e "${BCYAN}${LATEST_TAG}${RESET} already exists:"
|
||||
git_show_ref tags/$LATEST_TAG
|
||||
echo
|
||||
fi
|
||||
|
||||
echo -e "${BGREEN}HEAD${RESET}:"
|
||||
git_show
|
||||
echo
|
||||
|
||||
echo -e "${BGREEN}git remote -v${RESET}:"
|
||||
git remote -v
|
||||
echo
|
||||
|
||||
echo -e -n "Create tags ${BCYAN}${VERSION}${RESET} @ ${BGREEN}HEAD${RESET}, ${RED}deleting existing tags on origin remote${RESET}? "
|
||||
echo -e -n "Create tags ${BCYAN}${VERSION}${RESET} and ${BCYAN}${LATEST_TAG}${RESET} @ ${BGREEN}HEAD${RESET}, ${RED}deleting existing tags on remote${RESET}? "
|
||||
read -e -p 'y/n [n]: ' input
|
||||
RESPONSE=${input:='n'}
|
||||
if [ "$RESPONSE" == 'y' ]; then
|
||||
echo
|
||||
echo -e "Deleting ${BCYAN}${VERSION}${RESET} tag on origin remote..."
|
||||
git push origin :refs/tags/$VERSION
|
||||
echo -e "Deleting ${BCYAN}${VERSION}${RESET} tag on remote..."
|
||||
git push --delete origin $VERSION
|
||||
|
||||
echo -e "Tagging ${BGREEN}HEAD${RESET} with ${BCYAN}${VERSION}${RESET} on locally..."
|
||||
echo -e "Tagging ${BGREEN}HEAD${RESET} with ${BCYAN}${VERSION}${RESET} locally..."
|
||||
if ! git tag -fa $VERSION; then
|
||||
echo "Existing/invalid tag"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
echo -e "Pushing updated tags to origin remote..."
|
||||
echo -e "Deleting ${BCYAN}${LATEST_TAG}${RESET} tag on remote..."
|
||||
git push --delete origin $LATEST_TAG
|
||||
|
||||
echo -e "Tagging ${BGREEN}HEAD${RESET} with ${BCYAN}${LATEST_TAG}${RESET} locally..."
|
||||
git tag -fa $LATEST_TAG
|
||||
|
||||
echo -e "Pushing updated tags to remote..."
|
||||
git push origin --tags
|
||||
fi
|
||||
exit 0
|
||||
|
@ -7,15 +7,13 @@ from hashlib import sha1
|
||||
from random import randbytes
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from fastapi import Body, Depends, Path, Query, Response
|
||||
from fastapi import Body, Path, Query, Response
|
||||
from fastapi.routing import APIRouter
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from starlette.exceptions import HTTPException
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from invokeai.app.services.invocation_services import InvocationServices
|
||||
from invokeai.app.services.model_install import ModelInstallJob
|
||||
from invokeai.app.services.model_manager import ModelManagerServiceBase
|
||||
from invokeai.app.services.model_records import (
|
||||
DuplicateModelException,
|
||||
InvalidModelException,
|
||||
@ -41,22 +39,6 @@ from ..dependencies import ApiDependencies
|
||||
model_manager_router = APIRouter(prefix="/v2/models", tags=["model_manager"])
|
||||
|
||||
|
||||
def get_services() -> InvocationServices:
|
||||
"""DI magic to return services from the ApiDependencies global."""
|
||||
return ApiDependencies.invoker.services
|
||||
|
||||
|
||||
Services = Annotated[InvocationServices, Depends(get_services)]
|
||||
|
||||
|
||||
def get_model_manager(services: Services) -> ModelManagerServiceBase:
|
||||
"""DI magic to return the model manager from the ApiDependencies global."""
|
||||
return services.model_manager
|
||||
|
||||
|
||||
ModelManager = Annotated[ModelManagerServiceBase, Depends(get_model_manager)]
|
||||
|
||||
|
||||
class ModelsList(BaseModel):
|
||||
"""Return list of configs."""
|
||||
|
||||
@ -96,6 +78,7 @@ example_model_config = {
|
||||
"prediction_type": "epsilon",
|
||||
"repo_variant": "fp16",
|
||||
"upcast_attention": False,
|
||||
"ztsnr_training": False,
|
||||
}
|
||||
|
||||
example_model_input = {
|
||||
@ -106,6 +89,7 @@ example_model_input = {
|
||||
"format": "checkpoint",
|
||||
"config": "configs/stable-diffusion/v1-inference.yaml",
|
||||
"description": "Model description",
|
||||
"vae": None,
|
||||
"variant": "normal",
|
||||
}
|
||||
|
||||
@ -157,7 +141,6 @@ example_model_metadata = {
|
||||
operation_id="list_model_records",
|
||||
)
|
||||
async def list_model_records(
|
||||
model_manager: ModelManager,
|
||||
base_models: Optional[List[BaseModelType]] = Query(default=None, description="Base models to include"),
|
||||
model_type: Optional[ModelType] = Query(default=None, description="The type of model to get"),
|
||||
model_name: Optional[str] = Query(default=None, description="Exact match on the name of the model"),
|
||||
@ -166,7 +149,7 @@ async def list_model_records(
|
||||
),
|
||||
) -> ModelsList:
|
||||
"""Get a list of models."""
|
||||
record_store = model_manager.store
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
found_models: list[AnyModelConfig] = []
|
||||
if base_models:
|
||||
for base_model in base_models:
|
||||
@ -188,14 +171,15 @@ async def list_model_records(
|
||||
response_model=AnyModelConfig,
|
||||
)
|
||||
async def get_model_records_by_attrs(
|
||||
model_manager: ModelManager,
|
||||
name: str = Query(description="The name of the model"),
|
||||
type: ModelType = Query(description="The type of the model"),
|
||||
base: BaseModelType = Query(description="The base model of the model"),
|
||||
) -> AnyModelConfig:
|
||||
"""Gets a model by its attributes. The main use of this route is to provide backwards compatibility with the old
|
||||
model manager, which identified models by a combination of name, base and type."""
|
||||
configs = model_manager.store.search_by_attr(base_model=base, model_type=type, model_name=name)
|
||||
configs = ApiDependencies.invoker.services.model_manager.store.search_by_attr(
|
||||
base_model=base, model_type=type, model_name=name
|
||||
)
|
||||
if not configs:
|
||||
raise HTTPException(status_code=404, detail="No model found with these attributes")
|
||||
|
||||
@ -215,11 +199,10 @@ async def get_model_records_by_attrs(
|
||||
},
|
||||
)
|
||||
async def get_model_record(
|
||||
model_manager: ModelManager,
|
||||
key: str = Path(description="Key of the model record to fetch."),
|
||||
) -> AnyModelConfig:
|
||||
"""Get a model record"""
|
||||
record_store = model_manager.store
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
try:
|
||||
config: AnyModelConfig = record_store.get_model(key)
|
||||
return config
|
||||
@ -229,15 +212,13 @@ async def get_model_record(
|
||||
|
||||
@model_manager_router.get("/summary", operation_id="list_model_summary")
|
||||
async def list_model_summary(
|
||||
model_manager: ModelManager,
|
||||
page: int = Query(default=0, description="The page to get"),
|
||||
per_page: int = Query(default=10, description="The number of models per page"),
|
||||
order_by: ModelRecordOrderBy = Query(default=ModelRecordOrderBy.Default, description="The attribute to order by"),
|
||||
) -> PaginatedResults[ModelSummary]:
|
||||
"""Gets a page of model summary data."""
|
||||
results: PaginatedResults[ModelSummary] = model_manager.store.list_models(
|
||||
page=page, per_page=per_page, order_by=order_by
|
||||
)
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
results: PaginatedResults[ModelSummary] = record_store.list_models(page=page, per_page=per_page, order_by=order_by)
|
||||
return results
|
||||
|
||||
|
||||
@ -253,11 +234,11 @@ async def list_model_summary(
|
||||
},
|
||||
)
|
||||
async def get_model_metadata(
|
||||
model_manager: ModelManager,
|
||||
key: str = Path(description="Key of the model repo metadata to fetch."),
|
||||
) -> Optional[AnyModelRepoMetadata]:
|
||||
"""Get a model metadata object."""
|
||||
result: Optional[AnyModelRepoMetadata] = model_manager.store.get_metadata(key)
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
result: Optional[AnyModelRepoMetadata] = record_store.get_metadata(key)
|
||||
|
||||
return result
|
||||
|
||||
@ -266,9 +247,10 @@ async def get_model_metadata(
|
||||
"/tags",
|
||||
operation_id="list_tags",
|
||||
)
|
||||
async def list_tags(model_manager: ModelManager) -> Set[str]:
|
||||
async def list_tags() -> Set[str]:
|
||||
"""Get a unique set of all the model tags."""
|
||||
result: Set[str] = model_manager.store.list_tags()
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
result: Set[str] = record_store.list_tags()
|
||||
return result
|
||||
|
||||
|
||||
@ -288,8 +270,6 @@ class FoundModel(BaseModel):
|
||||
response_model=List[FoundModel],
|
||||
)
|
||||
async def scan_for_models(
|
||||
model_manager: ModelManager,
|
||||
services: Services,
|
||||
scan_path: str = Query(description="Directory path to search for models", default=None),
|
||||
) -> List[FoundModel]:
|
||||
path = pathlib.Path(scan_path)
|
||||
@ -302,7 +282,7 @@ async def scan_for_models(
|
||||
search = ModelSearch()
|
||||
try:
|
||||
found_model_paths = search.search(path)
|
||||
models_path = services.configuration.models_path
|
||||
models_path = ApiDependencies.invoker.services.configuration.models_path
|
||||
|
||||
# If the search path includes the main models directory, we need to exclude core models from the list.
|
||||
# TODO(MM2): Core models should be handled by the model manager so we can determine if they are installed
|
||||
@ -310,7 +290,7 @@ async def scan_for_models(
|
||||
core_models_path = pathlib.Path(models_path, "core").resolve()
|
||||
non_core_model_paths = [p for p in found_model_paths if not p.is_relative_to(core_models_path)]
|
||||
|
||||
installed_models = model_manager.store.search_by_attr()
|
||||
installed_models = ApiDependencies.invoker.services.model_manager.store.search_by_attr()
|
||||
resolved_installed_model_paths: list[str] = []
|
||||
installed_model_sources: list[str] = []
|
||||
|
||||
@ -348,11 +328,10 @@ async def scan_for_models(
|
||||
operation_id="search_by_metadata_tags",
|
||||
)
|
||||
async def search_by_metadata_tags(
|
||||
model_manager: ModelManager,
|
||||
tags: Set[str] = Query(default=None, description="Tags to search for"),
|
||||
) -> ModelsList:
|
||||
"""Get a list of models."""
|
||||
record_store = model_manager.store
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
results = record_store.search_by_metadata_tag(tags)
|
||||
return ModelsList(models=results)
|
||||
|
||||
@ -372,15 +351,14 @@ async def search_by_metadata_tags(
|
||||
status_code=200,
|
||||
)
|
||||
async def update_model_record(
|
||||
services: Services,
|
||||
key: Annotated[str, Path(description="Unique key of model")],
|
||||
info: Annotated[
|
||||
AnyModelConfig, Body(description="Model config", discriminator="type", example=example_model_input)
|
||||
],
|
||||
) -> AnyModelConfig:
|
||||
"""Update model contents with a new config. If the model name or base fields are changed, then the model is renamed."""
|
||||
logger = services.logger
|
||||
record_store = services.model_manager.store
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
try:
|
||||
model_response: AnyModelConfig = record_store.update_model(key, config=info)
|
||||
logger.info(f"Updated model: {key}")
|
||||
@ -402,7 +380,6 @@ async def update_model_record(
|
||||
status_code=204,
|
||||
)
|
||||
async def del_model_record(
|
||||
services: Services,
|
||||
key: str = Path(description="Unique key of model to remove from model registry."),
|
||||
) -> Response:
|
||||
"""
|
||||
@ -411,10 +388,10 @@ async def del_model_record(
|
||||
The configuration record will be removed. The corresponding weights files will be
|
||||
deleted as well if they reside within the InvokeAI "models" directory.
|
||||
"""
|
||||
logger = services.logger
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
|
||||
try:
|
||||
installer = services.model_manager.install
|
||||
installer = ApiDependencies.invoker.services.model_manager.install
|
||||
installer.delete(key)
|
||||
logger.info(f"Deleted model: {key}")
|
||||
return Response(status_code=204)
|
||||
@ -437,14 +414,13 @@ async def del_model_record(
|
||||
status_code=201,
|
||||
)
|
||||
async def add_model_record(
|
||||
services: Services,
|
||||
config: Annotated[
|
||||
AnyModelConfig, Body(description="Model config", discriminator="type", example=example_model_input)
|
||||
],
|
||||
) -> AnyModelConfig:
|
||||
"""Add a model using the configuration information appropriate for its type."""
|
||||
logger = services.logger
|
||||
record_store = services.model_manager.store
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
record_store = ApiDependencies.invoker.services.model_manager.store
|
||||
if config.key == "<NOKEY>":
|
||||
config.key = sha1(randbytes(100)).hexdigest()
|
||||
logger.info(f"Created model {config.key} for {config.name}")
|
||||
@ -474,7 +450,6 @@ async def add_model_record(
|
||||
status_code=201,
|
||||
)
|
||||
async def install_model(
|
||||
services: Services,
|
||||
source: str = Query(description="Model source to install, can be a local path, repo_id, or remote URL"),
|
||||
# TODO(MM2): Can we type this?
|
||||
config: Optional[Dict[str, Any]] = Body(
|
||||
@ -510,10 +485,10 @@ async def install_model(
|
||||
See the documentation for `import_model_record` for more information on
|
||||
interpreting the job information returned by this route.
|
||||
"""
|
||||
logger = services.logger
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
|
||||
try:
|
||||
installer = services.model_manager.install
|
||||
installer = ApiDependencies.invoker.services.model_manager.install
|
||||
result: ModelInstallJob = installer.heuristic_import(
|
||||
source=source,
|
||||
config=config,
|
||||
@ -536,7 +511,7 @@ async def install_model(
|
||||
"/import",
|
||||
operation_id="list_model_install_jobs",
|
||||
)
|
||||
async def list_model_install_jobs(services: Services) -> List[ModelInstallJob]:
|
||||
async def list_model_install_jobs() -> List[ModelInstallJob]:
|
||||
"""Return the list of model install jobs.
|
||||
|
||||
Install jobs have a numeric `id`, a `status`, and other fields that provide information on
|
||||
@ -556,7 +531,7 @@ async def list_model_install_jobs(services: Services) -> List[ModelInstallJob]:
|
||||
|
||||
See the example and schema below for more information.
|
||||
"""
|
||||
jobs: List[ModelInstallJob] = services.model_manager.install.list_jobs()
|
||||
jobs: List[ModelInstallJob] = ApiDependencies.invoker.services.model_manager.install.list_jobs()
|
||||
return jobs
|
||||
|
||||
|
||||
@ -568,13 +543,13 @@ async def list_model_install_jobs(services: Services) -> List[ModelInstallJob]:
|
||||
404: {"description": "No such job"},
|
||||
},
|
||||
)
|
||||
async def get_model_install_job(services: Services, id: int = Path(description="Model install id")) -> ModelInstallJob:
|
||||
async def get_model_install_job(id: int = Path(description="Model install id")) -> ModelInstallJob:
|
||||
"""
|
||||
Return model install job corresponding to the given source. See the documentation for 'List Model Install Jobs'
|
||||
for information on the format of the return value.
|
||||
"""
|
||||
try:
|
||||
result: ModelInstallJob = services.model_manager.install.get_job_by_id(id)
|
||||
result: ModelInstallJob = ApiDependencies.invoker.services.model_manager.install.get_job_by_id(id)
|
||||
return result
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
@ -589,9 +564,9 @@ async def get_model_install_job(services: Services, id: int = Path(description="
|
||||
},
|
||||
status_code=201,
|
||||
)
|
||||
async def cancel_model_install_job(services: Services, id: int = Path(description="Model install job ID")) -> None:
|
||||
async def cancel_model_install_job(id: int = Path(description="Model install job ID")) -> None:
|
||||
"""Cancel the model install job(s) corresponding to the given job ID."""
|
||||
installer = services.model_manager.install
|
||||
installer = ApiDependencies.invoker.services.model_manager.install
|
||||
try:
|
||||
job = installer.get_job_by_id(id)
|
||||
except ValueError as e:
|
||||
@ -607,9 +582,9 @@ async def cancel_model_install_job(services: Services, id: int = Path(descriptio
|
||||
400: {"description": "Bad request"},
|
||||
},
|
||||
)
|
||||
async def prune_model_install_jobs(model_manager: ModelManager) -> Response:
|
||||
async def prune_model_install_jobs() -> Response:
|
||||
"""Prune all completed and errored jobs from the install job list."""
|
||||
model_manager.install.prune_jobs()
|
||||
ApiDependencies.invoker.services.model_manager.install.prune_jobs()
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@ -621,14 +596,14 @@ async def prune_model_install_jobs(model_manager: ModelManager) -> Response:
|
||||
400: {"description": "Bad request"},
|
||||
},
|
||||
)
|
||||
async def sync_models_to_config(model_manager: ModelManager) -> Response:
|
||||
async def sync_models_to_config() -> Response:
|
||||
"""
|
||||
Traverse the models and autoimport directories.
|
||||
|
||||
Model files without a corresponding
|
||||
record in the database are added. Orphan records without a models file are deleted.
|
||||
"""
|
||||
model_manager.install.sync_to_config()
|
||||
ApiDependencies.invoker.services.model_manager.install.sync_to_config()
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
@ -646,7 +621,6 @@ async def sync_models_to_config(model_manager: ModelManager) -> Response:
|
||||
},
|
||||
)
|
||||
async def convert_model(
|
||||
services: Services,
|
||||
key: str = Path(description="Unique key of the safetensors main model to convert to diffusers format."),
|
||||
) -> AnyModelConfig:
|
||||
"""
|
||||
@ -654,11 +628,11 @@ async def convert_model(
|
||||
Note that during the conversion process the key and model hash will change.
|
||||
The return value is the model configuration for the converted model.
|
||||
"""
|
||||
model_manager = services.model_manager
|
||||
logger = services.logger
|
||||
loader = services.model_manager.load
|
||||
store = services.model_manager.store
|
||||
installer = services.model_manager.install
|
||||
model_manager = ApiDependencies.invoker.services.model_manager
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
loader = ApiDependencies.invoker.services.model_manager.load
|
||||
store = ApiDependencies.invoker.services.model_manager.store
|
||||
installer = ApiDependencies.invoker.services.model_manager.install
|
||||
|
||||
try:
|
||||
model_config = store.get_model(key)
|
||||
@ -726,7 +700,6 @@ async def convert_model(
|
||||
},
|
||||
)
|
||||
async def merge(
|
||||
services: Services,
|
||||
keys: List[str] = Body(description="Keys for two to three models to merge", min_length=2, max_length=3),
|
||||
merged_model_name: Optional[str] = Body(description="Name of destination model", default=None),
|
||||
alpha: float = Body(description="Alpha weighting strength to apply to 2d and 3d models", default=0.5),
|
||||
@ -753,11 +726,11 @@ async def merge(
|
||||
merge_dest_directory Specify a directory to store the merged model in [models directory]
|
||||
```
|
||||
"""
|
||||
logger = services.logger
|
||||
logger = ApiDependencies.invoker.services.logger
|
||||
try:
|
||||
logger.info(f"Merging models: {keys} into {merge_dest_directory or '<MODELS>'}/{merged_model_name}")
|
||||
dest = pathlib.Path(merge_dest_directory) if merge_dest_directory else None
|
||||
installer = services.model_manager.install
|
||||
installer = ApiDependencies.invoker.services.model_manager.install
|
||||
merger = ModelMerger(installer)
|
||||
model_names = [installer.record_store.get_model(x).name for x in keys]
|
||||
response = merger.merge_diffusion_models_and_save(
|
||||
|
@ -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")
|
||||
|
@ -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,23 @@ 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_by_attrs(
|
||||
model_name=name, base_model=text_encoder_info.config.base, model_type=ModelType.TextualInversion
|
||||
).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 +106,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 +157,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 +191,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 +217,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,
|
||||
|
@ -93,7 +93,7 @@ class IPAdapterInvocation(BaseInvocation):
|
||||
image_encoder_model_id = ip_adapter_info.image_encoder_model_id
|
||||
image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip()
|
||||
image_encoder_models = context.models.search_by_attrs(
|
||||
name=image_encoder_model_name, base=BaseModelType.Any, type=ModelType.CLIPVision
|
||||
model_name=image_encoder_model_name, base_model=BaseModelType.Any, model_type=ModelType.CLIPVision
|
||||
)
|
||||
assert len(image_encoder_models) == 1
|
||||
image_encoder_model = CLIPVisionModelField(key=image_encoder_models[0].key)
|
||||
|
@ -360,6 +360,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
||||
) -> ConditioningData:
|
||||
positive_cond_data = context.conditioning.load(self.positive_conditioning.conditioning_name)
|
||||
c = positive_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype)
|
||||
extra_conditioning_info = c.extra_conditioning
|
||||
|
||||
negative_cond_data = context.conditioning.load(self.negative_conditioning.conditioning_name)
|
||||
uc = negative_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype)
|
||||
@ -369,6 +370,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
||||
text_embeddings=c,
|
||||
guidance_scale=self.cfg_scale,
|
||||
guidance_rescale_multiplier=self.cfg_rescale_multiplier,
|
||||
extra=extra_conditioning_info,
|
||||
postprocessing_settings=PostprocessingSettings(
|
||||
threshold=0.0, # threshold,
|
||||
warmup=0.2, # warmup,
|
||||
|
@ -166,7 +166,6 @@ two configs are kept in separate sections of the config file:
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Init file for download queue."""
|
||||
|
||||
from .download_base import DownloadJob, DownloadJobStatus, DownloadQueueServiceBase, UnknownJobIDException
|
||||
from .download_default import DownloadQueueService, TqdmProgress
|
||||
|
||||
|
@ -224,6 +224,7 @@ class DownloadQueueService(DownloadQueueServiceBase):
|
||||
job.job_started = get_iso_timestamp()
|
||||
self._do_download(job)
|
||||
self._signal_job_complete(job)
|
||||
|
||||
except (OSError, HTTPError) as excp:
|
||||
job.error_type = excp.__class__.__name__ + f"({str(excp)})"
|
||||
job.error = traceback.format_exc()
|
||||
|
@ -28,7 +28,6 @@ class InstallStatus(str, Enum):
|
||||
|
||||
WAITING = "waiting" # waiting to be dequeued
|
||||
DOWNLOADING = "downloading" # downloading of model files in process
|
||||
DOWNLOADS_DONE = "downloads_done" # downloading done, waiting to run
|
||||
RUNNING = "running" # being processed
|
||||
COMPLETED = "completed" # finished running
|
||||
ERROR = "error" # terminated with an error message
|
||||
@ -230,11 +229,6 @@ class ModelInstallJob(BaseModel):
|
||||
"""Return true if job is downloading."""
|
||||
return self.status == InstallStatus.DOWNLOADING
|
||||
|
||||
@property
|
||||
def downloads_done(self) -> bool:
|
||||
"""Return true if job's downloads ae done."""
|
||||
return self.status == InstallStatus.DOWNLOADS_DONE
|
||||
|
||||
@property
|
||||
def running(self) -> bool:
|
||||
"""Return true if job is running."""
|
||||
|
@ -28,6 +28,7 @@ from invokeai.backend.model_manager.config import (
|
||||
ModelRepoVariant,
|
||||
ModelType,
|
||||
)
|
||||
from invokeai.backend.model_manager.hash import FastModelHash
|
||||
from invokeai.backend.model_manager.metadata import (
|
||||
AnyModelRepoMetadata,
|
||||
CivitaiMetadataFetch,
|
||||
@ -150,9 +151,9 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
config = config or {}
|
||||
if not config.get("source"):
|
||||
config["source"] = model_path.resolve().as_posix()
|
||||
config["key"] = config.get("key", self._create_key())
|
||||
|
||||
info: AnyModelConfig = self._probe_model(Path(model_path), config)
|
||||
old_hash = info.current_hash
|
||||
|
||||
if preferred_name := config.get("name"):
|
||||
preferred_name = Path(preferred_name).with_suffix(model_path.suffix)
|
||||
@ -166,6 +167,8 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
raise DuplicateModelException(
|
||||
f"A model named {model_path.name} is already installed at {dest_path.as_posix()}"
|
||||
) from excp
|
||||
new_hash = FastModelHash.hash(new_path)
|
||||
assert new_hash == old_hash, f"{model_path}: Model hash changed during installation, possibly corrupted."
|
||||
|
||||
return self._register(
|
||||
new_path,
|
||||
@ -279,9 +282,9 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
self._logger.info("Model installer (re)initialized")
|
||||
|
||||
def scan_directory(self, scan_dir: Path, install: bool = False) -> List[str]: # noqa D102
|
||||
self._cached_model_paths = {Path(x.path).absolute() for x in self.record_store.all_models()}
|
||||
self._cached_model_paths = {Path(x.path) for x in self.record_store.all_models()}
|
||||
callback = self._scan_install if install else self._scan_register
|
||||
search = ModelSearch(on_model_found=callback, config=self._app_config)
|
||||
search = ModelSearch(on_model_found=callback)
|
||||
self._models_installed.clear()
|
||||
search.search(scan_dir)
|
||||
return list(self._models_installed)
|
||||
@ -367,7 +370,7 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
self._signal_job_errored(job)
|
||||
|
||||
elif (
|
||||
job.waiting or job.downloads_done
|
||||
job.waiting or job.downloading
|
||||
): # local jobs will be in waiting state, remote jobs will be downloading state
|
||||
job.total_bytes = self._stat_size(job.local_path)
|
||||
job.bytes = job.total_bytes
|
||||
@ -445,7 +448,7 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
installed.update(self.scan_directory(models_dir))
|
||||
self._logger.info(f"{len(installed)} new models registered; {len(defunct_models)} unregistered")
|
||||
|
||||
def _sync_model_path(self, key: str) -> AnyModelConfig:
|
||||
def _sync_model_path(self, key: str, ignore_hash_change: bool = False) -> AnyModelConfig:
|
||||
"""
|
||||
Move model into the location indicated by its basetype, type and name.
|
||||
|
||||
@ -466,7 +469,14 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
new_path = models_dir / model.base.value / model.type.value / model.name
|
||||
self._logger.info(f"Moving {model.name} to {new_path}.")
|
||||
new_path = self._move_model(old_path, new_path)
|
||||
new_hash = FastModelHash.hash(new_path)
|
||||
model.path = new_path.relative_to(models_dir).as_posix()
|
||||
if model.current_hash != new_hash:
|
||||
assert (
|
||||
ignore_hash_change
|
||||
), f"{model.name}: Model hash changed during installation, model is possibly corrupted"
|
||||
model.current_hash = new_hash
|
||||
self._logger.info(f"Model has new hash {model.current_hash}, but will continue to be identified by {key}")
|
||||
self.record_store.update_model(key, model)
|
||||
return model
|
||||
|
||||
@ -532,9 +542,9 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
def _register(
|
||||
self, model_path: Path, config: Optional[Dict[str, Any]] = None, info: Optional[AnyModelConfig] = None
|
||||
) -> str:
|
||||
# Note that we may be passed a pre-populated AnyModelConfig object,
|
||||
# in which case the key field should have been populated by the caller (e.g. in `install_path`).
|
||||
config["key"] = config.get("key", self._create_key())
|
||||
key = self._create_key()
|
||||
if config and not config.get("key", None):
|
||||
config["key"] = key
|
||||
info = info or ModelProbe.probe(model_path, config)
|
||||
|
||||
model_path = model_path.absolute()
|
||||
@ -739,8 +749,8 @@ class ModelInstallService(ModelInstallServiceBase):
|
||||
self._download_cache.pop(download_job.source, None)
|
||||
|
||||
# are there any more active jobs left in this task?
|
||||
if install_job.downloading and all(x.complete for x in install_job.download_parts):
|
||||
install_job.status = InstallStatus.DOWNLOADS_DONE
|
||||
if all(x.complete for x in install_job.download_parts):
|
||||
# now enqueue job for actual installation into the models directory
|
||||
self._install_queue.put(install_job)
|
||||
|
||||
# Let other threads know that the number of downloads has changed
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Init file for model record services."""
|
||||
|
||||
from .model_records_base import ( # noqa F401
|
||||
DuplicateModelException,
|
||||
InvalidModelException,
|
||||
|
@ -39,6 +39,7 @@ Typical usage:
|
||||
configs = store.search_by_attr(base_model='sd-2', model_type='main')
|
||||
"""
|
||||
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
from math import ceil
|
||||
|
@ -65,86 +65,75 @@ class InvocationContextInterface:
|
||||
|
||||
class BoardsInterface(InvocationContextInterface):
|
||||
def create(self, board_name: str) -> BoardDTO:
|
||||
"""Creates a board.
|
||||
"""
|
||||
Creates a board.
|
||||
|
||||
Args:
|
||||
board_name: The name of the board to create.
|
||||
|
||||
Returns:
|
||||
The created board DTO.
|
||||
:param board_name: The name of the board to create.
|
||||
"""
|
||||
return self._services.boards.create(board_name)
|
||||
|
||||
def get_dto(self, board_id: str) -> BoardDTO:
|
||||
"""Gets a board DTO.
|
||||
"""
|
||||
Gets a board DTO.
|
||||
|
||||
Args:
|
||||
board_id: The ID of the board to get.
|
||||
|
||||
Returns:
|
||||
The board DTO.
|
||||
:param board_id: The ID of the board to get.
|
||||
"""
|
||||
return self._services.boards.get_dto(board_id)
|
||||
|
||||
def get_all(self) -> list[BoardDTO]:
|
||||
"""Gets all boards.
|
||||
|
||||
Returns:
|
||||
A list of all boards.
|
||||
"""
|
||||
Gets all boards.
|
||||
"""
|
||||
return self._services.boards.get_all()
|
||||
|
||||
def add_image_to_board(self, board_id: str, image_name: str) -> None:
|
||||
"""Adds an image to a board.
|
||||
"""
|
||||
Adds an image to a board.
|
||||
|
||||
Args:
|
||||
board_id: The ID of the board to add the image to.
|
||||
image_name: The name of the image to add to the board.
|
||||
:param board_id: The ID of the board to add the image to.
|
||||
:param image_name: The name of the image to add to the board.
|
||||
"""
|
||||
return self._services.board_images.add_image_to_board(board_id, image_name)
|
||||
|
||||
def get_all_image_names_for_board(self, board_id: str) -> list[str]:
|
||||
"""Gets all image names for a board.
|
||||
"""
|
||||
Gets all image names for a board.
|
||||
|
||||
Args:
|
||||
board_id: The ID of the board to get the image names for.
|
||||
|
||||
Returns:
|
||||
A list of all image names for the board.
|
||||
:param board_id: The ID of the board to get the image names for.
|
||||
"""
|
||||
return self._services.board_images.get_all_board_image_names_for_board(board_id)
|
||||
|
||||
|
||||
class LoggerInterface(InvocationContextInterface):
|
||||
def debug(self, message: str) -> None:
|
||||
"""Logs a debug message.
|
||||
"""
|
||||
Logs a debug message.
|
||||
|
||||
Args:
|
||||
message: The message to log.
|
||||
:param message: The message to log.
|
||||
"""
|
||||
self._services.logger.debug(message)
|
||||
|
||||
def info(self, message: str) -> None:
|
||||
"""Logs an info message.
|
||||
"""
|
||||
Logs an info message.
|
||||
|
||||
Args:
|
||||
message: The message to log.
|
||||
:param message: The message to log.
|
||||
"""
|
||||
self._services.logger.info(message)
|
||||
|
||||
def warning(self, message: str) -> None:
|
||||
"""Logs a warning message.
|
||||
"""
|
||||
Logs a warning message.
|
||||
|
||||
Args:
|
||||
message: The message to log.
|
||||
:param message: The message to log.
|
||||
"""
|
||||
self._services.logger.warning(message)
|
||||
|
||||
def error(self, message: str) -> None:
|
||||
"""Logs an error message.
|
||||
"""
|
||||
Logs an error message.
|
||||
|
||||
Args:
|
||||
message: The message to log.
|
||||
:param message: The message to log.
|
||||
"""
|
||||
self._services.logger.error(message)
|
||||
|
||||
@ -157,23 +146,20 @@ class ImagesInterface(InvocationContextInterface):
|
||||
image_category: ImageCategory = ImageCategory.GENERAL,
|
||||
metadata: Optional[MetadataField] = None,
|
||||
) -> ImageDTO:
|
||||
"""Saves an image, returning its DTO.
|
||||
"""
|
||||
Saves an image, returning its DTO.
|
||||
|
||||
If the current queue item has a workflow or metadata, it is automatically saved with the image.
|
||||
|
||||
Args:
|
||||
image: The image to save, as a PIL image.
|
||||
board_id: The board ID to add the image to, if it should be added. It the invocation \
|
||||
:param image: The image to save, as a PIL image.
|
||||
:param board_id: The board ID to add the image to, if it should be added. It the invocation \
|
||||
inherits from `WithBoard`, that board will be used automatically. **Use this only if \
|
||||
you want to override or provide a board manually!**
|
||||
image_category: The category of the image. Only the GENERAL category is added \
|
||||
:param image_category: The category of the image. Only the GENERAL category is added \
|
||||
to the gallery.
|
||||
metadata: The metadata to save with the image, if it should have any. If the \
|
||||
:param metadata: The metadata to save with the image, if it should have any. If the \
|
||||
invocation inherits from `WithMetadata`, that metadata will be used automatically. \
|
||||
**Use this only if you want to override or provide metadata manually!**
|
||||
|
||||
Returns:
|
||||
The saved image DTO.
|
||||
"""
|
||||
|
||||
# If `metadata` is provided directly, use that. Else, use the metadata provided by `WithMetadata`, falling back to None.
|
||||
@ -203,14 +189,11 @@ class ImagesInterface(InvocationContextInterface):
|
||||
)
|
||||
|
||||
def get_pil(self, image_name: str, mode: IMAGE_MODES | None = None) -> Image:
|
||||
"""Gets an image as a PIL Image object.
|
||||
"""
|
||||
Gets an image as a PIL Image object.
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to get.
|
||||
mode: The color mode to convert the image to. If None, the original mode is used.
|
||||
|
||||
Returns:
|
||||
The image as a PIL Image object.
|
||||
:param image_name: The name of the image to get.
|
||||
:param mode: The color mode to convert the image to. If None, the original mode is used.
|
||||
"""
|
||||
image = self._services.images.get_pil_image(image_name)
|
||||
if mode and mode != image.mode:
|
||||
@ -223,76 +206,58 @@ class ImagesInterface(InvocationContextInterface):
|
||||
return image
|
||||
|
||||
def get_metadata(self, image_name: str) -> Optional[MetadataField]:
|
||||
"""Gets an image's metadata, if it has any.
|
||||
"""
|
||||
Gets an image's metadata, if it has any.
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to get the metadata for.
|
||||
|
||||
Returns:
|
||||
The image's metadata, if it has any.
|
||||
:param image_name: The name of the image to get the metadata for.
|
||||
"""
|
||||
return self._services.images.get_metadata(image_name)
|
||||
|
||||
def get_dto(self, image_name: str) -> ImageDTO:
|
||||
"""Gets an image as an ImageDTO object.
|
||||
"""
|
||||
Gets an image as an ImageDTO object.
|
||||
|
||||
Args:
|
||||
image_name: The name of the image to get.
|
||||
|
||||
Returns:
|
||||
The image as an ImageDTO object.
|
||||
:param image_name: The name of the image to get.
|
||||
"""
|
||||
return self._services.images.get_dto(image_name)
|
||||
|
||||
|
||||
class TensorsInterface(InvocationContextInterface):
|
||||
def save(self, tensor: Tensor) -> str:
|
||||
"""Saves a tensor, returning its name.
|
||||
"""
|
||||
Saves a tensor, returning its name.
|
||||
|
||||
Args:
|
||||
tensor: The tensor to save.
|
||||
|
||||
Returns:
|
||||
The name of the saved tensor.
|
||||
:param tensor: The tensor to save.
|
||||
"""
|
||||
|
||||
name = self._services.tensors.save(obj=tensor)
|
||||
return name
|
||||
|
||||
def load(self, name: str) -> Tensor:
|
||||
"""Loads a tensor by name.
|
||||
"""
|
||||
Loads a tensor by name.
|
||||
|
||||
Args:
|
||||
name: The name of the tensor to load.
|
||||
|
||||
Returns:
|
||||
The loaded tensor.
|
||||
:param name: The name of the tensor to load.
|
||||
"""
|
||||
return self._services.tensors.load(name)
|
||||
|
||||
|
||||
class ConditioningInterface(InvocationContextInterface):
|
||||
def save(self, conditioning_data: ConditioningFieldData) -> str:
|
||||
"""Saves a conditioning data object, returning its name.
|
||||
"""
|
||||
Saves a conditioning data object, returning its name.
|
||||
|
||||
Args:
|
||||
conditioning_data: The conditioning data to save.
|
||||
|
||||
Returns:
|
||||
The name of the saved conditioning data.
|
||||
:param conditioning_data: The conditioning data to save.
|
||||
"""
|
||||
|
||||
name = self._services.conditioning.save(obj=conditioning_data)
|
||||
return name
|
||||
|
||||
def load(self, name: str) -> ConditioningFieldData:
|
||||
"""Loads conditioning data by name.
|
||||
"""
|
||||
Loads conditioning data by name.
|
||||
|
||||
Args:
|
||||
name: The name of the conditioning data to load.
|
||||
|
||||
Returns:
|
||||
The loaded conditioning data.
|
||||
:param name: The name of the conditioning data to load.
|
||||
"""
|
||||
|
||||
return self._services.conditioning.load(name)
|
||||
@ -300,25 +265,20 @@ class ConditioningInterface(InvocationContextInterface):
|
||||
|
||||
class ModelsInterface(InvocationContextInterface):
|
||||
def exists(self, key: str) -> bool:
|
||||
"""Checks if a model exists.
|
||||
"""
|
||||
Checks if a model exists.
|
||||
|
||||
Args:
|
||||
key: The key of the model.
|
||||
|
||||
Returns:
|
||||
True if the model exists, False if not.
|
||||
:param key: The key of the model.
|
||||
"""
|
||||
return self._services.model_manager.store.exists(key)
|
||||
|
||||
def load(self, key: str, submodel_type: Optional[SubModelType] = None) -> LoadedModel:
|
||||
"""Loads a model.
|
||||
"""
|
||||
Loads a model.
|
||||
|
||||
Args:
|
||||
key: The key of the model.
|
||||
submodel_type: The submodel of the model to get.
|
||||
|
||||
Returns:
|
||||
An object representing the loaded model.
|
||||
:param key: The key of the model.
|
||||
:param submodel_type: The submodel of the model to get.
|
||||
:returns: An object representing the loaded model.
|
||||
"""
|
||||
|
||||
# The model manager emits events as it loads the model. It needs the context data to build
|
||||
@ -329,95 +289,75 @@ class ModelsInterface(InvocationContextInterface):
|
||||
)
|
||||
|
||||
def load_by_attrs(
|
||||
self, name: str, base: BaseModelType, type: ModelType, submodel_type: Optional[SubModelType] = None
|
||||
self, model_name: str, base_model: BaseModelType, model_type: ModelType, submodel: Optional[SubModelType] = None
|
||||
) -> LoadedModel:
|
||||
"""Loads a model by its attributes.
|
||||
"""
|
||||
Loads a model by its attributes.
|
||||
|
||||
Args:
|
||||
name: Name of the model.
|
||||
base: The models' base type, e.g. `BaseModelType.StableDiffusion1`, `BaseModelType.StableDiffusionXL`, etc.
|
||||
type: Type of the model, e.g. `ModelType.Main`, `ModelType.Vae`, etc.
|
||||
submodel_type: The type of submodel to load, e.g. `SubModelType.UNet`, `SubModelType.TextEncoder`, etc. Only main
|
||||
models have submodels.
|
||||
|
||||
Returns:
|
||||
An object representing the loaded model.
|
||||
:param model_name: Name of to be fetched.
|
||||
:param base_model: Base model
|
||||
:param model_type: Type of the model
|
||||
:param submodel: For main (pipeline models), the submodel to fetch
|
||||
"""
|
||||
return self._services.model_manager.load_model_by_attr(
|
||||
model_name=name,
|
||||
base_model=base,
|
||||
model_type=type,
|
||||
submodel=submodel_type,
|
||||
model_name=model_name,
|
||||
base_model=base_model,
|
||||
model_type=model_type,
|
||||
submodel=submodel,
|
||||
context_data=self._data,
|
||||
)
|
||||
|
||||
def get_config(self, key: str) -> AnyModelConfig:
|
||||
"""Gets a model's config.
|
||||
"""
|
||||
Gets a model's info, an dict-like object.
|
||||
|
||||
Args:
|
||||
key: The key of the model.
|
||||
|
||||
Returns:
|
||||
The model's config.
|
||||
:param key: The key of the model.
|
||||
"""
|
||||
return self._services.model_manager.store.get_model(key=key)
|
||||
|
||||
def get_metadata(self, key: str) -> Optional[AnyModelRepoMetadata]:
|
||||
"""Gets a model's metadata, if it has any.
|
||||
"""
|
||||
Gets a model's metadata, if it has any.
|
||||
|
||||
Args:
|
||||
key: The key of the model.
|
||||
|
||||
Returns:
|
||||
The model's metadata, if it has any.
|
||||
:param key: The key of the model.
|
||||
"""
|
||||
return self._services.model_manager.store.get_metadata(key=key)
|
||||
|
||||
def search_by_path(self, path: Path) -> list[AnyModelConfig]:
|
||||
"""Searches for models by path.
|
||||
"""
|
||||
Searches for models by path.
|
||||
|
||||
Args:
|
||||
path: The path to search for.
|
||||
|
||||
Returns:
|
||||
A list of models that match the path.
|
||||
:param path: The path to search for.
|
||||
"""
|
||||
return self._services.model_manager.store.search_by_path(path)
|
||||
|
||||
def search_by_attrs(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
base: Optional[BaseModelType] = None,
|
||||
type: Optional[ModelType] = None,
|
||||
format: Optional[ModelFormat] = None,
|
||||
model_name: Optional[str] = None,
|
||||
base_model: Optional[BaseModelType] = None,
|
||||
model_type: Optional[ModelType] = None,
|
||||
model_format: Optional[ModelFormat] = None,
|
||||
) -> list[AnyModelConfig]:
|
||||
"""Searches for models by attributes.
|
||||
"""
|
||||
Searches for models by attributes.
|
||||
|
||||
Args:
|
||||
name: The name to search for (exact match).
|
||||
base: The base to search for, e.g. `BaseModelType.StableDiffusion1`, `BaseModelType.StableDiffusionXL`, etc.
|
||||
type: Type type of model to search for, e.g. `ModelType.Main`, `ModelType.Vae`, etc.
|
||||
format: The format of model to search for, e.g. `ModelFormat.Checkpoint`, `ModelFormat.Diffusers`, etc.
|
||||
|
||||
Returns:
|
||||
A list of models that match the attributes.
|
||||
:param model_name: Name of to be fetched.
|
||||
:param base_model: Base model
|
||||
:param model_type: Type of the model
|
||||
:param submodel: For main (pipeline models), the submodel to fetch
|
||||
"""
|
||||
|
||||
return self._services.model_manager.store.search_by_attr(
|
||||
model_name=name,
|
||||
base_model=base,
|
||||
model_type=type,
|
||||
model_format=format,
|
||||
model_name=model_name,
|
||||
base_model=base_model,
|
||||
model_type=model_type,
|
||||
model_format=model_format,
|
||||
)
|
||||
|
||||
|
||||
class ConfigInterface(InvocationContextInterface):
|
||||
def get(self) -> InvokeAIAppConfig:
|
||||
"""Gets the app's config.
|
||||
|
||||
Returns:
|
||||
The app's config.
|
||||
"""
|
||||
"""Gets the app's config."""
|
||||
|
||||
return self._services.configuration.get_config()
|
||||
|
||||
@ -430,11 +370,7 @@ class UtilInterface(InvocationContextInterface):
|
||||
self._cancel_event = cancel_event
|
||||
|
||||
def is_canceled(self) -> bool:
|
||||
"""Checks if the current session has been canceled.
|
||||
|
||||
Returns:
|
||||
True if the current session has been canceled, False if not.
|
||||
"""
|
||||
"""Checks if the current invocation has been canceled."""
|
||||
return self._cancel_event.is_set()
|
||||
|
||||
def sd_step_callback(self, intermediate_state: PipelineIntermediateState, base_model: BaseModelType) -> None:
|
||||
@ -444,9 +380,8 @@ class UtilInterface(InvocationContextInterface):
|
||||
|
||||
This should be called after each denoising step.
|
||||
|
||||
Args:
|
||||
intermediate_state: The intermediate state of the diffusion pipeline.
|
||||
base_model: The base model for the current denoising step.
|
||||
:param intermediate_state: The intermediate state of the diffusion pipeline.
|
||||
:param base_model: The base model for the current denoising step.
|
||||
"""
|
||||
|
||||
stable_diffusion_step_callback(
|
||||
@ -459,17 +394,8 @@ class UtilInterface(InvocationContextInterface):
|
||||
|
||||
|
||||
class InvocationContext:
|
||||
"""Provides access to various services and data for the current invocation.
|
||||
|
||||
Attributes:
|
||||
images (ImagesInterface): Methods to save, get and update images and their metadata.
|
||||
tensors (TensorsInterface): Methods to save and get tensors, including image, noise, masks, and masked images.
|
||||
conditioning (ConditioningInterface): Methods to save and get conditioning data.
|
||||
models (ModelsInterface): Methods to check if a model exists, get a model, and get a model's info.
|
||||
logger (LoggerInterface): The app logger.
|
||||
config (ConfigInterface): The app config.
|
||||
util (UtilInterface): Utility methods, including a method to check if an invocation was canceled and step callbacks.
|
||||
boards (BoardsInterface): Methods to interact with boards.
|
||||
"""
|
||||
The `InvocationContext` provides access to various services and data for the current invocation.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@ -512,14 +438,11 @@ def build_invocation_context(
|
||||
data: InvocationContextData,
|
||||
cancel_event: threading.Event,
|
||||
) -> InvocationContext:
|
||||
"""Builds the invocation context for a specific invocation execution.
|
||||
"""
|
||||
Builds the invocation context for a specific invocation execution.
|
||||
|
||||
Args:
|
||||
services: The invocation services to wrap.
|
||||
data: The invocation context data.
|
||||
|
||||
Returns:
|
||||
The invocation context.
|
||||
:param services: The invocation services to wrap.
|
||||
:param data: The invocation context data.
|
||||
"""
|
||||
|
||||
logger = LoggerInterface(services=services, data=data)
|
||||
|
@ -17,7 +17,8 @@ class MigrateCallback(Protocol):
|
||||
See :class:`Migration` for an example.
|
||||
"""
|
||||
|
||||
def __call__(self, cursor: sqlite3.Cursor) -> None: ...
|
||||
def __call__(self, cursor: sqlite3.Cursor) -> None:
|
||||
...
|
||||
|
||||
|
||||
class MigrationError(RuntimeError):
|
||||
|
@ -1,11 +1,6 @@
|
||||
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
|
||||
from typing import List
|
||||
|
||||
|
||||
def extract_ti_triggers_from_prompt(prompt: str) -> List[str]:
|
||||
@ -13,35 +8,3 @@ def extract_ti_triggers_from_prompt(prompt: str) -> List[str]:
|
||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", prompt):
|
||||
ti_triggers.append(str(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(
|
||||
name=name_or_key, base=base, 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
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Initialization file for invokeai.backend.image_util methods.
|
||||
"""
|
||||
|
||||
from .patchmatch import PatchMatch # noqa: F401
|
||||
from .pngwriter import PngWriter, PromptFormatter, retrieve_metadata, write_metadata # noqa: F401
|
||||
from .seamless import configure_model_padding # noqa: F401
|
||||
|
@ -3,7 +3,6 @@ This module defines a singleton object, "invisible_watermark" that
|
||||
wraps the invisible watermark model. It respects the global "invisible_watermark"
|
||||
configuration variable, that allows the watermarking to be supressed.
|
||||
"""
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from imwatermark import WatermarkEncoder
|
||||
|
@ -4,7 +4,6 @@ wraps the actual patchmatch object. It respects the global
|
||||
"try_patchmatch" attribute, so that patchmatch loading can
|
||||
be suppressed or deferred
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
import invokeai.backend.util.logging as logger
|
||||
|
@ -6,7 +6,6 @@ PngWriter -- Converts Images generated by T2I into PNGs, finds
|
||||
|
||||
Exports function retrieve_metadata(path)
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
@ -3,7 +3,6 @@ This module defines a singleton object, "safety_checker" that
|
||||
wraps the safety_checker model. It respects the global "nsfw_checker"
|
||||
configuration variable, that allows the checker to be supressed.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Check that the invokeai_root is correctly configured and exit if not.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from invokeai.app.services.config import InvokeAIAppConfig
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Utility (backend) functions used by model_install.py"""
|
||||
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Re-export frequently-used symbols from the Model Manager backend."""
|
||||
|
||||
from .config import (
|
||||
AnyModel,
|
||||
AnyModelConfig,
|
||||
|
@ -19,7 +19,6 @@ Typical usage:
|
||||
Validation errors will raise an InvalidModelConfigException error.
|
||||
|
||||
"""
|
||||
|
||||
import time
|
||||
from enum import Enum
|
||||
from typing import Literal, Optional, Type, Union
|
||||
|
@ -15,7 +15,7 @@
|
||||
#
|
||||
# Adapted for use in InvokeAI by Lincoln Stein, July 2023
|
||||
#
|
||||
"""Conversion script for the Stable Diffusion checkpoints."""
|
||||
""" Conversion script for the Stable Diffusion checkpoints."""
|
||||
|
||||
import re
|
||||
from contextlib import nullcontext
|
||||
|
@ -2,7 +2,6 @@
|
||||
"""
|
||||
Init file for the model loader.
|
||||
"""
|
||||
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Disk-based converted model cache.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
|
||||
|
@ -14,7 +14,6 @@ Use like this:
|
||||
).load_model(model_config, submodel_type)
|
||||
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team
|
||||
"""Class for LoRA model loading in InvokeAI."""
|
||||
|
||||
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team
|
||||
"""Class for StableDiffusion model loading in InvokeAI."""
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2024, Lincoln D. Stein and the InvokeAI Development Team
|
||||
"""Class for TI model loading in InvokeAI."""
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
|
||||
|
@ -18,7 +18,6 @@ assert isinstance(data, CivitaiMetadata)
|
||||
if data.allow_commercial_use:
|
||||
print("Commercial use of this model is allowed")
|
||||
"""
|
||||
|
||||
from .fetch import CivitaiMetadataFetch, HuggingFaceMetadataFetch, ModelMetadataFetchBase
|
||||
from .metadata_base import (
|
||||
AnyModelRepoMetadata,
|
||||
|
@ -160,7 +160,7 @@ 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"],
|
||||
),
|
||||
|
@ -54,8 +54,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
|
||||
)
|
||||
|
||||
|
||||
@ -142,10 +142,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:
|
||||
|
@ -188,7 +188,7 @@ class ModelProbe(object):
|
||||
and fields["prediction_type"] == SchedulerPredictionType.VPrediction
|
||||
)
|
||||
|
||||
model_info = ModelConfigFactory.make_config(fields) # , key=fields.get("key", None))
|
||||
model_info = ModelConfigFactory.make_config(fields, key=fields.get("key", None))
|
||||
return model_info
|
||||
|
||||
@classmethod
|
||||
|
@ -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))
|
||||
|
@ -1,16 +1,15 @@
|
||||
# Copyright (c) 2024 Ryan Dick, Lincoln D. Stein, and the InvokeAI Development Team
|
||||
"""These classes implement model patching with LoRAs and Textual Inversions."""
|
||||
|
||||
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
|
||||
@ -169,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
|
||||
@ -266,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 = []
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Initialization file for the invokeai.backend.stable_diffusion package
|
||||
"""
|
||||
|
||||
from .diffusers_pipeline import PipelineIntermediateState, StableDiffusionGeneratorPipeline # noqa: F401
|
||||
from .diffusion import InvokeAIDiffuserComponent # noqa: F401
|
||||
from .diffusion.cross_attention_map_saving import AttentionMapSaver # noqa: F401
|
||||
|
@ -427,11 +427,10 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
return latents, attention_map_saver
|
||||
|
||||
ip_adapter_unet_patcher = None
|
||||
extra_conditioning_info = conditioning_data.text_embeddings.extra_conditioning
|
||||
if extra_conditioning_info is not None and extra_conditioning_info.wants_cross_attention_control:
|
||||
if conditioning_data.extra is not None and conditioning_data.extra.wants_cross_attention_control:
|
||||
attn_ctx = self.invokeai_diffuser.custom_attention_context(
|
||||
self.invokeai_diffuser.model,
|
||||
extra_conditioning_info=extra_conditioning_info,
|
||||
extra_conditioning_info=conditioning_data.extra,
|
||||
step_count=len(self.scheduler.timesteps),
|
||||
)
|
||||
self.use_ip_adapter = False
|
||||
@ -545,9 +544,15 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
# Otherwise, set the IP-Adapter's scale to 0, so it has no effect.
|
||||
ip_adapter_unet_patcher.set_scale(i, 0.0)
|
||||
|
||||
# Handle ControlNet(s)
|
||||
# Handle ControlNet(s) and T2I-Adapter(s)
|
||||
down_block_additional_residuals = None
|
||||
mid_block_additional_residual = None
|
||||
down_intrablock_additional_residuals = None
|
||||
# if control_data is not None and t2i_adapter_data is not None:
|
||||
# TODO(ryand): This is a limitation of the UNet2DConditionModel API, not a fundamental incompatibility
|
||||
# between ControlNets and T2I-Adapters. We will try to fix this upstream in diffusers.
|
||||
# raise Exception("ControlNet(s) and T2I-Adapter(s) cannot be used simultaneously (yet).")
|
||||
# elif control_data is not None:
|
||||
if control_data is not None:
|
||||
down_block_additional_residuals, mid_block_additional_residual = self.invokeai_diffuser.do_controlnet_step(
|
||||
control_data=control_data,
|
||||
@ -557,9 +562,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
total_step_count=total_step_count,
|
||||
conditioning_data=conditioning_data,
|
||||
)
|
||||
|
||||
# Handle T2I-Adapter(s)
|
||||
down_intrablock_additional_residuals = None
|
||||
# elif t2i_adapter_data is not None:
|
||||
if t2i_adapter_data is not None:
|
||||
accum_adapter_state = None
|
||||
for single_t2i_adapter_data in t2i_adapter_data:
|
||||
@ -585,6 +588,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
for idx, value in enumerate(single_t2i_adapter_data.adapter_state):
|
||||
accum_adapter_state[idx] += value * t2i_adapter_weight
|
||||
|
||||
# down_block_additional_residuals = accum_adapter_state
|
||||
down_intrablock_additional_residuals = accum_adapter_state
|
||||
|
||||
uc_noise_pred, c_noise_pred = self.invokeai_diffuser.do_unet_step(
|
||||
@ -593,6 +597,7 @@ class StableDiffusionGeneratorPipeline(StableDiffusionPipeline):
|
||||
step_index=step_index,
|
||||
total_step_count=total_step_count,
|
||||
conditioning_data=conditioning_data,
|
||||
# extra:
|
||||
down_block_additional_residuals=down_block_additional_residuals, # for ControlNet
|
||||
mid_block_additional_residual=mid_block_additional_residual, # for ControlNet
|
||||
down_intrablock_additional_residuals=down_intrablock_additional_residuals, # for T2I-Adapter
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Initialization file for invokeai.models.diffusion
|
||||
"""
|
||||
|
||||
from .cross_attention_control import InvokeAICrossAttentionMixin # noqa: F401
|
||||
from .cross_attention_map_saving import AttentionMapSaver # noqa: F401
|
||||
from .shared_invokeai_diffusion import InvokeAIDiffuserComponent # noqa: F401
|
||||
|
@ -21,7 +21,11 @@ class ExtraConditioningInfo:
|
||||
@dataclass
|
||||
class BasicConditioningInfo:
|
||||
embeds: torch.Tensor
|
||||
# TODO(ryand): Right now we awkwardly copy the extra conditioning info from here up to `ConditioningData`. This
|
||||
# should only be stored in one place.
|
||||
extra_conditioning: Optional[ExtraConditioningInfo]
|
||||
# weight: float
|
||||
# mode: ConditioningAlgo
|
||||
|
||||
def to(self, device, dtype=None):
|
||||
self.embeds = self.embeds.to(device=device, dtype=dtype)
|
||||
@ -79,6 +83,7 @@ class ConditioningData:
|
||||
ref [Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf)
|
||||
"""
|
||||
guidance_rescale_multiplier: float = 0
|
||||
extra: Optional[ExtraConditioningInfo] = None
|
||||
scheduler_args: dict[str, Any] = field(default_factory=dict)
|
||||
"""
|
||||
Additional arguments to pass to invokeai_diffuser.do_latent_postprocessing().
|
||||
|
@ -533,6 +533,18 @@ class SwapCrossAttnContext:
|
||||
mask: torch.Tensor # in the target space of the index_map
|
||||
cross_attention_types_to_do: list[CrossAttentionType] = field(default_factory=list)
|
||||
|
||||
def __int__(
|
||||
self,
|
||||
cac_types_to_do: [CrossAttentionType],
|
||||
modified_text_embeddings: torch.Tensor,
|
||||
index_map: torch.Tensor,
|
||||
mask: torch.Tensor,
|
||||
):
|
||||
self.cross_attention_types_to_do = cac_types_to_do
|
||||
self.modified_text_embeddings = modified_text_embeddings
|
||||
self.index_map = index_map
|
||||
self.mask = mask
|
||||
|
||||
def wants_cross_attention_control(self, attn_type: CrossAttentionType) -> bool:
|
||||
return attn_type in self.cross_attention_types_to_do
|
||||
|
||||
|
@ -224,47 +224,51 @@ class InvokeAIDiffuserComponent:
|
||||
self,
|
||||
sample: torch.Tensor,
|
||||
timestep: torch.Tensor,
|
||||
conditioning_data: ConditioningData,
|
||||
conditioning_data, # TODO: type
|
||||
step_index: int,
|
||||
total_step_count: int,
|
||||
down_block_additional_residuals: Optional[torch.Tensor] = None, # for ControlNet
|
||||
mid_block_additional_residual: Optional[torch.Tensor] = None, # for ControlNet
|
||||
down_intrablock_additional_residuals: Optional[torch.Tensor] = None, # for T2I-Adapter
|
||||
**kwargs,
|
||||
):
|
||||
cross_attention_control_types_to_do = []
|
||||
context: Context = self.cross_attention_control_context
|
||||
if self.cross_attention_control_context is not None:
|
||||
percent_through = step_index / total_step_count
|
||||
cross_attention_control_types_to_do = (
|
||||
self.cross_attention_control_context.get_active_cross_attention_control_types_for_step(percent_through)
|
||||
cross_attention_control_types_to_do = context.get_active_cross_attention_control_types_for_step(
|
||||
percent_through
|
||||
)
|
||||
|
||||
wants_cross_attention_control = len(cross_attention_control_types_to_do) > 0
|
||||
|
||||
if wants_cross_attention_control or self.sequential_guidance:
|
||||
# If wants_cross_attention_control is True, we force the sequential mode to be used, because cross-attention
|
||||
# control is currently only supported in sequential mode.
|
||||
if wants_cross_attention_control:
|
||||
(
|
||||
unconditioned_next_x,
|
||||
conditioned_next_x,
|
||||
) = self._apply_cross_attention_controlled_conditioning(
|
||||
sample,
|
||||
timestep,
|
||||
conditioning_data,
|
||||
cross_attention_control_types_to_do,
|
||||
**kwargs,
|
||||
)
|
||||
elif self.sequential_guidance:
|
||||
(
|
||||
unconditioned_next_x,
|
||||
conditioned_next_x,
|
||||
) = self._apply_standard_conditioning_sequentially(
|
||||
x=sample,
|
||||
sigma=timestep,
|
||||
conditioning_data=conditioning_data,
|
||||
cross_attention_control_types_to_do=cross_attention_control_types_to_do,
|
||||
down_block_additional_residuals=down_block_additional_residuals,
|
||||
mid_block_additional_residual=mid_block_additional_residual,
|
||||
down_intrablock_additional_residuals=down_intrablock_additional_residuals,
|
||||
sample,
|
||||
timestep,
|
||||
conditioning_data,
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
(
|
||||
unconditioned_next_x,
|
||||
conditioned_next_x,
|
||||
) = self._apply_standard_conditioning(
|
||||
x=sample,
|
||||
sigma=timestep,
|
||||
conditioning_data=conditioning_data,
|
||||
down_block_additional_residuals=down_block_additional_residuals,
|
||||
mid_block_additional_residual=mid_block_additional_residual,
|
||||
down_intrablock_additional_residuals=down_intrablock_additional_residuals,
|
||||
sample,
|
||||
timestep,
|
||||
conditioning_data,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
return unconditioned_next_x, conditioned_next_x
|
||||
@ -331,15 +335,7 @@ class InvokeAIDiffuserComponent:
|
||||
|
||||
# methods below are called from do_diffusion_step and should be considered private to this class.
|
||||
|
||||
def _apply_standard_conditioning(
|
||||
self,
|
||||
x,
|
||||
sigma,
|
||||
conditioning_data: ConditioningData,
|
||||
down_block_additional_residuals: Optional[torch.Tensor] = None, # for ControlNet
|
||||
mid_block_additional_residual: Optional[torch.Tensor] = None, # for ControlNet
|
||||
down_intrablock_additional_residuals: Optional[torch.Tensor] = None, # for T2I-Adapter
|
||||
):
|
||||
def _apply_standard_conditioning(self, x, sigma, conditioning_data: ConditioningData, **kwargs):
|
||||
"""Runs the conditioned and unconditioned UNet forward passes in a single batch for faster inference speed at
|
||||
the cost of higher memory usage.
|
||||
"""
|
||||
@ -387,10 +383,8 @@ class InvokeAIDiffuserComponent:
|
||||
both_conditionings,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
down_block_additional_residuals=down_block_additional_residuals,
|
||||
mid_block_additional_residual=mid_block_additional_residual,
|
||||
down_intrablock_additional_residuals=down_intrablock_additional_residuals,
|
||||
added_cond_kwargs=added_cond_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
unconditioned_next_x, conditioned_next_x = both_results.chunk(2)
|
||||
return unconditioned_next_x, conditioned_next_x
|
||||
@ -400,17 +394,14 @@ class InvokeAIDiffuserComponent:
|
||||
x: torch.Tensor,
|
||||
sigma,
|
||||
conditioning_data: ConditioningData,
|
||||
cross_attention_control_types_to_do: list[CrossAttentionType],
|
||||
down_block_additional_residuals: Optional[torch.Tensor] = None, # for ControlNet
|
||||
mid_block_additional_residual: Optional[torch.Tensor] = None, # for ControlNet
|
||||
down_intrablock_additional_residuals: Optional[torch.Tensor] = None, # for T2I-Adapter
|
||||
**kwargs,
|
||||
):
|
||||
"""Runs the conditioned and unconditioned UNet forward passes sequentially for lower memory usage at the cost of
|
||||
slower execution speed.
|
||||
"""
|
||||
# Since we are running the conditioned and unconditioned passes sequentially, we need to split the ControlNet
|
||||
# and T2I-Adapter residuals into two chunks.
|
||||
# low-memory sequential path
|
||||
uncond_down_block, cond_down_block = None, None
|
||||
down_block_additional_residuals = kwargs.pop("down_block_additional_residuals", None)
|
||||
if down_block_additional_residuals is not None:
|
||||
uncond_down_block, cond_down_block = [], []
|
||||
for down_block in down_block_additional_residuals:
|
||||
@ -419,6 +410,7 @@ class InvokeAIDiffuserComponent:
|
||||
cond_down_block.append(_cond_down)
|
||||
|
||||
uncond_down_intrablock, cond_down_intrablock = None, None
|
||||
down_intrablock_additional_residuals = kwargs.pop("down_intrablock_additional_residuals", None)
|
||||
if down_intrablock_additional_residuals is not None:
|
||||
uncond_down_intrablock, cond_down_intrablock = [], []
|
||||
for down_intrablock in down_intrablock_additional_residuals:
|
||||
@ -427,29 +419,12 @@ class InvokeAIDiffuserComponent:
|
||||
cond_down_intrablock.append(_cond_down)
|
||||
|
||||
uncond_mid_block, cond_mid_block = None, None
|
||||
mid_block_additional_residual = kwargs.pop("mid_block_additional_residual", None)
|
||||
if mid_block_additional_residual is not None:
|
||||
uncond_mid_block, cond_mid_block = mid_block_additional_residual.chunk(2)
|
||||
|
||||
# If cross-attention control is enabled, prepare the SwapCrossAttnContext.
|
||||
cross_attn_processor_context = None
|
||||
if self.cross_attention_control_context is not None:
|
||||
# Note that the SwapCrossAttnContext is initialized with an empty list of cross_attention_types_to_do.
|
||||
# This list is empty because cross-attention control is not applied in the unconditioned pass. This field
|
||||
# will be populated before the conditioned pass.
|
||||
cross_attn_processor_context = SwapCrossAttnContext(
|
||||
modified_text_embeddings=self.cross_attention_control_context.arguments.edited_conditioning,
|
||||
index_map=self.cross_attention_control_context.cross_attention_index_map,
|
||||
mask=self.cross_attention_control_context.cross_attention_mask,
|
||||
cross_attention_types_to_do=[],
|
||||
)
|
||||
|
||||
#####################
|
||||
# Unconditioned pass
|
||||
#####################
|
||||
|
||||
# Run unconditional UNet denoising.
|
||||
cross_attention_kwargs = None
|
||||
|
||||
# Prepare IP-Adapter cross-attention kwargs for the unconditioned pass.
|
||||
if conditioning_data.ip_adapter_conditioning is not None:
|
||||
# Note that we 'unsqueeze' to produce tensors of shape (batch_size=1, num_ip_images, seq_len, token_len).
|
||||
cross_attention_kwargs = {
|
||||
@ -459,11 +434,6 @@ class InvokeAIDiffuserComponent:
|
||||
]
|
||||
}
|
||||
|
||||
# Prepare cross-attention control kwargs for the unconditioned pass.
|
||||
if cross_attn_processor_context is not None:
|
||||
cross_attention_kwargs = {"swap_cross_attn_context": cross_attn_processor_context}
|
||||
|
||||
# Prepare SDXL conditioning kwargs for the unconditioned pass.
|
||||
added_cond_kwargs = None
|
||||
is_sdxl = type(conditioning_data.text_embeddings) is SDXLConditioningInfo
|
||||
if is_sdxl:
|
||||
@ -472,7 +442,6 @@ class InvokeAIDiffuserComponent:
|
||||
"time_ids": conditioning_data.unconditioned_embeddings.add_time_ids,
|
||||
}
|
||||
|
||||
# Run unconditioned UNet denoising (i.e. negative prompt).
|
||||
unconditioned_next_x = self.model_forward_callback(
|
||||
x,
|
||||
sigma,
|
||||
@ -482,15 +451,11 @@ class InvokeAIDiffuserComponent:
|
||||
mid_block_additional_residual=uncond_mid_block,
|
||||
down_intrablock_additional_residuals=uncond_down_intrablock,
|
||||
added_cond_kwargs=added_cond_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
###################
|
||||
# Conditioned pass
|
||||
###################
|
||||
|
||||
# Run conditional UNet denoising.
|
||||
cross_attention_kwargs = None
|
||||
|
||||
# Prepare IP-Adapter cross-attention kwargs for the conditioned pass.
|
||||
if conditioning_data.ip_adapter_conditioning is not None:
|
||||
# Note that we 'unsqueeze' to produce tensors of shape (batch_size=1, num_ip_images, seq_len, token_len).
|
||||
cross_attention_kwargs = {
|
||||
@ -500,12 +465,6 @@ class InvokeAIDiffuserComponent:
|
||||
]
|
||||
}
|
||||
|
||||
# Prepare cross-attention control kwargs for the conditioned pass.
|
||||
if cross_attn_processor_context is not None:
|
||||
cross_attn_processor_context.cross_attention_types_to_do = cross_attention_control_types_to_do
|
||||
cross_attention_kwargs = {"swap_cross_attn_context": cross_attn_processor_context}
|
||||
|
||||
# Prepare SDXL conditioning kwargs for the conditioned pass.
|
||||
added_cond_kwargs = None
|
||||
if is_sdxl:
|
||||
added_cond_kwargs = {
|
||||
@ -513,7 +472,6 @@ class InvokeAIDiffuserComponent:
|
||||
"time_ids": conditioning_data.text_embeddings.add_time_ids,
|
||||
}
|
||||
|
||||
# Run conditioned UNet denoising (i.e. positive prompt).
|
||||
conditioned_next_x = self.model_forward_callback(
|
||||
x,
|
||||
sigma,
|
||||
@ -523,6 +481,89 @@ class InvokeAIDiffuserComponent:
|
||||
mid_block_additional_residual=cond_mid_block,
|
||||
down_intrablock_additional_residuals=cond_down_intrablock,
|
||||
added_cond_kwargs=added_cond_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
return unconditioned_next_x, conditioned_next_x
|
||||
|
||||
def _apply_cross_attention_controlled_conditioning(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
sigma,
|
||||
conditioning_data,
|
||||
cross_attention_control_types_to_do,
|
||||
**kwargs,
|
||||
):
|
||||
context: Context = self.cross_attention_control_context
|
||||
|
||||
uncond_down_block, cond_down_block = None, None
|
||||
down_block_additional_residuals = kwargs.pop("down_block_additional_residuals", None)
|
||||
if down_block_additional_residuals is not None:
|
||||
uncond_down_block, cond_down_block = [], []
|
||||
for down_block in down_block_additional_residuals:
|
||||
_uncond_down, _cond_down = down_block.chunk(2)
|
||||
uncond_down_block.append(_uncond_down)
|
||||
cond_down_block.append(_cond_down)
|
||||
|
||||
uncond_down_intrablock, cond_down_intrablock = None, None
|
||||
down_intrablock_additional_residuals = kwargs.pop("down_intrablock_additional_residuals", None)
|
||||
if down_intrablock_additional_residuals is not None:
|
||||
uncond_down_intrablock, cond_down_intrablock = [], []
|
||||
for down_intrablock in down_intrablock_additional_residuals:
|
||||
_uncond_down, _cond_down = down_intrablock.chunk(2)
|
||||
uncond_down_intrablock.append(_uncond_down)
|
||||
cond_down_intrablock.append(_cond_down)
|
||||
|
||||
uncond_mid_block, cond_mid_block = None, None
|
||||
mid_block_additional_residual = kwargs.pop("mid_block_additional_residual", None)
|
||||
if mid_block_additional_residual is not None:
|
||||
uncond_mid_block, cond_mid_block = mid_block_additional_residual.chunk(2)
|
||||
|
||||
cross_attn_processor_context = SwapCrossAttnContext(
|
||||
modified_text_embeddings=context.arguments.edited_conditioning,
|
||||
index_map=context.cross_attention_index_map,
|
||||
mask=context.cross_attention_mask,
|
||||
cross_attention_types_to_do=[],
|
||||
)
|
||||
|
||||
added_cond_kwargs = None
|
||||
is_sdxl = type(conditioning_data.text_embeddings) is SDXLConditioningInfo
|
||||
if is_sdxl:
|
||||
added_cond_kwargs = {
|
||||
"text_embeds": conditioning_data.unconditioned_embeddings.pooled_embeds,
|
||||
"time_ids": conditioning_data.unconditioned_embeddings.add_time_ids,
|
||||
}
|
||||
|
||||
# no cross attention for unconditioning (negative prompt)
|
||||
unconditioned_next_x = self.model_forward_callback(
|
||||
x,
|
||||
sigma,
|
||||
conditioning_data.unconditioned_embeddings.embeds,
|
||||
{"swap_cross_attn_context": cross_attn_processor_context},
|
||||
down_block_additional_residuals=uncond_down_block,
|
||||
mid_block_additional_residual=uncond_mid_block,
|
||||
down_intrablock_additional_residuals=uncond_down_intrablock,
|
||||
added_cond_kwargs=added_cond_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if is_sdxl:
|
||||
added_cond_kwargs = {
|
||||
"text_embeds": conditioning_data.text_embeddings.pooled_embeds,
|
||||
"time_ids": conditioning_data.text_embeddings.add_time_ids,
|
||||
}
|
||||
|
||||
# do requested cross attention types for conditioning (positive prompt)
|
||||
cross_attn_processor_context.cross_attention_types_to_do = cross_attention_control_types_to_do
|
||||
conditioned_next_x = self.model_forward_callback(
|
||||
x,
|
||||
sigma,
|
||||
conditioning_data.text_embeddings.embeds,
|
||||
{"swap_cross_attn_context": cross_attn_processor_context},
|
||||
down_block_additional_residuals=cond_down_block,
|
||||
mid_block_additional_residual=cond_mid_block,
|
||||
down_intrablock_additional_residuals=cond_down_intrablock,
|
||||
added_cond_kwargs=added_cond_kwargs,
|
||||
**kwargs,
|
||||
)
|
||||
return unconditioned_next_x, conditioned_next_x
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""
|
||||
Initialization file for invokeai.backend.training
|
||||
"""
|
||||
|
||||
from .textual_inversion_training import do_textual_inversion_training, parse_args # noqa: F401
|
||||
|
@ -858,9 +858,9 @@ def do_textual_inversion_training(
|
||||
# Let's make sure we don't update any embedding weights besides the newly added token
|
||||
index_no_updates = torch.arange(len(tokenizer)) != placeholder_token_id
|
||||
with torch.no_grad():
|
||||
accelerator.unwrap_model(text_encoder).get_input_embeddings().weight[index_no_updates] = (
|
||||
orig_embeds_params[index_no_updates]
|
||||
)
|
||||
accelerator.unwrap_model(text_encoder).get_input_embeddings().weight[
|
||||
index_no_updates
|
||||
] = orig_embeds_params[index_no_updates]
|
||||
|
||||
# Checks if the accelerator has performed an optimization step behind the scenes
|
||||
if accelerator.sync_gradients:
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Initialization file for invokeai.backend.util
|
||||
"""
|
||||
|
||||
from .attention import auto_detect_slice_size # noqa: F401
|
||||
from .devices import ( # noqa: F401
|
||||
CPU_DEVICE,
|
||||
|
@ -3,7 +3,6 @@
|
||||
Utility routine used for autodetection of optimal slice size
|
||||
for attention mechanism.
|
||||
"""
|
||||
|
||||
import psutil
|
||||
import torch
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Context class to silence transformers and diffusers warnings."""
|
||||
|
||||
import warnings
|
||||
from typing import Any
|
||||
|
||||
|
@ -340,17 +340,14 @@ def download_with_resume(url: str, dest: Path, access_token: str = None) -> Path
|
||||
logger.error(f"ERROR DOWNLOADING {url}: {resp.text}")
|
||||
return None
|
||||
|
||||
with (
|
||||
open(dest, open_mode) as file,
|
||||
tqdm(
|
||||
desc=str(dest),
|
||||
initial=exist_size,
|
||||
total=content_length,
|
||||
unit="iB",
|
||||
unit_scale=True,
|
||||
unit_divisor=1000,
|
||||
) as bar,
|
||||
):
|
||||
with open(dest, open_mode) as file, tqdm(
|
||||
desc=str(dest),
|
||||
initial=exist_size,
|
||||
total=content_length,
|
||||
unit="iB",
|
||||
unit_scale=True,
|
||||
unit_divisor=1000,
|
||||
) as bar:
|
||||
for data in resp.iter_content(chunk_size=1024):
|
||||
size = file.write(data)
|
||||
bar.update(size)
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""
|
||||
Initialization file for invokeai.frontend.CLI
|
||||
"""
|
||||
|
||||
from .CLI import main as invokeai_command_line_interface # noqa: F401
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Wrapper for invokeai.backend.configure.invokeai_configure
|
||||
"""
|
||||
|
||||
from ...backend.install.invokeai_configure import main as invokeai_configure # noqa: F401
|
||||
|
||||
__all__ = ["invokeai_configure"]
|
||||
|
@ -2,7 +2,6 @@
|
||||
Minimalist updater script. Prompts user for the tag or branch to update to and runs
|
||||
pip install <path_to_git_source>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
from distutils.version import LooseVersion
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
Widget class definitions used by model_select.py, merge_diffusers.py and textual_inversion.py
|
||||
"""
|
||||
|
||||
import curses
|
||||
import math
|
||||
import os
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""
|
||||
Initialization file for invokeai.frontend.merge
|
||||
"""
|
||||
|
||||
from .merge_diffusers import main as invokeai_merge_diffusers # noqa: F401
|
||||
|
@ -4,7 +4,6 @@ used to merge 2-3 models together and create a new InvokeAI-registered diffusion
|
||||
|
||||
Copyright (c) 2023 Lincoln Stein and the InvokeAI Development Team
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import curses
|
||||
import re
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""
|
||||
Initialization file for invokeai.frontend.training
|
||||
"""
|
||||
|
||||
from .textual_inversion import main as invokeai_textual_inversion # noqa: F401
|
||||
|
@ -6,6 +6,7 @@ This is the frontend to "textual_inversion_training.py".
|
||||
Copyright (c) 2023-24 Lincoln Stein and the InvokeAI Development Team
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -1456,8 +1456,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 +1478,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,7 +1495,7 @@
|
||||
},
|
||||
"compositingCoherenceMode": {
|
||||
"heading": "Mode",
|
||||
"paragraphs": ["Method used to create a coherent image with the newly generated masked area."]
|
||||
"paragraphs": ["The mode of the Coherence Pass."]
|
||||
},
|
||||
"compositingCoherenceEdgeSize": {
|
||||
"heading": "Edge Size",
|
||||
@ -1512,38 +1512,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",
|
||||
@ -1566,23 +1558,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",
|
||||
@ -1592,25 +1574,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": {
|
||||
@ -1620,16 +1591,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": [
|
||||
@ -1640,7 +1601,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": {
|
||||
@ -1654,7 +1616,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": {
|
||||
@ -1664,10 +1626,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."]
|
||||
@ -1675,82 +1633,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": {
|
||||
|
@ -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 porterà a impatti maggiori sull'immagine finale."
|
||||
"Quanto forte sarà l'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",
|
||||
|
@ -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": {
|
||||
|
@ -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": "встраивания не загружены",
|
||||
|
@ -1444,14 +1444,16 @@
|
||||
"clipSkip": {
|
||||
"paragraphs": [
|
||||
"选择要跳过 CLIP 模型多少层。",
|
||||
"部分模型跳过特定数值的层时效果会更好。"
|
||||
"部分模型跳过特定数值的层时效果会更好。",
|
||||
"较高的数值通常会导致图像细节更少。"
|
||||
],
|
||||
"heading": "CLIP 跳过层"
|
||||
},
|
||||
"paramModel": {
|
||||
"heading": "模型",
|
||||
"paragraphs": [
|
||||
"用于去噪过程的模型。"
|
||||
"用于去噪过程的模型。",
|
||||
"不同的模型一般会通过接受训练来专门产生特定的美学内容和结果。"
|
||||
]
|
||||
},
|
||||
"paramIterations": {
|
||||
|
@ -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,51 +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-',
|
||||
},
|
||||
compositingMaskBlur: {
|
||||
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',
|
||||
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',
|
||||
},
|
||||
@ -118,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',
|
||||
@ -132,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;
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
@ -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}
|
||||
|
@ -18,7 +18,7 @@ export const useMetadataItem = <T,>(metadata: unknown, handlers: MetadataHandler
|
||||
const [value, setValue] = useState<T | typeof MetadataParsePendingToken | typeof MetadataParseFailedToken>(
|
||||
MetadataParsePendingToken
|
||||
);
|
||||
const [renderedValueInternal, setRenderedValueInternal] = useState<React.ReactNode>(null);
|
||||
const [renderedValue, setRenderedValue] = useState<React.ReactNode>(Pending);
|
||||
|
||||
useEffect(() => {
|
||||
const _parse = async () => {
|
||||
@ -39,32 +39,26 @@ export const useMetadataItem = <T,>(metadata: unknown, handlers: MetadataHandler
|
||||
useEffect(() => {
|
||||
const _renderValue = async () => {
|
||||
if (value === MetadataParsePendingToken) {
|
||||
setRenderedValueInternal(null);
|
||||
setRenderedValue(Pending);
|
||||
return;
|
||||
}
|
||||
if (value === MetadataParseFailedToken) {
|
||||
setRenderedValueInternal(null);
|
||||
setRenderedValue(Failed);
|
||||
return;
|
||||
}
|
||||
|
||||
const rendered = await handlers.renderValue(value);
|
||||
|
||||
setRenderedValueInternal(rendered);
|
||||
if (typeof rendered === 'string') {
|
||||
setRenderedValue(<Text>{rendered}</Text>);
|
||||
return;
|
||||
}
|
||||
setRenderedValue(rendered);
|
||||
};
|
||||
|
||||
_renderValue();
|
||||
}, [handlers, value]);
|
||||
|
||||
const renderedValue = useMemo(() => {
|
||||
if (value === MetadataParsePendingToken) {
|
||||
return <Pending />;
|
||||
}
|
||||
if (value === MetadataParseFailedToken) {
|
||||
return <Failed />;
|
||||
}
|
||||
return <Text>{renderedValueInternal}</Text>;
|
||||
}, [renderedValueInternal, value]);
|
||||
|
||||
const onRecall = useCallback(() => {
|
||||
if (!handlers.recall || value === MetadataParsePendingToken || value === MetadataParseFailedToken) {
|
||||
return null;
|
||||
|
@ -4,7 +4,7 @@ import type { O } from 'ts-toolbelt';
|
||||
/**
|
||||
* Renders a value of type T as a React node.
|
||||
*/
|
||||
export type MetadataRenderValueFunc<T> = (value: T) => Promise<string>;
|
||||
export type MetadataRenderValueFunc<T> = (value: T) => Promise<React.ReactNode>;
|
||||
|
||||
/**
|
||||
* Gets the label of the current metadata item as a string.
|
||||
|
@ -150,7 +150,7 @@ const buildRecallItem =
|
||||
}
|
||||
};
|
||||
|
||||
const resolveToString = (value: unknown) => new Promise<string>((resolve) => resolve(String(value)));
|
||||
const resolveToString = (value: unknown) => new Promise<React.ReactNode>((resolve) => resolve(String(value)));
|
||||
|
||||
const buildHandlers: BuildMetadataHandlers = ({
|
||||
getLabel,
|
||||
|
@ -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}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user