mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into refactor/model-manager2/model-metadata-store
This commit is contained in:
commit
26c77e8522
@ -270,7 +270,7 @@ upgrade script.** See the next section for a Windows recipe.
|
|||||||
3. Select option [1] to upgrade to the latest release.
|
3. Select option [1] to upgrade to the latest release.
|
||||||
|
|
||||||
4. Once the upgrade is finished you will be returned to the launcher
|
4. Once the upgrade is finished you will be returned to the launcher
|
||||||
menu. Select option [7] "Re-run the configure script to fix a broken
|
menu. Select option [6] "Re-run the configure script to fix a broken
|
||||||
install or to complete a major upgrade".
|
install or to complete a major upgrade".
|
||||||
|
|
||||||
This will run the configure script against the v2.3 directory and
|
This will run the configure script against the v2.3 directory and
|
||||||
|
277
docs/contributing/DOWNLOAD_QUEUE.md
Normal file
277
docs/contributing/DOWNLOAD_QUEUE.md
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
# The InvokeAI Download Queue
|
||||||
|
|
||||||
|
The DownloadQueueService provides a multithreaded parallel download
|
||||||
|
queue for arbitrary URLs, with queue prioritization, event handling,
|
||||||
|
and restart capabilities.
|
||||||
|
|
||||||
|
## Simple Example
|
||||||
|
|
||||||
|
```
|
||||||
|
from invokeai.app.services.download import DownloadQueueService, TqdmProgress
|
||||||
|
|
||||||
|
download_queue = DownloadQueueService()
|
||||||
|
for url in ['https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/a-painting-of-a-fire.png?raw=true',
|
||||||
|
'https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/birdhouse.png?raw=true',
|
||||||
|
'https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/missing.png',
|
||||||
|
'https://civitai.com/api/download/models/152309?type=Model&format=SafeTensor',
|
||||||
|
]:
|
||||||
|
|
||||||
|
# urls start downloading as soon as download() is called
|
||||||
|
download_queue.download(source=url,
|
||||||
|
dest='/tmp/downloads',
|
||||||
|
on_progress=TqdmProgress().update
|
||||||
|
)
|
||||||
|
|
||||||
|
download_queue.join() # wait for all downloads to finish
|
||||||
|
for job in download_queue.list_jobs():
|
||||||
|
print(job.model_dump_json(exclude_none=True, indent=4),"\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/a-painting-of-a-fire.png?raw=true",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 0,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/a-painting-of-a-fire.png",
|
||||||
|
"job_started": "2023-12-04T05:34:41.742174",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.592035",
|
||||||
|
"bytes": 666734,
|
||||||
|
"total_bytes": 666734
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/birdhouse.png?raw=true",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 1,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/birdhouse.png",
|
||||||
|
"job_started": "2023-12-04T05:34:41.741975",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.652841",
|
||||||
|
"bytes": 774949,
|
||||||
|
"total_bytes": 774949
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/missing.png",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 2,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "error",
|
||||||
|
"job_started": "2023-12-04T05:34:41.742079",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.147625",
|
||||||
|
"bytes": 0,
|
||||||
|
"total_bytes": 0,
|
||||||
|
"error_type": "HTTPError(Not Found)",
|
||||||
|
"error": "Traceback (most recent call last):\n File \"/home/lstein/Projects/InvokeAI/invokeai/app/services/download/download_default.py\", line 182, in _download_next_item\n self._do_download(job)\n File \"/home/lstein/Projects/InvokeAI/invokeai/app/services/download/download_default.py\", line 206, in _do_download\n raise HTTPError(resp.reason)\nrequests.exceptions.HTTPError: Not Found\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://civitai.com/api/download/models/152309?type=Model&format=SafeTensor",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 3,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/xl_more_art-full_v1.safetensors",
|
||||||
|
"job_started": "2023-12-04T05:34:42.147645",
|
||||||
|
"job_ended": "2023-12-04T05:34:43.735990",
|
||||||
|
"bytes": 719020768,
|
||||||
|
"total_bytes": 719020768
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The API
|
||||||
|
|
||||||
|
The default download queue is `DownloadQueueService`, an
|
||||||
|
implementation of ABC `DownloadQueueServiceBase`. It juggles multiple
|
||||||
|
background download requests and provides facilities for interrogating
|
||||||
|
and cancelling the requests. Access to a current or past download task
|
||||||
|
is mediated via `DownloadJob` objects which report the current status
|
||||||
|
of a job request
|
||||||
|
|
||||||
|
### The Queue Object
|
||||||
|
|
||||||
|
A default download queue is located in
|
||||||
|
`ApiDependencies.invoker.services.download_queue`. However, you can
|
||||||
|
create additional instances if you need to isolate your queue from the
|
||||||
|
main one.
|
||||||
|
|
||||||
|
```
|
||||||
|
queue = DownloadQueueService(event_bus=events)
|
||||||
|
```
|
||||||
|
|
||||||
|
`DownloadQueueService()` takes three optional arguments:
|
||||||
|
|
||||||
|
| **Argument** | **Type** | **Default** | **Description** |
|
||||||
|
|----------------|-----------------|---------------|-----------------|
|
||||||
|
| `max_parallel_dl` | int | 5 | Maximum number of simultaneous downloads allowed |
|
||||||
|
| `event_bus` | EventServiceBase | None | System-wide FastAPI event bus for reporting download events |
|
||||||
|
| `requests_session` | requests.sessions.Session | None | An alternative requests Session object to use for the download |
|
||||||
|
|
||||||
|
`max_parallel_dl` specifies how many download jobs are allowed to run
|
||||||
|
simultaneously. Each will run in a different thread of execution.
|
||||||
|
|
||||||
|
`event_bus` is an EventServiceBase, typically the one created at
|
||||||
|
InvokeAI startup. If present, download events are periodically emitted
|
||||||
|
on this bus to allow clients to follow download progress.
|
||||||
|
|
||||||
|
`requests_session` is a url library requests Session object. It is
|
||||||
|
used for testing.
|
||||||
|
|
||||||
|
### The Job object
|
||||||
|
|
||||||
|
The queue operates on a series of download job objects. These objects
|
||||||
|
specify the source and destination of the download, and keep track of
|
||||||
|
the progress of the download.
|
||||||
|
|
||||||
|
The only job type currently implemented is `DownloadJob`, a pydantic object with the
|
||||||
|
following fields:
|
||||||
|
|
||||||
|
| **Field** | **Type** | **Default** | **Description** |
|
||||||
|
|----------------|-----------------|---------------|-----------------|
|
||||||
|
| _Fields passed in at job creation time_ |
|
||||||
|
| `source` | AnyHttpUrl | | Where to download from |
|
||||||
|
| `dest` | Path | | Where to download to |
|
||||||
|
| `access_token` | str | | [optional] string containing authentication token for access |
|
||||||
|
| `on_start` | Callable | | [optional] callback when the download starts |
|
||||||
|
| `on_progress` | Callable | | [optional] callback called at intervals during download progress |
|
||||||
|
| `on_complete` | Callable | | [optional] callback called after successful download completion |
|
||||||
|
| `on_error` | Callable | | [optional] callback called after an error occurs |
|
||||||
|
| `id` | int | auto assigned | Job ID, an integer >= 0 |
|
||||||
|
| `priority` | int | 10 | Job priority. Lower priorities run before higher priorities |
|
||||||
|
| |
|
||||||
|
| _Fields updated over the course of the download task_
|
||||||
|
| `status` | DownloadJobStatus| | Status code |
|
||||||
|
| `download_path` | Path | | Path to the location of the downloaded file |
|
||||||
|
| `job_started` | float | | Timestamp for when the job started running |
|
||||||
|
| `job_ended` | float | | Timestamp for when the job completed or errored out |
|
||||||
|
| `job_sequence` | int | | A counter that is incremented each time a model is dequeued |
|
||||||
|
| `bytes` | int | 0 | Bytes downloaded so far |
|
||||||
|
| `total_bytes` | int | 0 | Total size of the file at the remote site |
|
||||||
|
| `error_type` | str | | String version of the exception that caused an error during download |
|
||||||
|
| `error` | str | | String version of the traceback associated with an error |
|
||||||
|
| `cancelled` | bool | False | Set to true if the job was cancelled by the caller|
|
||||||
|
|
||||||
|
When you create a job, you can assign it a `priority`. If multiple
|
||||||
|
jobs are queued, the job with the lowest priority runs first.
|
||||||
|
|
||||||
|
Every job has a `source` and a `dest`. `source` is a pydantic.networks AnyHttpUrl object.
|
||||||
|
The `dest` is a path on the local filesystem that specifies the
|
||||||
|
destination for the downloaded object. Its semantics are
|
||||||
|
described below.
|
||||||
|
|
||||||
|
When the job is submitted, it is assigned a numeric `id`. The id can
|
||||||
|
then be used to fetch the job object from the queue.
|
||||||
|
|
||||||
|
The `status` field is updated by the queue to indicate where the job
|
||||||
|
is in its lifecycle. Values are defined in the string enum
|
||||||
|
`DownloadJobStatus`, a symbol available from
|
||||||
|
`invokeai.app.services.download_manager`. Possible values are:
|
||||||
|
|
||||||
|
| **Value** | **String Value** | ** Description ** |
|
||||||
|
|--------------|---------------------|-------------------|
|
||||||
|
| `WAITING` | waiting | Job is on the queue but not yet running|
|
||||||
|
| `RUNNING` | running | The download is started |
|
||||||
|
| `COMPLETED` | completed | Job has finished its work without an error |
|
||||||
|
| `ERROR` | error | Job encountered an error and will not run again|
|
||||||
|
|
||||||
|
`job_started` and `job_ended` indicate when the job
|
||||||
|
was started (using a python timestamp) and when it completed.
|
||||||
|
|
||||||
|
In case of an error, the job's status will be set to `DownloadJobStatus.ERROR`, the text of the
|
||||||
|
Exception that caused the error will be placed in the `error_type`
|
||||||
|
field and the traceback that led to the error will be in `error`.
|
||||||
|
|
||||||
|
A cancelled job will have status `DownloadJobStatus.ERROR` and an
|
||||||
|
`error_type` field of "DownloadJobCancelledException". In addition,
|
||||||
|
the job's `cancelled` property will be set to True.
|
||||||
|
|
||||||
|
### Callbacks
|
||||||
|
|
||||||
|
Download jobs can be associated with a series of callbacks, each with
|
||||||
|
the signature `Callable[["DownloadJob"], None]`. The callbacks are assigned
|
||||||
|
using optional arguments `on_start`, `on_progress`, `on_complete` and
|
||||||
|
`on_error`. When the corresponding event occurs, the callback wil be
|
||||||
|
invoked and passed the job. The callback will be run in a `try:`
|
||||||
|
context in the same thread as the download job. Any exceptions that
|
||||||
|
occur during execution of the callback will be caught and converted
|
||||||
|
into a log error message, thereby allowing the download to continue.
|
||||||
|
|
||||||
|
#### `TqdmProgress`
|
||||||
|
|
||||||
|
The `invokeai.app.services.download.download_default` module defines a
|
||||||
|
class named `TqdmProgress` which can be used as an `on_progress`
|
||||||
|
handler to display a completion bar in the console. Use as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
from invokeai.app.services.download import TqdmProgress
|
||||||
|
|
||||||
|
download_queue.download(source='http://some.server.somewhere/some_file',
|
||||||
|
dest='/tmp/downloads',
|
||||||
|
on_progress=TqdmProgress().update
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
If the queue was initialized with the InvokeAI event bus (the case
|
||||||
|
when using `ApiDependencies.invoker.services.download_queue`), then
|
||||||
|
download events will also be issued on the bus. The events are:
|
||||||
|
|
||||||
|
* `download_started` -- This is issued when a job is taken off the
|
||||||
|
queue and a request is made to the remote server for the URL headers, but before any data
|
||||||
|
has been downloaded. The event payload will contain the keys `source`
|
||||||
|
and `download_path`. The latter contains the path that the URL will be
|
||||||
|
downloaded to.
|
||||||
|
|
||||||
|
* `download_progress -- This is issued periodically as the download
|
||||||
|
runs. The payload contains the keys `source`, `download_path`,
|
||||||
|
`current_bytes` and `total_bytes`. The latter two fields can be
|
||||||
|
used to display the percent complete.
|
||||||
|
|
||||||
|
* `download_complete` -- This is issued when the download completes
|
||||||
|
successfully. The payload contains the keys `source`, `download_path`
|
||||||
|
and `total_bytes`.
|
||||||
|
|
||||||
|
* `download_error` -- This is issued when the download stops because
|
||||||
|
of an error condition. The payload contains the fields `error_type`
|
||||||
|
and `error`. The former is the text representation of the exception,
|
||||||
|
and the latter is a traceback showing where the error occurred.
|
||||||
|
|
||||||
|
### Job control
|
||||||
|
|
||||||
|
To create a job call the queue's `download()` method. You can list all
|
||||||
|
jobs using `list_jobs()`, fetch a single job by its with
|
||||||
|
`id_to_job()`, cancel a running job with `cancel_job()`, cancel all
|
||||||
|
running jobs with `cancel_all_jobs()`, and wait for all jobs to finish
|
||||||
|
with `join()`.
|
||||||
|
|
||||||
|
#### job = queue.download(source, dest, priority, access_token)
|
||||||
|
|
||||||
|
Create a new download job and put it on the queue, returning the
|
||||||
|
DownloadJob object.
|
||||||
|
|
||||||
|
#### jobs = queue.list_jobs()
|
||||||
|
|
||||||
|
Return a list of all active and inactive `DownloadJob`s.
|
||||||
|
|
||||||
|
#### job = queue.id_to_job(id)
|
||||||
|
|
||||||
|
Return the job corresponding to given ID.
|
||||||
|
|
||||||
|
Return a list of all active and inactive `DownloadJob`s.
|
||||||
|
|
||||||
|
#### queue.prune_jobs()
|
||||||
|
|
||||||
|
Remove inactive (complete or errored) jobs from the listing returned
|
||||||
|
by `list_jobs()`.
|
||||||
|
|
||||||
|
#### queue.join()
|
||||||
|
|
||||||
|
Block until all pending jobs have run to completion or errored out.
|
||||||
|
|
@ -46,17 +46,18 @@ We encourage you to ping @psychedelicious and @blessedcoolant on [Discord](http
|
|||||||
```bash
|
```bash
|
||||||
node --version
|
node --version
|
||||||
```
|
```
|
||||||
2. Install [yarn classic](https://classic.yarnpkg.com/lang/en/) and confirm it is installed by running this:
|
|
||||||
|
2. Install [pnpm](https://pnpm.io/) and confirm it is installed by running this:
|
||||||
```bash
|
```bash
|
||||||
npm install --global yarn
|
npm install --global pnpm
|
||||||
yarn --version
|
pnpm --version
|
||||||
```
|
```
|
||||||
|
|
||||||
From `invokeai/frontend/web/` run `yarn install` to get everything set up.
|
From `invokeai/frontend/web/` run `pnpm install` to get everything set up.
|
||||||
|
|
||||||
Start everything in dev mode:
|
Start everything in dev mode:
|
||||||
1. Ensure your virtual environment is running
|
1. Ensure your virtual environment is running
|
||||||
2. Start the dev server: `yarn dev`
|
2. Start the dev server: `pnpm dev`
|
||||||
3. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
3. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
||||||
4. Point your browser to the dev server address e.g. [http://localhost:5173/](http://localhost:5173/)
|
4. Point your browser to the dev server address e.g. [http://localhost:5173/](http://localhost:5173/)
|
||||||
|
|
||||||
@ -72,4 +73,4 @@ For a number of technical and logistical reasons, we need to commit UI build art
|
|||||||
|
|
||||||
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
|
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
|
||||||
|
|
||||||
To build for production, run `yarn build`.
|
To build for production, run `pnpm build`.
|
||||||
|
@ -13,6 +13,7 @@ If you'd prefer, you can also just download the whole node folder from the linke
|
|||||||
To use a community workflow, download the the `.json` node graph file and load it into Invoke AI via the **Load Workflow** button in the Workflow Editor.
|
To use a community workflow, download the the `.json` node graph file and load it into Invoke AI via the **Load Workflow** button in the Workflow Editor.
|
||||||
|
|
||||||
- Community Nodes
|
- Community Nodes
|
||||||
|
+ [Adapters-Linked](#adapters-linked-nodes)
|
||||||
+ [Average Images](#average-images)
|
+ [Average Images](#average-images)
|
||||||
+ [Clean Image Artifacts After Cut](#clean-image-artifacts-after-cut)
|
+ [Clean Image Artifacts After Cut](#clean-image-artifacts-after-cut)
|
||||||
+ [Close Color Mask](#close-color-mask)
|
+ [Close Color Mask](#close-color-mask)
|
||||||
@ -32,8 +33,9 @@ To use a community workflow, download the the `.json` node graph file and load i
|
|||||||
+ [Image Resize Plus](#image-resize-plus)
|
+ [Image Resize Plus](#image-resize-plus)
|
||||||
+ [Load Video Frame](#load-video-frame)
|
+ [Load Video Frame](#load-video-frame)
|
||||||
+ [Make 3D](#make-3d)
|
+ [Make 3D](#make-3d)
|
||||||
+ [Mask Operations](#mask-operations)
|
+ [Mask Operations](#mask-operations)
|
||||||
+ [Match Histogram](#match-histogram)
|
+ [Match Histogram](#match-histogram)
|
||||||
|
+ [Metadata-Linked](#metadata-linked-nodes)
|
||||||
+ [Negative Image](#negative-image)
|
+ [Negative Image](#negative-image)
|
||||||
+ [Oobabooga](#oobabooga)
|
+ [Oobabooga](#oobabooga)
|
||||||
+ [Prompt Tools](#prompt-tools)
|
+ [Prompt Tools](#prompt-tools)
|
||||||
@ -51,6 +53,19 @@ To use a community workflow, download the the `.json` node graph file and load i
|
|||||||
- [Help](#help)
|
- [Help](#help)
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Adapters Linked Nodes
|
||||||
|
|
||||||
|
**Description:** A set of nodes for linked adapters (ControlNet, IP-Adaptor & T2I-Adapter). This allows multiple adapters to be chained together without using a `collect` node which means it can be used inside an `iterate` node without any collecting on every iteration issues.
|
||||||
|
|
||||||
|
- `ControlNet-Linked` - Collects ControlNet info to pass to other nodes.
|
||||||
|
- `IP-Adapter-Linked` - Collects IP-Adapter info to pass to other nodes.
|
||||||
|
- `T2I-Adapter-Linked` - Collects T2I-Adapter info to pass to other nodes.
|
||||||
|
|
||||||
|
Note: These are inherited from the core nodes so any update to the core nodes should be reflected in these.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/adapters-linked-nodes
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Average Images
|
### Average Images
|
||||||
|
|
||||||
@ -307,6 +322,20 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
|
|||||||
|
|
||||||
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" width="300" />
|
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" width="300" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Metadata Linked Nodes
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/metadata-linked-nodes
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Negative Image
|
### Negative Image
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from ..services.board_images.board_images_default import BoardImagesService
|
|||||||
from ..services.board_records.board_records_sqlite import SqliteBoardRecordStorage
|
from ..services.board_records.board_records_sqlite import SqliteBoardRecordStorage
|
||||||
from ..services.boards.boards_default import BoardService
|
from ..services.boards.boards_default import BoardService
|
||||||
from ..services.config import InvokeAIAppConfig
|
from ..services.config import InvokeAIAppConfig
|
||||||
|
from ..services.download import DownloadQueueService
|
||||||
from ..services.image_files.image_files_disk import DiskImageFileStorage
|
from ..services.image_files.image_files_disk import DiskImageFileStorage
|
||||||
from ..services.image_records.image_records_sqlite import SqliteImageRecordStorage
|
from ..services.image_records.image_records_sqlite import SqliteImageRecordStorage
|
||||||
from ..services.images.images_default import ImageService
|
from ..services.images.images_default import ImageService
|
||||||
@ -29,8 +30,7 @@ from ..services.model_records import ModelRecordServiceSQL
|
|||||||
from ..services.names.names_default import SimpleNameService
|
from ..services.names.names_default import SimpleNameService
|
||||||
from ..services.session_processor.session_processor_default import DefaultSessionProcessor
|
from ..services.session_processor.session_processor_default import DefaultSessionProcessor
|
||||||
from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue
|
from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue
|
||||||
from ..services.shared.default_graphs import create_system_graphs
|
from ..services.shared.graph import GraphExecutionState
|
||||||
from ..services.shared.graph import GraphExecutionState, LibraryGraph
|
|
||||||
from ..services.urls.urls_default import LocalUrlService
|
from ..services.urls.urls_default import LocalUrlService
|
||||||
from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
||||||
from .events import FastAPIEventService
|
from .events import FastAPIEventService
|
||||||
@ -80,13 +80,13 @@ class ApiDependencies:
|
|||||||
boards = BoardService()
|
boards = BoardService()
|
||||||
events = FastAPIEventService(event_handler_id)
|
events = FastAPIEventService(event_handler_id)
|
||||||
graph_execution_manager = SqliteItemStorage[GraphExecutionState](db=db, table_name="graph_executions")
|
graph_execution_manager = SqliteItemStorage[GraphExecutionState](db=db, table_name="graph_executions")
|
||||||
graph_library = SqliteItemStorage[LibraryGraph](db=db, table_name="graphs")
|
|
||||||
image_records = SqliteImageRecordStorage(db=db)
|
image_records = SqliteImageRecordStorage(db=db)
|
||||||
images = ImageService()
|
images = ImageService()
|
||||||
invocation_cache = MemoryInvocationCache(max_cache_size=config.node_cache_size)
|
invocation_cache = MemoryInvocationCache(max_cache_size=config.node_cache_size)
|
||||||
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f"{output_folder}/latents"))
|
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f"{output_folder}/latents"))
|
||||||
model_manager = ModelManagerService(config, logger)
|
model_manager = ModelManagerService(config, logger)
|
||||||
model_record_service = ModelRecordServiceSQL(db=db)
|
model_record_service = ModelRecordServiceSQL(db=db)
|
||||||
|
download_queue_service = DownloadQueueService(event_bus=events)
|
||||||
model_install_service = ModelInstallService(
|
model_install_service = ModelInstallService(
|
||||||
app_config=config, record_store=model_record_service, event_bus=events
|
app_config=config, record_store=model_record_service, event_bus=events
|
||||||
)
|
)
|
||||||
@ -107,7 +107,6 @@ class ApiDependencies:
|
|||||||
configuration=configuration,
|
configuration=configuration,
|
||||||
events=events,
|
events=events,
|
||||||
graph_execution_manager=graph_execution_manager,
|
graph_execution_manager=graph_execution_manager,
|
||||||
graph_library=graph_library,
|
|
||||||
image_files=image_files,
|
image_files=image_files,
|
||||||
image_records=image_records,
|
image_records=image_records,
|
||||||
images=images,
|
images=images,
|
||||||
@ -116,6 +115,7 @@ class ApiDependencies:
|
|||||||
logger=logger,
|
logger=logger,
|
||||||
model_manager=model_manager,
|
model_manager=model_manager,
|
||||||
model_records=model_record_service,
|
model_records=model_record_service,
|
||||||
|
download_queue=download_queue_service,
|
||||||
model_install=model_install_service,
|
model_install=model_install_service,
|
||||||
names=names,
|
names=names,
|
||||||
performance_statistics=performance_statistics,
|
performance_statistics=performance_statistics,
|
||||||
@ -127,8 +127,6 @@ class ApiDependencies:
|
|||||||
workflow_records=workflow_records,
|
workflow_records=workflow_records,
|
||||||
)
|
)
|
||||||
|
|
||||||
create_system_graphs(services.graph_library)
|
|
||||||
|
|
||||||
ApiDependencies.invoker = Invoker(services)
|
ApiDependencies.invoker = Invoker(services)
|
||||||
db.clean()
|
db.clean()
|
||||||
|
|
||||||
|
111
invokeai/app/api/routers/download_queue.py
Normal file
111
invokeai/app/api/routers/download_queue.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# Copyright (c) 2023 Lincoln D. Stein
|
||||||
|
"""FastAPI route for the download queue."""
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from fastapi import Body, Path, Response
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
|
from pydantic.networks import AnyHttpUrl
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
from invokeai.app.services.download import (
|
||||||
|
DownloadJob,
|
||||||
|
UnknownJobIDException,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
|
download_queue_router = APIRouter(prefix="/v1/download_queue", tags=["download_queue"])
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.get(
|
||||||
|
"/",
|
||||||
|
operation_id="list_downloads",
|
||||||
|
)
|
||||||
|
async def list_downloads() -> List[DownloadJob]:
|
||||||
|
"""Get a list of active and inactive jobs."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
return queue.list_jobs()
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.patch(
|
||||||
|
"/",
|
||||||
|
operation_id="prune_downloads",
|
||||||
|
responses={
|
||||||
|
204: {"description": "All completed jobs have been pruned"},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def prune_downloads():
|
||||||
|
"""Prune completed and errored jobs."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
queue.prune_jobs()
|
||||||
|
return Response(status_code=204)
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.post(
|
||||||
|
"/i/",
|
||||||
|
operation_id="download",
|
||||||
|
)
|
||||||
|
async def download(
|
||||||
|
source: AnyHttpUrl = Body(description="download source"),
|
||||||
|
dest: str = Body(description="download destination"),
|
||||||
|
priority: int = Body(default=10, description="queue priority"),
|
||||||
|
access_token: Optional[str] = Body(default=None, description="token for authorization to download"),
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""Download the source URL to the file or directory indicted in dest."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
return queue.download(source, dest, priority, access_token)
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.get(
|
||||||
|
"/i/{id}",
|
||||||
|
operation_id="get_download_job",
|
||||||
|
responses={
|
||||||
|
200: {"description": "Success"},
|
||||||
|
404: {"description": "The requested download JobID could not be found"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_download_job(
|
||||||
|
id: int = Path(description="ID of the download job to fetch."),
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""Get a download job using its ID."""
|
||||||
|
try:
|
||||||
|
job = ApiDependencies.invoker.services.download_queue.id_to_job(id)
|
||||||
|
return job
|
||||||
|
except UnknownJobIDException as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.delete(
|
||||||
|
"/i/{id}",
|
||||||
|
operation_id="cancel_download_job",
|
||||||
|
responses={
|
||||||
|
204: {"description": "Job has been cancelled"},
|
||||||
|
404: {"description": "The requested download JobID could not be found"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def cancel_download_job(
|
||||||
|
id: int = Path(description="ID of the download job to cancel."),
|
||||||
|
):
|
||||||
|
"""Cancel a download job using its ID."""
|
||||||
|
try:
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
job = queue.id_to_job(id)
|
||||||
|
queue.cancel_job(job)
|
||||||
|
return Response(status_code=204)
|
||||||
|
except UnknownJobIDException as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.delete(
|
||||||
|
"/i",
|
||||||
|
operation_id="cancel_all_download_jobs",
|
||||||
|
responses={
|
||||||
|
204: {"description": "Download jobs have been cancelled"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def cancel_all_download_jobs():
|
||||||
|
"""Cancel all download jobs."""
|
||||||
|
ApiDependencies.invoker.services.download_queue.cancel_all_jobs()
|
||||||
|
return Response(status_code=204)
|
@ -45,6 +45,7 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
app_info,
|
app_info,
|
||||||
board_images,
|
board_images,
|
||||||
boards,
|
boards,
|
||||||
|
download_queue,
|
||||||
images,
|
images,
|
||||||
model_records,
|
model_records,
|
||||||
models,
|
models,
|
||||||
@ -116,6 +117,7 @@ app.include_router(sessions.session_router, prefix="/api")
|
|||||||
app.include_router(utilities.utilities_router, prefix="/api")
|
app.include_router(utilities.utilities_router, prefix="/api")
|
||||||
app.include_router(models.models_router, prefix="/api")
|
app.include_router(models.models_router, prefix="/api")
|
||||||
app.include_router(model_records.model_records_router, prefix="/api")
|
app.include_router(model_records.model_records_router, prefix="/api")
|
||||||
|
app.include_router(download_queue.download_queue_router, prefix="/api")
|
||||||
app.include_router(images.images_router, prefix="/api")
|
app.include_router(images.images_router, prefix="/api")
|
||||||
app.include_router(boards.boards_router, prefix="/api")
|
app.include_router(boards.boards_router, prefix="/api")
|
||||||
app.include_router(board_images.board_images_router, prefix="/api")
|
app.include_router(board_images.board_images_router, prefix="/api")
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import re
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
@ -17,6 +16,7 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
|||||||
from ...backend.model_management.lora import ModelPatcher
|
from ...backend.model_management.lora import ModelPatcher
|
||||||
from ...backend.model_management.models import ModelNotFoundException, ModelType
|
from ...backend.model_management.models import ModelNotFoundException, ModelType
|
||||||
from ...backend.util.devices import torch_dtype
|
from ...backend.util.devices import torch_dtype
|
||||||
|
from ..util.ti_utils import extract_ti_triggers_from_prompt
|
||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
@ -87,7 +87,7 @@ class CompelInvocation(BaseInvocation):
|
|||||||
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
||||||
|
|
||||||
ti_list = []
|
ti_list = []
|
||||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt):
|
for trigger in extract_ti_triggers_from_prompt(self.prompt):
|
||||||
name = trigger[1:-1]
|
name = trigger[1:-1]
|
||||||
try:
|
try:
|
||||||
ti_list.append(
|
ti_list.append(
|
||||||
@ -210,7 +210,7 @@ class SDXLPromptInvocationBase:
|
|||||||
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
||||||
|
|
||||||
ti_list = []
|
ti_list = []
|
||||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", prompt):
|
for trigger in extract_ti_triggers_from_prompt(prompt):
|
||||||
name = trigger[1:-1]
|
name = trigger[1:-1]
|
||||||
try:
|
try:
|
||||||
ti_list.append(
|
ti_list.append(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# Copyright (c) 2023 Borisov Sergey (https://github.com/StAlKeR7779)
|
# Copyright (c) 2023 Borisov Sergey (https://github.com/StAlKeR7779)
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
|
||||||
|
|
||||||
# from contextlib import ExitStack
|
# from contextlib import ExitStack
|
||||||
from typing import List, Literal, Union
|
from typing import List, Literal, Union
|
||||||
@ -21,6 +20,7 @@ from invokeai.backend import BaseModelType, ModelType, SubModelType
|
|||||||
from ...backend.model_management import ONNXModelPatcher
|
from ...backend.model_management import ONNXModelPatcher
|
||||||
from ...backend.stable_diffusion import PipelineIntermediateState
|
from ...backend.stable_diffusion import PipelineIntermediateState
|
||||||
from ...backend.util import choose_torch_device
|
from ...backend.util import choose_torch_device
|
||||||
|
from ..util.ti_utils import extract_ti_triggers_from_prompt
|
||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
@ -78,7 +78,7 @@ class ONNXPromptInvocation(BaseInvocation):
|
|||||||
]
|
]
|
||||||
|
|
||||||
ti_list = []
|
ti_list = []
|
||||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt):
|
for trigger in extract_ti_triggers_from_prompt(self.prompt):
|
||||||
name = trigger[1:-1]
|
name = trigger[1:-1]
|
||||||
try:
|
try:
|
||||||
ti_list.append(
|
ti_list.append(
|
||||||
|
@ -356,7 +356,7 @@ class InvokeAIAppConfig(InvokeAISettings):
|
|||||||
else:
|
else:
|
||||||
root = self.find_root().expanduser().absolute()
|
root = self.find_root().expanduser().absolute()
|
||||||
self.root = root # insulate ourselves from relative paths that may change
|
self.root = root # insulate ourselves from relative paths that may change
|
||||||
return root
|
return root.resolve()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def root_dir(self) -> Path:
|
def root_dir(self) -> Path:
|
||||||
|
12
invokeai/app/services/download/__init__.py
Normal file
12
invokeai/app/services/download/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""Init file for download queue."""
|
||||||
|
from .download_base import DownloadJob, DownloadJobStatus, DownloadQueueServiceBase, UnknownJobIDException
|
||||||
|
from .download_default import DownloadQueueService, TqdmProgress
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DownloadJob",
|
||||||
|
"DownloadQueueServiceBase",
|
||||||
|
"DownloadQueueService",
|
||||||
|
"TqdmProgress",
|
||||||
|
"DownloadJobStatus",
|
||||||
|
"UnknownJobIDException",
|
||||||
|
]
|
217
invokeai/app/services/download/download_base.py
Normal file
217
invokeai/app/services/download/download_base.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# Copyright (c) 2023 Lincoln D. Stein and the InvokeAI Development Team
|
||||||
|
"""Model download service."""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from enum import Enum
|
||||||
|
from functools import total_ordering
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Callable, List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, PrivateAttr
|
||||||
|
from pydantic.networks import AnyHttpUrl
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadJobStatus(str, Enum):
|
||||||
|
"""State of a download job."""
|
||||||
|
|
||||||
|
WAITING = "waiting" # not enqueued, will not run
|
||||||
|
RUNNING = "running" # actively downloading
|
||||||
|
COMPLETED = "completed" # finished running
|
||||||
|
CANCELLED = "cancelled" # user cancelled
|
||||||
|
ERROR = "error" # terminated with an error message
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadJobCancelledException(Exception):
|
||||||
|
"""This exception is raised when a download job is cancelled."""
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownJobIDException(Exception):
|
||||||
|
"""This exception is raised when an invalid job id is referened."""
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceInactiveException(Exception):
|
||||||
|
"""This exception is raised when user attempts to initiate a download before the service is started."""
|
||||||
|
|
||||||
|
|
||||||
|
DownloadEventHandler = Callable[["DownloadJob"], None]
|
||||||
|
|
||||||
|
|
||||||
|
@total_ordering
|
||||||
|
class DownloadJob(BaseModel):
|
||||||
|
"""Class to monitor and control a model download request."""
|
||||||
|
|
||||||
|
# required variables to be passed in on creation
|
||||||
|
source: AnyHttpUrl = Field(description="Where to download from. Specific types specified in child classes.")
|
||||||
|
dest: Path = Field(description="Destination of downloaded model on local disk; a directory or file path")
|
||||||
|
access_token: Optional[str] = Field(default=None, description="authorization token for protected resources")
|
||||||
|
# automatically assigned on creation
|
||||||
|
id: int = Field(description="Numeric ID of this job", default=-1) # default id is a sentinel
|
||||||
|
priority: int = Field(default=10, description="Queue priority; lower values are higher priority")
|
||||||
|
|
||||||
|
# set internally during download process
|
||||||
|
status: DownloadJobStatus = Field(default=DownloadJobStatus.WAITING, description="Status of the download")
|
||||||
|
download_path: Optional[Path] = Field(default=None, description="Final location of downloaded file")
|
||||||
|
job_started: Optional[str] = Field(default=None, description="Timestamp for when the download job started")
|
||||||
|
job_ended: Optional[str] = Field(
|
||||||
|
default=None, description="Timestamp for when the download job ende1d (completed or errored)"
|
||||||
|
)
|
||||||
|
bytes: int = Field(default=0, description="Bytes downloaded so far")
|
||||||
|
total_bytes: int = Field(default=0, description="Total file size (bytes)")
|
||||||
|
|
||||||
|
# set when an error occurs
|
||||||
|
error_type: Optional[str] = Field(default=None, description="Name of exception that caused an error")
|
||||||
|
error: Optional[str] = Field(default=None, description="Traceback of the exception that caused an error")
|
||||||
|
|
||||||
|
# internal flag
|
||||||
|
_cancelled: bool = PrivateAttr(default=False)
|
||||||
|
|
||||||
|
# optional event handlers passed in on creation
|
||||||
|
_on_start: Optional[DownloadEventHandler] = PrivateAttr(default=None)
|
||||||
|
_on_progress: Optional[DownloadEventHandler] = PrivateAttr(default=None)
|
||||||
|
_on_complete: Optional[DownloadEventHandler] = PrivateAttr(default=None)
|
||||||
|
_on_cancelled: Optional[DownloadEventHandler] = PrivateAttr(default=None)
|
||||||
|
_on_error: Optional[DownloadEventHandler] = PrivateAttr(default=None)
|
||||||
|
|
||||||
|
def __le__(self, other: "DownloadJob") -> bool:
|
||||||
|
"""Return True if this job's priority is less than another's."""
|
||||||
|
return self.priority <= other.priority
|
||||||
|
|
||||||
|
def cancel(self) -> None:
|
||||||
|
"""Call to cancel the job."""
|
||||||
|
self._cancelled = True
|
||||||
|
|
||||||
|
# cancelled and the callbacks are private attributes in order to prevent
|
||||||
|
# them from being serialized and/or used in the Json Schema
|
||||||
|
@property
|
||||||
|
def cancelled(self) -> bool:
|
||||||
|
"""Call to cancel the job."""
|
||||||
|
return self._cancelled
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_start(self) -> Optional[DownloadEventHandler]:
|
||||||
|
"""Return the on_start event handler."""
|
||||||
|
return self._on_start
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_progress(self) -> Optional[DownloadEventHandler]:
|
||||||
|
"""Return the on_progress event handler."""
|
||||||
|
return self._on_progress
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_complete(self) -> Optional[DownloadEventHandler]:
|
||||||
|
"""Return the on_complete event handler."""
|
||||||
|
return self._on_complete
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_error(self) -> Optional[DownloadEventHandler]:
|
||||||
|
"""Return the on_error event handler."""
|
||||||
|
return self._on_error
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_cancelled(self) -> Optional[DownloadEventHandler]:
|
||||||
|
"""Return the on_cancelled event handler."""
|
||||||
|
return self._on_cancelled
|
||||||
|
|
||||||
|
def set_callbacks(
|
||||||
|
self,
|
||||||
|
on_start: Optional[DownloadEventHandler] = None,
|
||||||
|
on_progress: Optional[DownloadEventHandler] = None,
|
||||||
|
on_complete: Optional[DownloadEventHandler] = None,
|
||||||
|
on_cancelled: Optional[DownloadEventHandler] = None,
|
||||||
|
on_error: Optional[DownloadEventHandler] = None,
|
||||||
|
) -> None:
|
||||||
|
"""Set the callbacks for download events."""
|
||||||
|
self._on_start = on_start
|
||||||
|
self._on_progress = on_progress
|
||||||
|
self._on_complete = on_complete
|
||||||
|
self._on_error = on_error
|
||||||
|
self._on_cancelled = on_cancelled
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadQueueServiceBase(ABC):
|
||||||
|
"""Multithreaded queue for downloading models via URL."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def start(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Start the download worker threads."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def stop(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Stop the download worker threads."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def download(
|
||||||
|
self,
|
||||||
|
source: AnyHttpUrl,
|
||||||
|
dest: Path,
|
||||||
|
priority: int = 10,
|
||||||
|
access_token: Optional[str] = None,
|
||||||
|
on_start: Optional[DownloadEventHandler] = None,
|
||||||
|
on_progress: Optional[DownloadEventHandler] = None,
|
||||||
|
on_complete: Optional[DownloadEventHandler] = None,
|
||||||
|
on_cancelled: Optional[DownloadEventHandler] = None,
|
||||||
|
on_error: Optional[DownloadEventHandler] = None,
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""
|
||||||
|
Create a download job.
|
||||||
|
|
||||||
|
:param source: Source of the download as a URL.
|
||||||
|
:param dest: Path to download to. See below.
|
||||||
|
:param on_start, on_progress, on_complete, on_error: Callbacks for the indicated
|
||||||
|
events.
|
||||||
|
:returns: A DownloadJob object for monitoring the state of the download.
|
||||||
|
|
||||||
|
The `dest` argument is a Path object. Its behavior is:
|
||||||
|
|
||||||
|
1. If the path exists and is a directory, then the URL contents will be downloaded
|
||||||
|
into that directory using the filename indicated in the response's `Content-Disposition` field.
|
||||||
|
If no content-disposition is present, then the last component of the URL will be used (similar to
|
||||||
|
wget's behavior).
|
||||||
|
2. If the path does not exist, then it is taken as the name of a new file to create with the downloaded
|
||||||
|
content.
|
||||||
|
3. If the path exists and is an existing file, then the downloader will try to resume the download from
|
||||||
|
the end of the existing file.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def list_jobs(self) -> List[DownloadJob]:
|
||||||
|
"""
|
||||||
|
List active download jobs.
|
||||||
|
|
||||||
|
:returns List[DownloadJob]: List of download jobs whose state is not "completed."
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def id_to_job(self, id: int) -> DownloadJob:
|
||||||
|
"""
|
||||||
|
Return the DownloadJob corresponding to the integer ID.
|
||||||
|
|
||||||
|
:param id: ID of the DownloadJob.
|
||||||
|
|
||||||
|
Exceptions:
|
||||||
|
* UnknownJobIDException
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cancel_all_jobs(self):
|
||||||
|
"""Cancel all active and enquedjobs."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def prune_jobs(self):
|
||||||
|
"""Prune completed and errored queue items from the job list."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cancel_job(self, job: DownloadJob):
|
||||||
|
"""Cancel the job, clearing partial downloads and putting it into ERROR state."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def join(self):
|
||||||
|
"""Wait until all jobs are off the queue."""
|
||||||
|
pass
|
418
invokeai/app/services/download/download_default.py
Normal file
418
invokeai/app/services/download/download_default.py
Normal file
@ -0,0 +1,418 @@
|
|||||||
|
# Copyright (c) 2023, Lincoln D. Stein
|
||||||
|
"""Implementation of multithreaded download queue for invokeai."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
from logging import Logger
|
||||||
|
from pathlib import Path
|
||||||
|
from queue import Empty, PriorityQueue
|
||||||
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from pydantic.networks import AnyHttpUrl
|
||||||
|
from requests import HTTPError
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from invokeai.app.services.events.events_base import EventServiceBase
|
||||||
|
from invokeai.app.util.misc import get_iso_timestamp
|
||||||
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
|
|
||||||
|
from .download_base import (
|
||||||
|
DownloadEventHandler,
|
||||||
|
DownloadJob,
|
||||||
|
DownloadJobCancelledException,
|
||||||
|
DownloadJobStatus,
|
||||||
|
DownloadQueueServiceBase,
|
||||||
|
ServiceInactiveException,
|
||||||
|
UnknownJobIDException,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Maximum number of bytes to download during each call to requests.iter_content()
|
||||||
|
DOWNLOAD_CHUNK_SIZE = 100000
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadQueueService(DownloadQueueServiceBase):
|
||||||
|
"""Class for queued download of models."""
|
||||||
|
|
||||||
|
_jobs: Dict[int, DownloadJob]
|
||||||
|
_max_parallel_dl: int = 5
|
||||||
|
_worker_pool: Set[threading.Thread]
|
||||||
|
_queue: PriorityQueue[DownloadJob]
|
||||||
|
_stop_event: threading.Event
|
||||||
|
_lock: threading.Lock
|
||||||
|
_logger: Logger
|
||||||
|
_events: Optional[EventServiceBase] = None
|
||||||
|
_next_job_id: int = 0
|
||||||
|
_accept_download_requests: bool = False
|
||||||
|
_requests: requests.sessions.Session
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
max_parallel_dl: int = 5,
|
||||||
|
event_bus: Optional[EventServiceBase] = None,
|
||||||
|
requests_session: Optional[requests.sessions.Session] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize DownloadQueue.
|
||||||
|
|
||||||
|
:param max_parallel_dl: Number of simultaneous downloads allowed [5].
|
||||||
|
:param requests_session: Optional requests.sessions.Session object, for unit tests.
|
||||||
|
"""
|
||||||
|
self._jobs = {}
|
||||||
|
self._next_job_id = 0
|
||||||
|
self._queue = PriorityQueue()
|
||||||
|
self._stop_event = threading.Event()
|
||||||
|
self._worker_pool = set()
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._logger = InvokeAILogger.get_logger("DownloadQueueService")
|
||||||
|
self._event_bus = event_bus
|
||||||
|
self._requests = requests_session or requests.Session()
|
||||||
|
self._accept_download_requests = False
|
||||||
|
self._max_parallel_dl = max_parallel_dl
|
||||||
|
|
||||||
|
def start(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Start the download worker threads."""
|
||||||
|
with self._lock:
|
||||||
|
if self._worker_pool:
|
||||||
|
raise Exception("Attempt to start the download service twice")
|
||||||
|
self._stop_event.clear()
|
||||||
|
self._start_workers(self._max_parallel_dl)
|
||||||
|
self._accept_download_requests = True
|
||||||
|
|
||||||
|
def stop(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Stop the download worker threads."""
|
||||||
|
with self._lock:
|
||||||
|
if not self._worker_pool:
|
||||||
|
raise Exception("Attempt to stop the download service before it was started")
|
||||||
|
self._accept_download_requests = False # reject attempts to add new jobs to queue
|
||||||
|
queued_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.WAITING]
|
||||||
|
active_jobs = [x for x in self.list_jobs() if x.status == DownloadJobStatus.RUNNING]
|
||||||
|
if queued_jobs:
|
||||||
|
self._logger.warning(f"Cancelling {len(queued_jobs)} queued downloads")
|
||||||
|
if active_jobs:
|
||||||
|
self._logger.info(f"Waiting for {len(active_jobs)} active download jobs to complete")
|
||||||
|
with self._queue.mutex:
|
||||||
|
self._queue.queue.clear()
|
||||||
|
self.join() # wait for all active jobs to finish
|
||||||
|
self._stop_event.set()
|
||||||
|
self._worker_pool.clear()
|
||||||
|
|
||||||
|
def download(
|
||||||
|
self,
|
||||||
|
source: AnyHttpUrl,
|
||||||
|
dest: Path,
|
||||||
|
priority: int = 10,
|
||||||
|
access_token: Optional[str] = None,
|
||||||
|
on_start: Optional[DownloadEventHandler] = None,
|
||||||
|
on_progress: Optional[DownloadEventHandler] = None,
|
||||||
|
on_complete: Optional[DownloadEventHandler] = None,
|
||||||
|
on_cancelled: Optional[DownloadEventHandler] = None,
|
||||||
|
on_error: Optional[DownloadEventHandler] = None,
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""Create a download job and return its ID."""
|
||||||
|
if not self._accept_download_requests:
|
||||||
|
raise ServiceInactiveException(
|
||||||
|
"The download service is not currently accepting requests. Please call start() to initialize the service."
|
||||||
|
)
|
||||||
|
with self._lock:
|
||||||
|
id = self._next_job_id
|
||||||
|
self._next_job_id += 1
|
||||||
|
job = DownloadJob(
|
||||||
|
id=id,
|
||||||
|
source=source,
|
||||||
|
dest=dest,
|
||||||
|
priority=priority,
|
||||||
|
access_token=access_token,
|
||||||
|
)
|
||||||
|
job.set_callbacks(
|
||||||
|
on_start=on_start,
|
||||||
|
on_progress=on_progress,
|
||||||
|
on_complete=on_complete,
|
||||||
|
on_cancelled=on_cancelled,
|
||||||
|
on_error=on_error,
|
||||||
|
)
|
||||||
|
self._jobs[id] = job
|
||||||
|
self._queue.put(job)
|
||||||
|
return job
|
||||||
|
|
||||||
|
def join(self) -> None:
|
||||||
|
"""Wait for all jobs to complete."""
|
||||||
|
self._queue.join()
|
||||||
|
|
||||||
|
def list_jobs(self) -> List[DownloadJob]:
|
||||||
|
"""List all the jobs."""
|
||||||
|
return list(self._jobs.values())
|
||||||
|
|
||||||
|
def prune_jobs(self) -> None:
|
||||||
|
"""Prune completed and errored queue items from the job list."""
|
||||||
|
with self._lock:
|
||||||
|
to_delete = set()
|
||||||
|
for job_id, job in self._jobs.items():
|
||||||
|
if self._in_terminal_state(job):
|
||||||
|
to_delete.add(job_id)
|
||||||
|
for job_id in to_delete:
|
||||||
|
del self._jobs[job_id]
|
||||||
|
|
||||||
|
def id_to_job(self, id: int) -> DownloadJob:
|
||||||
|
"""Translate a job ID into a DownloadJob object."""
|
||||||
|
try:
|
||||||
|
return self._jobs[id]
|
||||||
|
except KeyError as excp:
|
||||||
|
raise UnknownJobIDException("Unrecognized job") from excp
|
||||||
|
|
||||||
|
def cancel_job(self, job: DownloadJob) -> None:
|
||||||
|
"""
|
||||||
|
Cancel the indicated job.
|
||||||
|
|
||||||
|
If it is running it will be stopped.
|
||||||
|
job.status will be set to DownloadJobStatus.CANCELLED
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
job.cancel()
|
||||||
|
|
||||||
|
def cancel_all_jobs(self, preserve_partial: bool = False) -> None:
|
||||||
|
"""Cancel all jobs (those not in enqueued, running or paused state)."""
|
||||||
|
for job in self._jobs.values():
|
||||||
|
if not self._in_terminal_state(job):
|
||||||
|
self.cancel_job(job)
|
||||||
|
|
||||||
|
def _in_terminal_state(self, job: DownloadJob) -> bool:
|
||||||
|
return job.status in [
|
||||||
|
DownloadJobStatus.COMPLETED,
|
||||||
|
DownloadJobStatus.CANCELLED,
|
||||||
|
DownloadJobStatus.ERROR,
|
||||||
|
]
|
||||||
|
|
||||||
|
def _start_workers(self, max_workers: int) -> None:
|
||||||
|
"""Start the requested number of worker threads."""
|
||||||
|
self._stop_event.clear()
|
||||||
|
for i in range(0, max_workers): # noqa B007
|
||||||
|
worker = threading.Thread(target=self._download_next_item, daemon=True)
|
||||||
|
self._logger.debug(f"Download queue worker thread {worker.name} starting.")
|
||||||
|
worker.start()
|
||||||
|
self._worker_pool.add(worker)
|
||||||
|
|
||||||
|
def _download_next_item(self) -> None:
|
||||||
|
"""Worker thread gets next job on priority queue."""
|
||||||
|
done = False
|
||||||
|
while not done:
|
||||||
|
if self._stop_event.is_set():
|
||||||
|
done = True
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
job = self._queue.get(timeout=1)
|
||||||
|
except Empty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
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()
|
||||||
|
self._signal_job_error(job)
|
||||||
|
except DownloadJobCancelledException:
|
||||||
|
self._signal_job_cancelled(job)
|
||||||
|
self._cleanup_cancelled_job(job)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
job.job_ended = get_iso_timestamp()
|
||||||
|
self._queue.task_done()
|
||||||
|
self._logger.debug(f"Download queue worker thread {threading.current_thread().name} exiting.")
|
||||||
|
|
||||||
|
def _do_download(self, job: DownloadJob) -> None:
|
||||||
|
"""Do the actual download."""
|
||||||
|
url = job.source
|
||||||
|
header = {"Authorization": f"Bearer {job.access_token}"} if job.access_token else {}
|
||||||
|
open_mode = "wb"
|
||||||
|
|
||||||
|
# Make a streaming request. This will retrieve headers including
|
||||||
|
# content-length and content-disposition, but not fetch any content itself
|
||||||
|
resp = self._requests.get(str(url), headers=header, stream=True)
|
||||||
|
if not resp.ok:
|
||||||
|
raise HTTPError(resp.reason)
|
||||||
|
content_length = int(resp.headers.get("content-length", 0))
|
||||||
|
job.total_bytes = content_length
|
||||||
|
|
||||||
|
if job.dest.is_dir():
|
||||||
|
file_name = os.path.basename(str(url.path)) # default is to use the last bit of the URL
|
||||||
|
|
||||||
|
if match := re.search('filename="(.+)"', resp.headers.get("Content-Disposition", "")):
|
||||||
|
remote_name = match.group(1)
|
||||||
|
if self._validate_filename(job.dest.as_posix(), remote_name):
|
||||||
|
file_name = remote_name
|
||||||
|
|
||||||
|
job.download_path = job.dest / file_name
|
||||||
|
|
||||||
|
else:
|
||||||
|
job.dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
job.download_path = job.dest
|
||||||
|
|
||||||
|
assert job.download_path
|
||||||
|
|
||||||
|
# Don't clobber an existing file. See commit 82c2c85202f88c6d24ff84710f297cfc6ae174af
|
||||||
|
# for code that instead resumes an interrupted download.
|
||||||
|
if job.download_path.exists():
|
||||||
|
raise OSError(f"[Errno 17] File {job.download_path} exists")
|
||||||
|
|
||||||
|
# append ".downloading" to the path
|
||||||
|
in_progress_path = self._in_progress_path(job.download_path)
|
||||||
|
|
||||||
|
# signal caller that the download is starting. At this point, key fields such as
|
||||||
|
# download_path and total_bytes will be populated. We call it here because the might
|
||||||
|
# discover that the local file is already complete and generate a COMPLETED status.
|
||||||
|
self._signal_job_started(job)
|
||||||
|
|
||||||
|
# "range not satisfiable" - local file is at least as large as the remote file
|
||||||
|
if resp.status_code == 416 or (content_length > 0 and job.bytes >= content_length):
|
||||||
|
self._logger.warning(f"{job.download_path}: complete file found. Skipping.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# "partial content" - local file is smaller than remote file
|
||||||
|
elif resp.status_code == 206 or job.bytes > 0:
|
||||||
|
self._logger.warning(f"{job.download_path}: partial file found. Resuming")
|
||||||
|
|
||||||
|
# some other error
|
||||||
|
elif resp.status_code != 200:
|
||||||
|
raise HTTPError(resp.reason)
|
||||||
|
|
||||||
|
self._logger.debug(f"{job.source}: Downloading {job.download_path}")
|
||||||
|
report_delta = job.total_bytes / 100 # report every 1% change
|
||||||
|
last_report_bytes = 0
|
||||||
|
|
||||||
|
# DOWNLOAD LOOP
|
||||||
|
with open(in_progress_path, open_mode) as file:
|
||||||
|
for data in resp.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
|
||||||
|
if job.cancelled:
|
||||||
|
raise DownloadJobCancelledException("Job was cancelled at caller's request")
|
||||||
|
|
||||||
|
job.bytes += file.write(data)
|
||||||
|
if (job.bytes - last_report_bytes >= report_delta) or (job.bytes >= job.total_bytes):
|
||||||
|
last_report_bytes = job.bytes
|
||||||
|
self._signal_job_progress(job)
|
||||||
|
|
||||||
|
# if we get here we are done and can rename the file to the original dest
|
||||||
|
in_progress_path.rename(job.download_path)
|
||||||
|
|
||||||
|
def _validate_filename(self, directory: str, filename: str) -> bool:
|
||||||
|
pc_name_max = os.pathconf(directory, "PC_NAME_MAX") if hasattr(os, "pathconf") else 260 # hardcoded for windows
|
||||||
|
pc_path_max = (
|
||||||
|
os.pathconf(directory, "PC_PATH_MAX") if hasattr(os, "pathconf") else 32767
|
||||||
|
) # hardcoded for windows with long names enabled
|
||||||
|
if "/" in filename:
|
||||||
|
return False
|
||||||
|
if filename.startswith(".."):
|
||||||
|
return False
|
||||||
|
if len(filename) > pc_name_max:
|
||||||
|
return False
|
||||||
|
if len(os.path.join(directory, filename)) > pc_path_max:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _in_progress_path(self, path: Path) -> Path:
|
||||||
|
return path.with_name(path.name + ".downloading")
|
||||||
|
|
||||||
|
def _signal_job_started(self, job: DownloadJob) -> None:
|
||||||
|
job.status = DownloadJobStatus.RUNNING
|
||||||
|
if job.on_start:
|
||||||
|
try:
|
||||||
|
job.on_start(job)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(e)
|
||||||
|
if self._event_bus:
|
||||||
|
assert job.download_path
|
||||||
|
self._event_bus.emit_download_started(str(job.source), job.download_path.as_posix())
|
||||||
|
|
||||||
|
def _signal_job_progress(self, job: DownloadJob) -> None:
|
||||||
|
if job.on_progress:
|
||||||
|
try:
|
||||||
|
job.on_progress(job)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(e)
|
||||||
|
if self._event_bus:
|
||||||
|
assert job.download_path
|
||||||
|
self._event_bus.emit_download_progress(
|
||||||
|
str(job.source),
|
||||||
|
download_path=job.download_path.as_posix(),
|
||||||
|
current_bytes=job.bytes,
|
||||||
|
total_bytes=job.total_bytes,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _signal_job_complete(self, job: DownloadJob) -> None:
|
||||||
|
job.status = DownloadJobStatus.COMPLETED
|
||||||
|
if job.on_complete:
|
||||||
|
try:
|
||||||
|
job.on_complete(job)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(e)
|
||||||
|
if self._event_bus:
|
||||||
|
assert job.download_path
|
||||||
|
self._event_bus.emit_download_complete(
|
||||||
|
str(job.source), download_path=job.download_path.as_posix(), total_bytes=job.total_bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
def _signal_job_cancelled(self, job: DownloadJob) -> None:
|
||||||
|
job.status = DownloadJobStatus.CANCELLED
|
||||||
|
if job.on_cancelled:
|
||||||
|
try:
|
||||||
|
job.on_cancelled(job)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(e)
|
||||||
|
if self._event_bus:
|
||||||
|
self._event_bus.emit_download_cancelled(str(job.source))
|
||||||
|
|
||||||
|
def _signal_job_error(self, job: DownloadJob) -> None:
|
||||||
|
job.status = DownloadJobStatus.ERROR
|
||||||
|
if job.on_error:
|
||||||
|
try:
|
||||||
|
job.on_error(job)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(e)
|
||||||
|
if self._event_bus:
|
||||||
|
assert job.error_type
|
||||||
|
assert job.error
|
||||||
|
self._event_bus.emit_download_error(str(job.source), error_type=job.error_type, error=job.error)
|
||||||
|
|
||||||
|
def _cleanup_cancelled_job(self, job: DownloadJob) -> None:
|
||||||
|
self._logger.warning(f"Cleaning up leftover files from cancelled download job {job.download_path}")
|
||||||
|
try:
|
||||||
|
if job.download_path:
|
||||||
|
partial_file = self._in_progress_path(job.download_path)
|
||||||
|
partial_file.unlink()
|
||||||
|
except OSError as excp:
|
||||||
|
self._logger.warning(excp)
|
||||||
|
|
||||||
|
|
||||||
|
# Example on_progress event handler to display a TQDM status bar
|
||||||
|
# Activate with:
|
||||||
|
# download_service.download('http://foo.bar/baz', '/tmp', on_progress=TqdmProgress().job_update
|
||||||
|
class TqdmProgress(object):
|
||||||
|
"""TQDM-based progress bar object to use in on_progress handlers."""
|
||||||
|
|
||||||
|
_bars: Dict[int, tqdm] # the tqdm object
|
||||||
|
_last: Dict[int, int] # last bytes downloaded
|
||||||
|
|
||||||
|
def __init__(self) -> None: # noqa D107
|
||||||
|
self._bars = {}
|
||||||
|
self._last = {}
|
||||||
|
|
||||||
|
def update(self, job: DownloadJob) -> None: # noqa D102
|
||||||
|
job_id = job.id
|
||||||
|
# new job
|
||||||
|
if job_id not in self._bars:
|
||||||
|
assert job.download_path
|
||||||
|
dest = Path(job.download_path).name
|
||||||
|
self._bars[job_id] = tqdm(
|
||||||
|
desc=dest,
|
||||||
|
initial=0,
|
||||||
|
total=job.total_bytes,
|
||||||
|
unit="iB",
|
||||||
|
unit_scale=True,
|
||||||
|
)
|
||||||
|
self._last[job_id] = 0
|
||||||
|
self._bars[job_id].update(job.bytes - self._last[job_id])
|
||||||
|
self._last[job_id] = job.bytes
|
@ -17,6 +17,7 @@ from invokeai.backend.model_management.models.base import BaseModelType, ModelTy
|
|||||||
|
|
||||||
class EventServiceBase:
|
class EventServiceBase:
|
||||||
queue_event: str = "queue_event"
|
queue_event: str = "queue_event"
|
||||||
|
download_event: str = "download_event"
|
||||||
model_event: str = "model_event"
|
model_event: str = "model_event"
|
||||||
|
|
||||||
"""Basic event bus, to have an empty stand-in when not needed"""
|
"""Basic event bus, to have an empty stand-in when not needed"""
|
||||||
@ -32,6 +33,13 @@ class EventServiceBase:
|
|||||||
payload={"event": event_name, "data": payload},
|
payload={"event": event_name, "data": payload},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __emit_download_event(self, event_name: str, payload: dict) -> None:
|
||||||
|
payload["timestamp"] = get_timestamp()
|
||||||
|
self.dispatch(
|
||||||
|
event_name=EventServiceBase.download_event,
|
||||||
|
payload={"event": event_name, "data": payload},
|
||||||
|
)
|
||||||
|
|
||||||
def __emit_model_event(self, event_name: str, payload: dict) -> None:
|
def __emit_model_event(self, event_name: str, payload: dict) -> None:
|
||||||
payload["timestamp"] = get_timestamp()
|
payload["timestamp"] = get_timestamp()
|
||||||
self.dispatch(
|
self.dispatch(
|
||||||
@ -323,6 +331,79 @@ class EventServiceBase:
|
|||||||
payload={"queue_id": queue_id},
|
payload={"queue_id": queue_id},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def emit_download_started(self, source: str, download_path: str) -> None:
|
||||||
|
"""
|
||||||
|
Emit when a download job is started.
|
||||||
|
|
||||||
|
:param url: The downloaded url
|
||||||
|
"""
|
||||||
|
self.__emit_download_event(
|
||||||
|
event_name="download_started",
|
||||||
|
payload={"source": source, "download_path": download_path},
|
||||||
|
)
|
||||||
|
|
||||||
|
def emit_download_progress(self, source: str, download_path: str, current_bytes: int, total_bytes: int) -> None:
|
||||||
|
"""
|
||||||
|
Emit "download_progress" events at regular intervals during a download job.
|
||||||
|
|
||||||
|
:param source: The downloaded source
|
||||||
|
:param download_path: The local downloaded file
|
||||||
|
:param current_bytes: Number of bytes downloaded so far
|
||||||
|
:param total_bytes: The size of the file being downloaded (if known)
|
||||||
|
"""
|
||||||
|
self.__emit_download_event(
|
||||||
|
event_name="download_progress",
|
||||||
|
payload={
|
||||||
|
"source": source,
|
||||||
|
"download_path": download_path,
|
||||||
|
"current_bytes": current_bytes,
|
||||||
|
"total_bytes": total_bytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def emit_download_complete(self, source: str, download_path: str, total_bytes: int) -> None:
|
||||||
|
"""
|
||||||
|
Emit a "download_complete" event at the end of a successful download.
|
||||||
|
|
||||||
|
:param source: Source URL
|
||||||
|
:param download_path: Path to the locally downloaded file
|
||||||
|
:param total_bytes: The size of the downloaded file
|
||||||
|
"""
|
||||||
|
self.__emit_download_event(
|
||||||
|
event_name="download_complete",
|
||||||
|
payload={
|
||||||
|
"source": source,
|
||||||
|
"download_path": download_path,
|
||||||
|
"total_bytes": total_bytes,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def emit_download_cancelled(self, source: str) -> None:
|
||||||
|
"""Emit a "download_cancelled" event in the event that the download was cancelled by user."""
|
||||||
|
self.__emit_download_event(
|
||||||
|
event_name="download_cancelled",
|
||||||
|
payload={
|
||||||
|
"source": source,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def emit_download_error(self, source: str, error_type: str, error: str) -> None:
|
||||||
|
"""
|
||||||
|
Emit a "download_error" event when an download job encounters an exception.
|
||||||
|
|
||||||
|
:param source: Source URL
|
||||||
|
:param error_type: The name of the exception that raised the error
|
||||||
|
:param error: The traceback from this error
|
||||||
|
"""
|
||||||
|
self.__emit_download_event(
|
||||||
|
event_name="download_error",
|
||||||
|
payload={
|
||||||
|
"source": source,
|
||||||
|
"error_type": error_type,
|
||||||
|
"error": error,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def emit_model_install_started(self, source: str) -> None:
|
def emit_model_install_started(self, source: str) -> None:
|
||||||
"""
|
"""
|
||||||
Emitted when an install job is started.
|
Emitted when an install job is started.
|
||||||
|
@ -11,6 +11,7 @@ if TYPE_CHECKING:
|
|||||||
from .board_records.board_records_base import BoardRecordStorageBase
|
from .board_records.board_records_base import BoardRecordStorageBase
|
||||||
from .boards.boards_base import BoardServiceABC
|
from .boards.boards_base import BoardServiceABC
|
||||||
from .config import InvokeAIAppConfig
|
from .config import InvokeAIAppConfig
|
||||||
|
from .download import DownloadQueueServiceBase
|
||||||
from .events.events_base import EventServiceBase
|
from .events.events_base import EventServiceBase
|
||||||
from .image_files.image_files_base import ImageFileStorageBase
|
from .image_files.image_files_base import ImageFileStorageBase
|
||||||
from .image_records.image_records_base import ImageRecordStorageBase
|
from .image_records.image_records_base import ImageRecordStorageBase
|
||||||
@ -27,7 +28,7 @@ if TYPE_CHECKING:
|
|||||||
from .names.names_base import NameServiceBase
|
from .names.names_base import NameServiceBase
|
||||||
from .session_processor.session_processor_base import SessionProcessorBase
|
from .session_processor.session_processor_base import SessionProcessorBase
|
||||||
from .session_queue.session_queue_base import SessionQueueBase
|
from .session_queue.session_queue_base import SessionQueueBase
|
||||||
from .shared.graph import GraphExecutionState, LibraryGraph
|
from .shared.graph import GraphExecutionState
|
||||||
from .urls.urls_base import UrlServiceBase
|
from .urls.urls_base import UrlServiceBase
|
||||||
from .workflow_records.workflow_records_base import WorkflowRecordsStorageBase
|
from .workflow_records.workflow_records_base import WorkflowRecordsStorageBase
|
||||||
|
|
||||||
@ -43,7 +44,6 @@ class InvocationServices:
|
|||||||
configuration: "InvokeAIAppConfig"
|
configuration: "InvokeAIAppConfig"
|
||||||
events: "EventServiceBase"
|
events: "EventServiceBase"
|
||||||
graph_execution_manager: "ItemStorageABC[GraphExecutionState]"
|
graph_execution_manager: "ItemStorageABC[GraphExecutionState]"
|
||||||
graph_library: "ItemStorageABC[LibraryGraph]"
|
|
||||||
images: "ImageServiceABC"
|
images: "ImageServiceABC"
|
||||||
image_records: "ImageRecordStorageBase"
|
image_records: "ImageRecordStorageBase"
|
||||||
image_files: "ImageFileStorageBase"
|
image_files: "ImageFileStorageBase"
|
||||||
@ -51,6 +51,7 @@ class InvocationServices:
|
|||||||
logger: "Logger"
|
logger: "Logger"
|
||||||
model_manager: "ModelManagerServiceBase"
|
model_manager: "ModelManagerServiceBase"
|
||||||
model_records: "ModelRecordServiceBase"
|
model_records: "ModelRecordServiceBase"
|
||||||
|
download_queue: "DownloadQueueServiceBase"
|
||||||
model_install: "ModelInstallServiceBase"
|
model_install: "ModelInstallServiceBase"
|
||||||
processor: "InvocationProcessorABC"
|
processor: "InvocationProcessorABC"
|
||||||
performance_statistics: "InvocationStatsServiceBase"
|
performance_statistics: "InvocationStatsServiceBase"
|
||||||
@ -71,7 +72,6 @@ class InvocationServices:
|
|||||||
configuration: "InvokeAIAppConfig",
|
configuration: "InvokeAIAppConfig",
|
||||||
events: "EventServiceBase",
|
events: "EventServiceBase",
|
||||||
graph_execution_manager: "ItemStorageABC[GraphExecutionState]",
|
graph_execution_manager: "ItemStorageABC[GraphExecutionState]",
|
||||||
graph_library: "ItemStorageABC[LibraryGraph]",
|
|
||||||
images: "ImageServiceABC",
|
images: "ImageServiceABC",
|
||||||
image_files: "ImageFileStorageBase",
|
image_files: "ImageFileStorageBase",
|
||||||
image_records: "ImageRecordStorageBase",
|
image_records: "ImageRecordStorageBase",
|
||||||
@ -79,6 +79,7 @@ class InvocationServices:
|
|||||||
logger: "Logger",
|
logger: "Logger",
|
||||||
model_manager: "ModelManagerServiceBase",
|
model_manager: "ModelManagerServiceBase",
|
||||||
model_records: "ModelRecordServiceBase",
|
model_records: "ModelRecordServiceBase",
|
||||||
|
download_queue: "DownloadQueueServiceBase",
|
||||||
model_install: "ModelInstallServiceBase",
|
model_install: "ModelInstallServiceBase",
|
||||||
processor: "InvocationProcessorABC",
|
processor: "InvocationProcessorABC",
|
||||||
performance_statistics: "InvocationStatsServiceBase",
|
performance_statistics: "InvocationStatsServiceBase",
|
||||||
@ -97,7 +98,6 @@ class InvocationServices:
|
|||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.events = events
|
self.events = events
|
||||||
self.graph_execution_manager = graph_execution_manager
|
self.graph_execution_manager = graph_execution_manager
|
||||||
self.graph_library = graph_library
|
|
||||||
self.images = images
|
self.images = images
|
||||||
self.image_files = image_files
|
self.image_files = image_files
|
||||||
self.image_records = image_records
|
self.image_records = image_records
|
||||||
@ -105,6 +105,7 @@ class InvocationServices:
|
|||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.model_manager = model_manager
|
self.model_manager = model_manager
|
||||||
self.model_records = model_records
|
self.model_records = model_records
|
||||||
|
self.download_queue = download_queue
|
||||||
self.model_install = model_install
|
self.model_install = model_install
|
||||||
self.processor = processor
|
self.processor = processor
|
||||||
self.performance_statistics = performance_statistics
|
self.performance_statistics = performance_statistics
|
||||||
|
@ -11,7 +11,6 @@ from typing_extensions import Annotated
|
|||||||
|
|
||||||
from invokeai.app.services.config import InvokeAIAppConfig
|
from invokeai.app.services.config import InvokeAIAppConfig
|
||||||
from invokeai.app.services.events import EventServiceBase
|
from invokeai.app.services.events import EventServiceBase
|
||||||
from invokeai.app.services.invoker import Invoker
|
|
||||||
from invokeai.app.services.model_records import ModelRecordServiceBase
|
from invokeai.app.services.model_records import ModelRecordServiceBase
|
||||||
from invokeai.backend.model_manager import AnyModelConfig
|
from invokeai.backend.model_manager import AnyModelConfig
|
||||||
|
|
||||||
@ -157,12 +156,12 @@ class ModelInstallServiceBase(ABC):
|
|||||||
:param event_bus: InvokeAI event bus for reporting events to.
|
:param event_bus: InvokeAI event bus for reporting events to.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def start(self, invoker: Invoker) -> None:
|
@abstractmethod
|
||||||
"""Call at InvokeAI startup time."""
|
def start(self, *args: Any, **kwarg: Any) -> None:
|
||||||
self.sync_to_config()
|
"""Start the installer service."""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def stop(self) -> None:
|
def stop(self, *args: Any, **kwarg: Any) -> None:
|
||||||
"""Stop the model install service. After this the objection can be safely deleted."""
|
"""Stop the model install service. After this the objection can be safely deleted."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -71,7 +71,6 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
self._install_queue = Queue()
|
self._install_queue = Queue()
|
||||||
self._cached_model_paths = set()
|
self._cached_model_paths = set()
|
||||||
self._models_installed = set()
|
self._models_installed = set()
|
||||||
self._start_installer_thread()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def app_config(self) -> InvokeAIAppConfig: # noqa D102
|
def app_config(self) -> InvokeAIAppConfig: # noqa D102
|
||||||
@ -85,8 +84,13 @@ class ModelInstallService(ModelInstallServiceBase):
|
|||||||
def event_bus(self) -> Optional[EventServiceBase]: # noqa D102
|
def event_bus(self) -> Optional[EventServiceBase]: # noqa D102
|
||||||
return self._event_bus
|
return self._event_bus
|
||||||
|
|
||||||
def stop(self, *args, **kwargs) -> None:
|
def start(self, *args: Any, **kwarg: Any) -> None:
|
||||||
"""Stop the install thread; after this the object can be deleted and garbage collected."""
|
"""Start the installer thread."""
|
||||||
|
self._start_installer_thread()
|
||||||
|
self.sync_to_config()
|
||||||
|
|
||||||
|
def stop(self, *args: Any, **kwarg: Any) -> None:
|
||||||
|
"""Stop the installer thread; after this the object can be deleted and garbage collected."""
|
||||||
self._install_queue.put(STOP_JOB)
|
self._install_queue.put(STOP_JOB)
|
||||||
|
|
||||||
def _start_installer_thread(self) -> None:
|
def _start_installer_thread(self) -> None:
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,975 @@
|
|||||||
|
{
|
||||||
|
"name": "Prompt from File",
|
||||||
|
"author": "InvokeAI",
|
||||||
|
"description": "Sample workflow using Prompt from File node",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"contact": "invoke@invoke.ai",
|
||||||
|
"tags": "text2image, prompt from file, default",
|
||||||
|
"notes": "",
|
||||||
|
"exposedFields": [
|
||||||
|
{
|
||||||
|
"nodeId": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"fieldName": "model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
|
||||||
|
"fieldName": "file_path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"category": "default",
|
||||||
|
"version": "2.0.0"
|
||||||
|
},
|
||||||
|
"id": "d1609af5-eb0a-4f73-b573-c9af96a8d6bf",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
|
||||||
|
"type": "compel",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"prompt": {
|
||||||
|
"id": "dcdf3f6d-9b96-4bcd-9b8d-f992fefe4f62",
|
||||||
|
"name": "prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "3f1981c9-d8a9-42eb-a739-4f120eb80745",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"conditioning": {
|
||||||
|
"id": "46205e6c-c5e2-44cb-9c82-1cd20b95674a",
|
||||||
|
"name": "conditioning",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 925,
|
||||||
|
"y": -200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
|
||||||
|
"type": "prompt_from_file",
|
||||||
|
"label": "Prompts from File",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.1",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"file_path": {
|
||||||
|
"id": "37e37684-4f30-4ec8-beae-b333e550f904",
|
||||||
|
"name": "file_path",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "Prompts File Path",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"pre_prompt": {
|
||||||
|
"id": "7de02feb-819a-4992-bad3-72a30920ddea",
|
||||||
|
"name": "pre_prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"post_prompt": {
|
||||||
|
"id": "95f191d8-a282-428e-bd65-de8cb9b7513a",
|
||||||
|
"name": "post_prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"start_line": {
|
||||||
|
"id": "efee9a48-05ab-4829-8429-becfa64a0782",
|
||||||
|
"name": "start_line",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
"max_prompts": {
|
||||||
|
"id": "abebb428-3d3d-49fd-a482-4e96a16fff08",
|
||||||
|
"name": "max_prompts",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"collection": {
|
||||||
|
"id": "77d5d7f1-9877-4ab1-9a8c-33e9ffa9abf3",
|
||||||
|
"name": "collection",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": true,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 580,
|
||||||
|
"position": {
|
||||||
|
"x": 475,
|
||||||
|
"y": -400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1b89067c-3f6b-42c8-991f-e3055789b251",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "1b89067c-3f6b-42c8-991f-e3055789b251",
|
||||||
|
"type": "iterate",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.1.0",
|
||||||
|
"inputs": {
|
||||||
|
"collection": {
|
||||||
|
"id": "4c564bf8-5ed6-441e-ad2c-dda265d5785f",
|
||||||
|
"name": "collection",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": true,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "CollectionField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"item": {
|
||||||
|
"id": "36340f9a-e7a5-4afa-b4b5-313f4e292380",
|
||||||
|
"name": "item",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "CollectionItemField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"id": "1beca95a-2159-460f-97ff-c8bab7d89336",
|
||||||
|
"name": "index",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"id": "ead597b8-108e-4eda-88a8-5c29fa2f8df9",
|
||||||
|
"name": "total",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 925,
|
||||||
|
"y": -400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"type": "main_model_loader",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"model": {
|
||||||
|
"id": "3f264259-3418-47d5-b90d-b6600e36ae46",
|
||||||
|
"name": "model",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MainModelField"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"model_name": "stable-diffusion-v1-5",
|
||||||
|
"base_model": "sd-1",
|
||||||
|
"model_type": "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"unet": {
|
||||||
|
"id": "8e182ea2-9d0a-4c02-9407-27819288d4b5",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "d67d9d30-058c-46d5-bded-3d09d6d1aa39",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vae": {
|
||||||
|
"id": "89641601-0429-4448-98d5-190822d920d8",
|
||||||
|
"name": "vae",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 227,
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": -375
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"type": "compel",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"prompt": {
|
||||||
|
"id": "dcdf3f6d-9b96-4bcd-9b8d-f992fefe4f62",
|
||||||
|
"name": "prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "3f1981c9-d8a9-42eb-a739-4f120eb80745",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"conditioning": {
|
||||||
|
"id": "46205e6c-c5e2-44cb-9c82-1cd20b95674a",
|
||||||
|
"name": "conditioning",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 925,
|
||||||
|
"y": -275
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
|
||||||
|
"type": "noise",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.1",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"seed": {
|
||||||
|
"id": "b722d84a-eeee-484f-bef2-0250c027cb67",
|
||||||
|
"name": "seed",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "d5f8ce11-0502-4bfc-9a30-5757dddf1f94",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 512
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "f187d5ff-38a5-4c3f-b780-fc5801ef34af",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 512
|
||||||
|
},
|
||||||
|
"use_cpu": {
|
||||||
|
"id": "12f112b8-8b76-4816-b79e-662edc9f9aa5",
|
||||||
|
"name": "use_cpu",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"noise": {
|
||||||
|
"id": "08576ad1-96d9-42d2-96ef-6f5c1961933f",
|
||||||
|
"name": "noise",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "f3e1f94a-258d-41ff-9789-bd999bd9f40d",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "6cefc357-4339-415e-a951-49b9c2be32f4",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 925,
|
||||||
|
"y": 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
|
||||||
|
"type": "rand_int",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": false,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"low": {
|
||||||
|
"id": "b9fc6cf1-469c-4037-9bf0-04836965826f",
|
||||||
|
"name": "low",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
"id": "06eac725-0f60-4ba2-b8cd-7ad9f757488c",
|
||||||
|
"name": "high",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 2147483647
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"value": {
|
||||||
|
"id": "df08c84e-7346-4e92-9042-9e5cb773aaff",
|
||||||
|
"name": "value",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 925,
|
||||||
|
"y": -50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
|
||||||
|
"type": "l2i",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.2.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"metadata": {
|
||||||
|
"id": "022e4b33-562b-438d-b7df-41c3fd931f40",
|
||||||
|
"name": "metadata",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MetadataField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latents": {
|
||||||
|
"id": "67cb6c77-a394-4a66-a6a9-a0a7dcca69ec",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vae": {
|
||||||
|
"id": "7b3fd9ad-a4ef-4e04-89fa-3832a9902dbd",
|
||||||
|
"name": "vae",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tiled": {
|
||||||
|
"id": "5ac5680d-3add-4115-8ec0-9ef5bb87493b",
|
||||||
|
"name": "tiled",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
"fp32": {
|
||||||
|
"id": "db8297f5-55f8-452f-98cf-6572c2582152",
|
||||||
|
"name": "fp32",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"image": {
|
||||||
|
"id": "d8778d0c-592a-4960-9280-4e77e00a7f33",
|
||||||
|
"name": "image",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ImageField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "c8b0a75a-f5de-4ff2-9227-f25bb2b97bec",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "83c05fbf-76b9-49ab-93c4-fa4b10e793e4",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 267,
|
||||||
|
"position": {
|
||||||
|
"x": 2037.861329274915,
|
||||||
|
"y": -329.8393457509562
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "denoise_latents",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.5.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
|
"inputs": {
|
||||||
|
"positive_conditioning": {
|
||||||
|
"id": "751fb35b-3f23-45ce-af1c-053e74251337",
|
||||||
|
"name": "positive_conditioning",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"negative_conditioning": {
|
||||||
|
"id": "b9dc06b6-7481-4db1-a8c2-39d22a5eacff",
|
||||||
|
"name": "negative_conditioning",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noise": {
|
||||||
|
"id": "6e15e439-3390-48a4-8031-01e0e19f0e1d",
|
||||||
|
"name": "noise",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"id": "bfdfb3df-760b-4d51-b17b-0abb38b976c2",
|
||||||
|
"name": "steps",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
"cfg_scale": {
|
||||||
|
"id": "47770858-322e-41af-8494-d8b63ed735f3",
|
||||||
|
"name": "cfg_scale",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 7.5
|
||||||
|
},
|
||||||
|
"denoising_start": {
|
||||||
|
"id": "2ba78720-ee02-4130-a348-7bc3531f790b",
|
||||||
|
"name": "denoising_start",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"denoising_end": {
|
||||||
|
"id": "a874dffb-d433-4d1a-9f59-af4367bb05e4",
|
||||||
|
"name": "denoising_end",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
"scheduler": {
|
||||||
|
"id": "36e021ad-b762-4fe4-ad4d-17f0291c40b2",
|
||||||
|
"name": "scheduler",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "SchedulerField"
|
||||||
|
},
|
||||||
|
"value": "euler"
|
||||||
|
},
|
||||||
|
"unet": {
|
||||||
|
"id": "98d3282d-f9f6-4b5e-b9e8-58658f1cac78",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"id": "f2ea3216-43d5-42b4-887f-36e8f7166d53",
|
||||||
|
"name": "control",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "ControlField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ip_adapter": {
|
||||||
|
"id": "d0780610-a298-47c8-a54e-70e769e0dfe2",
|
||||||
|
"name": "ip_adapter",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "IPAdapterField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"t2i_adapter": {
|
||||||
|
"id": "fdb40970-185e-4ea8-8bb5-88f06f91f46a",
|
||||||
|
"name": "t2i_adapter",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "T2IAdapterField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cfg_rescale_multiplier": {
|
||||||
|
"id": "3af2d8c5-de83-425c-a100-49cb0f1f4385",
|
||||||
|
"name": "cfg_rescale_multiplier",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"latents": {
|
||||||
|
"id": "e05b538a-1b5a-4aa5-84b1-fd2361289a81",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"denoise_mask": {
|
||||||
|
"id": "463a419e-df30-4382-8ffb-b25b25abe425",
|
||||||
|
"name": "denoise_mask",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "DenoiseMaskField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"latents": {
|
||||||
|
"id": "559ee688-66cf-4139-8b82-3d3aa69995ce",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "0b4285c2-e8b9-48e5-98f6-0a49d3f98fd2",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "8b0881b9-45e5-47d5-b526-24b6661de0ee",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 705,
|
||||||
|
"position": {
|
||||||
|
"x": 1570.9941088179146,
|
||||||
|
"y": -407.6505491604564
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "1b89067c-3f6b-42c8-991f-e3055789b251-fc9d0e35-a6de-4a19-84e1-c72497c823f6-collapsed",
|
||||||
|
"source": "1b89067c-3f6b-42c8-991f-e3055789b251",
|
||||||
|
"target": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"type": "collapsed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5-0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77-collapsed",
|
||||||
|
"source": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
|
||||||
|
"target": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
|
||||||
|
"type": "collapsed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-1b7e0df8-8589-4915-a4ea-c0088f15d642collection-1b89067c-3f6b-42c8-991f-e3055789b251collection",
|
||||||
|
"source": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
|
||||||
|
"target": "1b89067c-3f6b-42c8-991f-e3055789b251",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "collection",
|
||||||
|
"targetHandle": "collection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426clip-fc9d0e35-a6de-4a19-84e1-c72497c823f6clip",
|
||||||
|
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"target": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-1b89067c-3f6b-42c8-991f-e3055789b251item-fc9d0e35-a6de-4a19-84e1-c72497c823f6prompt",
|
||||||
|
"source": "1b89067c-3f6b-42c8-991f-e3055789b251",
|
||||||
|
"target": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "item",
|
||||||
|
"targetHandle": "prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426clip-c2eaf1ba-5708-4679-9e15-945b8b432692clip",
|
||||||
|
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"target": "c2eaf1ba-5708-4679-9e15-945b8b432692",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5value-0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77seed",
|
||||||
|
"source": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
|
||||||
|
"target": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "value",
|
||||||
|
"targetHandle": "seed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-fc9d0e35-a6de-4a19-84e1-c72497c823f6conditioning-2fb1577f-0a56-4f12-8711-8afcaaaf1d5epositive_conditioning",
|
||||||
|
"source": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
|
||||||
|
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "conditioning",
|
||||||
|
"targetHandle": "positive_conditioning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-c2eaf1ba-5708-4679-9e15-945b8b432692conditioning-2fb1577f-0a56-4f12-8711-8afcaaaf1d5enegative_conditioning",
|
||||||
|
"source": "c2eaf1ba-5708-4679-9e15-945b8b432692",
|
||||||
|
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "conditioning",
|
||||||
|
"targetHandle": "negative_conditioning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77noise-2fb1577f-0a56-4f12-8711-8afcaaaf1d5enoise",
|
||||||
|
"source": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
|
||||||
|
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "noise",
|
||||||
|
"targetHandle": "noise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426unet-2fb1577f-0a56-4f12-8711-8afcaaaf1d5eunet",
|
||||||
|
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "unet",
|
||||||
|
"targetHandle": "unet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-2fb1577f-0a56-4f12-8711-8afcaaaf1d5elatents-491ec988-3c77-4c37-af8a-39a0c4e7a2a1latents",
|
||||||
|
"source": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
|
||||||
|
"target": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "latents",
|
||||||
|
"targetHandle": "latents"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426vae-491ec988-3c77-4c37-af8a-39a0c4e7a2a1vae",
|
||||||
|
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
|
||||||
|
"target": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "vae",
|
||||||
|
"targetHandle": "vae"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,903 @@
|
|||||||
|
{
|
||||||
|
"name": "Text to Image with LoRA",
|
||||||
|
"author": "InvokeAI",
|
||||||
|
"description": "Simple text to image workflow with a LoRA",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"contact": "invoke@invoke.ai",
|
||||||
|
"tags": "text to image, lora, default",
|
||||||
|
"notes": "",
|
||||||
|
"exposedFields": [
|
||||||
|
{
|
||||||
|
"nodeId": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"fieldName": "model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"fieldName": "lora"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"fieldName": "weight"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
|
||||||
|
"fieldName": "prompt"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"category": "default"
|
||||||
|
},
|
||||||
|
"id": "a9d70c39-4cdd-4176-9942-8ff3fe32d3b1",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "85b77bb2-c67a-416a-b3e8-291abe746c44",
|
||||||
|
"type": "compel",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputs": {
|
||||||
|
"prompt": {
|
||||||
|
"id": "39fe92c4-38eb-4cc7-bf5e-cbcd31847b11",
|
||||||
|
"name": "prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "Negative Prompt",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "14313164-e5c4-4e40-a599-41b614fe3690",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"conditioning": {
|
||||||
|
"id": "02140b9d-50f3-470b-a0b7-01fc6ed2dcd6",
|
||||||
|
"name": "conditioning",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 256,
|
||||||
|
"position": {
|
||||||
|
"x": 3425,
|
||||||
|
"y": -300
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"type": "main_model_loader",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputs": {
|
||||||
|
"model": {
|
||||||
|
"id": "e2e1c177-ae39-4244-920e-d621fa156a24",
|
||||||
|
"name": "model",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MainModelField"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"model_name": "Analog-Diffusion",
|
||||||
|
"base_model": "sd-1",
|
||||||
|
"model_type": "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"vae": {
|
||||||
|
"id": "f91410e8-9378-4298-b285-f0f40ffd9825",
|
||||||
|
"name": "vae",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "928d91bf-de0c-44a8-b0c8-4de0e2e5b438",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unet": {
|
||||||
|
"id": "eacaf530-4e7e-472e-b904-462192189fc1",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 227,
|
||||||
|
"position": {
|
||||||
|
"x": 2500,
|
||||||
|
"y": -600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"type": "lora_loader",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputs": {
|
||||||
|
"lora": {
|
||||||
|
"id": "36d867e8-92ea-4c3f-9ad5-ba05c64cf326",
|
||||||
|
"name": "lora",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LoRAModelField"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"model_name": "Ink scenery",
|
||||||
|
"base_model": "sd-1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"weight": {
|
||||||
|
"id": "8be86540-ba81-49b3-b394-2b18fa70b867",
|
||||||
|
"name": "weight",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "LoRA Weight",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0.75
|
||||||
|
},
|
||||||
|
"unet": {
|
||||||
|
"id": "9c4d5668-e9e1-411b-8f4b-e71115bc4a01",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "918ec00e-e76f-4ad0-aee1-3927298cf03b",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"unet": {
|
||||||
|
"id": "c63f7825-1bcf-451d-b7a7-aa79f5c77416",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "6f79ef2d-00f7-4917-bee3-53e845bf4192",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 252,
|
||||||
|
"position": {
|
||||||
|
"x": 2975,
|
||||||
|
"y": -600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
|
||||||
|
"type": "compel",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputs": {
|
||||||
|
"prompt": {
|
||||||
|
"id": "39fe92c4-38eb-4cc7-bf5e-cbcd31847b11",
|
||||||
|
"name": "prompt",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "Positive Prompt",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": "cute tiger cub"
|
||||||
|
},
|
||||||
|
"clip": {
|
||||||
|
"id": "14313164-e5c4-4e40-a599-41b614fe3690",
|
||||||
|
"name": "clip",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"conditioning": {
|
||||||
|
"id": "02140b9d-50f3-470b-a0b7-01fc6ed2dcd6",
|
||||||
|
"name": "conditioning",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 256,
|
||||||
|
"position": {
|
||||||
|
"x": 3425,
|
||||||
|
"y": -575
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "denoise_latents",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.5.0",
|
||||||
|
"inputs": {
|
||||||
|
"positive_conditioning": {
|
||||||
|
"id": "025ff44b-c4c6-4339-91b4-5f461e2cadc5",
|
||||||
|
"name": "positive_conditioning",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"negative_conditioning": {
|
||||||
|
"id": "2d92b45a-a7fb-4541-9a47-7c7495f50f54",
|
||||||
|
"name": "negative_conditioning",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noise": {
|
||||||
|
"id": "4d0deeff-24ed-4562-a1ca-7833c0649377",
|
||||||
|
"name": "noise",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"id": "c9907328-aece-4af9-8a95-211b4f99a325",
|
||||||
|
"name": "steps",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
"cfg_scale": {
|
||||||
|
"id": "7cf0f031-2078-49f4-9273-bb3a64ad7130",
|
||||||
|
"name": "cfg_scale",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 7.5
|
||||||
|
},
|
||||||
|
"denoising_start": {
|
||||||
|
"id": "44cec3ba-b404-4b51-ba98-add9d783279e",
|
||||||
|
"name": "denoising_start",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"denoising_end": {
|
||||||
|
"id": "3e7975f3-e438-4a13-8a14-395eba1fb7cd",
|
||||||
|
"name": "denoising_end",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 1
|
||||||
|
},
|
||||||
|
"scheduler": {
|
||||||
|
"id": "a6f6509b-7bb4-477d-b5fb-74baefa38111",
|
||||||
|
"name": "scheduler",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "SchedulerField"
|
||||||
|
},
|
||||||
|
"value": "euler"
|
||||||
|
},
|
||||||
|
"unet": {
|
||||||
|
"id": "5a87617a-b09f-417b-9b75-0cea4c255227",
|
||||||
|
"name": "unet",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"control": {
|
||||||
|
"id": "db87aace-ace8-4f2a-8f2b-1f752389fa9b",
|
||||||
|
"name": "control",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "ControlField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ip_adapter": {
|
||||||
|
"id": "f0c133ed-4d6d-4567-bb9a-b1779810993c",
|
||||||
|
"name": "ip_adapter",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "IPAdapterField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"t2i_adapter": {
|
||||||
|
"id": "59ee1233-887f-45e7-aa14-cbad5f6cb77f",
|
||||||
|
"name": "t2i_adapter",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "T2IAdapterField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cfg_rescale_multiplier": {
|
||||||
|
"id": "1a12e781-4b30-4707-b432-18c31866b5c3",
|
||||||
|
"name": "cfg_rescale_multiplier",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"latents": {
|
||||||
|
"id": "d0e593ae-305c-424b-9acd-3af830085832",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"denoise_mask": {
|
||||||
|
"id": "b81b5a79-fc2b-4011-aae6-64c92bae59a7",
|
||||||
|
"name": "denoise_mask",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "DenoiseMaskField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"latents": {
|
||||||
|
"id": "9ae4022a-548e-407e-90cf-cc5ca5ff8a21",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "730ba4bd-2c52-46bb-8c87-9b3aec155576",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "52b98f0b-b5ff-41b5-acc7-d0b1d1011a6f",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 705,
|
||||||
|
"position": {
|
||||||
|
"x": 3975,
|
||||||
|
"y": -575
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ea18915f-2c5b-4569-b725-8e9e9122e8d3",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "ea18915f-2c5b-4569-b725-8e9e9122e8d3",
|
||||||
|
"type": "noise",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.1",
|
||||||
|
"inputs": {
|
||||||
|
"seed": {
|
||||||
|
"id": "446ac80c-ba0a-4fea-a2d7-21128f52e5bf",
|
||||||
|
"name": "seed",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "779831b3-20b4-4f5f-9de7-d17de57288d8",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 512
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "08959766-6d67-4276-b122-e54b911f2316",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 512
|
||||||
|
},
|
||||||
|
"use_cpu": {
|
||||||
|
"id": "53b36a98-00c4-4dc5-97a4-ef3432c0a805",
|
||||||
|
"name": "use_cpu",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"noise": {
|
||||||
|
"id": "eed95824-580b-442f-aa35-c073733cecce",
|
||||||
|
"name": "noise",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "7985a261-dfee-47a8-908a-c5a8754f5dc4",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "3d00f6c1-84b0-4262-83d9-3bf755babeea",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 3425,
|
||||||
|
"y": 75
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6fd74a17-6065-47a5-b48b-f4e2b8fa7953",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "6fd74a17-6065-47a5-b48b-f4e2b8fa7953",
|
||||||
|
"type": "rand_int",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": false,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"inputs": {
|
||||||
|
"low": {
|
||||||
|
"id": "d25305f3-bfd6-446c-8e2c-0b025ec9e9ad",
|
||||||
|
"name": "low",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
|
},
|
||||||
|
"high": {
|
||||||
|
"id": "10376a3d-b8fe-4a51-b81a-ea46d8c12c78",
|
||||||
|
"name": "high",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 2147483647
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"value": {
|
||||||
|
"id": "c64878fa-53b1-4202-b88a-cfb854216a57",
|
||||||
|
"name": "value",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 32,
|
||||||
|
"position": {
|
||||||
|
"x": 3425,
|
||||||
|
"y": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
|
||||||
|
"type": "invocation",
|
||||||
|
"data": {
|
||||||
|
"id": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
|
||||||
|
"type": "l2i",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": false,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.2.0",
|
||||||
|
"inputs": {
|
||||||
|
"metadata": {
|
||||||
|
"id": "b1982e8a-14ad-4029-a697-beb30af8340f",
|
||||||
|
"name": "metadata",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MetadataField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"latents": {
|
||||||
|
"id": "f7669388-9f91-46cc-94fc-301fa7041c3e",
|
||||||
|
"name": "latents",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vae": {
|
||||||
|
"id": "c6f2d4db-4d0a-4e3d-acb4-b5c5a228a3e2",
|
||||||
|
"name": "vae",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tiled": {
|
||||||
|
"id": "19ef7d31-d96f-4e94-b7e5-95914e9076fc",
|
||||||
|
"name": "tiled",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
"fp32": {
|
||||||
|
"id": "a9454533-8ab7-4225-b411-646dc5e76d00",
|
||||||
|
"name": "fp32",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs": {
|
||||||
|
"image": {
|
||||||
|
"id": "4f81274e-e216-47f3-9fb6-f97493a40e6f",
|
||||||
|
"name": "image",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ImageField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"id": "61a9acfb-1547-4f1e-8214-e89bd3855ee5",
|
||||||
|
"name": "width",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"id": "b15cc793-4172-4b07-bcf4-5627bbc7d0d7",
|
||||||
|
"name": "height",
|
||||||
|
"fieldKind": "output",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 320,
|
||||||
|
"height": 267,
|
||||||
|
"position": {
|
||||||
|
"x": 4450,
|
||||||
|
"y": -550
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"id": "6fd74a17-6065-47a5-b48b-f4e2b8fa7953-ea18915f-2c5b-4569-b725-8e9e9122e8d3-collapsed",
|
||||||
|
"source": "6fd74a17-6065-47a5-b48b-f4e2b8fa7953",
|
||||||
|
"target": "ea18915f-2c5b-4569-b725-8e9e9122e8d3",
|
||||||
|
"type": "collapsed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-24e9d7ed-4836-4ec4-8f9e-e747721f9818clip-c41e705b-f2e3-4d1a-83c4-e34bb9344966clip",
|
||||||
|
"source": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"target": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-c41e705b-f2e3-4d1a-83c4-e34bb9344966clip-c3fa6872-2599-4a82-a596-b3446a66cf8bclip",
|
||||||
|
"source": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"target": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-24e9d7ed-4836-4ec4-8f9e-e747721f9818unet-c41e705b-f2e3-4d1a-83c4-e34bb9344966unet",
|
||||||
|
"source": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"target": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "unet",
|
||||||
|
"targetHandle": "unet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-c41e705b-f2e3-4d1a-83c4-e34bb9344966unet-ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63unet",
|
||||||
|
"source": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"target": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "unet",
|
||||||
|
"targetHandle": "unet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-85b77bb2-c67a-416a-b3e8-291abe746c44conditioning-ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63negative_conditioning",
|
||||||
|
"source": "85b77bb2-c67a-416a-b3e8-291abe746c44",
|
||||||
|
"target": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "conditioning",
|
||||||
|
"targetHandle": "negative_conditioning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-c3fa6872-2599-4a82-a596-b3446a66cf8bconditioning-ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63positive_conditioning",
|
||||||
|
"source": "c3fa6872-2599-4a82-a596-b3446a66cf8b",
|
||||||
|
"target": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "conditioning",
|
||||||
|
"targetHandle": "positive_conditioning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-ea18915f-2c5b-4569-b725-8e9e9122e8d3noise-ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63noise",
|
||||||
|
"source": "ea18915f-2c5b-4569-b725-8e9e9122e8d3",
|
||||||
|
"target": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "noise",
|
||||||
|
"targetHandle": "noise"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-6fd74a17-6065-47a5-b48b-f4e2b8fa7953value-ea18915f-2c5b-4569-b725-8e9e9122e8d3seed",
|
||||||
|
"source": "6fd74a17-6065-47a5-b48b-f4e2b8fa7953",
|
||||||
|
"target": "ea18915f-2c5b-4569-b725-8e9e9122e8d3",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "value",
|
||||||
|
"targetHandle": "seed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63latents-a9683c0a-6b1f-4a5e-8187-c57e764b3400latents",
|
||||||
|
"source": "ad487d0c-dcbb-49c5-bb8e-b28d4cbc5a63",
|
||||||
|
"target": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "latents",
|
||||||
|
"targetHandle": "latents"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-24e9d7ed-4836-4ec4-8f9e-e747721f9818vae-a9683c0a-6b1f-4a5e-8187-c57e764b3400vae",
|
||||||
|
"source": "24e9d7ed-4836-4ec4-8f9e-e747721f9818",
|
||||||
|
"target": "a9683c0a-6b1f-4a5e-8187-c57e764b3400",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "vae",
|
||||||
|
"targetHandle": "vae"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-c41e705b-f2e3-4d1a-83c4-e34bb9344966clip-85b77bb2-c67a-416a-b3e8-291abe746c44clip",
|
||||||
|
"source": "c41e705b-f2e3-4d1a-83c4-e34bb9344966",
|
||||||
|
"target": "85b77bb2-c67a-416a-b3e8-291abe746c44",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
8
invokeai/app/util/ti_utils.py
Normal file
8
invokeai/app/util/ti_utils.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def extract_ti_triggers_from_prompt(prompt: str) -> list[str]:
|
||||||
|
ti_triggers = []
|
||||||
|
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", prompt):
|
||||||
|
ti_triggers.append(trigger)
|
||||||
|
return ti_triggers
|
@ -28,7 +28,7 @@ def check_invokeai_root(config: InvokeAIAppConfig):
|
|||||||
print("== STARTUP ABORTED ==")
|
print("== STARTUP ABORTED ==")
|
||||||
print("** One or more necessary files is missing from your InvokeAI root directory **")
|
print("** One or more necessary files is missing from your InvokeAI root directory **")
|
||||||
print("** Please rerun the configuration script to fix this problem. **")
|
print("** Please rerun the configuration script to fix this problem. **")
|
||||||
print("** From the launcher, selection option [7]. **")
|
print("** From the launcher, selection option [6]. **")
|
||||||
print(
|
print(
|
||||||
'** From the command line, activate the virtual environment and run "invokeai-configure --yes --skip-sd-weights" **'
|
'** From the command line, activate the virtual environment and run "invokeai-configure --yes --skip-sd-weights" **'
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import platform
|
|
||||||
from contextlib import nullcontext
|
from contextlib import nullcontext
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from packaging import version
|
|
||||||
from torch import autocast
|
from torch import autocast
|
||||||
|
|
||||||
from invokeai.app.services.config import InvokeAIAppConfig
|
from invokeai.app.services.config import InvokeAIAppConfig
|
||||||
@ -37,7 +35,7 @@ def choose_precision(device: torch.device) -> str:
|
|||||||
device_name = torch.cuda.get_device_name(device)
|
device_name = torch.cuda.get_device_name(device)
|
||||||
if not ("GeForce GTX 1660" in device_name or "GeForce GTX 1650" in device_name):
|
if not ("GeForce GTX 1660" in device_name or "GeForce GTX 1650" in device_name):
|
||||||
return "float16"
|
return "float16"
|
||||||
elif device.type == "mps" and version.parse(platform.mac_ver()[0]) < version.parse("14.0.0"):
|
elif device.type == "mps":
|
||||||
return "float16"
|
return "float16"
|
||||||
return "float32"
|
return "float32"
|
||||||
|
|
||||||
|
@ -1119,7 +1119,10 @@
|
|||||||
"deletedInvalidEdge": "已删除无效的边缘 {{source}} -> {{target}}",
|
"deletedInvalidEdge": "已删除无效的边缘 {{source}} -> {{target}}",
|
||||||
"unknownInput": "未知输入:{{name}}",
|
"unknownInput": "未知输入:{{name}}",
|
||||||
"prototypeDesc": "此调用是一个原型 (prototype)。它可能会在本项目更新期间发生破坏性更改,并且随时可能被删除。",
|
"prototypeDesc": "此调用是一个原型 (prototype)。它可能会在本项目更新期间发生破坏性更改,并且随时可能被删除。",
|
||||||
"betaDesc": "此调用尚处于测试阶段。在稳定之前,它可能会在项目更新期间发生破坏性更改。本项目计划长期支持这种调用。"
|
"betaDesc": "此调用尚处于测试阶段。在稳定之前,它可能会在项目更新期间发生破坏性更改。本项目计划长期支持这种调用。",
|
||||||
|
"newWorkflow": "新建工作流",
|
||||||
|
"newWorkflowDesc": "是否创建一个新的工作流?",
|
||||||
|
"newWorkflowDesc2": "当前工作流有未保存的更改。"
|
||||||
},
|
},
|
||||||
"controlnet": {
|
"controlnet": {
|
||||||
"resize": "直接缩放",
|
"resize": "直接缩放",
|
||||||
@ -1635,7 +1638,7 @@
|
|||||||
"openWorkflow": "打开工作流",
|
"openWorkflow": "打开工作流",
|
||||||
"clearWorkflowSearchFilter": "清除工作流检索过滤器",
|
"clearWorkflowSearchFilter": "清除工作流检索过滤器",
|
||||||
"workflowLibrary": "工作流库",
|
"workflowLibrary": "工作流库",
|
||||||
"downloadWorkflow": "下载工作流",
|
"downloadWorkflow": "保存到文件",
|
||||||
"noRecentWorkflows": "无最近工作流",
|
"noRecentWorkflows": "无最近工作流",
|
||||||
"workflowSaved": "已保存工作流",
|
"workflowSaved": "已保存工作流",
|
||||||
"workflowIsOpen": "工作流已打开",
|
"workflowIsOpen": "工作流已打开",
|
||||||
@ -1648,8 +1651,9 @@
|
|||||||
"deleteWorkflow": "删除工作流",
|
"deleteWorkflow": "删除工作流",
|
||||||
"workflows": "工作流",
|
"workflows": "工作流",
|
||||||
"noDescription": "无描述",
|
"noDescription": "无描述",
|
||||||
"uploadWorkflow": "上传工作流",
|
"uploadWorkflow": "从文件中加载",
|
||||||
"userWorkflows": "我的工作流"
|
"userWorkflows": "我的工作流",
|
||||||
|
"newWorkflowCreated": "已创建新的工作流"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"storeNotInitialized": "商店尚未初始化"
|
"storeNotInitialized": "商店尚未初始化"
|
||||||
|
@ -34,6 +34,7 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
|
|||||||
import { actionsDenylist } from './middleware/devtools/actionsDenylist';
|
import { actionsDenylist } from './middleware/devtools/actionsDenylist';
|
||||||
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
||||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||||
|
import { authToastMiddleware } from 'services/api/authToastMiddleware';
|
||||||
|
|
||||||
const allReducers = {
|
const allReducers = {
|
||||||
canvas: canvasReducer,
|
canvas: canvasReducer,
|
||||||
@ -96,6 +97,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
|||||||
})
|
})
|
||||||
.concat(api.middleware)
|
.concat(api.middleware)
|
||||||
.concat(dynamicMiddlewares)
|
.concat(dynamicMiddlewares)
|
||||||
|
.concat(authToastMiddleware)
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
enhancers: (getDefaultEnhancers) => {
|
enhancers: (getDefaultEnhancers) => {
|
||||||
const _enhancers = getDefaultEnhancers().concat(autoBatchEnhancer());
|
const _enhancers = getDefaultEnhancers().concat(autoBatchEnhancer());
|
||||||
|
@ -2,11 +2,14 @@ import { Text } from '@chakra-ui/layout';
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
|
|
||||||
const TopCenterPanel = () => {
|
const TopCenterPanel = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const name = useAppSelector((state) => state.workflow.name);
|
const name = useAppSelector((state) => state.workflow.name);
|
||||||
const isTouched = useAppSelector((state) => state.workflow.isTouched);
|
const isTouched = useAppSelector((state) => state.workflow.isTouched);
|
||||||
|
const isWorkflowLibraryEnabled =
|
||||||
|
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
@ -19,7 +22,7 @@ const TopCenterPanel = () => {
|
|||||||
opacity={0.8}
|
opacity={0.8}
|
||||||
>
|
>
|
||||||
{name || t('workflows.unnamedWorkflow')}
|
{name || t('workflows.unnamedWorkflow')}
|
||||||
{isTouched ? ` (${t('common.unsaved')})` : ''}
|
{isTouched && isWorkflowLibraryEnabled ? ` (${t('common.unsaved')})` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,12 +5,10 @@ import { t } from 'i18next';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const zRejectedForbiddenAction = z.object({
|
const zRejectedForbiddenAction = z.object({
|
||||||
action: z.object({
|
payload: z.object({
|
||||||
payload: z.object({
|
status: z.literal(403),
|
||||||
status: z.literal(403),
|
data: z.object({
|
||||||
data: z.object({
|
detail: z.string(),
|
||||||
detail: z.string(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -22,8 +20,8 @@ export const authToastMiddleware: Middleware =
|
|||||||
const parsed = zRejectedForbiddenAction.parse(action);
|
const parsed = zRejectedForbiddenAction.parse(action);
|
||||||
const { dispatch } = api;
|
const { dispatch } = api;
|
||||||
const customMessage =
|
const customMessage =
|
||||||
parsed.action.payload.data.detail !== 'Forbidden'
|
parsed.payload.data.detail !== 'Forbidden'
|
||||||
? parsed.action.payload.data.detail
|
? parsed.payload.data.detail
|
||||||
: undefined;
|
: undefined;
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
@ -32,7 +30,7 @@ export const authToastMiddleware: Middleware =
|
|||||||
description: customMessage,
|
description: customMessage,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch {
|
} catch (error) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,8 @@ nav:
|
|||||||
- Adding Tests: 'contributing/TESTS.md'
|
- Adding Tests: 'contributing/TESTS.md'
|
||||||
- Documentation: 'contributing/contribution_guides/documentation.md'
|
- Documentation: 'contributing/contribution_guides/documentation.md'
|
||||||
- Nodes: 'contributing/INVOCATIONS.md'
|
- Nodes: 'contributing/INVOCATIONS.md'
|
||||||
|
- Model Manager: 'contributing/MODEL_MANAGER.md'
|
||||||
|
- Download Queue: 'contributing/DOWNLOAD_QUEUE.md'
|
||||||
- Translation: 'contributing/contribution_guides/translation.md'
|
- Translation: 'contributing/contribution_guides/translation.md'
|
||||||
- Tutorials: 'contributing/contribution_guides/tutorials.md'
|
- Tutorials: 'contributing/contribution_guides/tutorials.md'
|
||||||
- Changelog: 'CHANGELOG.md'
|
- Changelog: 'CHANGELOG.md'
|
||||||
|
@ -26,7 +26,6 @@ from invokeai.app.services.shared.graph import (
|
|||||||
Graph,
|
Graph,
|
||||||
GraphExecutionState,
|
GraphExecutionState,
|
||||||
IterateInvocation,
|
IterateInvocation,
|
||||||
LibraryGraph,
|
|
||||||
)
|
)
|
||||||
from invokeai.backend.util.logging import InvokeAILogger
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
from tests.fixtures.sqlite_database import create_mock_sqlite_database
|
from tests.fixtures.sqlite_database import create_mock_sqlite_database
|
||||||
@ -61,7 +60,6 @@ def mock_services() -> InvocationServices:
|
|||||||
configuration=configuration,
|
configuration=configuration,
|
||||||
events=TestEventService(),
|
events=TestEventService(),
|
||||||
graph_execution_manager=graph_execution_manager,
|
graph_execution_manager=graph_execution_manager,
|
||||||
graph_library=SqliteItemStorage[LibraryGraph](db=db, table_name="graphs"),
|
|
||||||
image_files=None, # type: ignore
|
image_files=None, # type: ignore
|
||||||
image_records=None, # type: ignore
|
image_records=None, # type: ignore
|
||||||
images=None, # type: ignore
|
images=None, # type: ignore
|
||||||
@ -70,6 +68,7 @@ def mock_services() -> InvocationServices:
|
|||||||
logger=logging, # type: ignore
|
logger=logging, # type: ignore
|
||||||
model_manager=None, # type: ignore
|
model_manager=None, # type: ignore
|
||||||
model_records=None, # type: ignore
|
model_records=None, # type: ignore
|
||||||
|
download_queue=None, # type: ignore
|
||||||
model_install=None, # type: ignore
|
model_install=None, # type: ignore
|
||||||
names=None, # type: ignore
|
names=None, # type: ignore
|
||||||
performance_statistics=InvocationStatsService(),
|
performance_statistics=InvocationStatsService(),
|
||||||
|
@ -24,7 +24,7 @@ from invokeai.app.services.invocation_stats.invocation_stats_default import Invo
|
|||||||
from invokeai.app.services.invoker import Invoker
|
from invokeai.app.services.invoker import Invoker
|
||||||
from invokeai.app.services.item_storage.item_storage_sqlite import SqliteItemStorage
|
from invokeai.app.services.item_storage.item_storage_sqlite import SqliteItemStorage
|
||||||
from invokeai.app.services.session_queue.session_queue_common import DEFAULT_QUEUE_ID
|
from invokeai.app.services.session_queue.session_queue_common import DEFAULT_QUEUE_ID
|
||||||
from invokeai.app.services.shared.graph import Graph, GraphExecutionState, GraphInvocation, LibraryGraph
|
from invokeai.app.services.shared.graph import Graph, GraphExecutionState, GraphInvocation
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -66,7 +66,6 @@ def mock_services() -> InvocationServices:
|
|||||||
configuration=configuration,
|
configuration=configuration,
|
||||||
events=TestEventService(),
|
events=TestEventService(),
|
||||||
graph_execution_manager=graph_execution_manager,
|
graph_execution_manager=graph_execution_manager,
|
||||||
graph_library=SqliteItemStorage[LibraryGraph](db=db, table_name="graphs"),
|
|
||||||
image_files=None, # type: ignore
|
image_files=None, # type: ignore
|
||||||
image_records=None, # type: ignore
|
image_records=None, # type: ignore
|
||||||
images=None, # type: ignore
|
images=None, # type: ignore
|
||||||
@ -75,6 +74,7 @@ def mock_services() -> InvocationServices:
|
|||||||
logger=logging, # type: ignore
|
logger=logging, # type: ignore
|
||||||
model_manager=None, # type: ignore
|
model_manager=None, # type: ignore
|
||||||
model_records=None, # type: ignore
|
model_records=None, # type: ignore
|
||||||
|
download_queue=None, # type: ignore
|
||||||
model_install=None, # type: ignore
|
model_install=None, # type: ignore
|
||||||
names=None, # type: ignore
|
names=None, # type: ignore
|
||||||
performance_statistics=InvocationStatsService(),
|
performance_statistics=InvocationStatsService(),
|
||||||
|
223
tests/app/services/download/test_download_queue.py
Normal file
223
tests/app/services/download/test_download_queue.py
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
"""Test the queued download facility"""
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import requests
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic.networks import AnyHttpUrl
|
||||||
|
from requests.sessions import Session
|
||||||
|
from requests_testadapter import TestAdapter
|
||||||
|
|
||||||
|
from invokeai.app.services.download import DownloadJob, DownloadJobStatus, DownloadQueueService
|
||||||
|
from invokeai.app.services.events.events_base import EventServiceBase
|
||||||
|
|
||||||
|
# Prevent pytest deprecation warnings
|
||||||
|
TestAdapter.__test__ = False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def session() -> requests.sessions.Session:
|
||||||
|
sess = requests.Session()
|
||||||
|
for i in ["12345", "9999", "54321"]:
|
||||||
|
content = (
|
||||||
|
b"I am a safetensors file " + bytearray(i, "utf-8") + bytearray(32_000)
|
||||||
|
) # for pause tests, must make content large
|
||||||
|
sess.mount(
|
||||||
|
f"http://www.civitai.com/models/{i}",
|
||||||
|
TestAdapter(
|
||||||
|
content,
|
||||||
|
headers={
|
||||||
|
"Content-Length": len(content),
|
||||||
|
"Content-Disposition": f'filename="mock{i}.safetensors"',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# here are some malformed URLs to test
|
||||||
|
# missing the content length
|
||||||
|
sess.mount(
|
||||||
|
"http://www.civitai.com/models/missing",
|
||||||
|
TestAdapter(
|
||||||
|
b"Missing content length",
|
||||||
|
headers={
|
||||||
|
"Content-Disposition": 'filename="missing.txt"',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# not found test
|
||||||
|
sess.mount("http://www.civitai.com/models/broken", TestAdapter(b"Not found", status=404))
|
||||||
|
|
||||||
|
return sess
|
||||||
|
|
||||||
|
|
||||||
|
class DummyEvent(BaseModel):
|
||||||
|
"""Dummy Event to use with Dummy Event service."""
|
||||||
|
|
||||||
|
event_name: str
|
||||||
|
payload: Dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
# A dummy event service for testing event issuing
|
||||||
|
class DummyEventService(EventServiceBase):
|
||||||
|
"""Dummy event service for testing."""
|
||||||
|
|
||||||
|
events: List[DummyEvent]
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.events = []
|
||||||
|
|
||||||
|
def dispatch(self, event_name: str, payload: Any) -> None:
|
||||||
|
"""Dispatch an event by appending it to self.events."""
|
||||||
|
self.events.append(DummyEvent(event_name=payload["event"], payload=payload["data"]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_queue_download(tmp_path: Path, session: Session) -> None:
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def event_handler(job: DownloadJob) -> None:
|
||||||
|
events.add(job.status)
|
||||||
|
|
||||||
|
queue = DownloadQueueService(
|
||||||
|
requests_session=session,
|
||||||
|
)
|
||||||
|
queue.start()
|
||||||
|
job = queue.download(
|
||||||
|
source=AnyHttpUrl("http://www.civitai.com/models/12345"),
|
||||||
|
dest=tmp_path,
|
||||||
|
on_start=event_handler,
|
||||||
|
on_progress=event_handler,
|
||||||
|
on_complete=event_handler,
|
||||||
|
on_error=event_handler,
|
||||||
|
)
|
||||||
|
assert isinstance(job, DownloadJob), "expected the job to be of type DownloadJobBase"
|
||||||
|
assert isinstance(job.id, int), "expected the job id to be numeric"
|
||||||
|
queue.join()
|
||||||
|
|
||||||
|
assert job.status == DownloadJobStatus("completed"), "expected job status to be completed"
|
||||||
|
assert Path(tmp_path, "mock12345.safetensors").exists(), f"expected {tmp_path}/mock12345.safetensors to exist"
|
||||||
|
|
||||||
|
assert events == {DownloadJobStatus.RUNNING, DownloadJobStatus.COMPLETED}
|
||||||
|
queue.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_errors(tmp_path: Path, session: Session) -> None:
|
||||||
|
queue = DownloadQueueService(
|
||||||
|
requests_session=session,
|
||||||
|
)
|
||||||
|
queue.start()
|
||||||
|
|
||||||
|
for bad_url in ["http://www.civitai.com/models/broken", "http://www.civitai.com/models/missing"]:
|
||||||
|
queue.download(AnyHttpUrl(bad_url), dest=tmp_path)
|
||||||
|
|
||||||
|
queue.join()
|
||||||
|
jobs = queue.list_jobs()
|
||||||
|
print(jobs)
|
||||||
|
assert len(jobs) == 2
|
||||||
|
jobs_dict = {str(x.source): x for x in jobs}
|
||||||
|
assert jobs_dict["http://www.civitai.com/models/broken"].status == DownloadJobStatus.ERROR
|
||||||
|
assert jobs_dict["http://www.civitai.com/models/broken"].error_type == "HTTPError(NOT FOUND)"
|
||||||
|
assert jobs_dict["http://www.civitai.com/models/missing"].status == DownloadJobStatus.COMPLETED
|
||||||
|
assert jobs_dict["http://www.civitai.com/models/missing"].total_bytes == 0
|
||||||
|
queue.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_event_bus(tmp_path: Path, session: Session) -> None:
|
||||||
|
event_bus = DummyEventService()
|
||||||
|
|
||||||
|
queue = DownloadQueueService(requests_session=session, event_bus=event_bus)
|
||||||
|
queue.start()
|
||||||
|
queue.download(
|
||||||
|
source=AnyHttpUrl("http://www.civitai.com/models/12345"),
|
||||||
|
dest=tmp_path,
|
||||||
|
)
|
||||||
|
queue.join()
|
||||||
|
events = event_bus.events
|
||||||
|
assert len(events) == 3
|
||||||
|
assert events[0].payload["timestamp"] <= events[1].payload["timestamp"]
|
||||||
|
assert events[1].payload["timestamp"] <= events[2].payload["timestamp"]
|
||||||
|
assert events[0].event_name == "download_started"
|
||||||
|
assert events[1].event_name == "download_progress"
|
||||||
|
assert events[1].payload["total_bytes"] > 0
|
||||||
|
assert events[1].payload["current_bytes"] <= events[1].payload["total_bytes"]
|
||||||
|
assert events[2].event_name == "download_complete"
|
||||||
|
assert events[2].payload["total_bytes"] == 32029
|
||||||
|
|
||||||
|
# test a failure
|
||||||
|
event_bus.events = [] # reset our accumulator
|
||||||
|
queue.download(source=AnyHttpUrl("http://www.civitai.com/models/broken"), dest=tmp_path)
|
||||||
|
queue.join()
|
||||||
|
events = event_bus.events
|
||||||
|
print("\n".join([x.model_dump_json() for x in events]))
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].event_name == "download_error"
|
||||||
|
assert events[0].payload["error_type"] == "HTTPError(NOT FOUND)"
|
||||||
|
assert events[0].payload["error"] is not None
|
||||||
|
assert re.search(r"requests.exceptions.HTTPError: NOT FOUND", events[0].payload["error"])
|
||||||
|
queue.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_broken_callbacks(tmp_path: Path, session: requests.sessions.Session, capsys) -> None:
|
||||||
|
queue = DownloadQueueService(
|
||||||
|
requests_session=session,
|
||||||
|
)
|
||||||
|
queue.start()
|
||||||
|
|
||||||
|
callback_ran = False
|
||||||
|
|
||||||
|
def broken_callback(job: DownloadJob) -> None:
|
||||||
|
nonlocal callback_ran
|
||||||
|
callback_ran = True
|
||||||
|
print(1 / 0) # deliberate error here
|
||||||
|
|
||||||
|
job = queue.download(
|
||||||
|
source=AnyHttpUrl("http://www.civitai.com/models/12345"),
|
||||||
|
dest=tmp_path,
|
||||||
|
on_progress=broken_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
queue.join()
|
||||||
|
assert job.status == DownloadJobStatus.COMPLETED # should complete even though the callback is borked
|
||||||
|
assert Path(tmp_path, "mock12345.safetensors").exists()
|
||||||
|
assert callback_ran
|
||||||
|
# LS: The pytest capsys fixture does not seem to be working. I can see the
|
||||||
|
# correct stderr message in the pytest log, but it is not appearing in
|
||||||
|
# capsys.readouterr().
|
||||||
|
# captured = capsys.readouterr()
|
||||||
|
# assert re.search("division by zero", captured.err)
|
||||||
|
queue.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def test_cancel(tmp_path: Path, session: requests.sessions.Session) -> None:
|
||||||
|
event_bus = DummyEventService()
|
||||||
|
|
||||||
|
queue = DownloadQueueService(requests_session=session, event_bus=event_bus)
|
||||||
|
queue.start()
|
||||||
|
|
||||||
|
cancelled = False
|
||||||
|
|
||||||
|
def slow_callback(job: DownloadJob) -> None:
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
def cancelled_callback(job: DownloadJob) -> None:
|
||||||
|
nonlocal cancelled
|
||||||
|
cancelled = True
|
||||||
|
|
||||||
|
job = queue.download(
|
||||||
|
source=AnyHttpUrl("http://www.civitai.com/models/12345"),
|
||||||
|
dest=tmp_path,
|
||||||
|
on_start=slow_callback,
|
||||||
|
on_cancelled=cancelled_callback,
|
||||||
|
)
|
||||||
|
queue.cancel_job(job)
|
||||||
|
queue.join()
|
||||||
|
|
||||||
|
assert job.status == DownloadJobStatus.CANCELLED
|
||||||
|
assert cancelled
|
||||||
|
events = event_bus.events
|
||||||
|
assert events[-1].event_name == "download_cancelled"
|
||||||
|
assert events[-1].payload["source"] == "http://www.civitai.com/models/12345"
|
||||||
|
queue.stop()
|
@ -48,11 +48,13 @@ def store(
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def installer(app_config: InvokeAIAppConfig, store: ModelRecordServiceBase) -> ModelInstallServiceBase:
|
def installer(app_config: InvokeAIAppConfig, store: ModelRecordServiceBase) -> ModelInstallServiceBase:
|
||||||
return ModelInstallService(
|
installer = ModelInstallService(
|
||||||
app_config=app_config,
|
app_config=app_config,
|
||||||
record_store=store,
|
record_store=store,
|
||||||
event_bus=DummyEventService(),
|
event_bus=DummyEventService(),
|
||||||
)
|
)
|
||||||
|
installer.start()
|
||||||
|
return installer
|
||||||
|
|
||||||
|
|
||||||
class DummyEvent(BaseModel):
|
class DummyEvent(BaseModel):
|
||||||
|
Loading…
Reference in New Issue
Block a user