mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
519 Commits
main
...
v4.2.9.dev
Author | SHA1 | Date | |
---|---|---|---|
|
06637161e3 | ||
|
c4f4b16a36 | ||
|
3001718f9f | ||
|
ac16fa65a3 | ||
|
bc0b5335ff | ||
|
e91c7c5a30 | ||
|
74791cc490 | ||
|
68409c6a0f | ||
|
85613b220c | ||
|
80085ad854 | ||
|
b6bfa65104 | ||
|
0bfa033089 | ||
|
6f0974b5bc | ||
|
c3b53fc4f6 | ||
|
8f59a32d81 | ||
|
3b4f20f433 | ||
|
73da6e9628 | ||
|
2fc482141d | ||
|
a41ec5f3fc | ||
|
c906225d03 | ||
|
2985ea3716 | ||
|
4f151c6c6f | ||
|
c4f5252c1a | ||
|
77c13f2cf3 | ||
|
3270d36fca | ||
|
b6b30ff01f | ||
|
aa9bfdff35 | ||
|
80308cc3b8 | ||
|
f6db73bf1f | ||
|
ef9f61a39f | ||
|
a1a0881133 | ||
|
9956919ab6 | ||
|
abc07f57d6 | ||
|
1a1cae79f1 | ||
|
bcfafe7b06 | ||
|
34e8ced592 | ||
|
1fdada65b6 | ||
|
433f3e1971 | ||
|
a60e23f825 | ||
|
f69de3148e | ||
|
cbcd36ef54 | ||
|
aa76134340 | ||
|
55758acae8 | ||
|
196e43b5e5 | ||
|
38b9828441 | ||
|
0048a7077e | ||
|
527a39a3ad | ||
|
30ce4c55c7 | ||
|
ca082d4288 | ||
|
5e59a4f43a | ||
|
9f86605049 | ||
|
79058a7894 | ||
|
bb3ad8c2f1 | ||
|
799688514b | ||
|
b7344b0df2 | ||
|
7e382c5f3f | ||
|
9cf357e184 | ||
|
95b6c773d4 | ||
|
89d8c5ba00 | ||
|
59580cf6ed | ||
|
2b0c084f5b | ||
|
4d896073ff | ||
|
9f69503a80 | ||
|
0311e852a0 | ||
|
7003a3d546 | ||
|
dc73072e27 | ||
|
e549c44ad7 | ||
|
45a4231cbe | ||
|
81f046ebac | ||
|
6ef6c593c4 | ||
|
5b53eefef7 | ||
|
9a9919c0af | ||
|
10661b33d4 | ||
|
52193d604d | ||
|
2568441e6a | ||
|
1a14860b3b | ||
|
9ff7647ec5 | ||
|
b49106e8fe | ||
|
906d0902a3 | ||
|
fbde6f5a7f | ||
|
b388268987 | ||
|
3b4164bd62 | ||
|
b7fc6fe573 | ||
|
2954a19d27 | ||
|
aa45ce7fbd | ||
|
77e5078e4a | ||
|
603cc7bf2e | ||
|
cd517a102d | ||
|
9a442918b5 | ||
|
f9c03d85a5 | ||
|
10d07c71c4 | ||
|
cd05a78219 | ||
|
f8ee572abc | ||
|
d918654509 | ||
|
582e30c542 | ||
|
34a6555301 | ||
|
fff860090b | ||
|
f4971197c1 | ||
|
621d5e0462 | ||
|
0b68a69a6c | ||
|
9a599ce595 | ||
|
1467ba276f | ||
|
708facf707 | ||
|
9c6c6adb1f | ||
|
c335b8581c | ||
|
f1348e45bd | ||
|
ce6cf9b079 | ||
|
13ec80736a | ||
|
c9690a4b21 | ||
|
489e875a6e | ||
|
8651396048 | ||
|
2bab5a6179 | ||
|
006f06b615 | ||
|
d603923d1b | ||
|
86878e855b | ||
|
35de60a8fa | ||
|
2c444a1941 | ||
|
3dfef01889 | ||
|
a845a2daa5 | ||
|
df41f4fbce | ||
|
76482da6f5 | ||
|
8205abbbbf | ||
|
926873de26 | ||
|
00cb1903ba | ||
|
58ba38b9c7 | ||
|
2f6a5617f9 | ||
|
e0d84743be | ||
|
ee7c62acc4 | ||
|
daf3e58bd9 | ||
|
c5b9209057 | ||
|
2a4d6d98e2 | ||
|
cfdf59d906 | ||
|
f91ce1a47c | ||
|
4af2888168 | ||
|
8471c6fe86 | ||
|
fe65a5a2db | ||
|
0df26e967c | ||
|
d4822b305e | ||
|
8df5447563 | ||
|
7b5a43df9b | ||
|
61ef630175 | ||
|
4eda2ef555 | ||
|
57f4489520 | ||
|
fb6cf9e3da | ||
|
f776326cff | ||
|
5be32d5733 | ||
|
1f73435241 | ||
|
3251a00631 | ||
|
49c4ad1dd7 | ||
|
5857e95c4a | ||
|
85be2532c6 | ||
|
8b81a00def | ||
|
8544595c27 | ||
|
a6a5d1470c | ||
|
febcc12ec9 | ||
|
ab64078b76 | ||
|
0ff3459b07 | ||
|
2abd7c9bfe | ||
|
8e5330bdc9 | ||
|
1ecec4ea3a | ||
|
700dbe69f3 | ||
|
af7ba3b7e4 | ||
|
1f7144d62e | ||
|
4e389e415b | ||
|
2db29fb6ab | ||
|
79653fcff5 | ||
|
9e39180fbc | ||
|
dc0f832d8f | ||
|
0dcd6aa5d9 | ||
|
9f4a8f11f8 | ||
|
6f9579d6ec | ||
|
2b2aabb234 | ||
|
a79b9633ab | ||
|
7b628c908b | ||
|
181703a709 | ||
|
c439e3c204 | ||
|
11e81eb456 | ||
|
3e24bf640e | ||
|
a17664fb75 | ||
|
5aed23dc91 | ||
|
7f389716d0 | ||
|
eca13b674a | ||
|
7c982a1bdf | ||
|
bdfe6870fd | ||
|
7eebbc0dd9 | ||
|
6ae46d7c8b | ||
|
8aae30566e | ||
|
4b7c3e221c | ||
|
4015795b7f | ||
|
132dd61d8d | ||
|
6f2b548dd1 | ||
|
49dd316f17 | ||
|
e0a8bb149d | ||
|
020b6db34b | ||
|
f6d2f0bf8c | ||
|
1280cce803 | ||
|
82463d12e2 | ||
|
ba6c1b84e4 | ||
|
92b6d3198a | ||
|
693ae1af50 | ||
|
3873a3096c | ||
|
4a9f6ab5ef | ||
|
2fc29c5125 | ||
|
5fbc876cfd | ||
|
89b0673ac9 | ||
|
3179a16189 | ||
|
3ce216b391 | ||
|
8a3a94e21a | ||
|
f61af188f9 | ||
|
73fc52bfed | ||
|
4eeff4eef8 | ||
|
cff2c43030 | ||
|
504b1f2425 | ||
|
eff5b56990 | ||
|
0e673f1a18 | ||
|
4fea22aea4 | ||
|
c2478c9ac3 | ||
|
ed8243825e | ||
|
c7ac2b5278 | ||
|
a783003556 | ||
|
44ba1c6113 | ||
|
a02d67fcc6 | ||
|
14c8f7c4f5 | ||
|
a487ecb50f | ||
|
fc55862823 | ||
|
e5400601d6 | ||
|
735f9f1483 | ||
|
d139db0a0f | ||
|
a35bb450b1 | ||
|
26c01dfa48 | ||
|
2b1839374a | ||
|
59f5f18e1d | ||
|
e41fcb081c | ||
|
3032042b35 | ||
|
544db61044 | ||
|
67a3aa6dff | ||
|
34f4468b20 | ||
|
d99ae58001 | ||
|
d60ec53762 | ||
|
0ece9361d5 | ||
|
69987a2f00 | ||
|
7346bfccb9 | ||
|
b705083ce2 | ||
|
4b3c82df6f | ||
|
46290205d5 | ||
|
7b15585b80 | ||
|
cbfeeb9079 | ||
|
fdac20b43e | ||
|
ae2312104e | ||
|
74b6674af6 | ||
|
81adce3238 | ||
|
48b7f460a8 | ||
|
38a8232341 | ||
|
39a51c4f4c | ||
|
220bfeb37d | ||
|
e766279950 | ||
|
db82406525 | ||
|
2d44f332d9 | ||
|
ea1526689a | ||
|
200338ed72 | ||
|
88003a61bd | ||
|
b82089b30b | ||
|
efd780d395 | ||
|
3f90f783de | ||
|
4f5b755117 | ||
|
a455f12581 | ||
|
de516db383 | ||
|
6781575293 | ||
|
65b0e40fc8 | ||
|
19e78a07b7 | ||
|
e3b60dda07 | ||
|
a9696d3193 | ||
|
336d72873f | ||
|
111a380bce | ||
|
012a8351af | ||
|
deeb80ea9b | ||
|
1e97a917d6 | ||
|
a15ba925db | ||
|
6e5a968aad | ||
|
915edaa02f | ||
|
f6b0fa7c18 | ||
|
3f1fba0f35 | ||
|
d9fa85a4c6 | ||
|
022bb8649c | ||
|
673bc33a01 | ||
|
579a64928d | ||
|
5316df7d7d | ||
|
62fe61dc30 | ||
|
ea819a4a2f | ||
|
868a25dae2 | ||
|
3a25d00cb6 | ||
|
6301b74d87 | ||
|
b43b90c299 | ||
|
0a43444ab3 | ||
|
75ea4d2155 | ||
|
9d281388e0 | ||
|
178e1cc50b | ||
|
6eafe53b2d | ||
|
7514b2b7d4 | ||
|
51dee2dba2 | ||
|
a81c3d841c | ||
|
154487f966 | ||
|
588daafcf5 | ||
|
ab00097aed | ||
|
5aefae71a2 | ||
|
0edd598970 | ||
|
0bb485031f | ||
|
01ffd86367 | ||
|
e2e02f31b6 | ||
|
0008617348 | ||
|
f8f21c0edd | ||
|
010916158b | ||
|
df0ba004ca | ||
|
038b29e15b | ||
|
763ab73923 | ||
|
9a060b4437 | ||
|
2307a30892 | ||
|
2006f84f6e | ||
|
23b15fef6a | ||
|
41e72f929d | ||
|
29fc49bb3b | ||
|
6f65b6a40f | ||
|
1e9b22e3a4 | ||
|
3dc2c723c3 | ||
|
6f007cbd48 | ||
|
9a402dd10e | ||
|
4249d0e13b | ||
|
f6050bad67 | ||
|
d3a4b7b51b | ||
|
3f5f9ac764 | ||
|
e7c1299a7f | ||
|
56a3918a1e | ||
|
dabf7718cf | ||
|
ef22c29288 | ||
|
74f06074f7 | ||
|
b97bf52faa | ||
|
0a77f5cec8 | ||
|
f8e92f7b73 | ||
|
530c6e3a59 | ||
|
11b95cfaf4 | ||
|
707c005a26 | ||
|
19fa8e7e33 | ||
|
b252ded366 | ||
|
84305d4e73 | ||
|
1150b41e14 | ||
|
88d8ccb34b | ||
|
4111b3f1aa | ||
|
36862be2aa | ||
|
425665e0d9 | ||
|
9abd604f69 | ||
|
59bdc288b5 | ||
|
eb37d2958e | ||
|
2a9738a341 | ||
|
6aac1cf33a | ||
|
9ca4d072ab | ||
|
7aaf14c26b | ||
|
cf598ca175 | ||
|
a722790afc | ||
|
320151a040 | ||
|
c090f511c3 | ||
|
86dd1475b3 | ||
|
0b71ac258c | ||
|
54e1eae509 | ||
|
bf57b2dc77 | ||
|
de3c27b44f | ||
|
05717fea93 | ||
|
191584d229 | ||
|
6069169e6b | ||
|
07438587f3 | ||
|
913e36d6fd | ||
|
139004b976 | ||
|
ef4269d585 | ||
|
954cb129a4 | ||
|
02c4b28de5 | ||
|
febea88b58 | ||
|
40ccfac514 | ||
|
831fb814cc | ||
|
bf166fdd61 | ||
|
384bde3539 | ||
|
6f1d238d0a | ||
|
ac524153a7 | ||
|
2cad2b15cf | ||
|
fd63e202fe | ||
|
a0250e47e3 | ||
|
7d8ece45bb | ||
|
ffb8f053da | ||
|
fb46f457f9 | ||
|
6d4f4152a7 | ||
|
d3d0ac7327 | ||
|
f57df46995 | ||
|
a747171745 | ||
|
e9ae9e80d4 | ||
|
3b9a59b98d | ||
|
8a381e7f74 | ||
|
61513fc800 | ||
|
1d26c49e92 | ||
|
77be9836d2 | ||
|
4427960acb | ||
|
84aa4fb7bc | ||
|
01df96cbe0 | ||
|
1ac0634f57 | ||
|
8e7d3634b1 | ||
|
fadafe5c77 | ||
|
b2ea1f6690 | ||
|
0a03c1f882 | ||
|
ce8b490ed8 | ||
|
86eccba80d | ||
|
97453e7c6c | ||
|
9e1084b701 | ||
|
41aec81f3f | ||
|
a5741a0551 | ||
|
72f73c231a | ||
|
b8fcaa274e | ||
|
a33bbf48bb | ||
|
98d9490fb9 | ||
|
51c643c4f8 | ||
|
2d7370ca6c | ||
|
0d68141387 | ||
|
e88a8c6639 | ||
|
2d04bb286e | ||
|
6d9ba24c32 | ||
|
7645a1c86e | ||
|
dc284b9a48 | ||
|
8a38332d44 | ||
|
2407d7d148 | ||
|
23275cf607 | ||
|
8a0e02d335 | ||
|
913873623c | ||
|
5d81e7dd4d | ||
|
418650fdf3 | ||
|
7c24e56d9f | ||
|
f6faed46c3 | ||
|
1d393eecf1 | ||
|
03a72240c0 | ||
|
acf62450fb | ||
|
d0269310cf | ||
|
43618a74e7 | ||
|
56fed637ec | ||
|
944ae4a604 | ||
|
f3da609102 | ||
|
11c8a8cf72 | ||
|
656978676f | ||
|
d216e6519f | ||
|
0a2bcae0e3 | ||
|
31cf244420 | ||
|
d761effac1 | ||
|
5efaf2c661 | ||
|
b79161fbec | ||
|
89a2f2134b | ||
|
343c3b19b1 | ||
|
e6ec646b2c | ||
|
b557fe4e40 | ||
|
45908dfbd2 | ||
|
1cf0673a22 | ||
|
189847f8a5 | ||
|
b27929f702 | ||
|
b1a6a9835d | ||
|
5564a16d4b | ||
|
b2aa447d50 | ||
|
8666f9a848 | ||
|
e7dc7c4917 | ||
|
513d0f1e5c | ||
|
c6ce7618cf | ||
|
9b78b8dc91 | ||
|
3bae233f40 | ||
|
0a305c4291 | ||
|
54fe4ddf3e | ||
|
aa877b981c | ||
|
b8ec4348a5 | ||
|
0e3e27668c | ||
|
e8c8025119 | ||
|
6a72eda5d2 | ||
|
35c8c54466 | ||
|
2eda45ca5b | ||
|
ecd6e7960c | ||
|
0d3a61cbdb | ||
|
6737c275d7 | ||
|
201a3e5838 | ||
|
85cb239219 | ||
|
002f45e383 | ||
|
470e5ba290 | ||
|
b6722b3a10 | ||
|
6a624916ca | ||
|
65653e0932 | ||
|
a7e59d6697 | ||
|
f1356105c1 | ||
|
8acc6379fb | ||
|
d13014c5d9 | ||
|
db11d8ba90 | ||
|
46a4ee2360 | ||
|
a19c053b88 | ||
|
5b47a32d31 | ||
|
32ae9efb6a | ||
|
b81bee551a | ||
|
8c9dffd082 | ||
|
cdfae643e4 | ||
|
cb93108206 | ||
|
c11343dc1c | ||
|
3733c6f89d | ||
|
3c23a0eac0 | ||
|
7fd69ab1f1 | ||
|
3f511774de | ||
|
3600100879 | ||
|
8fae372103 | ||
|
2fce1fe04c | ||
|
22f3e975c7 | ||
|
c6ced5a210 | ||
|
5d66e85205 | ||
|
649f163bf7 | ||
|
4c821bd930 | ||
|
6359de4e25 | ||
|
2c610c8cd4 | ||
|
7efe8a249b | ||
|
bd421d184e | ||
|
78f5844ba0 | ||
|
f6bb4d5051 | ||
|
56642c3e87 | ||
|
9fd8678d3d | ||
|
8b89518fd6 |
@ -11,6 +11,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
|
CancelByOriginResult,
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
PruneResult,
|
PruneResult,
|
||||||
@ -105,6 +106,19 @@ async def cancel_by_batch_ids(
|
|||||||
return ApiDependencies.invoker.services.session_queue.cancel_by_batch_ids(queue_id=queue_id, batch_ids=batch_ids)
|
return ApiDependencies.invoker.services.session_queue.cancel_by_batch_ids(queue_id=queue_id, batch_ids=batch_ids)
|
||||||
|
|
||||||
|
|
||||||
|
@session_queue_router.put(
|
||||||
|
"/{queue_id}/cancel_by_origin",
|
||||||
|
operation_id="cancel_by_origin",
|
||||||
|
responses={200: {"model": CancelByBatchIDsResult}},
|
||||||
|
)
|
||||||
|
async def cancel_by_origin(
|
||||||
|
queue_id: str = Path(description="The queue id to perform this operation on"),
|
||||||
|
origin: str = Query(description="The origin to cancel all queue items for"),
|
||||||
|
) -> CancelByOriginResult:
|
||||||
|
"""Immediately cancels all queue items with the given origin"""
|
||||||
|
return ApiDependencies.invoker.services.session_queue.cancel_by_origin(queue_id=queue_id, origin=origin)
|
||||||
|
|
||||||
|
|
||||||
@session_queue_router.put(
|
@session_queue_router.put(
|
||||||
"/{queue_id}/clear",
|
"/{queue_id}/clear",
|
||||||
operation_id="clear",
|
operation_id="clear",
|
||||||
|
@ -6,13 +6,19 @@ import cv2
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
from PIL import Image, ImageChops, ImageFilter, ImageOps
|
||||||
|
|
||||||
from invokeai.app.invocations.baseinvocation import BaseInvocation, Classification, invocation
|
from invokeai.app.invocations.baseinvocation import (
|
||||||
|
BaseInvocation,
|
||||||
|
Classification,
|
||||||
|
invocation,
|
||||||
|
invocation_output,
|
||||||
|
)
|
||||||
from invokeai.app.invocations.constants import IMAGE_MODES
|
from invokeai.app.invocations.constants import IMAGE_MODES
|
||||||
from invokeai.app.invocations.fields import (
|
from invokeai.app.invocations.fields import (
|
||||||
ColorField,
|
ColorField,
|
||||||
FieldDescriptions,
|
FieldDescriptions,
|
||||||
ImageField,
|
ImageField,
|
||||||
InputField,
|
InputField,
|
||||||
|
OutputField,
|
||||||
WithBoard,
|
WithBoard,
|
||||||
WithMetadata,
|
WithMetadata,
|
||||||
)
|
)
|
||||||
@ -1007,3 +1013,62 @@ class MaskFromIDInvocation(BaseInvocation, WithMetadata, WithBoard):
|
|||||||
image_dto = context.images.save(image=mask, image_category=ImageCategory.MASK)
|
image_dto = context.images.save(image=mask, image_category=ImageCategory.MASK)
|
||||||
|
|
||||||
return ImageOutput.build(image_dto)
|
return ImageOutput.build(image_dto)
|
||||||
|
|
||||||
|
|
||||||
|
@invocation_output("canvas_v2_mask_and_crop_output")
|
||||||
|
class CanvasV2MaskAndCropOutput(ImageOutput):
|
||||||
|
offset_x: int = OutputField(description="The x offset of the image, after cropping")
|
||||||
|
offset_y: int = OutputField(description="The y offset of the image, after cropping")
|
||||||
|
|
||||||
|
|
||||||
|
@invocation(
|
||||||
|
"canvas_v2_mask_and_crop",
|
||||||
|
title="Canvas V2 Mask and Crop",
|
||||||
|
tags=["image", "mask", "id"],
|
||||||
|
category="image",
|
||||||
|
version="1.0.0",
|
||||||
|
classification=Classification.Prototype,
|
||||||
|
)
|
||||||
|
class CanvasV2MaskAndCropInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
|
"""Handles Canvas V2 image output masking and cropping"""
|
||||||
|
|
||||||
|
source_image: ImageField | None = InputField(
|
||||||
|
default=None,
|
||||||
|
description="The source image onto which the masked generated image is pasted. If omitted, the masked generated image is returned with transparency.",
|
||||||
|
)
|
||||||
|
generated_image: ImageField = InputField(description="The image to apply the mask to")
|
||||||
|
mask: ImageField = InputField(description="The mask to apply")
|
||||||
|
mask_blur: int = InputField(default=0, ge=0, description="The amount to blur the mask by")
|
||||||
|
|
||||||
|
def _prepare_mask(self, mask: Image.Image) -> Image.Image:
|
||||||
|
mask_array = numpy.array(mask)
|
||||||
|
kernel = numpy.ones((self.mask_blur, self.mask_blur), numpy.uint8)
|
||||||
|
dilated_mask_array = cv2.erode(mask_array, kernel, iterations=3)
|
||||||
|
dilated_mask = Image.fromarray(dilated_mask_array)
|
||||||
|
if self.mask_blur > 0:
|
||||||
|
mask = dilated_mask.filter(ImageFilter.GaussianBlur(self.mask_blur))
|
||||||
|
return ImageOps.invert(mask.convert("L"))
|
||||||
|
|
||||||
|
def invoke(self, context: InvocationContext) -> CanvasV2MaskAndCropOutput:
|
||||||
|
mask = self._prepare_mask(context.images.get_pil(self.mask.image_name))
|
||||||
|
|
||||||
|
if self.source_image:
|
||||||
|
generated_image = context.images.get_pil(self.generated_image.image_name)
|
||||||
|
source_image = context.images.get_pil(self.source_image.image_name)
|
||||||
|
source_image.paste(generated_image, (0, 0), mask)
|
||||||
|
image_dto = context.images.save(image=source_image)
|
||||||
|
else:
|
||||||
|
generated_image = context.images.get_pil(self.generated_image.image_name)
|
||||||
|
generated_image.putalpha(mask)
|
||||||
|
image_dto = context.images.save(image=generated_image)
|
||||||
|
|
||||||
|
# bbox = image.getbbox()
|
||||||
|
# image = image.crop(bbox)
|
||||||
|
|
||||||
|
return CanvasV2MaskAndCropOutput(
|
||||||
|
image=ImageField(image_name=image_dto.image_name),
|
||||||
|
offset_x=0,
|
||||||
|
offset_y=0,
|
||||||
|
width=image_dto.width,
|
||||||
|
height=image_dto.height,
|
||||||
|
)
|
||||||
|
@ -88,6 +88,7 @@ class QueueItemEventBase(QueueEventBase):
|
|||||||
|
|
||||||
item_id: int = Field(description="The ID of the queue item")
|
item_id: int = Field(description="The ID of the queue item")
|
||||||
batch_id: str = Field(description="The ID of the queue batch")
|
batch_id: str = Field(description="The ID of the queue batch")
|
||||||
|
origin: str | None = Field(default=None, description="The origin of the batch")
|
||||||
|
|
||||||
|
|
||||||
class InvocationEventBase(QueueItemEventBase):
|
class InvocationEventBase(QueueItemEventBase):
|
||||||
@ -95,8 +96,6 @@ class InvocationEventBase(QueueItemEventBase):
|
|||||||
|
|
||||||
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
||||||
queue_id: str = Field(description="The ID of the queue")
|
queue_id: str = Field(description="The ID of the queue")
|
||||||
item_id: int = Field(description="The ID of the queue item")
|
|
||||||
batch_id: str = Field(description="The ID of the queue batch")
|
|
||||||
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
session_id: str = Field(description="The ID of the session (aka graph execution state)")
|
||||||
invocation: AnyInvocation = Field(description="The ID of the invocation")
|
invocation: AnyInvocation = Field(description="The ID of the invocation")
|
||||||
invocation_source_id: str = Field(description="The ID of the prepared invocation's source node")
|
invocation_source_id: str = Field(description="The ID of the prepared invocation's source node")
|
||||||
@ -114,6 +113,7 @@ class InvocationStartedEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
|
origin=queue_item.origin,
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -147,6 +147,7 @@ class InvocationDenoiseProgressEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
|
origin=queue_item.origin,
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -184,6 +185,7 @@ class InvocationCompleteEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
|
origin=queue_item.origin,
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -216,6 +218,7 @@ class InvocationErrorEvent(InvocationEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
|
origin=queue_item.origin,
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
invocation=invocation,
|
invocation=invocation,
|
||||||
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
invocation_source_id=queue_item.session.prepared_source_mapping[invocation.id],
|
||||||
@ -253,6 +256,7 @@ class QueueItemStatusChangedEvent(QueueItemEventBase):
|
|||||||
queue_id=queue_item.queue_id,
|
queue_id=queue_item.queue_id,
|
||||||
item_id=queue_item.item_id,
|
item_id=queue_item.item_id,
|
||||||
batch_id=queue_item.batch_id,
|
batch_id=queue_item.batch_id,
|
||||||
|
origin=queue_item.origin,
|
||||||
session_id=queue_item.session_id,
|
session_id=queue_item.session_id,
|
||||||
status=queue_item.status,
|
status=queue_item.status,
|
||||||
error_type=queue_item.error_type,
|
error_type=queue_item.error_type,
|
||||||
@ -279,12 +283,14 @@ class BatchEnqueuedEvent(QueueEventBase):
|
|||||||
description="The number of invocations initially requested to be enqueued (may be less than enqueued if queue was full)"
|
description="The number of invocations initially requested to be enqueued (may be less than enqueued if queue was full)"
|
||||||
)
|
)
|
||||||
priority: int = Field(description="The priority of the batch")
|
priority: int = Field(description="The priority of the batch")
|
||||||
|
origin: str | None = Field(default=None, description="The origin of the batch")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, enqueue_result: EnqueueBatchResult) -> "BatchEnqueuedEvent":
|
def build(cls, enqueue_result: EnqueueBatchResult) -> "BatchEnqueuedEvent":
|
||||||
return cls(
|
return cls(
|
||||||
queue_id=enqueue_result.queue_id,
|
queue_id=enqueue_result.queue_id,
|
||||||
batch_id=enqueue_result.batch.batch_id,
|
batch_id=enqueue_result.batch.batch_id,
|
||||||
|
origin=enqueue_result.batch.origin,
|
||||||
enqueued=enqueue_result.enqueued,
|
enqueued=enqueue_result.enqueued,
|
||||||
requested=enqueue_result.requested,
|
requested=enqueue_result.requested,
|
||||||
priority=enqueue_result.priority,
|
priority=enqueue_result.priority,
|
||||||
|
@ -6,6 +6,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
|
CancelByOriginResult,
|
||||||
CancelByQueueIDResult,
|
CancelByQueueIDResult,
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
@ -95,6 +96,11 @@ class SessionQueueBase(ABC):
|
|||||||
"""Cancels all queue items with matching batch IDs"""
|
"""Cancels all queue items with matching batch IDs"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
|
||||||
|
"""Cancels all queue items with the given batch origin"""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
||||||
"""Cancels all queue items with matching queue ID"""
|
"""Cancels all queue items with matching queue ID"""
|
||||||
|
@ -77,6 +77,7 @@ BatchDataCollection: TypeAlias = list[list[BatchDatum]]
|
|||||||
|
|
||||||
class Batch(BaseModel):
|
class Batch(BaseModel):
|
||||||
batch_id: str = Field(default_factory=uuid_string, description="The ID of the batch")
|
batch_id: str = Field(default_factory=uuid_string, description="The ID of the batch")
|
||||||
|
origin: str | None = Field(default=None, description="The origin of this batch.")
|
||||||
data: Optional[BatchDataCollection] = Field(default=None, description="The batch data collection.")
|
data: Optional[BatchDataCollection] = Field(default=None, description="The batch data collection.")
|
||||||
graph: Graph = Field(description="The graph to initialize the session with")
|
graph: Graph = Field(description="The graph to initialize the session with")
|
||||||
workflow: Optional[WorkflowWithoutID] = Field(
|
workflow: Optional[WorkflowWithoutID] = Field(
|
||||||
@ -195,6 +196,7 @@ class SessionQueueItemWithoutGraph(BaseModel):
|
|||||||
status: QUEUE_ITEM_STATUS = Field(default="pending", description="The status of this queue item")
|
status: QUEUE_ITEM_STATUS = Field(default="pending", description="The status of this queue item")
|
||||||
priority: int = Field(default=0, description="The priority of this queue item")
|
priority: int = Field(default=0, description="The priority of this queue item")
|
||||||
batch_id: str = Field(description="The ID of the batch associated with this queue item")
|
batch_id: str = Field(description="The ID of the batch associated with this queue item")
|
||||||
|
origin: str | None = Field(default=None, description="The origin of this queue item. ")
|
||||||
session_id: str = Field(
|
session_id: str = Field(
|
||||||
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
|
description="The ID of the session associated with this queue item. The session doesn't exist in graph_executions until the queue item is executed."
|
||||||
)
|
)
|
||||||
@ -294,6 +296,7 @@ class SessionQueueStatus(BaseModel):
|
|||||||
class BatchStatus(BaseModel):
|
class BatchStatus(BaseModel):
|
||||||
queue_id: str = Field(..., description="The ID of the queue")
|
queue_id: str = Field(..., description="The ID of the queue")
|
||||||
batch_id: str = Field(..., description="The ID of the batch")
|
batch_id: str = Field(..., description="The ID of the batch")
|
||||||
|
origin: str | None = Field(..., description="The origin of the batch")
|
||||||
pending: int = Field(..., description="Number of queue items with status 'pending'")
|
pending: int = Field(..., description="Number of queue items with status 'pending'")
|
||||||
in_progress: int = Field(..., description="Number of queue items with status 'in_progress'")
|
in_progress: int = Field(..., description="Number of queue items with status 'in_progress'")
|
||||||
completed: int = Field(..., description="Number of queue items with status 'complete'")
|
completed: int = Field(..., description="Number of queue items with status 'complete'")
|
||||||
@ -328,6 +331,12 @@ class CancelByBatchIDsResult(BaseModel):
|
|||||||
canceled: int = Field(..., description="Number of queue items canceled")
|
canceled: int = Field(..., description="Number of queue items canceled")
|
||||||
|
|
||||||
|
|
||||||
|
class CancelByOriginResult(BaseModel):
|
||||||
|
"""Result of canceling by list of batch ids"""
|
||||||
|
|
||||||
|
canceled: int = Field(..., description="Number of queue items canceled")
|
||||||
|
|
||||||
|
|
||||||
class CancelByQueueIDResult(CancelByBatchIDsResult):
|
class CancelByQueueIDResult(CancelByBatchIDsResult):
|
||||||
"""Result of canceling by queue id"""
|
"""Result of canceling by queue id"""
|
||||||
|
|
||||||
@ -433,6 +442,7 @@ class SessionQueueValueToInsert(NamedTuple):
|
|||||||
field_values: Optional[str] # field_values json
|
field_values: Optional[str] # field_values json
|
||||||
priority: int # priority
|
priority: int # priority
|
||||||
workflow: Optional[str] # workflow json
|
workflow: Optional[str] # workflow json
|
||||||
|
origin: str | None
|
||||||
|
|
||||||
|
|
||||||
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
|
ValuesToInsert: TypeAlias = list[SessionQueueValueToInsert]
|
||||||
@ -453,6 +463,7 @@ def prepare_values_to_insert(queue_id: str, batch: Batch, priority: int, max_new
|
|||||||
json.dumps(field_values, default=to_jsonable_python) if field_values else None, # field_values (json)
|
json.dumps(field_values, default=to_jsonable_python) if field_values else None, # field_values (json)
|
||||||
priority, # priority
|
priority, # priority
|
||||||
json.dumps(workflow, default=to_jsonable_python) if workflow else None, # workflow (json)
|
json.dumps(workflow, default=to_jsonable_python) if workflow else None, # workflow (json)
|
||||||
|
batch.origin, # origin
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return values_to_insert
|
return values_to_insert
|
||||||
|
@ -10,6 +10,7 @@ from invokeai.app.services.session_queue.session_queue_common import (
|
|||||||
Batch,
|
Batch,
|
||||||
BatchStatus,
|
BatchStatus,
|
||||||
CancelByBatchIDsResult,
|
CancelByBatchIDsResult,
|
||||||
|
CancelByOriginResult,
|
||||||
CancelByQueueIDResult,
|
CancelByQueueIDResult,
|
||||||
ClearResult,
|
ClearResult,
|
||||||
EnqueueBatchResult,
|
EnqueueBatchResult,
|
||||||
@ -127,8 +128,8 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
|
|
||||||
self.__cursor.executemany(
|
self.__cursor.executemany(
|
||||||
"""--sql
|
"""--sql
|
||||||
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow)
|
INSERT INTO session_queue (queue_id, session, session_id, batch_id, field_values, priority, workflow, origin)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
values_to_insert,
|
values_to_insert,
|
||||||
)
|
)
|
||||||
@ -417,11 +418,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
)
|
)
|
||||||
self.__conn.commit()
|
self.__conn.commit()
|
||||||
if current_queue_item is not None and current_queue_item.batch_id in batch_ids:
|
if current_queue_item is not None and current_queue_item.batch_id in batch_ids:
|
||||||
batch_status = self.get_batch_status(queue_id=queue_id, batch_id=current_queue_item.batch_id)
|
self._set_queue_item_status(current_queue_item.item_id, "canceled")
|
||||||
queue_status = self.get_queue_status(queue_id=queue_id)
|
|
||||||
self.__invoker.services.events.emit_queue_item_status_changed(
|
|
||||||
current_queue_item, batch_status, queue_status
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.__conn.rollback()
|
self.__conn.rollback()
|
||||||
raise
|
raise
|
||||||
@ -429,6 +426,46 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
return CancelByBatchIDsResult(canceled=count)
|
return CancelByBatchIDsResult(canceled=count)
|
||||||
|
|
||||||
|
def cancel_by_origin(self, queue_id: str, origin: str) -> CancelByOriginResult:
|
||||||
|
try:
|
||||||
|
current_queue_item = self.get_current(queue_id)
|
||||||
|
self.__lock.acquire()
|
||||||
|
where = """--sql
|
||||||
|
WHERE
|
||||||
|
queue_id == ?
|
||||||
|
AND origin == ?
|
||||||
|
AND status != 'canceled'
|
||||||
|
AND status != 'completed'
|
||||||
|
AND status != 'failed'
|
||||||
|
"""
|
||||||
|
params = (queue_id, origin)
|
||||||
|
self.__cursor.execute(
|
||||||
|
f"""--sql
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM session_queue
|
||||||
|
{where};
|
||||||
|
""",
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
count = self.__cursor.fetchone()[0]
|
||||||
|
self.__cursor.execute(
|
||||||
|
f"""--sql
|
||||||
|
UPDATE session_queue
|
||||||
|
SET status = 'canceled'
|
||||||
|
{where};
|
||||||
|
""",
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
self.__conn.commit()
|
||||||
|
if current_queue_item is not None and current_queue_item.origin == origin:
|
||||||
|
self._set_queue_item_status(current_queue_item.item_id, "canceled")
|
||||||
|
except Exception:
|
||||||
|
self.__conn.rollback()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self.__lock.release()
|
||||||
|
return CancelByOriginResult(canceled=count)
|
||||||
|
|
||||||
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
def cancel_by_queue_id(self, queue_id: str) -> CancelByQueueIDResult:
|
||||||
try:
|
try:
|
||||||
current_queue_item = self.get_current(queue_id)
|
current_queue_item = self.get_current(queue_id)
|
||||||
@ -541,7 +578,8 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
started_at,
|
started_at,
|
||||||
session_id,
|
session_id,
|
||||||
batch_id,
|
batch_id,
|
||||||
queue_id
|
queue_id,
|
||||||
|
origin
|
||||||
FROM session_queue
|
FROM session_queue
|
||||||
WHERE queue_id = ?
|
WHERE queue_id = ?
|
||||||
"""
|
"""
|
||||||
@ -621,7 +659,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
self.__lock.acquire()
|
self.__lock.acquire()
|
||||||
self.__cursor.execute(
|
self.__cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
SELECT status, count(*)
|
SELECT status, count(*), origin
|
||||||
FROM session_queue
|
FROM session_queue
|
||||||
WHERE
|
WHERE
|
||||||
queue_id = ?
|
queue_id = ?
|
||||||
@ -633,6 +671,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
result = cast(list[sqlite3.Row], self.__cursor.fetchall())
|
result = cast(list[sqlite3.Row], self.__cursor.fetchall())
|
||||||
total = sum(row[1] for row in result)
|
total = sum(row[1] for row in result)
|
||||||
counts: dict[str, int] = {row[0]: row[1] for row in result}
|
counts: dict[str, int] = {row[0]: row[1] for row in result}
|
||||||
|
origin = result[0]["origin"] if result else None
|
||||||
except Exception:
|
except Exception:
|
||||||
self.__conn.rollback()
|
self.__conn.rollback()
|
||||||
raise
|
raise
|
||||||
@ -641,6 +680,7 @@ class SqliteSessionQueue(SessionQueueBase):
|
|||||||
|
|
||||||
return BatchStatus(
|
return BatchStatus(
|
||||||
batch_id=batch_id,
|
batch_id=batch_id,
|
||||||
|
origin=origin,
|
||||||
queue_id=queue_id,
|
queue_id=queue_id,
|
||||||
pending=counts.get("pending", 0),
|
pending=counts.get("pending", 0),
|
||||||
in_progress=counts.get("in_progress", 0),
|
in_progress=counts.get("in_progress", 0),
|
||||||
|
@ -17,6 +17,7 @@ from invokeai.app.services.shared.sqlite_migrator.migrations.migration_11 import
|
|||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_12 import build_migration_12
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_12 import build_migration_12
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_13 import build_migration_13
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_13 import build_migration_13
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_14 import build_migration_14
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_14 import build_migration_14
|
||||||
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_15 import build_migration_15
|
||||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
||||||
|
|
||||||
|
|
||||||
@ -51,6 +52,7 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto
|
|||||||
migrator.register_migration(build_migration_12(app_config=config))
|
migrator.register_migration(build_migration_12(app_config=config))
|
||||||
migrator.register_migration(build_migration_13())
|
migrator.register_migration(build_migration_13())
|
||||||
migrator.register_migration(build_migration_14())
|
migrator.register_migration(build_migration_14())
|
||||||
|
migrator.register_migration(build_migration_15())
|
||||||
migrator.run_migrations()
|
migrator.run_migrations()
|
||||||
|
|
||||||
return db
|
return db
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||||
|
|
||||||
|
|
||||||
|
class Migration15Callback:
|
||||||
|
def __call__(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
self._add_origin_col(cursor)
|
||||||
|
|
||||||
|
def _add_origin_col(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
"""
|
||||||
|
- Adds `origin` column to the session queue table.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute("ALTER TABLE session_queue ADD COLUMN origin TEXT;")
|
||||||
|
|
||||||
|
|
||||||
|
def build_migration_15() -> Migration:
|
||||||
|
"""
|
||||||
|
Build the migration from database version 14 to 15.
|
||||||
|
|
||||||
|
This migration does the following:
|
||||||
|
- Adds `origin` column to the session queue table.
|
||||||
|
"""
|
||||||
|
migration_15 = Migration(
|
||||||
|
from_version=14,
|
||||||
|
to_version=15,
|
||||||
|
callback=Migration15Callback(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return migration_15
|
@ -12,6 +12,10 @@ module.exports = {
|
|||||||
'i18next/no-literal-string': 'error',
|
'i18next/no-literal-string': 'error',
|
||||||
// https://eslint.org/docs/latest/rules/no-console
|
// https://eslint.org/docs/latest/rules/no-console
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
|
// https://eslint.org/docs/latest/rules/no-promise-executor-return
|
||||||
|
'no-promise-executor-return': 'error',
|
||||||
|
// https://eslint.org/docs/latest/rules/require-await
|
||||||
|
'require-await': 'error',
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PropsWithChildren, memo, useEffect } from 'react';
|
import { PropsWithChildren, memo, useEffect } from 'react';
|
||||||
import { modelChanged } from '../src/features/parameters/store/generationSlice';
|
import { modelChanged } from '../src/features/controlLayers/store/paramsSlice';
|
||||||
import { useAppDispatch } from '../src/app/store/storeHooks';
|
import { useAppDispatch } from '../src/app/store/storeHooks';
|
||||||
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
import { useGlobalModifiersInit } from '@invoke-ai/ui-library';
|
||||||
/**
|
/**
|
||||||
@ -10,7 +10,9 @@ export const ReduxInit = memo((props: PropsWithChildren) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
useGlobalModifiersInit();
|
useGlobalModifiersInit();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(modelChanged({ key: 'test_model', hash: 'some_hash', name: 'some name', base: 'sd-1', type: 'main' }));
|
dispatch(
|
||||||
|
modelChanged({ model: { key: 'test_model', hash: 'some_hash', name: 'some name', base: 'sd-1', type: 'main' } })
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return props.children;
|
return props.children;
|
||||||
|
@ -9,6 +9,8 @@ const config: KnipConfig = {
|
|||||||
'src/services/api/schema.ts',
|
'src/services/api/schema.ts',
|
||||||
'src/features/nodes/types/v1/**',
|
'src/features/nodes/types/v1/**',
|
||||||
'src/features/nodes/types/v2/**',
|
'src/features/nodes/types/v2/**',
|
||||||
|
// TODO(psyche): maybe we can clean up these utils after canvas v2 release
|
||||||
|
'src/features/controlLayers/konva/util.ts',
|
||||||
],
|
],
|
||||||
ignoreBinaries: ['only-allow'],
|
ignoreBinaries: ['only-allow'],
|
||||||
paths: {
|
paths: {
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
"build": "pnpm run lint && vite build",
|
"build": "pnpm run lint && vite build",
|
||||||
"typegen": "node scripts/typegen.js",
|
"typegen": "node scripts/typegen.js",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint:knip": "knip",
|
"lint:knip": "knip --tags=-knipignore",
|
||||||
"lint:dpdm": "dpdm --no-warning --no-tree --transform --exit-code circular:1 src/main.tsx",
|
"lint:dpdm": "dpdm --no-warning --no-tree --transform --exit-code circular:1 src/main.tsx",
|
||||||
"lint:eslint": "eslint --max-warnings=0 .",
|
"lint:eslint": "eslint --max-warnings=0 .",
|
||||||
"lint:prettier": "prettier --check .",
|
"lint:prettier": "prettier --check .",
|
||||||
@ -52,17 +52,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react-use-size": "^2.1.0",
|
|
||||||
"@dagrejs/dagre": "^1.1.3",
|
"@dagrejs/dagre": "^1.1.3",
|
||||||
"@dagrejs/graphlib": "^2.2.3",
|
"@dagrejs/graphlib": "^2.2.3",
|
||||||
"@dnd-kit/core": "^6.1.0",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource-variable/inter": "^5.0.20",
|
"@fontsource-variable/inter": "^5.0.20",
|
||||||
"@invoke-ai/ui-library": "^0.0.29",
|
"@invoke-ai/ui-library": "^0.0.32",
|
||||||
"@nanostores/react": "^0.7.3",
|
"@nanostores/react": "^0.7.3",
|
||||||
"@reduxjs/toolkit": "2.2.3",
|
"@reduxjs/toolkit": "2.2.3",
|
||||||
"@roarr/browser-log-writer": "^1.3.0",
|
"@roarr/browser-log-writer": "^1.3.0",
|
||||||
|
"async-mutex": "^0.5.0",
|
||||||
"chakra-react-select": "^4.9.1",
|
"chakra-react-select": "^4.9.1",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"dateformat": "^5.0.3",
|
"dateformat": "^5.0.3",
|
||||||
@ -74,6 +74,8 @@
|
|||||||
"jsondiffpatch": "^0.6.0",
|
"jsondiffpatch": "^0.6.0",
|
||||||
"konva": "^9.3.14",
|
"konva": "^9.3.14",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"lru-cache": "^11.0.0",
|
||||||
|
"nanoid": "^5.0.7",
|
||||||
"nanostores": "^0.11.2",
|
"nanostores": "^0.11.2",
|
||||||
"new-github-issue-url": "^1.0.0",
|
"new-github-issue-url": "^1.0.0",
|
||||||
"overlayscrollbars": "^2.10.0",
|
"overlayscrollbars": "^2.10.0",
|
||||||
@ -88,7 +90,6 @@
|
|||||||
"react-hotkeys-hook": "4.5.0",
|
"react-hotkeys-hook": "4.5.0",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.2.1",
|
||||||
"react-konva": "^18.2.10",
|
|
||||||
"react-redux": "9.1.2",
|
"react-redux": "9.1.2",
|
||||||
"react-resizable-panels": "^2.0.23",
|
"react-resizable-panels": "^2.0.23",
|
||||||
"react-select": "5.8.0",
|
"react-select": "5.8.0",
|
||||||
@ -102,9 +103,9 @@
|
|||||||
"roarr": "^7.21.1",
|
"roarr": "^7.21.1",
|
||||||
"serialize-error": "^11.0.3",
|
"serialize-error": "^11.0.3",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.7.5",
|
||||||
|
"stable-hash": "^0.0.4",
|
||||||
"use-debounce": "^10.0.2",
|
"use-debounce": "^10.0.2",
|
||||||
"use-device-pixel-ratio": "^1.1.2",
|
"use-device-pixel-ratio": "^1.1.2",
|
||||||
"use-image": "^1.1.1",
|
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zod-validation-error": "^3.3.1"
|
"zod-validation-error": "^3.3.1"
|
||||||
|
297
invokeai/frontend/web/pnpm-lock.yaml
generated
297
invokeai/frontend/web/pnpm-lock.yaml
generated
@ -5,9 +5,6 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-use-size':
|
|
||||||
specifier: ^2.1.0
|
|
||||||
version: 2.1.0(react@18.3.1)
|
|
||||||
'@dagrejs/dagre':
|
'@dagrejs/dagre':
|
||||||
specifier: ^1.1.3
|
specifier: ^1.1.3
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
@ -27,8 +24,8 @@ dependencies:
|
|||||||
specifier: ^5.0.20
|
specifier: ^5.0.20
|
||||||
version: 5.0.20
|
version: 5.0.20
|
||||||
'@invoke-ai/ui-library':
|
'@invoke-ai/ui-library':
|
||||||
specifier: ^0.0.29
|
specifier: ^0.0.32
|
||||||
version: 0.0.29(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
|
version: 0.0.32(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@nanostores/react':
|
'@nanostores/react':
|
||||||
specifier: ^0.7.3
|
specifier: ^0.7.3
|
||||||
version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
version: 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
||||||
@ -38,9 +35,12 @@ dependencies:
|
|||||||
'@roarr/browser-log-writer':
|
'@roarr/browser-log-writer':
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.0
|
version: 1.3.0
|
||||||
|
async-mutex:
|
||||||
|
specifier: ^0.5.0
|
||||||
|
version: 0.5.0
|
||||||
chakra-react-select:
|
chakra-react-select:
|
||||||
specifier: ^4.9.1
|
specifier: ^4.9.1
|
||||||
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
version: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
compare-versions:
|
compare-versions:
|
||||||
specifier: ^6.1.1
|
specifier: ^6.1.1
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
@ -71,6 +71,12 @@ dependencies:
|
|||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
|
lru-cache:
|
||||||
|
specifier: ^11.0.0
|
||||||
|
version: 11.0.0
|
||||||
|
nanoid:
|
||||||
|
specifier: ^5.0.7
|
||||||
|
version: 5.0.7
|
||||||
nanostores:
|
nanostores:
|
||||||
specifier: ^0.11.2
|
specifier: ^0.11.2
|
||||||
version: 0.11.2
|
version: 0.11.2
|
||||||
@ -113,9 +119,6 @@ dependencies:
|
|||||||
react-icons:
|
react-icons:
|
||||||
specifier: ^5.2.1
|
specifier: ^5.2.1
|
||||||
version: 5.2.1(react@18.3.1)
|
version: 5.2.1(react@18.3.1)
|
||||||
react-konva:
|
|
||||||
specifier: ^18.2.10
|
|
||||||
version: 18.2.10(konva@9.3.14)(react-dom@18.3.1)(react@18.3.1)
|
|
||||||
react-redux:
|
react-redux:
|
||||||
specifier: 9.1.2
|
specifier: 9.1.2
|
||||||
version: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
|
version: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
|
||||||
@ -155,15 +158,15 @@ dependencies:
|
|||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.7.5
|
specifier: ^4.7.5
|
||||||
version: 4.7.5
|
version: 4.7.5
|
||||||
|
stable-hash:
|
||||||
|
specifier: ^0.0.4
|
||||||
|
version: 0.0.4
|
||||||
use-debounce:
|
use-debounce:
|
||||||
specifier: ^10.0.2
|
specifier: ^10.0.2
|
||||||
version: 10.0.2(react@18.3.1)
|
version: 10.0.2(react@18.3.1)
|
||||||
use-device-pixel-ratio:
|
use-device-pixel-ratio:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2(react@18.3.1)
|
version: 1.1.2(react@18.3.1)
|
||||||
use-image:
|
|
||||||
specifier: ^1.1.1
|
|
||||||
version: 1.1.1(react-dom@18.3.1)(react@18.3.1)
|
|
||||||
uuid:
|
uuid:
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.0.0
|
version: 10.0.0
|
||||||
@ -1749,6 +1752,13 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.1
|
regenerator-runtime: 0.14.1
|
||||||
|
|
||||||
|
/@babel/runtime@7.25.4:
|
||||||
|
resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime: 0.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@babel/template@7.24.0:
|
/@babel/template@7.24.0:
|
||||||
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@ -1835,7 +1845,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -1851,7 +1861,7 @@ packages:
|
|||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1869,7 +1879,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1882,7 +1892,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1902,7 +1912,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1913,7 +1923,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1932,7 +1942,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@zag-js/focus-visible': 0.16.0
|
'@zag-js/focus-visible': 0.16.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -1955,7 +1965,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1974,7 +1984,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -1989,13 +1999,13 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.0)(react@18.3.1):
|
/@chakra-ui/css-reset@2.3.0(@emotion/react@11.13.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
resolution: {integrity: sha512-cQwwBy5O0jzvl0K7PLTLgp8ijqLPKyuEMiDXwYzl95seD3AoeuoCLyzZcJtVqaUZ573PiBdAbY/IlZcwDOItWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': '>=10.0.35'
|
'@emotion/react': '>=10.0.35'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2028,7 +2038,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2059,7 +2069,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2082,7 +2092,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2093,7 +2103,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2105,7 +2115,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2120,7 +2130,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2136,7 +2146,7 @@ packages:
|
|||||||
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
'@chakra-ui/react-children-utils': 2.0.6(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2161,7 +2171,7 @@ packages:
|
|||||||
'@chakra-ui/breakpoint-utils': 2.0.8
|
'@chakra-ui/breakpoint-utils': 2.0.8
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2186,7 +2196,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2213,7 +2223,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
'@chakra-ui/react-use-outside-click': 2.2.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@11.3.24)(react@18.3.1)
|
||||||
framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 11.3.24(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2234,7 +2244,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
aria-hidden: 1.2.4
|
aria-hidden: 1.2.4
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
@ -2263,7 +2273,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2287,7 +2297,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-controllable-state': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2309,7 +2319,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-focus-on-pointer-down': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
@ -2344,11 +2354,11 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/provider@2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1):
|
/@chakra-ui/provider@2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
resolution: {integrity: sha512-w0Tef5ZCJK1mlJorcSjItCSbyvVuqpvyWdxZiVQmE6fvSJR83wZof42ux0+sfWD+I7rHSfj+f9nzhNaEWClysw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2356,13 +2366,13 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
react-dom: '>=18'
|
react-dom: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1)
|
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
dev: false
|
dev: false
|
||||||
@ -2378,7 +2388,7 @@ packages:
|
|||||||
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
'@chakra-ui/react-types': 2.0.7(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@zag-js/focus-visible': 0.16.0
|
'@zag-js/focus-visible': 0.16.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
@ -2578,7 +2588,7 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/react@2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1):
|
/@chakra-ui/react@2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
resolution: {integrity: sha512-Hn0moyxxyCDKuR9ywYpqgX8dvjqwu9ArwpIb9wHNYjnODETjLwazgNIliCVBRcJvysGRiV51U2/JtJVrpeCjUQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2597,7 +2607,7 @@ packages:
|
|||||||
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/close-button': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/control-box': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/counter': 2.1.0(react@18.3.1)
|
'@chakra-ui/counter': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.0)(react@18.3.1)
|
'@chakra-ui/css-reset': 2.3.0(@emotion/react@11.13.3)(react@18.3.1)
|
||||||
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/editable': 3.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1)
|
'@chakra-ui/focus-lock': 2.1.0(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2616,7 +2626,7 @@ packages:
|
|||||||
'@chakra-ui/popper': 3.1.0(react@18.3.1)
|
'@chakra-ui/popper': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/progress': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/provider': 2.4.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/provider': 2.4.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/radio': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
'@chakra-ui/react-env': 3.1.0(react@18.3.1)
|
||||||
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/select': 2.1.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2628,7 +2638,7 @@ packages:
|
|||||||
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/stepper': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/switch': 2.1.2(@chakra-ui/system@2.6.2)(framer-motion@10.18.0)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/table': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/tabs': 3.0.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/tag': 3.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
@ -2640,8 +2650,8 @@ packages:
|
|||||||
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
'@chakra-ui/transition': 2.1.0(framer-motion@10.18.0)(react@18.3.1)
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/visually-hidden': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@ -2657,7 +2667,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2674,7 +2684,7 @@ packages:
|
|||||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-use-previous': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-previous': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2684,7 +2694,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2704,7 +2714,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-pan-event': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-size': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-size': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2715,7 +2725,7 @@ packages:
|
|||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2728,7 +2738,7 @@ packages:
|
|||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2741,7 +2751,7 @@ packages:
|
|||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2762,12 +2772,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/checkbox': 2.3.2(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@chakra-ui/system@2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1):
|
/@chakra-ui/system@2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
resolution: {integrity: sha512-EGtpoEjLrUu4W1fHD+a62XR+hzC5YfsWm+6lO0Kybcga3yYEij9beegO0jZgug27V+Rf7vns95VPVP6mFd/DEQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0
|
'@emotion/react': ^11.0.0
|
||||||
@ -2780,8 +2790,8 @@ packages:
|
|||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/theme-utils': 2.0.21
|
'@chakra-ui/theme-utils': 2.0.21
|
||||||
'@chakra-ui/utils': 2.0.15
|
'@chakra-ui/utils': 2.0.15
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-fast-compare: 3.2.2
|
react-fast-compare: 3.2.2
|
||||||
dev: false
|
dev: false
|
||||||
@ -2794,7 +2804,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2813,7 +2823,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-safe-layout-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2825,7 +2835,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icon': 3.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-context': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2837,7 +2847,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/form-control': 2.2.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -2888,7 +2898,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-update-effect': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
'@chakra-ui/theme': 3.3.1(@chakra-ui/styled-system@2.9.2)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -2911,7 +2921,7 @@ packages:
|
|||||||
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-event-listener': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
'@chakra-ui/react-use-merge-refs': 2.1.0(react@18.3.1)
|
||||||
'@chakra-ui/shared-utils': 2.0.5
|
'@chakra-ui/shared-utils': 2.0.5
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
@ -2954,7 +2964,7 @@ packages:
|
|||||||
'@chakra-ui/system': '>=2.0.0'
|
'@chakra-ui/system': '>=2.0.0'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -3037,10 +3047,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
|
resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/helper-module-imports': 7.24.7
|
'@babel/helper-module-imports': 7.24.7
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
'@emotion/hash': 0.9.2
|
'@emotion/hash': 0.9.2
|
||||||
'@emotion/memoize': 0.9.0
|
'@emotion/memoize': 0.9.0
|
||||||
'@emotion/serialize': 1.3.0
|
'@emotion/serialize': 1.3.1
|
||||||
babel-plugin-macros: 3.1.0
|
babel-plugin-macros: 3.1.0
|
||||||
convert-source-map: 1.9.0
|
convert-source-map: 1.9.0
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
@ -3128,8 +3138,8 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/react@11.13.0(@types/react@18.3.3)(react@18.3.1):
|
/@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==}
|
resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': '*'
|
'@types/react': '*'
|
||||||
react: '>=16.8.0'
|
react: '>=16.8.0'
|
||||||
@ -3137,10 +3147,10 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
'@emotion/babel-plugin': 11.12.0
|
'@emotion/babel-plugin': 11.12.0
|
||||||
'@emotion/cache': 11.13.1
|
'@emotion/cache': 11.13.1
|
||||||
'@emotion/serialize': 1.3.0
|
'@emotion/serialize': 1.3.1
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
'@emotion/weak-memoize': 0.4.0
|
'@emotion/weak-memoize': 0.4.0
|
||||||
@ -3161,12 +3171,12 @@ packages:
|
|||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/serialize@1.3.0:
|
/@emotion/serialize@1.3.1:
|
||||||
resolution: {integrity: sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==}
|
resolution: {integrity: sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/hash': 0.9.2
|
'@emotion/hash': 0.9.2
|
||||||
'@emotion/memoize': 0.9.0
|
'@emotion/memoize': 0.9.0
|
||||||
'@emotion/unitless': 0.9.0
|
'@emotion/unitless': 0.10.0
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
dev: false
|
dev: false
|
||||||
@ -3179,7 +3189,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/styled@11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1):
|
/@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
|
resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@emotion/react': ^11.0.0-rc.0
|
'@emotion/react': ^11.0.0-rc.0
|
||||||
@ -3189,11 +3199,11 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
'@emotion/babel-plugin': 11.12.0
|
'@emotion/babel-plugin': 11.12.0
|
||||||
'@emotion/is-prop-valid': 1.3.0
|
'@emotion/is-prop-valid': 1.3.0
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/serialize': 1.3.0
|
'@emotion/serialize': 1.3.1
|
||||||
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
'@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.3.1)
|
||||||
'@emotion/utils': 1.4.0
|
'@emotion/utils': 1.4.0
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
@ -3202,12 +3212,12 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/unitless@0.8.1:
|
/@emotion/unitless@0.10.0:
|
||||||
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/unitless@0.9.0:
|
/@emotion/unitless@0.8.1:
|
||||||
resolution: {integrity: sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==}
|
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1):
|
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1):
|
||||||
@ -3561,8 +3571,8 @@ packages:
|
|||||||
prettier: 3.3.3
|
prettier: 3.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@invoke-ai/ui-library@0.0.29(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
|
/@invoke-ai/ui-library@0.0.32(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.0.20)(@types/react@18.3.3)(i18next@23.12.2)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-7SYOaiEEKk9iHk0hg2R2yVxiuV3I1x6bDEv0R3Y2tCH/Aq5XDG2tR+d7SQAPqf5+za3S+qfNFjbjl7GvEMwqmA==}
|
resolution: {integrity: sha512-JxAoblrDu/cZ4ha9KO4ry5OWvyLUE1Dj28i+ciMaDNUpC/cN+IyiTbUBoFoPaoN5JP8Zpd/MYCcmF2qsziHDzg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@fontsource-variable/inter': ^5.0.16
|
'@fontsource-variable/inter': ^5.0.16
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
@ -3572,14 +3582,14 @@ packages:
|
|||||||
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/icons': 2.1.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/react': 2.8.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1)
|
'@chakra-ui/react': 2.8.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.3)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1)
|
||||||
'@chakra-ui/styled-system': 2.9.2
|
'@chakra-ui/styled-system': 2.9.2
|
||||||
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
'@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2)
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@emotion/styled': 11.13.0(@emotion/react@11.13.0)(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.3)(react@18.3.1)
|
||||||
'@fontsource-variable/inter': 5.0.20
|
'@fontsource-variable/inter': 5.0.20
|
||||||
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@18.3.1)
|
||||||
chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
chakra-react-select: 4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
framer-motion: 10.18.0(react-dom@18.3.1)(react@18.3.1)
|
||||||
lodash-es: 4.17.21
|
lodash-es: 4.17.21
|
||||||
nanostores: 0.11.2
|
nanostores: 0.11.2
|
||||||
@ -5202,12 +5212,6 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react-reconciler@0.28.8:
|
|
||||||
resolution: {integrity: sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g==}
|
|
||||||
dependencies:
|
|
||||||
'@types/react': 18.3.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@types/react-transition-group@4.4.10:
|
/@types/react-transition-group@4.4.10:
|
||||||
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5777,7 +5781,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/aria-query@5.3.0:
|
/aria-query@5.3.0:
|
||||||
@ -5903,6 +5907,12 @@ packages:
|
|||||||
tslib: 2.6.3
|
tslib: 2.6.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/async-mutex@0.5.0:
|
||||||
|
resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.6.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/attr-accept@2.2.2:
|
/attr-accept@2.2.2:
|
||||||
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
|
resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -6123,7 +6133,7 @@ packages:
|
|||||||
type-detect: 4.0.8
|
type-detect: 4.0.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
/chakra-react-select@4.9.1(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/layout@2.3.1)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@emotion/react@11.13.3)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==}
|
resolution: {integrity: sha512-jmgfN+S/wnTaCp3pW30GYDIZ5J8jWcT1gIbhpw6RdKV+atm/U4/sT+gaHOHHhRL8xeaYip+iI/m8MPGREkve0w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@chakra-ui/form-control': ^2.0.0
|
'@chakra-ui/form-control': ^2.0.0
|
||||||
@ -6143,8 +6153,8 @@ packages:
|
|||||||
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/media-query': 3.3.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1)
|
'@chakra-ui/menu': 2.2.1(@chakra-ui/system@2.6.2)(framer-motion@11.3.24)(react@18.3.1)
|
||||||
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
'@chakra-ui/spinner': 2.1.0(@chakra-ui/system@2.6.2)(react@18.3.1)
|
||||||
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.0)(@emotion/styled@11.13.0)(react@18.3.1)
|
'@chakra-ui/system': 2.6.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.3.1)
|
||||||
'@emotion/react': 11.13.0(@types/react@18.3.3)(react@18.3.1)
|
'@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
react-select: 5.8.0(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
|
||||||
@ -7569,7 +7579,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==}
|
resolution: {integrity: sha512-QFaHbhv9WPUeLYBDe/PAuLKJ4Dd9OPvKs9xZBr3yLXnUrDNaVXKu2baDBXe3naPY30hgHYSsf2JW4jzas2mDEQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/for-each@0.3.3:
|
/for-each@0.3.3:
|
||||||
@ -7608,7 +7618,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@emotion/is-prop-valid': 0.8.8
|
'@emotion/is-prop-valid': 0.8.8
|
||||||
dev: false
|
dev: false
|
||||||
@ -8384,15 +8394,6 @@ packages:
|
|||||||
set-function-name: 2.0.2
|
set-function-name: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/its-fine@1.2.5(react@18.3.1):
|
|
||||||
resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=18.0'
|
|
||||||
dependencies:
|
|
||||||
'@types/react-reconciler': 0.28.8
|
|
||||||
react: 18.3.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/jackspeak@2.3.6:
|
/jackspeak@2.3.6:
|
||||||
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@ -8710,6 +8711,11 @@ packages:
|
|||||||
engines: {node: 14 || >=16.14}
|
engines: {node: 14 || >=16.14}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/lru-cache@11.0.0:
|
||||||
|
resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==}
|
||||||
|
engines: {node: 20 || >=22}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lru-cache@5.1.1:
|
/lru-cache@5.1.1:
|
||||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8997,6 +9003,12 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/nanoid@5.0.7:
|
||||||
|
resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==}
|
||||||
|
engines: {node: ^18 || >=20}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/nanostores@0.11.2:
|
/nanostores@0.11.2:
|
||||||
resolution: {integrity: sha512-6bucNxMJA5rNV554WQl+MWGng0QVMzlRgpKTHHfIbVLrhQ+yRXBychV9ECGVuuUfCMQPjfIG9bj8oJFZ9hYP/Q==}
|
resolution: {integrity: sha512-6bucNxMJA5rNV554WQl+MWGng0QVMzlRgpKTHHfIbVLrhQ+yRXBychV9ECGVuuUfCMQPjfIG9bj8oJFZ9hYP/Q==}
|
||||||
engines: {node: ^18.0.0 || >=20.0.0}
|
engines: {node: ^18.0.0 || >=20.0.0}
|
||||||
@ -9614,7 +9626,7 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
react: ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@ -9709,7 +9721,7 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
focus-lock: 1.3.5
|
focus-lock: 1.3.5
|
||||||
prop-types: 15.8.1
|
prop-types: 15.8.1
|
||||||
@ -9771,7 +9783,7 @@ packages:
|
|||||||
react-native:
|
react-native:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.25.0
|
'@babel/runtime': 7.25.4
|
||||||
html-parse-stringify: 3.0.1
|
html-parse-stringify: 3.0.1
|
||||||
i18next: 23.12.2
|
i18next: 23.12.2
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
@ -9801,33 +9813,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/react-konva@18.2.10(konva@9.3.14)(react-dom@18.3.1)(react@18.3.1):
|
|
||||||
resolution: {integrity: sha512-ohcX1BJINL43m4ynjZ24MxFI1syjBdrXhqVxYVDw2rKgr3yuS0x/6m1Y2Z4sl4T/gKhfreBx8KHisd0XC6OT1g==}
|
|
||||||
peerDependencies:
|
|
||||||
konva: ^8.0.1 || ^7.2.5 || ^9.0.0
|
|
||||||
react: '>=18.0.0'
|
|
||||||
react-dom: '>=18.0.0'
|
|
||||||
dependencies:
|
|
||||||
'@types/react-reconciler': 0.28.8
|
|
||||||
its-fine: 1.2.5(react@18.3.1)
|
|
||||||
konva: 9.3.14
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
react-reconciler: 0.29.2(react@18.3.1)
|
|
||||||
scheduler: 0.23.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-reconciler@0.29.2(react@18.3.1):
|
|
||||||
resolution: {integrity: sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^18.3.1
|
|
||||||
dependencies:
|
|
||||||
loose-envify: 1.4.0
|
|
||||||
react: 18.3.1
|
|
||||||
scheduler: 0.23.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1):
|
/react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1):
|
||||||
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -9860,7 +9845,7 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1):
|
/react-remove-scroll@2.5.10(@types/react@18.3.3)(react@18.3.1):
|
||||||
@ -9877,7 +9862,7 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
|
react-remove-scroll-bar: 2.3.6(@types/react@18.3.3)(react@18.3.1)
|
||||||
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1)
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
|
use-callback-ref: 1.3.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
use-sidecar: 1.1.2(@types/react@18.3.3)(react@18.3.1)
|
||||||
dev: false
|
dev: false
|
||||||
@ -9927,7 +9912,7 @@ packages:
|
|||||||
get-nonce: 1.0.1
|
get-nonce: 1.0.1
|
||||||
invariant: 2.2.4
|
invariant: 2.2.4
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
|
/react-transition-group@4.4.5(react-dom@18.3.1)(react@18.3.1):
|
||||||
@ -10625,6 +10610,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/stable-hash@0.0.4:
|
||||||
|
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/stack-generator@2.0.10:
|
/stack-generator@2.0.10:
|
||||||
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -11063,6 +11052,10 @@ packages:
|
|||||||
/tslib@2.6.3:
|
/tslib@2.6.3:
|
||||||
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
|
||||||
|
|
||||||
|
/tslib@2.7.0:
|
||||||
|
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tsutils@3.21.0(typescript@5.5.4):
|
/tsutils@3.21.0(typescript@5.5.4):
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@ -11310,7 +11303,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-debounce@10.0.2(react@18.3.1):
|
/use-debounce@10.0.2(react@18.3.1):
|
||||||
@ -11330,16 +11323,6 @@ packages:
|
|||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-image@1.1.1(react-dom@18.3.1)(react@18.3.1):
|
|
||||||
resolution: {integrity: sha512-n4YO2k8AJG/BcDtxmBx8Aa+47kxY5m335dJiCQA5tTeVU4XdhrhqR6wT0WISRXwdMEOv5CSjqekDZkEMiiWaYQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16.8.0'
|
|
||||||
react-dom: '>=16.8.0'
|
|
||||||
dependencies:
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/use-isomorphic-layout-effect@1.1.2(@types/react@18.3.3)(react@18.3.1):
|
/use-isomorphic-layout-effect@1.1.2(@types/react@18.3.3)(react@18.3.1):
|
||||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -11366,7 +11349,7 @@ packages:
|
|||||||
'@types/react': 18.3.3
|
'@types/react': 18.3.3
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
tslib: 2.6.3
|
tslib: 2.7.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/use-sync-external-store@1.2.0(react@18.3.1):
|
/use-sync-external-store@1.2.0(react@18.3.1):
|
||||||
|
@ -80,6 +80,7 @@
|
|||||||
"aboutDesc": "Using Invoke for work? Check out:",
|
"aboutDesc": "Using Invoke for work? Check out:",
|
||||||
"aboutHeading": "Own Your Creative Power",
|
"aboutHeading": "Own Your Creative Power",
|
||||||
"accept": "Accept",
|
"accept": "Accept",
|
||||||
|
"apply": "Apply",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"advanced": "Advanced",
|
"advanced": "Advanced",
|
||||||
"ai": "ai",
|
"ai": "ai",
|
||||||
@ -115,6 +116,7 @@
|
|||||||
"githubLabel": "Github",
|
"githubLabel": "Github",
|
||||||
"goTo": "Go to",
|
"goTo": "Go to",
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
|
"loadingImage": "Loading Image",
|
||||||
"imageFailedToLoad": "Unable to Load Image",
|
"imageFailedToLoad": "Unable to Load Image",
|
||||||
"img2img": "Image To Image",
|
"img2img": "Image To Image",
|
||||||
"inpaint": "inpaint",
|
"inpaint": "inpaint",
|
||||||
@ -325,6 +327,10 @@
|
|||||||
"canceled": "Canceled",
|
"canceled": "Canceled",
|
||||||
"completedIn": "Completed in",
|
"completedIn": "Completed in",
|
||||||
"batch": "Batch",
|
"batch": "Batch",
|
||||||
|
"origin": "Origin",
|
||||||
|
"originCanvas": "Canvas",
|
||||||
|
"originWorkflows": "Workflows",
|
||||||
|
"originOther": "Other",
|
||||||
"batchFieldValues": "Batch Field Values",
|
"batchFieldValues": "Batch Field Values",
|
||||||
"item": "Item",
|
"item": "Item",
|
||||||
"session": "Session",
|
"session": "Session",
|
||||||
@ -1096,7 +1102,6 @@
|
|||||||
"confirmOnDelete": "Confirm On Delete",
|
"confirmOnDelete": "Confirm On Delete",
|
||||||
"developer": "Developer",
|
"developer": "Developer",
|
||||||
"displayInProgress": "Display Progress Images",
|
"displayInProgress": "Display Progress Images",
|
||||||
"enableImageDebugging": "Enable Image Debugging",
|
|
||||||
"enableInformationalPopovers": "Enable Informational Popovers",
|
"enableInformationalPopovers": "Enable Informational Popovers",
|
||||||
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
"informationalPopoversDisabled": "Informational Popovers Disabled",
|
||||||
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
"informationalPopoversDisabledDesc": "Informational popovers have been disabled. Enable them in Settings.",
|
||||||
@ -1563,7 +1568,7 @@
|
|||||||
"copyToClipboard": "Copy to Clipboard",
|
"copyToClipboard": "Copy to Clipboard",
|
||||||
"cursorPosition": "Cursor Position",
|
"cursorPosition": "Cursor Position",
|
||||||
"darkenOutsideSelection": "Darken Outside Selection",
|
"darkenOutsideSelection": "Darken Outside Selection",
|
||||||
"discardAll": "Discard All",
|
"discardAll": "Discard All & Cancel Pending Generations",
|
||||||
"discardCurrent": "Discard Current",
|
"discardCurrent": "Discard Current",
|
||||||
"downloadAsImage": "Download As Image",
|
"downloadAsImage": "Download As Image",
|
||||||
"enableMask": "Enable Mask",
|
"enableMask": "Enable Mask",
|
||||||
@ -1641,39 +1646,121 @@
|
|||||||
"storeNotInitialized": "Store is not initialized"
|
"storeNotInitialized": "Store is not initialized"
|
||||||
},
|
},
|
||||||
"controlLayers": {
|
"controlLayers": {
|
||||||
"deleteAll": "Delete All",
|
"clearHistory": "Clear History",
|
||||||
|
"generateMode": "Generate",
|
||||||
|
"generateModeDesc": "Create individual images. Generated images are added directly to the gallery.",
|
||||||
|
"composeMode": "Compose",
|
||||||
|
"composeModeDesc": "Compose your work iterative. Generated images are added back to the canvas.",
|
||||||
|
"autoSave": "Auto-save to Gallery",
|
||||||
|
"resetCanvas": "Reset Canvas",
|
||||||
|
"resetAll": "Reset All",
|
||||||
|
"clearCaches": "Clear Caches",
|
||||||
|
"recalculateRects": "Recalculate Rects",
|
||||||
|
"clipToBbox": "Clip Strokes to Bbox",
|
||||||
"addLayer": "Add Layer",
|
"addLayer": "Add Layer",
|
||||||
|
"duplicate": "Duplicate",
|
||||||
"moveToFront": "Move to Front",
|
"moveToFront": "Move to Front",
|
||||||
"moveToBack": "Move to Back",
|
"moveToBack": "Move to Back",
|
||||||
"moveForward": "Move Forward",
|
"moveForward": "Move Forward",
|
||||||
"moveBackward": "Move Backward",
|
"moveBackward": "Move Backward",
|
||||||
"brushSize": "Brush Size",
|
"brushSize": "Brush Size",
|
||||||
|
"width": "Width",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"resetView": "Reset View",
|
||||||
"controlLayers": "Control Layers",
|
"controlLayers": "Control Layers",
|
||||||
"globalMaskOpacity": "Global Mask Opacity",
|
"globalMaskOpacity": "Global Mask Opacity",
|
||||||
"autoNegative": "Auto Negative",
|
"autoNegative": "Auto Negative",
|
||||||
|
"enableAutoNegative": "Enable Auto Negative",
|
||||||
|
"disableAutoNegative": "Disable Auto Negative",
|
||||||
"deletePrompt": "Delete Prompt",
|
"deletePrompt": "Delete Prompt",
|
||||||
"resetRegion": "Reset Region",
|
"resetRegion": "Reset Region",
|
||||||
"debugLayers": "Debug Layers",
|
"debugLayers": "Debug Layers",
|
||||||
"rectangle": "Rectangle",
|
"rectangle": "Rectangle",
|
||||||
"maskPreviewColor": "Mask Preview Color",
|
"maskFill": "Mask Fill",
|
||||||
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
"addPositivePrompt": "Add $t(common.positivePrompt)",
|
||||||
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
"addNegativePrompt": "Add $t(common.negativePrompt)",
|
||||||
"addIPAdapter": "Add $t(common.ipAdapter)",
|
"addIPAdapter": "Add $t(common.ipAdapter)",
|
||||||
"regionalGuidance": "Regional Guidance",
|
|
||||||
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
"regionalGuidanceLayer": "$t(controlLayers.regionalGuidance) $t(unifiedCanvas.layer)",
|
||||||
|
"raster": "Raster",
|
||||||
|
"rasterLayer_one": "Raster Layer",
|
||||||
|
"controlLayer_one": "Control Layer",
|
||||||
|
"inpaintMask_one": "Inpaint Mask",
|
||||||
|
"regionalGuidance_one": "Regional Guidance",
|
||||||
|
"ipAdapter_one": "IP Adapter",
|
||||||
|
"rasterLayer_other": "Raster Layers",
|
||||||
|
"controlLayer_other": "Control Layers",
|
||||||
|
"inpaintMask_other": "Inpaint Masks",
|
||||||
|
"regionalGuidance_other": "Regional Guidance",
|
||||||
|
"ipAdapter_other": "IP Adapters",
|
||||||
"opacity": "Opacity",
|
"opacity": "Opacity",
|
||||||
|
"regionalGuidance_withCount_hidden": "Regional Guidance ({{count}} hidden)",
|
||||||
|
"controlAdapters_withCount_hidden": "Control Adapters ({{count}} hidden)",
|
||||||
|
"controlLayers_withCount_hidden": "Control Layers ({{count}} hidden)",
|
||||||
|
"rasterLayers_withCount_hidden": "Raster Layers ({{count}} hidden)",
|
||||||
|
"ipAdapters_withCount_hidden": "IP Adapters ({{count}} hidden)",
|
||||||
|
"inpaintMasks_withCount_hidden": "Inpaint Masks ({{count}} hidden)",
|
||||||
|
"regionalGuidance_withCount_visible": "Regional Guidance ({{count}})",
|
||||||
|
"controlAdapters_withCount_visible": "Control Adapters ({{count}})",
|
||||||
|
"controlLayers_withCount_visible": "Control Layers ({{count}})",
|
||||||
|
"rasterLayers_withCount_visible": "Raster Layers ({{count}})",
|
||||||
|
"ipAdapters_withCount_visible": "IP Adapters ({{count}})",
|
||||||
|
"inpaintMasks_withCount_visible": "Inpaint Masks ({{count}})",
|
||||||
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
||||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||||
"globalInitialImage": "Global Initial Image",
|
"globalInitialImage": "Global Initial Image",
|
||||||
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
"globalInitialImageLayer": "$t(controlLayers.globalInitialImage) $t(unifiedCanvas.layer)",
|
||||||
|
"layer": "Layer",
|
||||||
"opacityFilter": "Opacity Filter",
|
"opacityFilter": "Opacity Filter",
|
||||||
"clearProcessor": "Clear Processor",
|
"clearProcessor": "Clear Processor",
|
||||||
"resetProcessor": "Reset Processor to Defaults",
|
"resetProcessor": "Reset Processor to Defaults",
|
||||||
"noLayersAdded": "No Layers Added",
|
"noLayersAdded": "No Layers Added",
|
||||||
"layers_one": "Layer",
|
"layers_one": "Layer",
|
||||||
"layers_other": "Layers"
|
"layers_other": "Layers",
|
||||||
|
"objects_zero": "empty",
|
||||||
|
"objects_one": "{{count}} object",
|
||||||
|
"objects_other": "{{count}} objects",
|
||||||
|
"convertToControlLayer": "Convert to Control Layer",
|
||||||
|
"convertToRasterLayer": "Convert to Raster Layer",
|
||||||
|
"transparency": "Transparency",
|
||||||
|
"enableTransparencyEffect": "Enable Transparency Effect",
|
||||||
|
"disableTransparencyEffect": "Disable Transparency Effect",
|
||||||
|
"hidingType": "Hiding {{type}}",
|
||||||
|
"showingType": "Showing {{type}}",
|
||||||
|
"dynamicGrid": "Dynamic Grid",
|
||||||
|
"logDebugInfo": "Log Debug Info",
|
||||||
|
"locked": "Locked",
|
||||||
|
"unlocked": "Unlocked",
|
||||||
|
"deleteSelected": "Delete Selected",
|
||||||
|
"deleteAll": "Delete All",
|
||||||
|
"fill": {
|
||||||
|
"fillStyle": "Fill Style",
|
||||||
|
"solid": "Solid",
|
||||||
|
"grid": "Grid",
|
||||||
|
"crosshatch": "Crosshatch",
|
||||||
|
"vertical": "Vertical",
|
||||||
|
"horizontal": "Horizontal",
|
||||||
|
"diagonal": "Diagonal"
|
||||||
|
},
|
||||||
|
"tool": {
|
||||||
|
"brush": "Brush",
|
||||||
|
"eraser": "Eraser",
|
||||||
|
"rectangle": "Rectangle",
|
||||||
|
"bbox": "Bbox",
|
||||||
|
"move": "Move",
|
||||||
|
"view": "View",
|
||||||
|
"transform": "Transform",
|
||||||
|
"colorPicker": "Color Picker"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"filter": "Filter",
|
||||||
|
"filters": "Filters",
|
||||||
|
"filterType": "Filter Type",
|
||||||
|
"preview": "Preview",
|
||||||
|
"apply": "Apply",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"upscaling": {
|
"upscaling": {
|
||||||
"upscale": "Upscale",
|
"upscale": "Upscale",
|
||||||
@ -1761,5 +1848,30 @@
|
|||||||
"upscaling": "Upscaling",
|
"upscaling": "Upscaling",
|
||||||
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
"upscalingTab": "$t(ui.tabs.upscaling) $t(common.tab)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"enableLogging": "Enable Logging",
|
||||||
|
"logLevel": {
|
||||||
|
"logLevel": "Log Level",
|
||||||
|
"trace": "Trace",
|
||||||
|
"debug": "Debug",
|
||||||
|
"info": "Info",
|
||||||
|
"warn": "Warn",
|
||||||
|
"error": "Error",
|
||||||
|
"fatal": "Fatal"
|
||||||
|
},
|
||||||
|
"logNamespaces": {
|
||||||
|
"logNamespaces": "Log Namespaces",
|
||||||
|
"gallery": "Gallery",
|
||||||
|
"models": "Models",
|
||||||
|
"config": "Config",
|
||||||
|
"canvas": "Canvas",
|
||||||
|
"generation": "Generation",
|
||||||
|
"workflows": "Workflows",
|
||||||
|
"system": "System",
|
||||||
|
"events": "Events",
|
||||||
|
"queue": "Queue",
|
||||||
|
"metadata": "Metadata"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ async function generateTypes(schema) {
|
|||||||
process.stdout.write(`\nOK!\r\n`);
|
process.stdout.write(`\nOK!\r\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
function main() {
|
||||||
const encoding = 'utf-8';
|
const encoding = 'utf-8';
|
||||||
|
|
||||||
if (process.stdin.isTTY) {
|
if (process.stdin.isTTY) {
|
||||||
|
@ -6,6 +6,7 @@ import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/ap
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { PartialAppConfig } from 'app/types/invokeai';
|
import type { PartialAppConfig } from 'app/types/invokeai';
|
||||||
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
import ImageUploadOverlay from 'common/components/ImageUploadOverlay';
|
||||||
|
import { useScopeFocusWatcher } from 'common/hooks/interactionScopes';
|
||||||
import { useClearStorage } from 'common/hooks/useClearStorage';
|
import { useClearStorage } from 'common/hooks/useClearStorage';
|
||||||
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
import { useFullscreenDropzone } from 'common/hooks/useFullscreenDropzone';
|
||||||
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
import { useGlobalHotkeys } from 'common/hooks/useGlobalHotkeys';
|
||||||
@ -13,12 +14,13 @@ import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardMo
|
|||||||
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
|
||||||
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
|
||||||
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
import { useStarterModelsToast } from 'features/modelManagerV2/hooks/useStarterModelsToast';
|
||||||
|
import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/ClearQueueConfirmationAlertDialog';
|
||||||
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
|
import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal';
|
||||||
import { configChanged } from 'features/system/store/configSlice';
|
import { configChanged } from 'features/system/store/configSlice';
|
||||||
import { languageSelector } from 'features/system/store/systemSelectors';
|
import { selectLanguage } from 'features/system/store/systemSelectors';
|
||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import { AppContent } from 'features/ui/components/AppContent';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import i18n from 'i18n';
|
import i18n from 'i18n';
|
||||||
@ -39,11 +41,11 @@ interface Props {
|
|||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
selectedWorkflowId?: string;
|
selectedWorkflowId?: string;
|
||||||
destination?: InvokeTabName | undefined;
|
destination?: TabName | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, destination }: Props) => {
|
const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, destination }: Props) => {
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(selectLanguage);
|
||||||
const logger = useLogger('system');
|
const logger = useLogger('system');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const clearStorage = useClearStorage();
|
const clearStorage = useClearStorage();
|
||||||
@ -93,6 +95,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
|
|
||||||
useStarterModelsToast();
|
useStarterModelsToast();
|
||||||
useSyncQueueStatus();
|
useSyncQueueStatus();
|
||||||
|
useScopeFocusWatcher();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
||||||
@ -105,7 +108,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
{...dropzone.getRootProps()}
|
{...dropzone.getRootProps()}
|
||||||
>
|
>
|
||||||
<input {...dropzone.getInputProps()} />
|
<input {...dropzone.getInputProps()} />
|
||||||
<InvokeTabs />
|
<AppContent />
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{dropzone.isDragActive && isHandlingUpload && (
|
{dropzone.isDragActive && isHandlingUpload && (
|
||||||
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
||||||
@ -116,6 +119,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage, selectedWorkflowId, desti
|
|||||||
<ChangeBoardModal />
|
<ChangeBoardModal />
|
||||||
<DynamicPromptsModal />
|
<DynamicPromptsModal />
|
||||||
<StylePresetModal />
|
<StylePresetModal />
|
||||||
|
<ClearQueueConfirmationsAlertDialog />
|
||||||
<PreselectedImage selectedImage={selectedImage} />
|
<PreselectedImage selectedImage={selectedImage} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
import { Button, Flex, Heading, Image, Link, Text } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import newGithubIssueUrl from 'new-github-issue-url';
|
import newGithubIssueUrl from 'new-github-issue-url';
|
||||||
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
import InvokeLogoYellow from 'public/assets/images/invoke-symbol-ylw-lrg.svg';
|
||||||
@ -13,9 +15,11 @@ type Props = {
|
|||||||
resetErrorBoundary: () => void;
|
resetErrorBoundary: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
|
||||||
|
|
||||||
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isLocal = useAppSelector((s) => s.config.isLocal);
|
const isLocal = useAppSelector(selectIsLocal);
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
const text = JSON.stringify(serializeError(error), null, 2);
|
const text = JSON.stringify(serializeError(error), null, 2);
|
||||||
|
@ -19,7 +19,7 @@ import type { PartialAppConfig } from 'app/types/invokeai';
|
|||||||
import Loading from 'common/components/Loading/Loading';
|
import Loading from 'common/components/Loading/Loading';
|
||||||
import AppDndContext from 'features/dnd/components/AppDndContext';
|
import AppDndContext from 'features/dnd/components/AppDndContext';
|
||||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
import type { PropsWithChildren, ReactNode } from 'react';
|
import type { PropsWithChildren, ReactNode } from 'react';
|
||||||
import React, { lazy, memo, useEffect, useMemo } from 'react';
|
import React, { lazy, memo, useEffect, useMemo } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
@ -45,7 +45,7 @@ interface Props extends PropsWithChildren {
|
|||||||
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
|
||||||
};
|
};
|
||||||
selectedWorkflowId?: string;
|
selectedWorkflowId?: string;
|
||||||
destination?: InvokeTabName;
|
destination?: TabName;
|
||||||
customStarUi?: CustomStarUi;
|
customStarUi?: CustomStarUi;
|
||||||
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
socketOptions?: Partial<ManagerOptions & SocketOptions>;
|
||||||
isDebugging?: boolean;
|
isDebugging?: boolean;
|
||||||
|
@ -2,7 +2,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { $authToken } from 'app/store/nanostores/authToken';
|
import { $authToken } from 'app/store/nanostores/authToken';
|
||||||
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
import { $baseUrl } from 'app/store/nanostores/baseUrl';
|
||||||
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
import { $isDebugging } from 'app/store/nanostores/isDebugging';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppStore } from 'app/store/nanostores/store';
|
||||||
import type { MapStore } from 'nanostores';
|
import type { MapStore } from 'nanostores';
|
||||||
import { atom, map } from 'nanostores';
|
import { atom, map } from 'nanostores';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
@ -18,14 +18,19 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||||
|
|
||||||
|
export const $socket = atom<AppSocket | null>(null);
|
||||||
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
|
export const $socketOptions = map<Partial<ManagerOptions & SocketOptions>>({});
|
||||||
|
|
||||||
const $isSocketInitialized = atom<boolean>(false);
|
const $isSocketInitialized = atom<boolean>(false);
|
||||||
|
export const $isConnected = atom<boolean>(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the socket.io connection and sets up event listeners.
|
* Initializes the socket.io connection and sets up event listeners.
|
||||||
*/
|
*/
|
||||||
export const useSocketIO = () => {
|
export const useSocketIO = () => {
|
||||||
const dispatch = useAppDispatch();
|
const { dispatch, getState } = useAppStore();
|
||||||
const baseUrl = useStore($baseUrl);
|
const baseUrl = useStore($baseUrl);
|
||||||
const authToken = useStore($authToken);
|
const authToken = useStore($authToken);
|
||||||
const addlSocketOptions = useStore($socketOptions);
|
const addlSocketOptions = useStore($socketOptions);
|
||||||
@ -61,8 +66,9 @@ export const useSocketIO = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(socketUrl, socketOptions);
|
const socket: AppSocket = io(socketUrl, socketOptions);
|
||||||
setEventListeners({ dispatch, socket });
|
$socket.set(socket);
|
||||||
|
setEventListeners({ socket, dispatch, getState, setIsConnected: $isConnected.set });
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
|
||||||
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
|
if ($isDebugging.get() || import.meta.env.MODE === 'development') {
|
||||||
@ -84,5 +90,5 @@ export const useSocketIO = () => {
|
|||||||
socket.disconnect();
|
socket.disconnect();
|
||||||
$isSocketInitialized.set(false);
|
$isSocketInitialized.set(false);
|
||||||
};
|
};
|
||||||
}, [dispatch, socketOptions, socketUrl]);
|
}, [dispatch, getState, socketOptions, socketUrl]);
|
||||||
};
|
};
|
||||||
|
@ -15,21 +15,21 @@ export const BASE_CONTEXT = {};
|
|||||||
|
|
||||||
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
||||||
|
|
||||||
export type LoggerNamespace =
|
export const zLogNamespace = z.enum([
|
||||||
| 'images'
|
'canvas',
|
||||||
| 'models'
|
'config',
|
||||||
| 'config'
|
'events',
|
||||||
| 'canvas'
|
'gallery',
|
||||||
| 'generation'
|
'generation',
|
||||||
| 'nodes'
|
'metadata',
|
||||||
| 'system'
|
'models',
|
||||||
| 'socketio'
|
'system',
|
||||||
| 'session'
|
'queue',
|
||||||
| 'queue'
|
'workflows',
|
||||||
| 'dnd'
|
]);
|
||||||
| 'controlLayers';
|
export type LogNamespace = z.infer<typeof zLogNamespace>;
|
||||||
|
|
||||||
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });
|
export const logger = (namespace: LogNamespace) => $logger.get().child({ namespace });
|
||||||
|
|
||||||
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
||||||
export type LogLevel = z.infer<typeof zLogLevel>;
|
export type LogLevel = z.infer<typeof zLogLevel>;
|
||||||
|
@ -1,29 +1,41 @@
|
|||||||
import { createLogWriter } from '@roarr/browser-log-writer';
|
import { createLogWriter } from '@roarr/browser-log-writer';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
selectSystemLogIsEnabled,
|
||||||
|
selectSystemLogLevel,
|
||||||
|
selectSystemLogNamespaces,
|
||||||
|
} from 'features/system/store/systemSlice';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { ROARR, Roarr } from 'roarr';
|
import { ROARR, Roarr } from 'roarr';
|
||||||
|
|
||||||
import type { LoggerNamespace } from './logger';
|
import type { LogNamespace } from './logger';
|
||||||
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
|
||||||
|
|
||||||
export const useLogger = (namespace: LoggerNamespace) => {
|
export const useLogger = (namespace: LogNamespace) => {
|
||||||
const consoleLogLevel = useAppSelector((s) => s.system.consoleLogLevel);
|
const logLevel = useAppSelector(selectSystemLogLevel);
|
||||||
const shouldLogToConsole = useAppSelector((s) => s.system.shouldLogToConsole);
|
const logNamespaces = useAppSelector(selectSystemLogNamespaces);
|
||||||
|
const logIsEnabled = useAppSelector(selectSystemLogIsEnabled);
|
||||||
|
|
||||||
// The provided Roarr browser log writer uses localStorage to config logging to console
|
// The provided Roarr browser log writer uses localStorage to config logging to console
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shouldLogToConsole) {
|
if (logIsEnabled) {
|
||||||
// Enable console log output
|
// Enable console log output
|
||||||
localStorage.setItem('ROARR_LOG', 'true');
|
localStorage.setItem('ROARR_LOG', 'true');
|
||||||
|
|
||||||
// Use a filter to show only logs of the given level
|
// Use a filter to show only logs of the given level
|
||||||
localStorage.setItem('ROARR_FILTER', `context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`);
|
let filter = `context.logLevel:>=${LOG_LEVEL_MAP[logLevel]}`;
|
||||||
|
if (logNamespaces.length > 0) {
|
||||||
|
filter += ` AND (${logNamespaces.map((ns) => `context.namespace:${ns}`).join(' OR ')})`;
|
||||||
|
} else {
|
||||||
|
filter += ' AND context.namespace:undefined';
|
||||||
|
}
|
||||||
|
localStorage.setItem('ROARR_FILTER', filter);
|
||||||
} else {
|
} else {
|
||||||
// Disable console log output
|
// Disable console log output
|
||||||
localStorage.setItem('ROARR_LOG', 'false');
|
localStorage.setItem('ROARR_LOG', 'false');
|
||||||
}
|
}
|
||||||
ROARR.write = createLogWriter();
|
ROARR.write = createLogWriter();
|
||||||
}, [consoleLogLevel, shouldLogToConsole]);
|
}, [logLevel, logIsEnabled, logNamespaces]);
|
||||||
|
|
||||||
// Update the module-scoped logger context as needed
|
// Update the module-scoped logger context as needed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
|
|
||||||
export const enqueueRequested = createAction<{
|
export const enqueueRequested = createAction<{
|
||||||
tabName: InvokeTabName;
|
tabName: TabName;
|
||||||
prepend: boolean;
|
prepend: boolean;
|
||||||
}>('app/enqueueRequested');
|
}>('app/enqueueRequested');
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export const STORAGE_PREFIX = '@@invokeai-';
|
export const STORAGE_PREFIX = '@@invokeai-';
|
||||||
export const EMPTY_ARRAY = [];
|
export const EMPTY_ARRAY = [];
|
||||||
|
export const EMPTY_OBJECT = {};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
||||||
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
||||||
|
import type { RootState } from 'app/store/store';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,3 +20,5 @@ export const getSelectorsOptions: GetSelectorsOptions = {
|
|||||||
argsMemoize: lruMemoize,
|
argsMemoize: lruMemoize,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createMemoizedAppSelector = createMemoizedSelector.withTypes<RootState>();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { PersistError, RehydrateError } from 'redux-remember';
|
import { PersistError, RehydrateError } from 'redux-remember';
|
||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
|
|
||||||
@ -41,6 +40,6 @@ export const errorHandler = (err: PersistError | RehydrateError) => {
|
|||||||
} else if (err instanceof RehydrateError) {
|
} else if (err instanceof RehydrateError) {
|
||||||
log.error({ error: serializeError(err) }, 'Problem rehydrating state');
|
log.error({ error: serializeError(err) }, 'Problem rehydrating state');
|
||||||
} else {
|
} else {
|
||||||
log.error({ error: parseify(err) }, 'Problem in persistence layer');
|
log.error({ error: serializeError(err) }, 'Problem in persistence layer');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import type { UnknownAction } from '@reduxjs/toolkit';
|
import type { UnknownAction } from '@reduxjs/toolkit';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
|
import { isAnyGraphBuilt } from 'features/nodes/store/actions';
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
import type { Graph } from 'services/api/types';
|
import type { Graph } from 'services/api/types';
|
||||||
import { socketGeneratorProgress } from 'services/events/actions';
|
|
||||||
|
|
||||||
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
||||||
if (isAnyGraphBuilt(action)) {
|
if (isAnyGraphBuilt(action)) {
|
||||||
@ -24,13 +22,5 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socketGeneratorProgress.match(action)) {
|
|
||||||
const sanitized = deepClone(action);
|
|
||||||
if (sanitized.payload.data.progress_image) {
|
|
||||||
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
|
|
||||||
}
|
|
||||||
return sanitized;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { TypedStartListening } from '@reduxjs/toolkit';
|
import type { TypedStartListening } from '@reduxjs/toolkit';
|
||||||
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
import { createListenerMiddleware } from '@reduxjs/toolkit';
|
||||||
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
import { addAdHocPostProcessingRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/addAdHocPostProcessingRequestedListener';
|
||||||
import { addCommitStagingAreaImageListener } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
|
import { addStagingListeners } from 'app/store/middleware/listenerMiddleware/listeners/addCommitStagingAreaImageListener';
|
||||||
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
import { addAnyEnqueuedListener } from 'app/store/middleware/listenerMiddleware/listeners/anyEnqueued';
|
||||||
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
|
import { addAppConfigReceivedListener } from 'app/store/middleware/listenerMiddleware/listeners/appConfigReceived';
|
||||||
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
import { addAppStartedListener } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
|
||||||
@ -9,17 +9,6 @@ import { addBatchEnqueuedListener } from 'app/store/middleware/listenerMiddlewar
|
|||||||
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
import { addDeleteBoardAndImagesFulfilledListener } from 'app/store/middleware/listenerMiddleware/listeners/boardAndImagesDeleted';
|
||||||
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
import { addBoardIdSelectedListener } from 'app/store/middleware/listenerMiddleware/listeners/boardIdSelected';
|
||||||
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
import { addBulkDownloadListeners } from 'app/store/middleware/listenerMiddleware/listeners/bulkDownload';
|
||||||
import { addCanvasCopiedToClipboardListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasCopiedToClipboard';
|
|
||||||
import { addCanvasDownloadedAsImageListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasDownloadedAsImage';
|
|
||||||
import { addCanvasImageToControlNetListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasImageToControlNet';
|
|
||||||
import { addCanvasMaskSavedToGalleryListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMaskSavedToGallery';
|
|
||||||
import { addCanvasMaskToControlNetListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMaskToControlNet';
|
|
||||||
import { addCanvasMergedListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasMerged';
|
|
||||||
import { addCanvasSavedToGalleryListener } from 'app/store/middleware/listenerMiddleware/listeners/canvasSavedToGallery';
|
|
||||||
import { addControlAdapterPreprocessor } from 'app/store/middleware/listenerMiddleware/listeners/controlAdapterPreprocessor';
|
|
||||||
import { addControlNetAutoProcessListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetAutoProcess';
|
|
||||||
import { addControlNetImageProcessedListener } from 'app/store/middleware/listenerMiddleware/listeners/controlNetImageProcessed';
|
|
||||||
import { addEnqueueRequestedCanvasListener } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedCanvas';
|
|
||||||
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
import { addEnqueueRequestedLinear } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear';
|
||||||
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
import { addEnqueueRequestedNodes } from 'app/store/middleware/listenerMiddleware/listeners/enqueueRequestedNodes';
|
||||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
@ -37,16 +26,7 @@ import { addModelSelectedListener } from 'app/store/middleware/listenerMiddlewar
|
|||||||
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
import { addModelsLoadedListener } from 'app/store/middleware/listenerMiddleware/listeners/modelsLoaded';
|
||||||
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
import { addDynamicPromptsListener } from 'app/store/middleware/listenerMiddleware/listeners/promptChanged';
|
||||||
import { addSetDefaultSettingsListener } from 'app/store/middleware/listenerMiddleware/listeners/setDefaultSettings';
|
import { addSetDefaultSettingsListener } from 'app/store/middleware/listenerMiddleware/listeners/setDefaultSettings';
|
||||||
import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketConnected';
|
import { addSocketConnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketConnected';
|
||||||
import { addSocketDisconnectedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketDisconnected';
|
|
||||||
import { addGeneratorProgressEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketGeneratorProgress';
|
|
||||||
import { addInvocationCompleteEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationComplete';
|
|
||||||
import { addInvocationErrorEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationError';
|
|
||||||
import { addInvocationStartedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketInvocationStarted';
|
|
||||||
import { addModelInstallEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelInstall';
|
|
||||||
import { addModelLoadEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketModelLoad';
|
|
||||||
import { addSocketQueueItemStatusChangedEventListener } from 'app/store/middleware/listenerMiddleware/listeners/socketio/socketQueueItemStatusChanged';
|
|
||||||
import { addStagingAreaImageSavedListener } from 'app/store/middleware/listenerMiddleware/listeners/stagingAreaImageSaved';
|
|
||||||
import { addUpdateAllNodesRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested';
|
import { addUpdateAllNodesRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/updateAllNodesRequested';
|
||||||
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
|
import { addWorkflowLoadRequestedListener } from 'app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
@ -83,7 +63,6 @@ addGalleryImageClickedListener(startAppListening);
|
|||||||
addGalleryOffsetChangedListener(startAppListening);
|
addGalleryOffsetChangedListener(startAppListening);
|
||||||
|
|
||||||
// User Invoked
|
// User Invoked
|
||||||
addEnqueueRequestedCanvasListener(startAppListening);
|
|
||||||
addEnqueueRequestedNodes(startAppListening);
|
addEnqueueRequestedNodes(startAppListening);
|
||||||
addEnqueueRequestedLinear(startAppListening);
|
addEnqueueRequestedLinear(startAppListening);
|
||||||
addEnqueueRequestedUpscale(startAppListening);
|
addEnqueueRequestedUpscale(startAppListening);
|
||||||
@ -91,31 +70,22 @@ addAnyEnqueuedListener(startAppListening);
|
|||||||
addBatchEnqueuedListener(startAppListening);
|
addBatchEnqueuedListener(startAppListening);
|
||||||
|
|
||||||
// Canvas actions
|
// Canvas actions
|
||||||
addCanvasSavedToGalleryListener(startAppListening);
|
// addCanvasSavedToGalleryListener(startAppListening);
|
||||||
addCanvasMaskSavedToGalleryListener(startAppListening);
|
// addCanvasMaskSavedToGalleryListener(startAppListening);
|
||||||
addCanvasImageToControlNetListener(startAppListening);
|
// addCanvasImageToControlNetListener(startAppListening);
|
||||||
addCanvasMaskToControlNetListener(startAppListening);
|
// addCanvasMaskToControlNetListener(startAppListening);
|
||||||
addCanvasDownloadedAsImageListener(startAppListening);
|
// addCanvasDownloadedAsImageListener(startAppListening);
|
||||||
addCanvasCopiedToClipboardListener(startAppListening);
|
// addCanvasCopiedToClipboardListener(startAppListening);
|
||||||
addCanvasMergedListener(startAppListening);
|
// addCanvasMergedListener(startAppListening);
|
||||||
addStagingAreaImageSavedListener(startAppListening);
|
// addStagingAreaImageSavedListener(startAppListening);
|
||||||
addCommitStagingAreaImageListener(startAppListening);
|
// addCommitStagingAreaImageListener(startAppListening);
|
||||||
|
addStagingListeners(startAppListening);
|
||||||
|
|
||||||
// Socket.IO
|
// Socket.IO
|
||||||
addGeneratorProgressEventListener(startAppListening);
|
|
||||||
addInvocationCompleteEventListener(startAppListening);
|
|
||||||
addInvocationErrorEventListener(startAppListening);
|
|
||||||
addInvocationStartedEventListener(startAppListening);
|
|
||||||
addSocketConnectedEventListener(startAppListening);
|
addSocketConnectedEventListener(startAppListening);
|
||||||
addSocketDisconnectedEventListener(startAppListening);
|
|
||||||
addModelLoadEventListener(startAppListening);
|
|
||||||
addModelInstallEventListener(startAppListening);
|
|
||||||
addSocketQueueItemStatusChangedEventListener(startAppListening);
|
|
||||||
addBulkDownloadListeners(startAppListening);
|
|
||||||
|
|
||||||
// ControlNet
|
// Gallery bulk download
|
||||||
addControlNetImageProcessedListener(startAppListening);
|
addBulkDownloadListeners(startAppListening);
|
||||||
addControlNetAutoProcessListener(startAppListening);
|
|
||||||
|
|
||||||
// Boards
|
// Boards
|
||||||
addImageAddedToBoardFulfilledListener(startAppListening);
|
addImageAddedToBoardFulfilledListener(startAppListening);
|
||||||
@ -148,4 +118,4 @@ addAdHocPostProcessingRequestedListener(startAppListening);
|
|||||||
addDynamicPromptsListener(startAppListening);
|
addDynamicPromptsListener(startAppListening);
|
||||||
|
|
||||||
addSetDefaultSettingsListener(startAppListening);
|
addSetDefaultSettingsListener(startAppListening);
|
||||||
addControlAdapterPreprocessor(startAppListening);
|
// addControlAdapterPreprocessor(startAppListening);
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
import type { SerializableObject } from 'common/types';
|
||||||
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
import { buildAdHocPostProcessingGraph } from 'features/nodes/util/graph/buildAdHocPostProcessingGraph';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
|
const log = logger('queue');
|
||||||
|
|
||||||
export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`);
|
export const adHocPostProcessingRequested = createAction<{ imageDTO: ImageDTO }>(`upscaling/postProcessingRequested`);
|
||||||
|
|
||||||
export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => {
|
export const addAdHocPostProcessingRequestedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: adHocPostProcessingRequested,
|
actionCreator: adHocPostProcessingRequested,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const log = logger('session');
|
|
||||||
|
|
||||||
const { imageDTO } = action.payload;
|
const { imageDTO } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
@ -39,9 +39,9 @@ export const addAdHocPostProcessingRequestedListener = (startAppListening: AppSt
|
|||||||
|
|
||||||
const enqueueResult = await req.unwrap();
|
const enqueueResult = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
log.debug({ enqueueResult } as SerializableObject, t('queue.graphQueued'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
log.error({ enqueueBatchArg } as SerializableObject, t('queue.graphFailedToQueue'));
|
||||||
|
|
||||||
if (error instanceof Object && 'status' in error && error.status === 403) {
|
if (error instanceof Object && 'status' in error && error.status === 403) {
|
||||||
return;
|
return;
|
||||||
|
@ -23,7 +23,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
*/
|
*/
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: matchAnyBoardDeleted,
|
matcher: matchAnyBoardDeleted,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const deletedBoardId = action.meta.arg.originalArgs;
|
const deletedBoardId = action.meta.arg.originalArgs;
|
||||||
const { autoAddBoardId, selectedBoardId } = state.gallery;
|
const { autoAddBoardId, selectedBoardId } = state.gallery;
|
||||||
@ -44,7 +44,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
// If we archived a board, it may end up hidden. If it's selected or the auto-add board, we should reset those.
|
// If we archived a board, it may end up hidden. If it's selected or the auto-add board, we should reset those.
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: boardsApi.endpoints.updateBoard.matchFulfilled,
|
matcher: boardsApi.endpoints.updateBoard.matchFulfilled,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { shouldShowArchivedBoards } = state.gallery;
|
const { shouldShowArchivedBoards } = state.gallery;
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
// When we hide archived boards, if the selected or the auto-add board is archived, we should reset those.
|
// When we hide archived boards, if the selected or the auto-add board is archived, we should reset those.
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: shouldShowArchivedBoardsChanged,
|
actionCreator: shouldShowArchivedBoardsChanged,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const shouldShowArchivedBoards = action.payload;
|
const shouldShowArchivedBoards = action.payload;
|
||||||
|
|
||||||
// We only need to take action if we have just hidden archived boards.
|
// We only need to take action if we have just hidden archived boards.
|
||||||
@ -100,7 +100,7 @@ export const addArchivedOrDeletedBoardListener = (startAppListening: AppStartLis
|
|||||||
*/
|
*/
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: boardsApi.endpoints.listAllBoards.matchFulfilled,
|
matcher: boardsApi.endpoints.listAllBoards.matchFulfilled,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const boards = action.payload;
|
const boards = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { selectedBoardId, autoAddBoardId } = state.gallery;
|
const { selectedBoardId, autoAddBoardId } = state.gallery;
|
||||||
|
@ -1,33 +1,37 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import {
|
import {
|
||||||
canvasBatchIdsReset,
|
sessionStagingAreaImageAccepted,
|
||||||
commitStagingAreaImage,
|
sessionStagingAreaReset,
|
||||||
discardStagedImages,
|
} from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
resetCanvas,
|
import { rasterLayerAdded } from 'features/controlLayers/store/canvasSlice';
|
||||||
setInitialCanvasImage,
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
} from 'features/canvas/store/canvasSlice';
|
import type { CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||||
|
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
import { $lastCanvasProgressEvent } from 'services/events/setEventListeners';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages, resetCanvas, setInitialCanvasImage);
|
const log = logger('canvas');
|
||||||
|
|
||||||
export const addCommitStagingAreaImageListener = (startAppListening: AppStartListening) => {
|
export const addStagingListeners = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher,
|
actionCreator: sessionStagingAreaReset,
|
||||||
effect: async (_, { dispatch, getState }) => {
|
effect: async (_, { dispatch }) => {
|
||||||
const log = logger('canvas');
|
|
||||||
const state = getState();
|
|
||||||
const { batchIds } = state.canvas;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: batchIds }, { fixedCacheKey: 'cancelByBatchIds' })
|
queueApi.endpoints.cancelByBatchOrigin.initiate(
|
||||||
|
{ origin: 'canvas' },
|
||||||
|
{ fixedCacheKey: 'cancelByBatchOrigin' }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const { canceled } = await req.unwrap();
|
const { canceled } = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
|
|
||||||
|
$lastCanvasProgressEvent.set(null);
|
||||||
|
|
||||||
if (canceled > 0) {
|
if (canceled > 0) {
|
||||||
log.debug(`Canceled ${canceled} canvas batches`);
|
log.debug(`Canceled ${canceled} canvas batches`);
|
||||||
toast({
|
toast({
|
||||||
@ -36,7 +40,6 @@ export const addCommitStagingAreaImageListener = (startAppListening: AppStartLis
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch(canvasBatchIdsReset());
|
|
||||||
} catch {
|
} catch {
|
||||||
log.error('Failed to cancel canvas batches');
|
log.error('Failed to cancel canvas batches');
|
||||||
toast({
|
toast({
|
||||||
@ -47,4 +50,26 @@ export const addCommitStagingAreaImageListener = (startAppListening: AppStartLis
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: sessionStagingAreaImageAccepted,
|
||||||
|
effect: (action, api) => {
|
||||||
|
const { index } = action.payload;
|
||||||
|
const state = api.getState();
|
||||||
|
const stagingAreaImage = state.canvasSession.stagedImages[index];
|
||||||
|
|
||||||
|
assert(stagingAreaImage, 'No staged image found to accept');
|
||||||
|
const { x, y } = selectCanvasSlice(state).bbox.rect;
|
||||||
|
|
||||||
|
const { imageDTO, offsetX, offsetY } = stagingAreaImage;
|
||||||
|
const imageObject = imageDTOToImageObject(imageDTO);
|
||||||
|
const overrides: Partial<CanvasRasterLayerState> = {
|
||||||
|
position: { x: x + offsetX, y: y + offsetY },
|
||||||
|
objects: [imageObject],
|
||||||
|
};
|
||||||
|
|
||||||
|
api.dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||||
|
api.dispatch(sessionStagingAreaReset());
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
|||||||
export const addAnyEnqueuedListener = (startAppListening: AppStartListening) => {
|
export const addAnyEnqueuedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
||||||
effect: async (_, { dispatch, getState }) => {
|
effect: (_, { dispatch, getState }) => {
|
||||||
const { data } = selectQueueStatus(getState());
|
const { data } = selectQueueStatus(getState());
|
||||||
|
|
||||||
if (!data || data.processor.is_started) {
|
if (!data || data.processor.is_started) {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
import { setInfillMethod } from 'features/controlLayers/store/paramsSlice';
|
||||||
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
export const addAppConfigReceivedListener = (startAppListening: AppStartListening) => {
|
export const addAppConfigReceivedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
||||||
const infillMethod = getState().generation.infillMethod;
|
const infillMethod = getState().params.infillMethod;
|
||||||
|
|
||||||
if (!infill_methods.includes(infillMethod)) {
|
if (!infill_methods.includes(infillMethod)) {
|
||||||
// if there is no infill method, set it to the first one
|
// if there is no infill method, set it to the first one
|
||||||
|
@ -6,7 +6,7 @@ export const appStarted = createAction('app/appStarted');
|
|||||||
export const addAppStartedListener = (startAppListening: AppStartListening) => {
|
export const addAppStartedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: appStarted,
|
actionCreator: appStarted,
|
||||||
effect: async (action, { unsubscribe, cancelActiveListeners }) => {
|
effect: (action, { unsubscribe, cancelActiveListeners }) => {
|
||||||
// this should only run once
|
// this should only run once
|
||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
import type { SerializableObject } from 'common/types';
|
||||||
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { truncate, upperFirst } from 'lodash-es';
|
import { truncate, upperFirst } from 'lodash-es';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
|
||||||
|
const log = logger('queue');
|
||||||
|
|
||||||
export const addBatchEnqueuedListener = (startAppListening: AppStartListening) => {
|
export const addBatchEnqueuedListener = (startAppListening: AppStartListening) => {
|
||||||
// success
|
// success
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
matcher: queueApi.endpoints.enqueueBatch.matchFulfilled,
|
||||||
effect: async (action) => {
|
effect: (action) => {
|
||||||
const response = action.payload;
|
const enqueueResult = action.payload;
|
||||||
const arg = action.meta.arg.originalArgs;
|
const arg = action.meta.arg.originalArgs;
|
||||||
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
|
log.debug({ enqueueResult } as SerializableObject, 'Batch enqueued');
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
id: 'QUEUE_BATCH_SUCCEEDED',
|
id: 'QUEUE_BATCH_SUCCEEDED',
|
||||||
title: t('queue.batchQueued'),
|
title: t('queue.batchQueued'),
|
||||||
status: 'success',
|
status: 'success',
|
||||||
description: t('queue.batchQueuedDesc', {
|
description: t('queue.batchQueuedDesc', {
|
||||||
count: response.enqueued,
|
count: enqueueResult.enqueued,
|
||||||
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
direction: arg.prepend ? t('queue.front') : t('queue.back'),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -31,9 +34,9 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
// error
|
// error
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: queueApi.endpoints.enqueueBatch.matchRejected,
|
matcher: queueApi.endpoints.enqueueBatch.matchRejected,
|
||||||
effect: async (action) => {
|
effect: (action) => {
|
||||||
const response = action.payload;
|
const response = action.payload;
|
||||||
const arg = action.meta.arg.originalArgs;
|
const batchConfig = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
if (!response) {
|
if (!response) {
|
||||||
toast({
|
toast({
|
||||||
@ -42,7 +45,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
description: t('common.unknownError'),
|
description: t('common.unknownError'),
|
||||||
});
|
});
|
||||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
log.error({ batchConfig } as SerializableObject, t('queue.batchFailedToQueue'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ export const addBatchEnqueuedListener = (startAppListening: AppStartListening) =
|
|||||||
description: t('common.unknownError'),
|
description: t('common.unknownError'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
log.error({ batchConfig, error: serializeError(response) } as SerializableObject, t('queue.batchFailedToQueue'));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,47 +1,31 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
import { controlAdaptersReset } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { allLayersDeleted } from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
import { getImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addDeleteBoardAndImagesFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
matcher: imagesApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const { deleted_images } = action.payload;
|
const { deleted_images } = action.payload;
|
||||||
|
|
||||||
// Remove all deleted images from the UI
|
// Remove all deleted images from the UI
|
||||||
|
|
||||||
let wasCanvasReset = false;
|
|
||||||
let wasNodeEditorReset = false;
|
let wasNodeEditorReset = false;
|
||||||
let wereControlAdaptersReset = false;
|
|
||||||
let wereControlLayersReset = false;
|
|
||||||
|
|
||||||
const { canvas, nodes, controlAdapters, controlLayers } = getState();
|
const state = getState();
|
||||||
|
const nodes = selectNodesSlice(state);
|
||||||
|
const canvas = selectCanvasSlice(state);
|
||||||
|
|
||||||
deleted_images.forEach((image_name) => {
|
deleted_images.forEach((image_name) => {
|
||||||
const imageUsage = getImageUsage(canvas, nodes.present, controlAdapters, controlLayers.present, image_name);
|
const imageUsage = getImageUsage(nodes, canvas, image_name);
|
||||||
|
|
||||||
if (imageUsage.isCanvasImage && !wasCanvasReset) {
|
|
||||||
dispatch(resetCanvas());
|
|
||||||
wasCanvasReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageUsage.isNodesImage && !wasNodeEditorReset) {
|
if (imageUsage.isNodesImage && !wasNodeEditorReset) {
|
||||||
dispatch(nodeEditorReset());
|
dispatch(nodeEditorReset());
|
||||||
wasNodeEditorReset = true;
|
wasNodeEditorReset = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageUsage.isControlImage && !wereControlAdaptersReset) {
|
|
||||||
dispatch(controlAdaptersReset());
|
|
||||||
wereControlAdaptersReset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageUsage.isControlLayerImage && !wereControlLayersReset) {
|
|
||||||
dispatch(allLayersDeleted());
|
|
||||||
wereControlLayersReset = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
import { ExternalLink } from '@invoke-ai/ui-library';
|
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import {
|
|
||||||
socketBulkDownloadComplete,
|
|
||||||
socketBulkDownloadError,
|
|
||||||
socketBulkDownloadStarted,
|
|
||||||
} from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('images');
|
const log = logger('gallery');
|
||||||
|
|
||||||
export const addBulkDownloadListeners = (startAppListening: AppStartListening) => {
|
export const addBulkDownloadListeners = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchFulfilled,
|
matcher: imagesApi.endpoints.bulkDownloadImages.matchFulfilled,
|
||||||
effect: async (action) => {
|
effect: (action) => {
|
||||||
log.debug(action.payload, 'Bulk download requested');
|
log.debug(action.payload, 'Bulk download requested');
|
||||||
|
|
||||||
// If we have an item name, we are processing the bulk download locally and should use it as the toast id to
|
// If we have an item name, we are processing the bulk download locally and should use it as the toast id to
|
||||||
@ -33,7 +27,7 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
|||||||
|
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.bulkDownloadImages.matchRejected,
|
matcher: imagesApi.endpoints.bulkDownloadImages.matchRejected,
|
||||||
effect: async () => {
|
effect: () => {
|
||||||
log.debug('Bulk download request failed');
|
log.debug('Bulk download request failed');
|
||||||
|
|
||||||
// There isn't any toast to update if we get this event.
|
// There isn't any toast to update if we get this event.
|
||||||
@ -44,55 +38,4 @@ export const addBulkDownloadListeners = (startAppListening: AppStartListening) =
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketBulkDownloadStarted,
|
|
||||||
effect: async (action) => {
|
|
||||||
// This should always happen immediately after the bulk download request, so we don't need to show a toast here.
|
|
||||||
log.debug(action.payload.data, 'Bulk download preparation started');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketBulkDownloadComplete,
|
|
||||||
effect: async (action) => {
|
|
||||||
log.debug(action.payload.data, 'Bulk download preparation completed');
|
|
||||||
|
|
||||||
const { bulk_download_item_name } = action.payload.data;
|
|
||||||
|
|
||||||
// TODO(psyche): This URL may break in in some environments (e.g. Nvidia workbench) but we need to test it first
|
|
||||||
const url = `/api/v1/images/download/${bulk_download_item_name}`;
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: bulk_download_item_name,
|
|
||||||
title: t('gallery.bulkDownloadReady', 'Download ready'),
|
|
||||||
status: 'success',
|
|
||||||
description: (
|
|
||||||
<ExternalLink
|
|
||||||
label={t('gallery.clickToDownload', 'Click here to download')}
|
|
||||||
href={url}
|
|
||||||
download={bulk_download_item_name}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
duration: null,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketBulkDownloadError,
|
|
||||||
effect: async (action) => {
|
|
||||||
log.debug(action.payload.data, 'Bulk download preparation failed');
|
|
||||||
|
|
||||||
const { bulk_download_item_name } = action.payload.data;
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: bulk_download_item_name,
|
|
||||||
title: t('gallery.bulkDownloadFailed'),
|
|
||||||
status: 'error',
|
|
||||||
description: action.payload.data.error,
|
|
||||||
duration: null,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { $logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
|
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
|
||||||
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export const addCanvasCopiedToClipboardListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasCopiedToClipboard,
|
|
||||||
effect: async (action, { getState }) => {
|
|
||||||
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const blob = getBaseLayerBlob(state);
|
|
||||||
|
|
||||||
copyBlobToClipboard(blob);
|
|
||||||
} catch (err) {
|
|
||||||
moduleLog.error(String(err));
|
|
||||||
toast({
|
|
||||||
id: 'CANVAS_COPY_FAILED',
|
|
||||||
title: t('toast.problemCopyingCanvas'),
|
|
||||||
description: t('toast.problemCopyingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: 'CANVAS_COPY_SUCCEEDED',
|
|
||||||
title: t('toast.canvasCopiedClipboard'),
|
|
||||||
status: 'success',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
import { $logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
|
|
||||||
import { downloadBlob } from 'features/canvas/util/downloadBlob';
|
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
|
|
||||||
export const addCanvasDownloadedAsImageListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasDownloadedAsImage,
|
|
||||||
effect: async (action, { getState }) => {
|
|
||||||
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
let blob;
|
|
||||||
try {
|
|
||||||
blob = await getBaseLayerBlob(state);
|
|
||||||
} catch (err) {
|
|
||||||
moduleLog.error(String(err));
|
|
||||||
toast({
|
|
||||||
id: 'CANVAS_DOWNLOAD_FAILED',
|
|
||||||
title: t('toast.problemDownloadingCanvas'),
|
|
||||||
description: t('toast.problemDownloadingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadBlob(blob, 'canvas.png');
|
|
||||||
toast({ id: 'CANVAS_DOWNLOAD_SUCCEEDED', title: t('toast.canvasDownloaded'), status: 'success' });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,60 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
|
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
|
||||||
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addCanvasImageToControlNetListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasImageToControlAdapter,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const log = logger('canvas');
|
|
||||||
const state = getState();
|
|
||||||
const { id } = action.payload;
|
|
||||||
|
|
||||||
let blob: Blob;
|
|
||||||
try {
|
|
||||||
blob = await getBaseLayerBlob(state, true);
|
|
||||||
} catch (err) {
|
|
||||||
log.error(String(err));
|
|
||||||
toast({
|
|
||||||
id: 'PROBLEM_SAVING_CANVAS',
|
|
||||||
title: t('toast.problemSavingCanvas'),
|
|
||||||
description: t('toast.problemSavingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { autoAddBoardId } = state.gallery;
|
|
||||||
|
|
||||||
const imageDTO = await dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([blob], 'savedCanvas.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'control',
|
|
||||||
is_intermediate: true,
|
|
||||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
|
||||||
crop_visible: false,
|
|
||||||
postUploadAction: {
|
|
||||||
type: 'TOAST',
|
|
||||||
title: t('toast.canvasSentControlnetAssets'),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
const { image_name } = imageDTO;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id,
|
|
||||||
controlImage: image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,60 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
|
|
||||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addCanvasMaskSavedToGalleryListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasMaskSavedToGallery,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const log = logger('canvas');
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
const canvasBlobsAndImageData = await getCanvasData(
|
|
||||||
state.canvas.layerState,
|
|
||||||
state.canvas.boundingBoxCoordinates,
|
|
||||||
state.canvas.boundingBoxDimensions,
|
|
||||||
state.canvas.isMaskEnabled,
|
|
||||||
state.canvas.shouldPreserveMaskedArea
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!canvasBlobsAndImageData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { maskBlob } = canvasBlobsAndImageData;
|
|
||||||
|
|
||||||
if (!maskBlob) {
|
|
||||||
log.error('Problem getting mask layer blob');
|
|
||||||
toast({
|
|
||||||
id: 'PROBLEM_SAVING_MASK',
|
|
||||||
title: t('toast.problemSavingMask'),
|
|
||||||
description: t('toast.problemSavingMaskDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { autoAddBoardId } = state.gallery;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([maskBlob], 'canvasMaskImage.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'mask',
|
|
||||||
is_intermediate: false,
|
|
||||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
|
||||||
crop_visible: true,
|
|
||||||
postUploadAction: {
|
|
||||||
type: 'TOAST',
|
|
||||||
title: t('toast.maskSavedAssets'),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,70 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasMaskToControlAdapter } from 'features/canvas/store/actions';
|
|
||||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
|
||||||
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addCanvasMaskToControlNetListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasMaskToControlAdapter,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const log = logger('canvas');
|
|
||||||
const state = getState();
|
|
||||||
const { id } = action.payload;
|
|
||||||
const canvasBlobsAndImageData = await getCanvasData(
|
|
||||||
state.canvas.layerState,
|
|
||||||
state.canvas.boundingBoxCoordinates,
|
|
||||||
state.canvas.boundingBoxDimensions,
|
|
||||||
state.canvas.isMaskEnabled,
|
|
||||||
state.canvas.shouldPreserveMaskedArea
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!canvasBlobsAndImageData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { maskBlob } = canvasBlobsAndImageData;
|
|
||||||
|
|
||||||
if (!maskBlob) {
|
|
||||||
log.error('Problem getting mask layer blob');
|
|
||||||
toast({
|
|
||||||
id: 'PROBLEM_IMPORTING_MASK',
|
|
||||||
title: t('toast.problemImportingMask'),
|
|
||||||
description: t('toast.problemImportingMaskDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { autoAddBoardId } = state.gallery;
|
|
||||||
|
|
||||||
const imageDTO = await dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([maskBlob], 'canvasMaskImage.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'mask',
|
|
||||||
is_intermediate: true,
|
|
||||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
|
||||||
crop_visible: false,
|
|
||||||
postUploadAction: {
|
|
||||||
type: 'TOAST',
|
|
||||||
title: t('toast.maskSentControlnetAssets'),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
const { image_name } = imageDTO;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id,
|
|
||||||
controlImage: image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,73 +0,0 @@
|
|||||||
import { $logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { canvasMerged } from 'features/canvas/store/actions';
|
|
||||||
import { $canvasBaseLayer } from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addCanvasMergedListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasMerged,
|
|
||||||
effect: async (action, { dispatch }) => {
|
|
||||||
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
|
||||||
const blob = await getFullBaseLayerBlob();
|
|
||||||
|
|
||||||
if (!blob) {
|
|
||||||
moduleLog.error('Problem getting base layer blob');
|
|
||||||
toast({
|
|
||||||
id: 'PROBLEM_MERGING_CANVAS',
|
|
||||||
title: t('toast.problemMergingCanvas'),
|
|
||||||
description: t('toast.problemMergingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canvasBaseLayer = $canvasBaseLayer.get();
|
|
||||||
|
|
||||||
if (!canvasBaseLayer) {
|
|
||||||
moduleLog.error('Problem getting canvas base layer');
|
|
||||||
toast({
|
|
||||||
id: 'PROBLEM_MERGING_CANVAS',
|
|
||||||
title: t('toast.problemMergingCanvas'),
|
|
||||||
description: t('toast.problemMergingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseLayerRect = canvasBaseLayer.getClientRect({
|
|
||||||
relativeTo: canvasBaseLayer.getParent() ?? undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const imageDTO = await dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([blob], 'mergedCanvas.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'general',
|
|
||||||
is_intermediate: true,
|
|
||||||
postUploadAction: {
|
|
||||||
type: 'TOAST',
|
|
||||||
title: t('toast.canvasMerged'),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// TODO: I can't figure out how to do the type narrowing in the `take()` so just brute forcing it here
|
|
||||||
const { image_name } = imageDTO;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
setMergedCanvas({
|
|
||||||
kind: 'image',
|
|
||||||
layer: 'base',
|
|
||||||
imageName: image_name,
|
|
||||||
...baseLayerRect,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,53 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addCanvasSavedToGalleryListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: canvasSavedToGallery,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const log = logger('canvas');
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
let blob;
|
|
||||||
try {
|
|
||||||
blob = await getBaseLayerBlob(state);
|
|
||||||
} catch (err) {
|
|
||||||
log.error(String(err));
|
|
||||||
toast({
|
|
||||||
id: 'CANVAS_SAVE_FAILED',
|
|
||||||
title: t('toast.problemSavingCanvas'),
|
|
||||||
description: t('toast.problemSavingCanvasDesc'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { autoAddBoardId } = state.gallery;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([blob], 'savedCanvas.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'general',
|
|
||||||
is_intermediate: false,
|
|
||||||
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
|
|
||||||
crop_visible: true,
|
|
||||||
postUploadAction: {
|
|
||||||
type: 'TOAST',
|
|
||||||
title: t('toast.canvasSavedGallery'),
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
_canvas_objects: parseify(state.canvas.layerState.objects),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,194 +0,0 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
|
||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import type { AppDispatch } from 'app/store/store';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import {
|
|
||||||
caLayerImageChanged,
|
|
||||||
caLayerModelChanged,
|
|
||||||
caLayerProcessedImageChanged,
|
|
||||||
caLayerProcessorConfigChanged,
|
|
||||||
caLayerProcessorPendingBatchIdChanged,
|
|
||||||
caLayerRecalled,
|
|
||||||
isControlAdapterLayer,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { CA_PROCESSOR_DATA } from 'features/controlLayers/util/controlAdapters';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
import { getImageDTO } from 'services/api/endpoints/images';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
|
||||||
import type { BatchConfig } from 'services/api/types';
|
|
||||||
import { socketInvocationComplete } from 'services/events/actions';
|
|
||||||
import { assert } from 'tsafe';
|
|
||||||
|
|
||||||
const matcher = isAnyOf(
|
|
||||||
caLayerImageChanged,
|
|
||||||
caLayerProcessedImageChanged,
|
|
||||||
caLayerProcessorConfigChanged,
|
|
||||||
caLayerModelChanged,
|
|
||||||
caLayerRecalled
|
|
||||||
);
|
|
||||||
|
|
||||||
const DEBOUNCE_MS = 300;
|
|
||||||
const log = logger('session');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple helper to cancel a batch and reset the pending batch ID
|
|
||||||
*/
|
|
||||||
const cancelProcessorBatch = async (dispatch: AppDispatch, layerId: string, batchId: string) => {
|
|
||||||
const req = dispatch(queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: [batchId] }));
|
|
||||||
log.trace({ batchId }, 'Cancelling existing preprocessor batch');
|
|
||||||
try {
|
|
||||||
await req.unwrap();
|
|
||||||
} catch {
|
|
||||||
// no-op
|
|
||||||
} finally {
|
|
||||||
req.reset();
|
|
||||||
// Always reset the pending batch ID - the cancel req could fail if the batch doesn't exist
|
|
||||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addControlAdapterPreprocessor = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
matcher,
|
|
||||||
effect: async (action, { dispatch, getState, getOriginalState, cancelActiveListeners, delay, take, signal }) => {
|
|
||||||
const layerId = caLayerRecalled.match(action) ? action.payload.id : action.payload.layerId;
|
|
||||||
const state = getState();
|
|
||||||
const originalState = getOriginalState();
|
|
||||||
|
|
||||||
// Cancel any in-progress instances of this listener
|
|
||||||
cancelActiveListeners();
|
|
||||||
log.trace('Control Layer CA auto-process triggered');
|
|
||||||
|
|
||||||
// Delay before starting actual work
|
|
||||||
await delay(DEBOUNCE_MS);
|
|
||||||
|
|
||||||
const layer = state.controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
|
||||||
|
|
||||||
if (!layer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should only process if the processor settings or image have changed
|
|
||||||
const originalLayer = originalState.controlLayers.present.layers
|
|
||||||
.filter(isControlAdapterLayer)
|
|
||||||
.find((l) => l.id === layerId);
|
|
||||||
const originalImage = originalLayer?.controlAdapter.image;
|
|
||||||
const originalConfig = originalLayer?.controlAdapter.processorConfig;
|
|
||||||
|
|
||||||
const image = layer.controlAdapter.image;
|
|
||||||
const processedImage = layer.controlAdapter.processedImage;
|
|
||||||
const config = layer.controlAdapter.processorConfig;
|
|
||||||
|
|
||||||
if (isEqual(config, originalConfig) && isEqual(image, originalImage) && processedImage) {
|
|
||||||
// Neither config nor image have changed, we can bail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image || !config) {
|
|
||||||
// - If we have no image, we have nothing to process
|
|
||||||
// - If we have no processor config, we have nothing to process
|
|
||||||
// Clear the processed image and bail
|
|
||||||
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO: null }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, the user has stopped fiddling with the processor settings and there is a processor selected.
|
|
||||||
|
|
||||||
// If there is a pending processor batch, cancel it.
|
|
||||||
if (layer.controlAdapter.processorPendingBatchId) {
|
|
||||||
cancelProcessorBatch(dispatch, layerId, layer.controlAdapter.processorPendingBatchId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
|
||||||
const processorNode = CA_PROCESSOR_DATA[config.type].buildNode(image, config as never);
|
|
||||||
const enqueueBatchArg: BatchConfig = {
|
|
||||||
prepend: true,
|
|
||||||
batch: {
|
|
||||||
graph: {
|
|
||||||
nodes: {
|
|
||||||
[processorNode.id]: {
|
|
||||||
...processorNode,
|
|
||||||
// Control images are always intermediate - do not save to gallery
|
|
||||||
is_intermediate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
edges: [],
|
|
||||||
},
|
|
||||||
runs: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Kick off the processor batch
|
|
||||||
const req = dispatch(
|
|
||||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const enqueueResult = await req.unwrap();
|
|
||||||
// TODO(psyche): Update the pydantic models, pretty sure we will _always_ have a batch_id here, but the model says it's optional
|
|
||||||
assert(enqueueResult.batch.batch_id, 'Batch ID not returned from queue');
|
|
||||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: enqueueResult.batch.batch_id }));
|
|
||||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
|
||||||
|
|
||||||
// Wait for the processor node to complete
|
|
||||||
const [invocationCompleteAction] = await take(
|
|
||||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
|
||||||
socketInvocationComplete.match(action) &&
|
|
||||||
action.payload.data.batch_id === enqueueResult.batch.batch_id &&
|
|
||||||
action.payload.data.invocation_source_id === processorNode.id
|
|
||||||
);
|
|
||||||
|
|
||||||
// We still have to check the output type
|
|
||||||
assert(
|
|
||||||
invocationCompleteAction.payload.data.result.type === 'image_output',
|
|
||||||
`Processor did not return an image output, got: ${invocationCompleteAction.payload.data.result}`
|
|
||||||
);
|
|
||||||
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
|
||||||
|
|
||||||
const imageDTO = await getImageDTO(image_name);
|
|
||||||
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
|
||||||
|
|
||||||
// Whew! We made it. Update the layer with the processed image
|
|
||||||
log.debug({ layerId, imageDTO }, 'ControlNet image processed');
|
|
||||||
dispatch(caLayerProcessedImageChanged({ layerId, imageDTO }));
|
|
||||||
dispatch(caLayerProcessorPendingBatchIdChanged({ layerId, batchId: null }));
|
|
||||||
} catch (error) {
|
|
||||||
if (signal.aborted) {
|
|
||||||
// The listener was canceled - we need to cancel the pending processor batch, if there is one (could have changed by now).
|
|
||||||
const pendingBatchId = getState()
|
|
||||||
.controlLayers.present.layers.filter(isControlAdapterLayer)
|
|
||||||
.find((l) => l.id === layerId)?.controlAdapter.processorPendingBatchId;
|
|
||||||
if (pendingBatchId) {
|
|
||||||
cancelProcessorBatch(dispatch, layerId, pendingBatchId);
|
|
||||||
}
|
|
||||||
log.trace('Control Adapter preprocessor cancelled');
|
|
||||||
} else {
|
|
||||||
// Some other error condition...
|
|
||||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
|
||||||
|
|
||||||
if (error instanceof Object) {
|
|
||||||
if ('data' in error && 'status' in error) {
|
|
||||||
if (error.status === 403) {
|
|
||||||
dispatch(caLayerImageChanged({ layerId, imageDTO: null }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: 'GRAPH_QUEUE_FAILED',
|
|
||||||
title: t('queue.graphFailedToQueue'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
req.reset();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,85 +0,0 @@
|
|||||||
import type { AnyListenerPredicate } from '@reduxjs/toolkit';
|
|
||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import type { RootState } from 'app/store/store';
|
|
||||||
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
|
|
||||||
import {
|
|
||||||
controlAdapterAutoConfigToggled,
|
|
||||||
controlAdapterImageChanged,
|
|
||||||
controlAdapterModelChanged,
|
|
||||||
controlAdapterProcessorParamsChanged,
|
|
||||||
controlAdapterProcessortTypeChanged,
|
|
||||||
selectControlAdapterById,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
||||||
|
|
||||||
type AnyControlAdapterParamChangeAction =
|
|
||||||
| ReturnType<typeof controlAdapterProcessorParamsChanged>
|
|
||||||
| ReturnType<typeof controlAdapterModelChanged>
|
|
||||||
| ReturnType<typeof controlAdapterImageChanged>
|
|
||||||
| ReturnType<typeof controlAdapterProcessortTypeChanged>
|
|
||||||
| ReturnType<typeof controlAdapterAutoConfigToggled>;
|
|
||||||
|
|
||||||
const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) => {
|
|
||||||
const isActionMatched =
|
|
||||||
controlAdapterProcessorParamsChanged.match(action) ||
|
|
||||||
controlAdapterModelChanged.match(action) ||
|
|
||||||
controlAdapterImageChanged.match(action) ||
|
|
||||||
controlAdapterProcessortTypeChanged.match(action) ||
|
|
||||||
controlAdapterAutoConfigToggled.match(action);
|
|
||||||
|
|
||||||
if (!isActionMatched) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = action.payload;
|
|
||||||
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
|
|
||||||
const ca = selectControlAdapterById(state.controlAdapters, id);
|
|
||||||
if (!prevCA || !isControlNetOrT2IAdapter(prevCA) || !ca || !isControlNetOrT2IAdapter(ca)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controlAdapterAutoConfigToggled.match(action)) {
|
|
||||||
// do not process if the user just disabled auto-config
|
|
||||||
if (prevCA.shouldAutoConfig === true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { controlImage, processorType, shouldAutoConfig } = ca;
|
|
||||||
if (controlAdapterModelChanged.match(action) && !shouldAutoConfig) {
|
|
||||||
// do not process if the action is a model change but the processor settings are dirty
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isProcessorSelected = processorType !== 'none';
|
|
||||||
|
|
||||||
const hasControlImage = Boolean(controlImage);
|
|
||||||
|
|
||||||
return isProcessorSelected && hasControlImage;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEBOUNCE_MS = 300;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener that automatically processes a ControlNet image when its processor parameters are changed.
|
|
||||||
*
|
|
||||||
* The network request is debounced.
|
|
||||||
*/
|
|
||||||
export const addControlNetAutoProcessListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
predicate,
|
|
||||||
effect: async (action, { dispatch, cancelActiveListeners, delay }) => {
|
|
||||||
const log = logger('session');
|
|
||||||
const { id } = (action as AnyControlAdapterParamChangeAction).payload;
|
|
||||||
|
|
||||||
// Cancel any in-progress instances of this listener
|
|
||||||
cancelActiveListeners();
|
|
||||||
log.trace('ControlNet auto-process triggered');
|
|
||||||
// Delay before starting actual work
|
|
||||||
await delay(DEBOUNCE_MS);
|
|
||||||
|
|
||||||
dispatch(controlAdapterImageProcessed({ id }));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,118 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
|
|
||||||
import {
|
|
||||||
controlAdapterImageChanged,
|
|
||||||
controlAdapterProcessedImageChanged,
|
|
||||||
pendingControlImagesCleared,
|
|
||||||
selectControlAdapterById,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
|
||||||
import type { BatchConfig, ImageDTO } from 'services/api/types';
|
|
||||||
import { socketInvocationComplete } from 'services/events/actions';
|
|
||||||
|
|
||||||
export const addControlNetImageProcessedListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: controlAdapterImageProcessed,
|
|
||||||
effect: async (action, { dispatch, getState, take }) => {
|
|
||||||
const log = logger('session');
|
|
||||||
const { id } = action.payload;
|
|
||||||
const ca = selectControlAdapterById(getState().controlAdapters, id);
|
|
||||||
|
|
||||||
if (!ca?.controlImage || !isControlNetOrT2IAdapter(ca)) {
|
|
||||||
log.error('Unable to process ControlNet image');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ca.processorType === 'none' || ca.processorNode.type === 'none') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControlNet one-off procressing graph is just the processor node, no edges.
|
|
||||||
// Also we need to grab the image.
|
|
||||||
|
|
||||||
const nodeId = ca.processorNode.id;
|
|
||||||
const enqueueBatchArg: BatchConfig = {
|
|
||||||
prepend: true,
|
|
||||||
batch: {
|
|
||||||
graph: {
|
|
||||||
nodes: {
|
|
||||||
[ca.processorNode.id]: {
|
|
||||||
...ca.processorNode,
|
|
||||||
is_intermediate: true,
|
|
||||||
use_cache: false,
|
|
||||||
image: { image_name: ca.controlImage },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
edges: [],
|
|
||||||
},
|
|
||||||
runs: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const req = dispatch(
|
|
||||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const enqueueResult = await req.unwrap();
|
|
||||||
req.reset();
|
|
||||||
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
|
||||||
|
|
||||||
const [invocationCompleteAction] = await take(
|
|
||||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
|
||||||
socketInvocationComplete.match(action) &&
|
|
||||||
action.payload.data.batch_id === enqueueResult.batch.batch_id &&
|
|
||||||
action.payload.data.invocation_source_id === nodeId
|
|
||||||
);
|
|
||||||
|
|
||||||
// We still have to check the output type
|
|
||||||
if (invocationCompleteAction.payload.data.result.type === 'image_output') {
|
|
||||||
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
|
||||||
|
|
||||||
// Wait for the ImageDTO to be received
|
|
||||||
const [{ payload }] = await take(
|
|
||||||
(action) =>
|
|
||||||
imagesApi.endpoints.getImageDTO.matchFulfilled(action) && action.payload.image_name === image_name
|
|
||||||
);
|
|
||||||
|
|
||||||
const processedControlImage = payload as ImageDTO;
|
|
||||||
|
|
||||||
log.debug({ controlNetId: action.payload, processedControlImage }, 'ControlNet image processed');
|
|
||||||
|
|
||||||
// Update the processed image in the store
|
|
||||||
dispatch(
|
|
||||||
controlAdapterProcessedImageChanged({
|
|
||||||
id,
|
|
||||||
processedControlImage: processedControlImage.image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
|
||||||
|
|
||||||
if (error instanceof Object) {
|
|
||||||
if ('data' in error && 'status' in error) {
|
|
||||||
if (error.status === 403) {
|
|
||||||
dispatch(pendingControlImagesCleared());
|
|
||||||
dispatch(controlAdapterImageChanged({ id, controlImage: null }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: 'GRAPH_QUEUE_FAILED',
|
|
||||||
title: t('queue.graphFailedToQueue'),
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,144 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import { enqueueRequested } from 'app/store/actions';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { canvasBatchIdAdded, stagingAreaInitialized } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
|
||||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
|
||||||
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
|
|
||||||
import { canvasGraphBuilt } from 'features/nodes/store/actions';
|
|
||||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
|
||||||
import { buildCanvasGraph } from 'features/nodes/util/graph/canvas/buildCanvasGraph';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
|
||||||
import type { ImageDTO } from 'services/api/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This listener is responsible invoking the canvas. This involves a number of steps:
|
|
||||||
*
|
|
||||||
* 1. Generate image blobs from the canvas layers
|
|
||||||
* 2. Determine the generation mode from the layers (txt2img, img2img, inpaint)
|
|
||||||
* 3. Build the canvas graph
|
|
||||||
* 4. Create the session with the graph
|
|
||||||
* 5. Upload the init image if necessary
|
|
||||||
* 6. Upload the mask image if necessary
|
|
||||||
* 7. Update the init and mask images with the session ID
|
|
||||||
* 8. Initialize the staging area if not yet initialized
|
|
||||||
* 9. Dispatch the sessionReadyToInvoke action to invoke the session
|
|
||||||
*/
|
|
||||||
export const addEnqueueRequestedCanvasListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
|
||||||
enqueueRequested.match(action) && action.payload.tabName === 'canvas',
|
|
||||||
effect: async (action, { getState, dispatch }) => {
|
|
||||||
const log = logger('queue');
|
|
||||||
const { prepend } = action.payload;
|
|
||||||
const state = getState();
|
|
||||||
|
|
||||||
const { layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea } =
|
|
||||||
state.canvas;
|
|
||||||
|
|
||||||
// Build canvas blobs
|
|
||||||
const canvasBlobsAndImageData = await getCanvasData(
|
|
||||||
layerState,
|
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
isMaskEnabled,
|
|
||||||
shouldPreserveMaskedArea
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!canvasBlobsAndImageData) {
|
|
||||||
log.error('Unable to create canvas data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { baseBlob, baseImageData, maskBlob, maskImageData } = canvasBlobsAndImageData;
|
|
||||||
|
|
||||||
// Determine the generation mode
|
|
||||||
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
|
||||||
|
|
||||||
if (state.system.enableImageDebugging) {
|
|
||||||
const baseDataURL = await blobToDataURL(baseBlob);
|
|
||||||
const maskDataURL = await blobToDataURL(maskBlob);
|
|
||||||
openBase64ImageInTab([
|
|
||||||
{ base64: maskDataURL, caption: 'mask b64' },
|
|
||||||
{ base64: baseDataURL, caption: 'image b64' },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug(`Generation mode: ${generationMode}`);
|
|
||||||
|
|
||||||
// Temp placeholders for the init and mask images
|
|
||||||
let canvasInitImage: ImageDTO | undefined;
|
|
||||||
let canvasMaskImage: ImageDTO | undefined;
|
|
||||||
|
|
||||||
// For img2img and inpaint/outpaint, we need to upload the init images
|
|
||||||
if (['img2img', 'inpaint', 'outpaint'].includes(generationMode)) {
|
|
||||||
// upload the image, saving the request id
|
|
||||||
canvasInitImage = await dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([baseBlob], 'canvasInitImage.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'general',
|
|
||||||
is_intermediate: true,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For inpaint/outpaint, we also need to upload the mask layer
|
|
||||||
if (['inpaint', 'outpaint'].includes(generationMode)) {
|
|
||||||
// upload the image, saving the request id
|
|
||||||
canvasMaskImage = await dispatch(
|
|
||||||
imagesApi.endpoints.uploadImage.initiate({
|
|
||||||
file: new File([maskBlob], 'canvasMaskImage.png', {
|
|
||||||
type: 'image/png',
|
|
||||||
}),
|
|
||||||
image_category: 'mask',
|
|
||||||
is_intermediate: true,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
const graph = await buildCanvasGraph(state, generationMode, canvasInitImage, canvasMaskImage);
|
|
||||||
|
|
||||||
log.debug({ graph: parseify(graph) }, `Canvas graph built`);
|
|
||||||
|
|
||||||
// currently this action is just listened to for logging
|
|
||||||
dispatch(canvasGraphBuilt(graph));
|
|
||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const req = dispatch(
|
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
|
||||||
fixedCacheKey: 'enqueueBatch',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const enqueueResult = await req.unwrap();
|
|
||||||
req.reset();
|
|
||||||
|
|
||||||
const batchId = enqueueResult.batch.batch_id as string; // we know the is a string, backend provides it
|
|
||||||
|
|
||||||
// Prep the canvas staging area if it is not yet initialized
|
|
||||||
if (!state.canvas.layerState.stagingArea.boundingBox) {
|
|
||||||
dispatch(
|
|
||||||
stagingAreaInitialized({
|
|
||||||
boundingBox: {
|
|
||||||
...state.canvas.boundingBoxCoordinates,
|
|
||||||
...state.canvas.boundingBoxDimensions,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate the session with the canvas session ID
|
|
||||||
dispatch(canvasBatchIdAdded(batchId));
|
|
||||||
} catch {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,10 +1,21 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
import type { SerializableObject } from 'common/types';
|
||||||
|
import type { Result } from 'common/util/result';
|
||||||
|
import { isErr, withResult, withResultAsync } from 'common/util/result';
|
||||||
|
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
|
import { sessionStagingAreaReset, sessionStartedStaging } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
|
||||||
import { buildGenerationTabGraph } from 'features/nodes/util/graph/generation/buildGenerationTabGraph';
|
import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph';
|
||||||
import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/generation/buildGenerationTabSDXLGraph';
|
import { buildSDXLGraph } from 'features/nodes/util/graph/generation/buildSDXLGraph';
|
||||||
|
import type { Graph } from 'features/nodes/util/graph/generation/Graph';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
|
import type { Invocation } from 'services/api/types';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
const log = logger('generation');
|
||||||
|
|
||||||
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -12,33 +23,77 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening)
|
|||||||
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
enqueueRequested.match(action) && action.payload.tabName === 'generation',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { shouldShowProgressInViewer } = state.ui;
|
const model = state.params.model;
|
||||||
const model = state.generation.model;
|
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
|
|
||||||
let graph;
|
const manager = $canvasManager.get();
|
||||||
|
assert(manager, 'No model found in state');
|
||||||
|
|
||||||
if (model?.base === 'sdxl') {
|
let didStartStaging = false;
|
||||||
graph = await buildGenerationTabSDXLGraph(state);
|
|
||||||
} else {
|
if (!state.canvasSession.isStaging && state.canvasSession.mode === 'compose') {
|
||||||
graph = await buildGenerationTabGraph(state);
|
dispatch(sessionStartedStaging());
|
||||||
|
didStartStaging = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
const abortStaging = () => {
|
||||||
|
if (didStartStaging && getState().canvasSession.isStaging) {
|
||||||
|
dispatch(sessionStagingAreaReset());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let buildGraphResult: Result<
|
||||||
|
{ g: Graph; noise: Invocation<'noise'>; posCond: Invocation<'compel' | 'sdxl_compel_prompt'> },
|
||||||
|
Error
|
||||||
|
>;
|
||||||
|
|
||||||
|
assert(model, 'No model found in state');
|
||||||
|
const base = model.base;
|
||||||
|
|
||||||
|
switch (base) {
|
||||||
|
case 'sdxl':
|
||||||
|
buildGraphResult = await withResultAsync(() => buildSDXLGraph(state, manager));
|
||||||
|
break;
|
||||||
|
case 'sd-1':
|
||||||
|
case `sd-2`:
|
||||||
|
buildGraphResult = await withResultAsync(() => buildSD1Graph(state, manager));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false, `No graph builders for base ${base}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isErr(buildGraphResult)) {
|
||||||
|
log.error({ error: serializeError(buildGraphResult.error) }, 'Failed to build graph');
|
||||||
|
abortStaging();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { g, noise, posCond } = buildGraphResult.value;
|
||||||
|
|
||||||
|
const prepareBatchResult = withResult(() => prepareLinearUIBatch(state, g, prepend, noise, posCond));
|
||||||
|
|
||||||
|
if (isErr(prepareBatchResult)) {
|
||||||
|
log.error({ error: serializeError(prepareBatchResult.error) }, 'Failed to prepare batch');
|
||||||
|
abortStaging();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
queueApi.endpoints.enqueueBatch.initiate(prepareBatchResult.value, {
|
||||||
fixedCacheKey: 'enqueueBatch',
|
fixedCacheKey: 'enqueueBatch',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
try {
|
req.reset();
|
||||||
await req.unwrap();
|
|
||||||
if (shouldShowProgressInViewer) {
|
const enqueueResult = await withResultAsync(() => req.unwrap());
|
||||||
dispatch(isImageViewerOpenChanged(true));
|
|
||||||
}
|
if (isErr(enqueueResult)) {
|
||||||
} finally {
|
log.error({ error: serializeError(enqueueResult.error) }, 'Failed to enqueue batch');
|
||||||
req.reset();
|
abortStaging();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug({ batchConfig: prepareBatchResult.value } as SerializableObject, 'Enqueued batch');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||||
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
|
||||||
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
import { buildWorkflowWithValidation } from 'features/nodes/util/workflow/buildWorkflow';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
@ -11,12 +12,12 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
|||||||
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
enqueueRequested.match(action) && action.payload.tabName === 'workflows',
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { nodes, edges } = state.nodes.present;
|
const nodes = selectNodesSlice(state);
|
||||||
const workflow = state.workflow;
|
const workflow = state.workflow;
|
||||||
const graph = buildNodesGraph(state.nodes.present);
|
const graph = buildNodesGraph(nodes);
|
||||||
const builtWorkflow = buildWorkflowWithValidation({
|
const builtWorkflow = buildWorkflowWithValidation({
|
||||||
nodes,
|
nodes: nodes.nodes,
|
||||||
edges,
|
edges: nodes.edges,
|
||||||
workflow,
|
workflow,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ export const addEnqueueRequestedNodes = (startAppListening: AppStartListening) =
|
|||||||
batch: {
|
batch: {
|
||||||
graph,
|
graph,
|
||||||
workflow: builtWorkflow,
|
workflow: builtWorkflow,
|
||||||
runs: state.generation.iterations,
|
runs: state.params.iterations,
|
||||||
|
origin: 'workflows',
|
||||||
},
|
},
|
||||||
prepend: action.payload.prepend,
|
prepend: action.payload.prepend,
|
||||||
};
|
};
|
||||||
|
@ -14,9 +14,9 @@ export const addEnqueueRequestedUpscale = (startAppListening: AppStartListening)
|
|||||||
const { shouldShowProgressInViewer } = state.ui;
|
const { shouldShowProgressInViewer } = state.ui;
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
|
|
||||||
const graph = await buildMultidiffusionUpscaleGraph(state);
|
const { g, noise, posCond } = await buildMultidiffusionUpscaleGraph(state);
|
||||||
|
|
||||||
const batchConfig = prepareLinearUIBatch(state, graph, prepend);
|
const batchConfig = prepareLinearUIBatch(state, g, prepend, noise, posCond);
|
||||||
|
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
queueApi.endpoints.enqueueBatch.initiate(batchConfig, {
|
||||||
|
@ -27,7 +27,7 @@ export const galleryImageClicked = createAction<{
|
|||||||
export const addGalleryImageClickedListener = (startAppListening: AppStartListening) => {
|
export const addGalleryImageClickedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: galleryImageClicked,
|
actionCreator: galleryImageClicked,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const { imageDTO, shiftKey, ctrlKey, metaKey, altKey } = action.payload;
|
const { imageDTO, shiftKey, ctrlKey, metaKey, altKey } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const queryArgs = selectListImagesQueryArgs(state);
|
const queryArgs = selectListImagesQueryArgs(state);
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
|
import type { SerializableObject } from 'common/types';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { parseSchema } from 'features/nodes/util/schema/parseSchema';
|
import { parseSchema } from 'features/nodes/util/schema/parseSchema';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
|
const log = logger('system');
|
||||||
|
|
||||||
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
|
export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
|
matcher: appInfoApi.endpoints.getOpenAPISchema.matchFulfilled,
|
||||||
effect: (action, { getState }) => {
|
effect: (action, { getState }) => {
|
||||||
const log = logger('system');
|
|
||||||
const schemaJSON = action.payload;
|
const schemaJSON = action.payload;
|
||||||
|
|
||||||
log.debug({ schemaJSON: parseify(schemaJSON) }, 'Received OpenAPI schema');
|
log.debug({ schemaJSON: parseify(schemaJSON) } as SerializableObject, 'Received OpenAPI schema');
|
||||||
const { nodesAllowlist, nodesDenylist } = getState().config;
|
const { nodesAllowlist, nodesDenylist } = getState().config;
|
||||||
|
|
||||||
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
||||||
|
|
||||||
log.debug({ nodeTemplates: parseify(nodeTemplates) }, `Built ${size(nodeTemplates)} node templates`);
|
log.debug({ nodeTemplates } as SerializableObject, `Built ${size(nodeTemplates)} node templates`);
|
||||||
|
|
||||||
$templates.set(nodeTemplates);
|
$templates.set(nodeTemplates);
|
||||||
},
|
},
|
||||||
@ -30,8 +33,7 @@ export const addGetOpenAPISchemaListener = (startAppListening: AppStartListening
|
|||||||
// If action.meta.condition === true, the request was canceled/skipped because another request was in flight or
|
// If action.meta.condition === true, the request was canceled/skipped because another request was in flight or
|
||||||
// the value was already in the cache. We don't want to log these errors.
|
// the value was already in the cache. We don't want to log these errors.
|
||||||
if (!action.meta.condition) {
|
if (!action.meta.condition) {
|
||||||
const log = logger('system');
|
log.error({ error: serializeError(action.error) }, 'Problem retrieving OpenAPI Schema');
|
||||||
log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,15 +2,13 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
const log = logger('gallery');
|
||||||
|
|
||||||
export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.addImageToBoard.matchFulfilled,
|
matcher: imagesApi.endpoints.addImageToBoard.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
// TODO: update listImages cache for this board
|
|
||||||
|
|
||||||
log.debug({ board_id, imageDTO }, 'Image added to board');
|
log.debug({ board_id, imageDTO }, 'Image added to board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -18,9 +16,7 @@ export const addImageAddedToBoardFulfilledListener = (startAppListening: AppStar
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.addImageToBoard.matchRejected,
|
matcher: imagesApi.endpoints.addImageToBoard.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
const { board_id, imageDTO } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ board_id, imageDTO }, 'Problem adding image to board');
|
log.debug({ board_id, imageDTO }, 'Problem adding image to board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,9 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { entityDeleted, ipaImageChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
import {
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
controlAdapterImageChanged,
|
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
controlAdapterProcessedImageChanged,
|
|
||||||
selectControlAdapterAll,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
||||||
import {
|
|
||||||
isControlAdapterLayer,
|
|
||||||
isInitialImageLayer,
|
|
||||||
isIPAdapterLayer,
|
|
||||||
isRegionalGuidanceLayer,
|
|
||||||
layerDeleted,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
import { isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
@ -26,6 +15,10 @@ import { forEach, intersectionBy } from 'lodash-es';
|
|||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import type { ImageDTO } from 'services/api/types';
|
import type { ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
|
const log = logger('gallery');
|
||||||
|
|
||||||
|
//TODO(psyche): handle image deletion (canvas sessions?)
|
||||||
|
|
||||||
// Some utils to delete images from different parts of the app
|
// Some utils to delete images from different parts of the app
|
||||||
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.nodes.present.nodes.forEach((node) => {
|
state.nodes.present.nodes.forEach((node) => {
|
||||||
@ -47,52 +40,37 @@ const deleteNodesImages = (state: RootState, dispatch: AppDispatch, imageDTO: Im
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
// const deleteControlAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
forEach(selectControlAdapterAll(state.controlAdapters), (ca) => {
|
// state.canvas.present.controlAdapters.entities.forEach(({ id, imageObject, processedImageObject }) => {
|
||||||
if (
|
// if (
|
||||||
ca.controlImage === imageDTO.image_name ||
|
// imageObject?.image.image_name === imageDTO.image_name ||
|
||||||
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
// processedImageObject?.image.image_name === imageDTO.image_name
|
||||||
) {
|
// ) {
|
||||||
dispatch(
|
// dispatch(caImageChanged({ id, imageDTO: null }));
|
||||||
controlAdapterImageChanged({
|
// dispatch(caProcessedImageChanged({ id, imageDTO: null }));
|
||||||
id: ca.id,
|
// }
|
||||||
controlImage: null,
|
// });
|
||||||
})
|
// };
|
||||||
);
|
|
||||||
dispatch(
|
const deleteIPAdapterImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
controlAdapterProcessedImageChanged({
|
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
|
||||||
id: ca.id,
|
if (entity.ipAdapter.image?.image_name === imageDTO.image_name) {
|
||||||
processedControlImage: null,
|
dispatch(ipaImageChanged({ entityIdentifier: getEntityIdentifier(entity), imageDTO: null }));
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteControlLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
const deleteLayerImages = (state: RootState, dispatch: AppDispatch, imageDTO: ImageDTO) => {
|
||||||
state.controlLayers.present.layers.forEach((l) => {
|
selectCanvasSlice(state).rasterLayers.entities.forEach(({ id, objects }) => {
|
||||||
if (isRegionalGuidanceLayer(l)) {
|
let shouldDelete = false;
|
||||||
if (l.ipAdapters.some((ipa) => ipa.image?.name === imageDTO.image_name)) {
|
for (const obj of objects) {
|
||||||
dispatch(layerDeleted(l.id));
|
if (obj.type === 'image' && obj.image.image_name === imageDTO.image_name) {
|
||||||
|
shouldDelete = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isControlAdapterLayer(l)) {
|
if (shouldDelete) {
|
||||||
if (
|
dispatch(entityDeleted({ entityIdentifier: { id, type: 'raster_layer' } }));
|
||||||
l.controlAdapter.image?.name === imageDTO.image_name ||
|
|
||||||
l.controlAdapter.processedImage?.name === imageDTO.image_name
|
|
||||||
) {
|
|
||||||
dispatch(layerDeleted(l.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isIPAdapterLayer(l)) {
|
|
||||||
if (l.ipAdapter.image?.name === imageDTO.image_name) {
|
|
||||||
dispatch(layerDeleted(l.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isInitialImageLayer(l)) {
|
|
||||||
if (l.image?.name === imageDTO.image_name) {
|
|
||||||
dispatch(layerDeleted(l.id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -145,14 +123,10 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
|
||||||
if (imageUsage.isCanvasImage) {
|
|
||||||
dispatch(resetCanvas());
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
deleteControlLayerImages(state, dispatch, imageDTO);
|
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
|
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||||
|
deleteLayerImages(state, dispatch, imageDTO);
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
} finally {
|
} finally {
|
||||||
@ -189,14 +163,11 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
|
|
||||||
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
// We need to reset the features where the image is in use - none of these work if their image(s) don't exist
|
||||||
|
|
||||||
if (imagesUsage.some((i) => i.isCanvasImage)) {
|
|
||||||
dispatch(resetCanvas());
|
|
||||||
}
|
|
||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
deleteControlAdapterImages(state, dispatch, imageDTO);
|
|
||||||
deleteNodesImages(state, dispatch, imageDTO);
|
deleteNodesImages(state, dispatch, imageDTO);
|
||||||
deleteControlLayerImages(state, dispatch, imageDTO);
|
// deleteControlAdapterImages(state, dispatch, imageDTO);
|
||||||
|
deleteIPAdapterImages(state, dispatch, imageDTO);
|
||||||
|
deleteLayerImages(state, dispatch, imageDTO);
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
@ -220,7 +191,6 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteImage.matchFulfilled,
|
matcher: imagesApi.endpoints.deleteImage.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Image deleted');
|
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Image deleted');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -228,7 +198,6 @@ export const addImageDeletionListeners = (startAppListening: AppStartListening)
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,28 +1,19 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
|
||||||
import {
|
import {
|
||||||
controlAdapterImageChanged,
|
controlLayerAdded,
|
||||||
controlAdapterIsEnabledChanged,
|
ipaImageChanged,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
rasterLayerAdded,
|
||||||
import {
|
rgIPAdapterImageChanged,
|
||||||
caLayerImageChanged,
|
} from 'features/controlLayers/store/canvasSlice';
|
||||||
iiLayerImageChanged,
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
ipaLayerImageChanged,
|
import type { CanvasControlLayerState, CanvasRasterLayerState } from 'features/controlLayers/store/types';
|
||||||
rgLayerIPAdapterImageChanged,
|
import { imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
||||||
import {
|
import { imageToCompareChanged, isImageViewerOpenChanged, selectionChanged } from 'features/gallery/store/gallerySlice';
|
||||||
imageSelected,
|
|
||||||
imageToCompareChanged,
|
|
||||||
isImageViewerOpenChanged,
|
|
||||||
selectionChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
@ -31,11 +22,12 @@ export const dndDropped = createAction<{
|
|||||||
activeData: TypesafeDraggableData;
|
activeData: TypesafeDraggableData;
|
||||||
}>('dnd/dndDropped');
|
}>('dnd/dndDropped');
|
||||||
|
|
||||||
|
const log = logger('system');
|
||||||
|
|
||||||
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
export const addImageDroppedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: dndDropped,
|
actionCreator: dndDropped,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const log = logger('dnd');
|
|
||||||
const { activeData, overData } = action.payload;
|
const { activeData, overData } = action.payload;
|
||||||
if (!isValidDrop(overData, activeData)) {
|
if (!isValidDrop(overData, activeData)) {
|
||||||
return;
|
return;
|
||||||
@ -46,80 +38,22 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
||||||
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
||||||
} else if (activeData.payloadType === 'NODE_FIELD') {
|
} else if (activeData.payloadType === 'NODE_FIELD') {
|
||||||
log.debug({ activeData: parseify(activeData), overData: parseify(overData) }, 'Node field dropped');
|
log.debug({ activeData, overData }, 'Node field dropped');
|
||||||
} else {
|
} else {
|
||||||
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on current image
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'SET_CURRENT_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
dispatch(imageSelected(activeData.payload.imageDTO));
|
|
||||||
dispatch(isImageViewerOpenChanged(true));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on ControlNet
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'SET_CONTROL_ADAPTER_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
const { id } = overData.context;
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id,
|
|
||||||
controlImage: activeData.payload.imageDTO.image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
controlAdapterIsEnabledChanged({
|
|
||||||
id,
|
|
||||||
isEnabled: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image dropped on Control Adapter Layer
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
overData.actionType === 'SET_CA_LAYER_IMAGE' &&
|
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
activeData.payload.imageDTO
|
|
||||||
) {
|
|
||||||
const { layerId } = overData.context;
|
|
||||||
dispatch(
|
|
||||||
caLayerImageChanged({
|
|
||||||
layerId,
|
|
||||||
imageDTO: activeData.payload.imageDTO,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on IP Adapter Layer
|
* Image dropped on IP Adapter Layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_IPA_LAYER_IMAGE' &&
|
overData.actionType === 'SET_IPA_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { layerId } = overData.context;
|
const { id } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
ipaLayerImageChanged({
|
ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO: activeData.payload.imageDTO })
|
||||||
layerId,
|
|
||||||
imageDTO: activeData.payload.imageDTO,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,14 +62,14 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
* Image dropped on RG Layer IP Adapter
|
* Image dropped on RG Layer IP Adapter
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_RG_LAYER_IP_ADAPTER_IMAGE' &&
|
overData.actionType === 'SET_RG_IP_ADAPTER_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { layerId, ipAdapterId } = overData.context;
|
const { id, ipAdapterId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
rgLayerIPAdapterImageChanged({
|
rgIPAdapterImageChanged({
|
||||||
layerId,
|
entityIdentifier: { id, type: 'regional_guidance' },
|
||||||
ipAdapterId,
|
ipAdapterId,
|
||||||
imageDTO: activeData.payload.imageDTO,
|
imageDTO: activeData.payload.imageDTO,
|
||||||
})
|
})
|
||||||
@ -144,32 +78,38 @@ export const addImageDroppedListener = (startAppListening: AppStartListening) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on II Layer Image
|
* Image dropped on Raster layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_II_LAYER_IMAGE' &&
|
overData.actionType === 'ADD_RASTER_LAYER_FROM_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
const { layerId } = overData.context;
|
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
||||||
dispatch(
|
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
||||||
iiLayerImageChanged({
|
const overrides: Partial<CanvasRasterLayerState> = {
|
||||||
layerId,
|
objects: [imageObject],
|
||||||
imageDTO: activeData.payload.imageDTO,
|
position: { x, y },
|
||||||
})
|
};
|
||||||
);
|
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image dropped on Canvas
|
* Image dropped on Raster layer
|
||||||
*/
|
*/
|
||||||
if (
|
if (
|
||||||
overData.actionType === 'SET_CANVAS_INITIAL_IMAGE' &&
|
overData.actionType === 'ADD_CONTROL_LAYER_FROM_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(setInitialCanvasImage(activeData.payload.imageDTO, selectOptimalDimension(getState())));
|
const imageObject = imageDTOToImageObject(activeData.payload.imageDTO);
|
||||||
|
const { x, y } = selectCanvasSlice(getState()).bbox.rect;
|
||||||
|
const overrides: Partial<CanvasControlLayerState> = {
|
||||||
|
objects: [imageObject],
|
||||||
|
position: { x, y },
|
||||||
|
};
|
||||||
|
dispatch(controlLayerAdded({ overrides, isSelected: true }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,13 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
const log = logger('gallery');
|
||||||
|
|
||||||
export const addImageRemovedFromBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageRemovedFromBoardFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.removeImageFromBoard.matchFulfilled,
|
matcher: imagesApi.endpoints.removeImageFromBoard.matchFulfilled,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
const imageDTO = action.meta.arg.originalArgs;
|
const imageDTO = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ imageDTO }, 'Image removed from board');
|
log.debug({ imageDTO }, 'Image removed from board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -16,9 +16,7 @@ export const addImageRemovedFromBoardFulfilledListener = (startAppListening: App
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.removeImageFromBoard.matchRejected,
|
matcher: imagesApi.endpoints.removeImageFromBoard.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
const imageDTO = action.meta.arg.originalArgs;
|
const imageDTO = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
log.debug({ imageDTO }, 'Problem removing image from board');
|
log.debug({ imageDTO }, 'Problem removing image from board');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -6,16 +6,17 @@ import { imagesToDeleteSelected, isModalOpenChanged } from 'features/deleteImage
|
|||||||
export const addImageToDeleteSelectedListener = (startAppListening: AppStartListening) => {
|
export const addImageToDeleteSelectedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imagesToDeleteSelected,
|
actionCreator: imagesToDeleteSelected,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const imageDTOs = action.payload;
|
const imageDTOs = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { shouldConfirmOnDelete } = state.system;
|
const { shouldConfirmOnDelete } = state.system;
|
||||||
const imagesUsage = selectImageUsage(getState());
|
const imagesUsage = selectImageUsage(getState());
|
||||||
|
|
||||||
const isImageInUse =
|
const isImageInUse =
|
||||||
imagesUsage.some((i) => i.isCanvasImage) ||
|
imagesUsage.some((i) => i.isLayerImage) ||
|
||||||
imagesUsage.some((i) => i.isControlImage) ||
|
imagesUsage.some((i) => i.isControlAdapterImage) ||
|
||||||
imagesUsage.some((i) => i.isNodesImage);
|
imagesUsage.some((i) => i.isIPAdapterImage) ||
|
||||||
|
imagesUsage.some((i) => i.isLayerImage);
|
||||||
|
|
||||||
if (shouldConfirmOnDelete || isImageInUse) {
|
if (shouldConfirmOnDelete || isImageInUse) {
|
||||||
dispatch(isModalOpenChanged(true));
|
dispatch(isModalOpenChanged(true));
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { ipaImageChanged, rgIPAdapterImageChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
import {
|
|
||||||
controlAdapterImageChanged,
|
|
||||||
controlAdapterIsEnabledChanged,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import {
|
|
||||||
caLayerImageChanged,
|
|
||||||
iiLayerImageChanged,
|
|
||||||
ipaLayerImageChanged,
|
|
||||||
rgLayerIPAdapterImageChanged,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
import { selectListBoardsQueryArgs } from 'features/gallery/store/gallerySelectors';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
|
||||||
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
import { upscaleInitialImageChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
@ -21,11 +10,12 @@ import { omit } from 'lodash-es';
|
|||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
|
const log = logger('gallery');
|
||||||
|
|
||||||
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
|
export const addImageUploadedFulfilledListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
|
matcher: imagesApi.endpoints.uploadImage.matchFulfilled,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const log = logger('images');
|
|
||||||
const imageDTO = action.payload;
|
const imageDTO = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { autoAddBoardId } = state.gallery;
|
const { autoAddBoardId } = state.gallery;
|
||||||
@ -81,15 +71,6 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
|
||||||
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
|
|
||||||
toast({
|
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: t('toast.setAsCanvasInitialImage'),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
|
if (postUploadAction?.type === 'SET_UPSCALE_INITIAL_IMAGE') {
|
||||||
dispatch(upscaleInitialImageChanged(imageDTO));
|
dispatch(upscaleInitialImageChanged(imageDTO));
|
||||||
toast({
|
toast({
|
||||||
@ -99,70 +80,33 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CONTROL_ADAPTER_IMAGE') {
|
// if (postUploadAction?.type === 'SET_CA_IMAGE') {
|
||||||
|
// const { id } = postUploadAction;
|
||||||
|
// dispatch(caImageChanged({ id, imageDTO }));
|
||||||
|
// toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (postUploadAction?.type === 'SET_IPA_IMAGE') {
|
||||||
const { id } = postUploadAction;
|
const { id } = postUploadAction;
|
||||||
dispatch(
|
dispatch(ipaImageChanged({ entityIdentifier: { id, type: 'ip_adapter' }, imageDTO }));
|
||||||
controlAdapterIsEnabledChanged({
|
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||||
id,
|
|
||||||
isEnabled: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageChanged({
|
|
||||||
id,
|
|
||||||
controlImage: imageDTO.image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
toast({
|
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: t('toast.setControlImage'),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CA_LAYER_IMAGE') {
|
if (postUploadAction?.type === 'SET_RG_IP_ADAPTER_IMAGE') {
|
||||||
const { layerId } = postUploadAction;
|
const { id, ipAdapterId } = postUploadAction;
|
||||||
dispatch(caLayerImageChanged({ layerId, imageDTO }));
|
dispatch(
|
||||||
toast({
|
rgIPAdapterImageChanged({ entityIdentifier: { id, type: 'regional_guidance' }, ipAdapterId, imageDTO })
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
);
|
||||||
description: t('toast.setControlImage'),
|
toast({ ...DEFAULT_UPLOADED_TOAST, description: t('toast.setControlImage') });
|
||||||
});
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_IPA_LAYER_IMAGE') {
|
|
||||||
const { layerId } = postUploadAction;
|
|
||||||
dispatch(ipaLayerImageChanged({ layerId, imageDTO }));
|
|
||||||
toast({
|
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: t('toast.setControlImage'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_RG_LAYER_IP_ADAPTER_IMAGE') {
|
|
||||||
const { layerId, ipAdapterId } = postUploadAction;
|
|
||||||
dispatch(rgLayerIPAdapterImageChanged({ layerId, ipAdapterId, imageDTO }));
|
|
||||||
toast({
|
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: t('toast.setControlImage'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_II_LAYER_IMAGE') {
|
|
||||||
const { layerId } = postUploadAction;
|
|
||||||
dispatch(iiLayerImageChanged({ layerId, imageDTO }));
|
|
||||||
toast({
|
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: t('toast.setControlImage'),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||||
const { nodeId, fieldName } = postUploadAction;
|
const { nodeId, fieldName } = postUploadAction;
|
||||||
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
||||||
toast({
|
toast({ ...DEFAULT_UPLOADED_TOAST, description: `${t('toast.setNodeField')} ${fieldName}` });
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
|
||||||
description: `${t('toast.setNodeField')} ${fieldName}`,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -171,7 +115,6 @@ export const addImageUploadedFulfilledListener = (startAppListening: AppStartLis
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.uploadImage.matchRejected,
|
matcher: imagesApi.endpoints.uploadImage.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
|
||||||
const sanitizedData = {
|
const sanitizedData = {
|
||||||
arg: {
|
arg: {
|
||||||
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),
|
...omit(action.meta.arg.originalArgs, ['file', 'postUploadAction']),
|
||||||
|
@ -6,7 +6,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
export const addImagesStarredListener = (startAppListening: AppStartListening) => {
|
export const addImagesStarredListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.starImages.matchFulfilled,
|
matcher: imagesApi.endpoints.starImages.matchFulfilled,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const { updated_image_names: starredImages } = action.payload;
|
const { updated_image_names: starredImages } = action.payload;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -6,7 +6,7 @@ import type { ImageDTO } from 'services/api/types';
|
|||||||
export const addImagesUnstarredListener = (startAppListening: AppStartListening) => {
|
export const addImagesUnstarredListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.unstarImages.matchFulfilled,
|
matcher: imagesApi.endpoints.unstarImages.matchFulfilled,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const { updated_image_names: unstarredImages } = action.payload;
|
const { updated_image_names: unstarredImages } = action.payload;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import {
|
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
||||||
controlAdapterIsEnabledChanged,
|
import { modelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
||||||
selectControlAdapterAll,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
|
||||||
import { modelSelected } from 'features/parameters/store/actions';
|
import { modelSelected } from 'features/parameters/store/actions';
|
||||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
|
||||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { forEach } from 'lodash-es';
|
|
||||||
|
const log = logger('models');
|
||||||
|
|
||||||
export const addModelSelectedListener = (startAppListening: AppStartListening) => {
|
export const addModelSelectedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: modelSelected,
|
actionCreator: modelSelected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
const log = logger('models');
|
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const result = zParameterModel.safeParse(action.payload);
|
const result = zParameterModel.safeParse(action.payload);
|
||||||
|
|
||||||
@ -29,34 +24,36 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
const newModel = result.data;
|
const newModel = result.data;
|
||||||
|
|
||||||
const newBaseModel = newModel.base;
|
const newBaseModel = newModel.base;
|
||||||
const didBaseModelChange = state.generation.model?.base !== newBaseModel;
|
const didBaseModelChange = state.params.model?.base !== newBaseModel;
|
||||||
|
|
||||||
if (didBaseModelChange) {
|
if (didBaseModelChange) {
|
||||||
// we may need to reset some incompatible submodels
|
// we may need to reset some incompatible submodels
|
||||||
let modelsCleared = 0;
|
let modelsCleared = 0;
|
||||||
|
|
||||||
// handle incompatible loras
|
// handle incompatible loras
|
||||||
forEach(state.lora.loras, (lora, id) => {
|
state.loras.loras.forEach((lora) => {
|
||||||
if (lora.model.base !== newBaseModel) {
|
if (lora.model.base !== newBaseModel) {
|
||||||
dispatch(loraRemoved(id));
|
dispatch(loraDeleted({ id: lora.id }));
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle incompatible vae
|
// handle incompatible vae
|
||||||
const { vae } = state.generation;
|
const { vae } = state.params;
|
||||||
if (vae && vae.base !== newBaseModel) {
|
if (vae && vae.base !== newBaseModel) {
|
||||||
dispatch(vaeSelected(null));
|
dispatch(vaeSelected(null));
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle incompatible controlnets
|
// handle incompatible controlnets
|
||||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
// state.canvas.present.controlAdapters.entities.forEach((ca) => {
|
||||||
if (ca.model?.base !== newBaseModel) {
|
// if (ca.model?.base !== newBaseModel) {
|
||||||
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
// modelsCleared += 1;
|
||||||
modelsCleared += 1;
|
// if (ca.isEnabled) {
|
||||||
}
|
// dispatch(entityIsEnabledToggled({ entityIdentifier: { id: ca.id, type: 'control_adapter' } }));
|
||||||
});
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
if (modelsCleared > 0) {
|
if (modelsCleared > 0) {
|
||||||
toast({
|
toast({
|
||||||
@ -70,7 +67,7 @@ export const addModelSelectedListener = (startAppListening: AppStartListening) =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(modelChanged(newModel, state.generation.model));
|
dispatch(modelChanged({ model: newModel, previousModel: state.params.model }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,36 +1,42 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
import type { JSONObject } from 'common/types';
|
import type { SerializableObject } from 'common/types';
|
||||||
import {
|
import {
|
||||||
controlAdapterModelCleared,
|
bboxHeightChanged,
|
||||||
selectControlAdapterAll,
|
bboxWidthChanged,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
controlLayerModelChanged,
|
||||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
ipaModelChanged,
|
||||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
rgIPAdapterModelChanged,
|
||||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
} from 'features/controlLayers/store/canvasSlice';
|
||||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
import { loraDeleted } from 'features/controlLayers/store/lorasSlice';
|
||||||
|
import { modelChanged, refinerModelChanged, vaeSelected } from 'features/controlLayers/store/paramsSlice';
|
||||||
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
|
import { getEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
|
||||||
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
|
||||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||||
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
|
||||||
import { forEach } from 'lodash-es';
|
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, modelsApi } from 'services/api/endpoints/models';
|
||||||
import type { AnyModelConfig } from 'services/api/types';
|
import type { AnyModelConfig } from 'services/api/types';
|
||||||
import {
|
import {
|
||||||
|
isControlNetOrT2IAdapterModelConfig,
|
||||||
|
isIPAdapterModelConfig,
|
||||||
|
isLoRAModelConfig,
|
||||||
isNonRefinerMainModelConfig,
|
isNonRefinerMainModelConfig,
|
||||||
isRefinerMainModelModelConfig,
|
isRefinerMainModelModelConfig,
|
||||||
isSpandrelImageToImageModelConfig,
|
isSpandrelImageToImageModelConfig,
|
||||||
isVAEModelConfig,
|
isVAEModelConfig,
|
||||||
} from 'services/api/types';
|
} from 'services/api/types';
|
||||||
|
|
||||||
|
const log = logger('models');
|
||||||
|
|
||||||
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
export const addModelsLoadedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
predicate: modelsApi.endpoints.getModelConfigs.matchFulfilled,
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||||
const log = logger('models');
|
|
||||||
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
log.info({ models: action.payload.entities }, `Models loaded (${action.payload.ids.length})`);
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -43,6 +49,7 @@ export const addModelsLoadedListener = (startAppListening: AppStartListening) =>
|
|||||||
handleLoRAModels(models, state, dispatch, log);
|
handleLoRAModels(models, state, dispatch, log);
|
||||||
handleControlAdapterModels(models, state, dispatch, log);
|
handleControlAdapterModels(models, state, dispatch, log);
|
||||||
handleSpandrelImageToImageModels(models, state, dispatch, log);
|
handleSpandrelImageToImageModels(models, state, dispatch, log);
|
||||||
|
handleIPAdapterModels(models, state, dispatch, log);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -51,15 +58,15 @@ type ModelHandler = (
|
|||||||
models: AnyModelConfig[],
|
models: AnyModelConfig[],
|
||||||
state: RootState,
|
state: RootState,
|
||||||
dispatch: AppDispatch,
|
dispatch: AppDispatch,
|
||||||
log: Logger<JSONObject>
|
log: Logger<SerializableObject>
|
||||||
) => undefined;
|
) => undefined;
|
||||||
|
|
||||||
const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||||
const currentModel = state.generation.model;
|
const currentModel = state.params.model;
|
||||||
const mainModels = models.filter(isNonRefinerMainModelConfig);
|
const mainModels = models.filter(isNonRefinerMainModelConfig);
|
||||||
if (mainModels.length === 0) {
|
if (mainModels.length === 0) {
|
||||||
// No models loaded at all
|
// No models loaded at all
|
||||||
dispatch(modelChanged(null));
|
dispatch(modelChanged({ model: null }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,25 +81,16 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
if (defaultModelInList) {
|
if (defaultModelInList) {
|
||||||
const result = zParameterModel.safeParse(defaultModelInList);
|
const result = zParameterModel.safeParse(defaultModelInList);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
dispatch(modelChanged(defaultModelInList, currentModel));
|
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
|
||||||
|
const { bbox } = selectCanvasSlice(state);
|
||||||
const optimalDimension = getOptimalDimension(defaultModelInList);
|
const optimalDimension = getOptimalDimension(defaultModelInList);
|
||||||
if (
|
if (getIsSizeOptimal(bbox.rect.width, bbox.rect.height, optimalDimension)) {
|
||||||
getIsSizeOptimal(
|
|
||||||
state.controlLayers.present.size.width,
|
|
||||||
state.controlLayers.present.size.height,
|
|
||||||
optimalDimension
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { width, height } = calculateNewSize(
|
const { width, height } = calculateNewSize(bbox.aspectRatio.value, optimalDimension * optimalDimension);
|
||||||
state.controlLayers.present.size.aspectRatio.value,
|
|
||||||
optimalDimension * optimalDimension
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(widthChanged({ width }));
|
dispatch(bboxWidthChanged({ width }));
|
||||||
dispatch(heightChanged({ height }));
|
dispatch(bboxHeightChanged({ height }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,11 +102,11 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(modelChanged(result.data, currentModel));
|
dispatch(modelChanged({ model: result.data, previousModel: currentModel }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const currentRefinerModel = state.sdxl.refinerModel;
|
const currentRefinerModel = state.params.refinerModel;
|
||||||
const refinerModels = models.filter(isRefinerMainModelModelConfig);
|
const refinerModels = models.filter(isRefinerMainModelModelConfig);
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
// No models loaded at all
|
// No models loaded at all
|
||||||
@ -127,7 +125,7 @@ const handleRefinerModels: ModelHandler = (models, state, dispatch, _log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
||||||
const currentVae = state.generation.vae;
|
const currentVae = state.params.vae;
|
||||||
|
|
||||||
if (currentVae === null) {
|
if (currentVae === null) {
|
||||||
// null is a valid VAE! it means "use the default with the main model"
|
// null is a valid VAE! it means "use the default with the main model"
|
||||||
@ -160,28 +158,47 @@ const handleVAEModels: ModelHandler = (models, state, dispatch, log) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleLoRAModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
const loras = state.lora.loras;
|
const loraModels = models.filter(isLoRAModelConfig);
|
||||||
|
state.loras.loras.forEach((lora) => {
|
||||||
forEach(loras, (lora, id) => {
|
const isLoRAAvailable = loraModels.some((m) => m.key === lora.model.key);
|
||||||
const isLoRAAvailable = models.some((m) => m.key === lora.model.key);
|
|
||||||
|
|
||||||
if (isLoRAAvailable) {
|
if (isLoRAAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
dispatch(loraDeleted({ id: lora.id }));
|
||||||
dispatch(loraRemoved(id));
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
const handleControlAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
const caModels = models.filter(isControlNetOrT2IAdapterModelConfig);
|
||||||
const isModelAvailable = models.some((m) => m.key === ca.model?.key);
|
selectCanvasSlice(state).controlLayers.entities.forEach((entity) => {
|
||||||
|
const isModelAvailable = caModels.some((m) => m.key === entity.controlAdapter.model?.key);
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
dispatch(controlLayerModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
dispatch(controlAdapterModelCleared({ id: ca.id }));
|
const handleIPAdapterModels: ModelHandler = (models, state, dispatch, _log) => {
|
||||||
|
const ipaModels = models.filter(isIPAdapterModelConfig);
|
||||||
|
selectCanvasSlice(state).ipAdapters.entities.forEach((entity) => {
|
||||||
|
const isModelAvailable = ipaModels.some((m) => m.key === entity.ipAdapter.model?.key);
|
||||||
|
if (isModelAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(ipaModelChanged({ entityIdentifier: getEntityIdentifier(entity), modelConfig: null }));
|
||||||
|
});
|
||||||
|
|
||||||
|
selectCanvasSlice(state).regions.entities.forEach((entity) => {
|
||||||
|
entity.ipAdapters.forEach(({ id: ipAdapterId, model }) => {
|
||||||
|
const isModelAvailable = ipaModels.some((m) => m.key === model?.key);
|
||||||
|
if (isModelAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
rgIPAdapterModelChanged({ entityIdentifier: getEntityIdentifier(entity), ipAdapterId, modelConfig: null })
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { positivePromptChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { positivePromptChanged } from 'features/controlLayers/store/paramsSlice';
|
||||||
import {
|
import {
|
||||||
combinatorialToggled,
|
combinatorialToggled,
|
||||||
isErrorChanged,
|
isErrorChanged,
|
||||||
@ -15,7 +15,7 @@ import { getPresetModifiedPrompts } from 'features/nodes/util/graph/graphBuilder
|
|||||||
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
import { activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
import { stylePresetsApi } from 'services/api/endpoints/stylePresets';
|
||||||
import { utilitiesApi } from 'services/api/endpoints/utilities';
|
import { utilitiesApi } from 'services/api/endpoints/utilities';
|
||||||
import { socketConnected } from 'services/events/actions';
|
import { socketConnected } from 'services/events/setEventListeners';
|
||||||
|
|
||||||
const matcher = isAnyOf(
|
const matcher = isAnyOf(
|
||||||
positivePromptChanged,
|
positivePromptChanged,
|
||||||
@ -24,8 +24,6 @@ const matcher = isAnyOf(
|
|||||||
maxPromptsReset,
|
maxPromptsReset,
|
||||||
socketConnected,
|
socketConnected,
|
||||||
activeStylePresetIdChanged,
|
activeStylePresetIdChanged,
|
||||||
stylePresetsApi.endpoints.deleteStylePreset.matchFulfilled,
|
|
||||||
stylePresetsApi.endpoints.updateStylePreset.matchFulfilled,
|
|
||||||
stylePresetsApi.endpoints.listStylePresets.matchFulfilled
|
stylePresetsApi.endpoints.listStylePresets.matchFulfilled
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { heightChanged, widthChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
|
||||||
import {
|
import {
|
||||||
setCfgRescaleMultiplier,
|
setCfgRescaleMultiplier,
|
||||||
setCfgScale,
|
setCfgScale,
|
||||||
@ -8,7 +7,8 @@ import {
|
|||||||
setSteps,
|
setSteps,
|
||||||
vaePrecisionChanged,
|
vaePrecisionChanged,
|
||||||
vaeSelected,
|
vaeSelected,
|
||||||
} from 'features/parameters/store/generationSlice';
|
} from 'features/controlLayers/store/paramsSlice';
|
||||||
|
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||||
import {
|
import {
|
||||||
isParameterCFGRescaleMultiplier,
|
isParameterCFGRescaleMultiplier,
|
||||||
isParameterCFGScale,
|
isParameterCFGScale,
|
||||||
@ -30,7 +30,7 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
|||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const currentModel = state.generation.model;
|
const currentModel = state.params.model;
|
||||||
|
|
||||||
if (!currentModel) {
|
if (!currentModel) {
|
||||||
return;
|
return;
|
||||||
@ -98,13 +98,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
|||||||
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
const setSizeOptions = { updateAspectRatio: true, clamp: true };
|
||||||
if (width) {
|
if (width) {
|
||||||
if (isParameterWidth(width)) {
|
if (isParameterWidth(width)) {
|
||||||
dispatch(widthChanged({ width, ...setSizeOptions }));
|
dispatch(bboxWidthChanged({ width, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (height) {
|
if (height) {
|
||||||
if (isParameterHeight(height)) {
|
if (isParameterHeight(height)) {
|
||||||
dispatch(heightChanged({ height, ...setSizeOptions }));
|
dispatch(bboxHeightChanged({ height, ...setSizeOptions }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ import { atom } from 'nanostores';
|
|||||||
import { api } from 'services/api';
|
import { api } from 'services/api';
|
||||||
import { modelsApi } from 'services/api/endpoints/models';
|
import { modelsApi } from 'services/api/endpoints/models';
|
||||||
import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
import { queueApi, selectQueueStatus } from 'services/api/endpoints/queue';
|
||||||
import { socketConnected } from 'services/events/actions';
|
import { socketConnected } from 'services/events/setEventListeners';
|
||||||
|
|
||||||
const log = logger('socketio');
|
const log = logger('events');
|
||||||
|
|
||||||
const $isFirstConnection = atom(true);
|
const $isFirstConnection = atom(true);
|
||||||
|
|
@ -1,14 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { socketDisconnected } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addSocketDisconnectedEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketDisconnected,
|
|
||||||
effect: () => {
|
|
||||||
log.debug('Disconnected');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,26 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
|
||||||
import { socketGeneratorProgress } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addGeneratorProgressEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketGeneratorProgress,
|
|
||||||
effect: (action) => {
|
|
||||||
log.trace(parseify(action.payload), `Generator progress`);
|
|
||||||
const { invocation_source_id, step, total_steps, progress_image } = action.payload.data;
|
|
||||||
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
|
||||||
if (nes) {
|
|
||||||
nes.status = zNodeStatus.enum.IN_PROGRESS;
|
|
||||||
nes.progress = (step + 1) / total_steps;
|
|
||||||
nes.progressImage = progress_image ?? null;
|
|
||||||
upsertExecutionState(nes.nodeId, nes);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,122 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
|
||||||
import {
|
|
||||||
boardIdSelected,
|
|
||||||
galleryViewChanged,
|
|
||||||
imageSelected,
|
|
||||||
isImageViewerOpenChanged,
|
|
||||||
offsetChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
|
||||||
import { CANVAS_OUTPUT } from 'features/nodes/util/graph/constants';
|
|
||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
import { getCategories, getListImagesUrl } from 'services/api/util';
|
|
||||||
import { socketInvocationComplete } from 'services/events/actions';
|
|
||||||
|
|
||||||
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
|
|
||||||
const nodeTypeDenylist = ['load_image', 'image'];
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addInvocationCompleteEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketInvocationComplete,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const { data } = action.payload;
|
|
||||||
log.debug({ data: parseify(data) }, `Invocation complete (${data.invocation.type})`);
|
|
||||||
|
|
||||||
const { result, invocation_source_id } = data;
|
|
||||||
// This complete event has an associated image output
|
|
||||||
if (data.result.type === 'image_output' && !nodeTypeDenylist.includes(data.invocation.type)) {
|
|
||||||
const { image_name } = data.result.image;
|
|
||||||
const { canvas, gallery } = getState();
|
|
||||||
|
|
||||||
// This populates the `getImageDTO` cache
|
|
||||||
const imageDTORequest = dispatch(
|
|
||||||
imagesApi.endpoints.getImageDTO.initiate(image_name, {
|
|
||||||
forceRefetch: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const imageDTO = await imageDTORequest.unwrap();
|
|
||||||
imageDTORequest.unsubscribe();
|
|
||||||
|
|
||||||
// Add canvas images to the staging area
|
|
||||||
if (canvas.batchIds.includes(data.batch_id) && data.invocation_source_id === CANVAS_OUTPUT) {
|
|
||||||
dispatch(addImageToStagingArea(imageDTO));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageDTO.is_intermediate) {
|
|
||||||
// update the total images for the board
|
|
||||||
dispatch(
|
|
||||||
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
draft.total += 1;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imagesApi.util.invalidateTags([
|
|
||||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
|
||||||
{
|
|
||||||
type: 'ImageList',
|
|
||||||
id: getListImagesUrl({
|
|
||||||
board_id: imageDTO.board_id ?? 'none',
|
|
||||||
categories: getCategories(imageDTO),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
const { shouldAutoSwitch } = gallery;
|
|
||||||
|
|
||||||
// If auto-switch is enabled, select the new image
|
|
||||||
if (shouldAutoSwitch) {
|
|
||||||
// if auto-add is enabled, switch the gallery view and board if needed as the image comes in
|
|
||||||
if (gallery.galleryView !== 'images') {
|
|
||||||
dispatch(galleryViewChanged('images'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageDTO.board_id && imageDTO.board_id !== gallery.selectedBoardId) {
|
|
||||||
dispatch(
|
|
||||||
boardIdSelected({
|
|
||||||
boardId: imageDTO.board_id,
|
|
||||||
selectedImageName: imageDTO.image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(offsetChanged({ offset: 0 }));
|
|
||||||
|
|
||||||
if (!imageDTO.board_id && gallery.selectedBoardId !== 'none') {
|
|
||||||
dispatch(
|
|
||||||
boardIdSelected({
|
|
||||||
boardId: 'none',
|
|
||||||
selectedImageName: imageDTO.image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(imageSelected(imageDTO));
|
|
||||||
dispatch(isImageViewerOpenChanged(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
|
||||||
if (nes) {
|
|
||||||
nes.status = zNodeStatus.enum.COMPLETED;
|
|
||||||
if (nes.progress !== null) {
|
|
||||||
nes.progress = 1;
|
|
||||||
}
|
|
||||||
nes.outputs.push(result);
|
|
||||||
upsertExecutionState(nes.nodeId, nes);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
|
||||||
import { socketInvocationError } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addInvocationErrorEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketInvocationError,
|
|
||||||
effect: (action) => {
|
|
||||||
const { invocation_source_id, invocation, error_type, error_message, error_traceback } = action.payload.data;
|
|
||||||
log.error(parseify(action.payload), `Invocation error (${invocation.type})`);
|
|
||||||
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
|
||||||
if (nes) {
|
|
||||||
nes.status = zNodeStatus.enum.FAILED;
|
|
||||||
nes.progress = null;
|
|
||||||
nes.progressImage = null;
|
|
||||||
nes.error = {
|
|
||||||
error_type,
|
|
||||||
error_message,
|
|
||||||
error_traceback,
|
|
||||||
};
|
|
||||||
upsertExecutionState(nes.nodeId, nes);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { parseify } from 'common/util/serialize';
|
|
||||||
import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
|
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
|
||||||
import { socketInvocationStarted } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addInvocationStartedEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketInvocationStarted,
|
|
||||||
effect: (action) => {
|
|
||||||
log.debug(parseify(action.payload), `Invocation started (${action.payload.data.invocation.type})`);
|
|
||||||
const { invocation_source_id } = action.payload.data;
|
|
||||||
const nes = deepClone($nodeExecutionStates.get()[invocation_source_id]);
|
|
||||||
if (nes) {
|
|
||||||
nes.status = zNodeStatus.enum.IN_PROGRESS;
|
|
||||||
upsertExecutionState(nes.nodeId, nes);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,196 +0,0 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { api, LIST_TAG } from 'services/api';
|
|
||||||
import { modelsApi } from 'services/api/endpoints/models';
|
|
||||||
import {
|
|
||||||
socketModelInstallCancelled,
|
|
||||||
socketModelInstallComplete,
|
|
||||||
socketModelInstallDownloadProgress,
|
|
||||||
socketModelInstallDownloadsComplete,
|
|
||||||
socketModelInstallDownloadStarted,
|
|
||||||
socketModelInstallError,
|
|
||||||
socketModelInstallStarted,
|
|
||||||
} from 'services/events/actions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A model install has two main stages - downloading and installing. All these events are namespaced under `model_install_`
|
|
||||||
* which is a bit misleading. For example, a `model_install_started` event is actually fired _after_ the model has fully
|
|
||||||
* downloaded and is being "physically" installed.
|
|
||||||
*
|
|
||||||
* Note: the download events are only fired for remote model installs, not local.
|
|
||||||
*
|
|
||||||
* Here's the expected flow:
|
|
||||||
* - API receives install request, model manager preps the install
|
|
||||||
* - `model_install_download_started` fired when the download starts
|
|
||||||
* - `model_install_download_progress` fired continually until the download is complete
|
|
||||||
* - `model_install_download_complete` fired when the download is complete
|
|
||||||
* - `model_install_started` fired when the "physical" installation starts
|
|
||||||
* - `model_install_complete` fired when the installation is complete
|
|
||||||
* - `model_install_cancelled` fired if the installation is cancelled
|
|
||||||
* - `model_install_error` fired if the installation has an error
|
|
||||||
*/
|
|
||||||
|
|
||||||
const selectModelInstalls = modelsApi.endpoints.listModelInstalls.select();
|
|
||||||
|
|
||||||
export const addModelInstallEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallDownloadStarted,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const { id } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'downloading';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallStarted,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const { id } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'running';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallDownloadProgress,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const { bytes, total_bytes, id } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.bytes = bytes;
|
|
||||||
modelImport.total_bytes = total_bytes;
|
|
||||||
modelImport.status = 'downloading';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallComplete,
|
|
||||||
effect: (action, { dispatch, getState }) => {
|
|
||||||
const { id } = action.payload.data;
|
|
||||||
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'completed';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelConfig', id: LIST_TAG }]));
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelScanFolderResults', id: LIST_TAG }]));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallError,
|
|
||||||
effect: (action, { dispatch, getState }) => {
|
|
||||||
const { id, error, error_type } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'error';
|
|
||||||
modelImport.error_reason = error_type;
|
|
||||||
modelImport.error = error;
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallCancelled,
|
|
||||||
effect: (action, { dispatch, getState }) => {
|
|
||||||
const { id } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'cancelled';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelInstallDownloadsComplete,
|
|
||||||
effect: (action, { dispatch, getState }) => {
|
|
||||||
const { id } = action.payload.data;
|
|
||||||
const { data } = selectModelInstalls(getState());
|
|
||||||
|
|
||||||
if (!data || !data.find((m) => m.id === id)) {
|
|
||||||
dispatch(api.util.invalidateTags([{ type: 'ModelInstalls' }]));
|
|
||||||
} else {
|
|
||||||
dispatch(
|
|
||||||
modelsApi.util.updateQueryData('listModelInstalls', undefined, (draft) => {
|
|
||||||
const modelImport = draft.find((m) => m.id === id);
|
|
||||||
if (modelImport) {
|
|
||||||
modelImport.status = 'downloads_done';
|
|
||||||
}
|
|
||||||
return draft;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,42 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { socketModelLoadComplete, socketModelLoadStarted } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addModelLoadEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelLoadStarted,
|
|
||||||
effect: (action) => {
|
|
||||||
const { config, submodel_type } = action.payload.data;
|
|
||||||
const { name, base, type } = config;
|
|
||||||
|
|
||||||
const extras: string[] = [base, type];
|
|
||||||
|
|
||||||
if (submodel_type) {
|
|
||||||
extras.push(submodel_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = `Model load started: ${name} (${extras.join(', ')})`;
|
|
||||||
|
|
||||||
log.debug(action.payload, message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketModelLoadComplete,
|
|
||||||
effect: (action) => {
|
|
||||||
const { config, submodel_type } = action.payload.data;
|
|
||||||
const { name, base, type } = config;
|
|
||||||
|
|
||||||
const extras: string[] = [base, type];
|
|
||||||
if (submodel_type) {
|
|
||||||
extras.push(submodel_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = `Model load complete: ${name} (${extras.join(', ')})`;
|
|
||||||
|
|
||||||
log.debug(action.payload, message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,114 +0,0 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { deepClone } from 'common/util/deepClone';
|
|
||||||
import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState';
|
|
||||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
|
||||||
import ErrorToastDescription, { getTitleFromErrorType } from 'features/toast/ErrorToastDescription';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { forEach } from 'lodash-es';
|
|
||||||
import { queueApi, queueItemsAdapter } from 'services/api/endpoints/queue';
|
|
||||||
import { socketQueueItemStatusChanged } from 'services/events/actions';
|
|
||||||
|
|
||||||
const log = logger('socketio');
|
|
||||||
|
|
||||||
export const addSocketQueueItemStatusChangedEventListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: socketQueueItemStatusChanged,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
// we've got new status for the queue item, batch and queue
|
|
||||||
const {
|
|
||||||
item_id,
|
|
||||||
session_id,
|
|
||||||
status,
|
|
||||||
started_at,
|
|
||||||
updated_at,
|
|
||||||
completed_at,
|
|
||||||
batch_status,
|
|
||||||
queue_status,
|
|
||||||
error_type,
|
|
||||||
error_message,
|
|
||||||
error_traceback,
|
|
||||||
} = action.payload.data;
|
|
||||||
|
|
||||||
log.debug(action.payload, `Queue item ${item_id} status updated: ${status}`);
|
|
||||||
|
|
||||||
// Update this specific queue item in the list of queue items (this is the queue item DTO, without the session)
|
|
||||||
dispatch(
|
|
||||||
queueApi.util.updateQueryData('listQueueItems', undefined, (draft) => {
|
|
||||||
queueItemsAdapter.updateOne(draft, {
|
|
||||||
id: String(item_id),
|
|
||||||
changes: {
|
|
||||||
status,
|
|
||||||
started_at,
|
|
||||||
updated_at: updated_at ?? undefined,
|
|
||||||
completed_at: completed_at ?? undefined,
|
|
||||||
error_type,
|
|
||||||
error_message,
|
|
||||||
error_traceback,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the queue status (we do not get the processor status here)
|
|
||||||
dispatch(
|
|
||||||
queueApi.util.updateQueryData('getQueueStatus', undefined, (draft) => {
|
|
||||||
if (!draft) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object.assign(draft.queue, queue_status);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the batch status
|
|
||||||
dispatch(
|
|
||||||
queueApi.util.updateQueryData('getBatchStatus', { batch_id: batch_status.batch_id }, () => batch_status)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Invalidate caches for things we cannot update
|
|
||||||
// TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again
|
|
||||||
dispatch(
|
|
||||||
queueApi.util.invalidateTags([
|
|
||||||
'CurrentSessionQueueItem',
|
|
||||||
'NextSessionQueueItem',
|
|
||||||
'InvocationCacheStatus',
|
|
||||||
{ type: 'SessionQueueItem', id: item_id },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
if (status === 'in_progress') {
|
|
||||||
forEach($nodeExecutionStates.get(), (nes) => {
|
|
||||||
if (!nes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const clone = deepClone(nes);
|
|
||||||
clone.status = zNodeStatus.enum.PENDING;
|
|
||||||
clone.error = null;
|
|
||||||
clone.progress = null;
|
|
||||||
clone.progressImage = null;
|
|
||||||
clone.outputs = [];
|
|
||||||
$nodeExecutionStates.setKey(clone.nodeId, clone);
|
|
||||||
});
|
|
||||||
} else if (status === 'failed' && error_type) {
|
|
||||||
const isLocal = getState().config.isLocal ?? true;
|
|
||||||
const sessionId = session_id;
|
|
||||||
|
|
||||||
toast({
|
|
||||||
id: `INVOCATION_ERROR_${error_type}`,
|
|
||||||
title: getTitleFromErrorType(error_type),
|
|
||||||
status: 'error',
|
|
||||||
duration: null,
|
|
||||||
updateDescription: isLocal,
|
|
||||||
description: (
|
|
||||||
<ErrorToastDescription
|
|
||||||
errorType={error_type}
|
|
||||||
errorMessage={error_message}
|
|
||||||
sessionId={sessionId}
|
|
||||||
isLocal={isLocal}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,43 +0,0 @@
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
|
||||||
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
|
||||||
import { toast } from 'features/toast/toast';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
export const addStagingAreaImageSavedListener = (startAppListening: AppStartListening) => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: stagingAreaImageSaved,
|
|
||||||
effect: async (action, { dispatch, getState }) => {
|
|
||||||
const { imageDTO } = action.payload;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const newImageDTO = await dispatch(
|
|
||||||
imagesApi.endpoints.changeImageIsIntermediate.initiate({
|
|
||||||
imageDTO,
|
|
||||||
is_intermediate: false,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
// we may need to add it to the autoadd board
|
|
||||||
const { autoAddBoardId } = getState().gallery;
|
|
||||||
|
|
||||||
if (autoAddBoardId && autoAddBoardId !== 'none') {
|
|
||||||
await dispatch(
|
|
||||||
imagesApi.endpoints.addImageToBoard.initiate({
|
|
||||||
imageDTO: newImageDTO,
|
|
||||||
board_id: autoAddBoardId,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
toast({ id: 'IMAGE_SAVED', title: t('toast.imageSaved'), status: 'success' });
|
|
||||||
} catch (error) {
|
|
||||||
toast({
|
|
||||||
id: 'IMAGE_SAVE_FAILED',
|
|
||||||
title: t('toast.imageSavingFailed'),
|
|
||||||
description: (error as Error)?.message,
|
|
||||||
status: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -2,18 +2,20 @@ import { logger } from 'app/logging/logger';
|
|||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||||
import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
|
import { $templates, nodesChanged } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectNodes } from 'features/nodes/store/selectors';
|
||||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
|
const log = logger('workflows');
|
||||||
|
|
||||||
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
|
export const addUpdateAllNodesRequestedListener = (startAppListening: AppStartListening) => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: updateAllNodesRequested,
|
actionCreator: updateAllNodesRequested,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: (action, { dispatch, getState }) => {
|
||||||
const log = logger('nodes');
|
const nodes = selectNodes(getState());
|
||||||
const { nodes } = getState().nodes.present;
|
|
||||||
const templates = $templates.get();
|
const templates = $templates.get();
|
||||||
|
|
||||||
let unableToUpdateCount = 0;
|
let unableToUpdateCount = 0;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState';
|
||||||
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
||||||
import { $templates } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
import { $needsFit } from 'features/nodes/store/reactFlowInstance';
|
import { $needsFit } from 'features/nodes/store/reactFlowInstance';
|
||||||
@ -10,11 +10,14 @@ import { graphToWorkflow } from 'features/nodes/util/workflow/graphToWorkflow';
|
|||||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
|
import { checkBoardAccess, checkImageAccess, checkModelAccess } from 'services/api/hooks/accessChecks';
|
||||||
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
|
import type { GraphAndWorkflowResponse, NonNullableGraph } from 'services/api/types';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { fromZodError } from 'zod-validation-error';
|
import { fromZodError } from 'zod-validation-error';
|
||||||
|
|
||||||
|
const log = logger('workflows');
|
||||||
|
|
||||||
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
|
const getWorkflow = async (data: GraphAndWorkflowResponse, templates: Templates) => {
|
||||||
if (data.workflow) {
|
if (data.workflow) {
|
||||||
// Prefer to load the workflow if it's available - it has more information
|
// Prefer to load the workflow if it's available - it has more information
|
||||||
@ -34,7 +37,6 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: workflowLoadRequested,
|
actionCreator: workflowLoadRequested,
|
||||||
effect: async (action, { dispatch }) => {
|
effect: async (action, { dispatch }) => {
|
||||||
const log = logger('nodes');
|
|
||||||
const { data, asCopy } = action.payload;
|
const { data, asCopy } = action.payload;
|
||||||
const nodeTemplates = $templates.get();
|
const nodeTemplates = $templates.get();
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
delete workflow.id;
|
delete workflow.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$nodeExecutionStates.set({});
|
||||||
dispatch(workflowLoaded(workflow));
|
dispatch(workflowLoaded(workflow));
|
||||||
if (!warnings.length) {
|
if (!warnings.length) {
|
||||||
toast({
|
toast({
|
||||||
@ -69,7 +72,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof WorkflowVersionError) {
|
if (e instanceof WorkflowVersionError) {
|
||||||
// The workflow version was not recognized in the valid list of versions
|
// The workflow version was not recognized in the valid list of versions
|
||||||
log.error({ error: parseify(e) }, e.message);
|
log.error({ error: serializeError(e) }, e.message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -78,7 +81,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
});
|
});
|
||||||
} else if (e instanceof WorkflowMigrationError) {
|
} else if (e instanceof WorkflowMigrationError) {
|
||||||
// There was a problem migrating the workflow to the latest version
|
// There was a problem migrating the workflow to the latest version
|
||||||
log.error({ error: parseify(e) }, e.message);
|
log.error({ error: serializeError(e) }, e.message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -90,7 +93,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
const { message } = fromZodError(e, {
|
const { message } = fromZodError(e, {
|
||||||
prefix: t('nodes.workflowValidation'),
|
prefix: t('nodes.workflowValidation'),
|
||||||
});
|
});
|
||||||
log.error({ error: parseify(e) }, message);
|
log.error({ error: serializeError(e) }, message);
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
@ -99,7 +102,7 @@ export const addWorkflowLoadRequestedListener = (startAppListening: AppStartList
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Some other error occurred
|
// Some other error occurred
|
||||||
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
log.error({ error: serializeError(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
||||||
toast({
|
toast({
|
||||||
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
id: 'UNABLE_TO_VALIDATE_WORKFLOW',
|
||||||
title: t('nodes.unableToValidateWorkflow'),
|
title: t('nodes.unableToValidateWorkflow'),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { createStore } from 'app/store/store';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import type { AppStore } from 'app/store/store';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
// Inject socket options and url into window for debugging
|
// Inject socket options and url into window for debugging
|
||||||
@ -22,7 +23,7 @@ class ReduxStoreNotInitialized extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const $store = atom<Readonly<ReturnType<typeof createStore>> | undefined>();
|
export const $store = atom<Readonly<AppStore | undefined>>();
|
||||||
|
|
||||||
export const getStore = () => {
|
export const getStore = () => {
|
||||||
const store = $store.get();
|
const store = $store.get();
|
||||||
@ -31,3 +32,11 @@ export const getStore = () => {
|
|||||||
}
|
}
|
||||||
return store;
|
return store;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useAppStore = () => {
|
||||||
|
const store = useStore($store);
|
||||||
|
if (!store) {
|
||||||
|
throw new ReduxStoreNotInitialized();
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
};
|
||||||
|
@ -3,37 +3,31 @@ import { autoBatchEnhancer, combineReducers, configureStore } from '@reduxjs/too
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||||
import type { JSONObject } from 'common/types';
|
import type { SerializableObject } from 'common/types';
|
||||||
import { canvasPersistConfig, canvasSlice } from 'features/canvas/store/canvasSlice';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
import { changeBoardModalSlice } from 'features/changeBoardModal/store/slice';
|
||||||
import {
|
import { canvasSessionPersistConfig, canvasSessionSlice } from 'features/controlLayers/store/canvasSessionSlice';
|
||||||
controlAdaptersPersistConfig,
|
import { canvasSettingsPersistConfig, canvasSettingsSlice } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||||
controlAdaptersSlice,
|
import { canvasPersistConfig, canvasSlice, canvasUndoableConfig } from 'features/controlLayers/store/canvasSlice';
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
import { lorasPersistConfig, lorasSlice } from 'features/controlLayers/store/lorasSlice';
|
||||||
import {
|
import { paramsPersistConfig, paramsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
controlLayersPersistConfig,
|
import { toolPersistConfig, toolSlice } from 'features/controlLayers/store/toolSlice';
|
||||||
controlLayersSlice,
|
|
||||||
controlLayersUndoableConfig,
|
|
||||||
} from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
import { deleteImageModalSlice } from 'features/deleteImageModal/store/slice';
|
||||||
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { dynamicPromptsPersistConfig, dynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
|
import { galleryPersistConfig, gallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice';
|
import { hrfPersistConfig, hrfSlice } from 'features/hrf/store/hrfSlice';
|
||||||
import { loraPersistConfig, loraSlice } from 'features/lora/store/loraSlice';
|
|
||||||
import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/modelManagerV2/store/modelManagerV2Slice';
|
||||||
import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice';
|
import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice';
|
||||||
import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
||||||
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
|
import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice';
|
||||||
import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice';
|
|
||||||
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice';
|
||||||
import { queueSlice } from 'features/queue/store/queueSlice';
|
import { queueSlice } from 'features/queue/store/queueSlice';
|
||||||
import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice';
|
|
||||||
import { stylePresetPersistConfig, stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
|
import { stylePresetPersistConfig, stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice';
|
||||||
import { configSlice } from 'features/system/store/configSlice';
|
import { configSlice } from 'features/system/store/configSlice';
|
||||||
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice';
|
||||||
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
import { uiPersistConfig, uiSlice } from 'features/ui/store/uiSlice';
|
||||||
import { diff } from 'jsondiffpatch';
|
import { diff } from 'jsondiffpatch';
|
||||||
import { defaultsDeep, keys, omit, pick } from 'lodash-es';
|
import { keys, mergeWith, omit, pick } from 'lodash-es';
|
||||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||||
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
|
import type { SerializeFunction, UnserializeFunction } from 'redux-remember';
|
||||||
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
import { rememberEnhancer, rememberReducer } from 'redux-remember';
|
||||||
@ -48,29 +42,31 @@ 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';
|
||||||
|
|
||||||
|
const log = logger('system');
|
||||||
|
|
||||||
const allReducers = {
|
const allReducers = {
|
||||||
[canvasSlice.name]: canvasSlice.reducer,
|
[api.reducerPath]: api.reducer,
|
||||||
[gallerySlice.name]: gallerySlice.reducer,
|
[gallerySlice.name]: gallerySlice.reducer,
|
||||||
[generationSlice.name]: generationSlice.reducer,
|
|
||||||
[nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig),
|
[nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig),
|
||||||
[systemSlice.name]: systemSlice.reducer,
|
[systemSlice.name]: systemSlice.reducer,
|
||||||
[configSlice.name]: configSlice.reducer,
|
[configSlice.name]: configSlice.reducer,
|
||||||
[uiSlice.name]: uiSlice.reducer,
|
[uiSlice.name]: uiSlice.reducer,
|
||||||
[controlAdaptersSlice.name]: controlAdaptersSlice.reducer,
|
|
||||||
[dynamicPromptsSlice.name]: dynamicPromptsSlice.reducer,
|
[dynamicPromptsSlice.name]: dynamicPromptsSlice.reducer,
|
||||||
[deleteImageModalSlice.name]: deleteImageModalSlice.reducer,
|
[deleteImageModalSlice.name]: deleteImageModalSlice.reducer,
|
||||||
[changeBoardModalSlice.name]: changeBoardModalSlice.reducer,
|
[changeBoardModalSlice.name]: changeBoardModalSlice.reducer,
|
||||||
[loraSlice.name]: loraSlice.reducer,
|
|
||||||
[modelManagerV2Slice.name]: modelManagerV2Slice.reducer,
|
[modelManagerV2Slice.name]: modelManagerV2Slice.reducer,
|
||||||
[sdxlSlice.name]: sdxlSlice.reducer,
|
|
||||||
[queueSlice.name]: queueSlice.reducer,
|
[queueSlice.name]: queueSlice.reducer,
|
||||||
[workflowSlice.name]: workflowSlice.reducer,
|
[workflowSlice.name]: workflowSlice.reducer,
|
||||||
[hrfSlice.name]: hrfSlice.reducer,
|
[hrfSlice.name]: hrfSlice.reducer,
|
||||||
[controlLayersSlice.name]: undoable(controlLayersSlice.reducer, controlLayersUndoableConfig),
|
[canvasSlice.name]: undoable(canvasSlice.reducer, canvasUndoableConfig),
|
||||||
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
[workflowSettingsSlice.name]: workflowSettingsSlice.reducer,
|
||||||
[api.reducerPath]: api.reducer,
|
|
||||||
[upscaleSlice.name]: upscaleSlice.reducer,
|
[upscaleSlice.name]: upscaleSlice.reducer,
|
||||||
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
[stylePresetSlice.name]: stylePresetSlice.reducer,
|
||||||
|
[paramsSlice.name]: paramsSlice.reducer,
|
||||||
|
[toolSlice.name]: toolSlice.reducer,
|
||||||
|
[canvasSettingsSlice.name]: canvasSettingsSlice.reducer,
|
||||||
|
[canvasSessionSlice.name]: canvasSessionSlice.reducer,
|
||||||
|
[lorasSlice.name]: lorasSlice.reducer,
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootReducer = combineReducers(allReducers);
|
const rootReducer = combineReducers(allReducers);
|
||||||
@ -100,27 +96,26 @@ export type PersistConfig<T = any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = {
|
||||||
[canvasPersistConfig.name]: canvasPersistConfig,
|
|
||||||
[galleryPersistConfig.name]: galleryPersistConfig,
|
[galleryPersistConfig.name]: galleryPersistConfig,
|
||||||
[generationPersistConfig.name]: generationPersistConfig,
|
|
||||||
[nodesPersistConfig.name]: nodesPersistConfig,
|
[nodesPersistConfig.name]: nodesPersistConfig,
|
||||||
[systemPersistConfig.name]: systemPersistConfig,
|
[systemPersistConfig.name]: systemPersistConfig,
|
||||||
[workflowPersistConfig.name]: workflowPersistConfig,
|
[workflowPersistConfig.name]: workflowPersistConfig,
|
||||||
[uiPersistConfig.name]: uiPersistConfig,
|
[uiPersistConfig.name]: uiPersistConfig,
|
||||||
[controlAdaptersPersistConfig.name]: controlAdaptersPersistConfig,
|
|
||||||
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
|
[dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig,
|
||||||
[sdxlPersistConfig.name]: sdxlPersistConfig,
|
|
||||||
[loraPersistConfig.name]: loraPersistConfig,
|
|
||||||
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
|
[modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig,
|
||||||
[hrfPersistConfig.name]: hrfPersistConfig,
|
[hrfPersistConfig.name]: hrfPersistConfig,
|
||||||
[controlLayersPersistConfig.name]: controlLayersPersistConfig,
|
[canvasPersistConfig.name]: canvasPersistConfig,
|
||||||
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
[workflowSettingsPersistConfig.name]: workflowSettingsPersistConfig,
|
||||||
[upscalePersistConfig.name]: upscalePersistConfig,
|
[upscalePersistConfig.name]: upscalePersistConfig,
|
||||||
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
[stylePresetPersistConfig.name]: stylePresetPersistConfig,
|
||||||
|
[paramsPersistConfig.name]: paramsPersistConfig,
|
||||||
|
[toolPersistConfig.name]: toolPersistConfig,
|
||||||
|
[canvasSettingsPersistConfig.name]: canvasSettingsPersistConfig,
|
||||||
|
[canvasSessionPersistConfig.name]: canvasSessionPersistConfig,
|
||||||
|
[lorasPersistConfig.name]: lorasPersistConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
const unserialize: UnserializeFunction = (data, key) => {
|
const unserialize: UnserializeFunction = (data, key) => {
|
||||||
const log = logger('system');
|
|
||||||
const persistConfig = persistConfigs[key as keyof typeof persistConfigs];
|
const persistConfig = persistConfigs[key as keyof typeof persistConfigs];
|
||||||
if (!persistConfig) {
|
if (!persistConfig) {
|
||||||
throw new Error(`No persist config for slice "${key}"`);
|
throw new Error(`No persist config for slice "${key}"`);
|
||||||
@ -130,17 +125,21 @@ const unserialize: UnserializeFunction = (data, key) => {
|
|||||||
const parsed = JSON.parse(data);
|
const parsed = JSON.parse(data);
|
||||||
|
|
||||||
// strip out old keys
|
// strip out old keys
|
||||||
const stripped = pick(parsed, keys(initialState));
|
const stripped = pick(deepClone(parsed), keys(initialState));
|
||||||
// run (additive) migrations
|
// run (additive) migrations
|
||||||
const migrated = migrate(stripped);
|
const migrated = migrate(stripped);
|
||||||
// merge in initial state as default values, covering any missing keys
|
/*
|
||||||
const transformed = defaultsDeep(migrated, initialState);
|
* Merge in initial state as default values, covering any missing keys. You might be tempted to use _.defaultsDeep,
|
||||||
|
* but that merges arrays by index and partial objects by key. Using an identity function as the customizer results
|
||||||
|
* in behaviour like defaultsDeep, but doesn't overwrite any values that are not undefined in the migrated state.
|
||||||
|
*/
|
||||||
|
const transformed = mergeWith(migrated, initialState, (objVal) => objVal);
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
{
|
{
|
||||||
persistedData: parsed,
|
persistedData: parsed,
|
||||||
rehydratedData: transformed,
|
rehydratedData: transformed,
|
||||||
diff: diff(parsed, transformed) as JSONObject, // this is always serializable
|
diff: diff(parsed, transformed) as SerializableObject, // this is always serializable
|
||||||
},
|
},
|
||||||
`Rehydrated slice "${key}"`
|
`Rehydrated slice "${key}"`
|
||||||
);
|
);
|
||||||
@ -202,7 +201,8 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
export type AppStore = ReturnType<typeof createStore>;
|
||||||
|
export type RootState = ReturnType<AppStore['getState']>;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
||||||
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
|
export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
|
import type { FilterType } from 'features/controlLayers/store/types';
|
||||||
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
import type { ParameterPrecision, ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||||
import type { InvokeTabName } from 'features/ui/store/tabMap';
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
import type { O } from 'ts-toolbelt';
|
import type { O } from 'ts-toolbelt';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +72,7 @@ export type AppConfig = {
|
|||||||
maxUpscaleDimension?: number;
|
maxUpscaleDimension?: number;
|
||||||
allowPrivateBoards: boolean;
|
allowPrivateBoards: boolean;
|
||||||
allowPrivateStylePresets: boolean;
|
allowPrivateStylePresets: boolean;
|
||||||
disabledTabs: InvokeTabName[];
|
disabledTabs: TabName[];
|
||||||
disabledFeatures: AppFeature[];
|
disabledFeatures: AppFeature[];
|
||||||
disabledSDFeatures: SDFeature[];
|
disabledSDFeatures: SDFeature[];
|
||||||
nodesAllowlist: string[] | undefined;
|
nodesAllowlist: string[] | undefined;
|
||||||
@ -83,7 +83,7 @@ export type AppConfig = {
|
|||||||
sd: {
|
sd: {
|
||||||
defaultModel?: string;
|
defaultModel?: string;
|
||||||
disabledControlNetModels: string[];
|
disabledControlNetModels: string[];
|
||||||
disabledControlNetProcessors: (keyof typeof CONTROLNET_PROCESSORS)[];
|
disabledControlNetProcessors: FilterType[];
|
||||||
// Core parameters
|
// Core parameters
|
||||||
iterations: NumericalParameterConfig;
|
iterations: NumericalParameterConfig;
|
||||||
width: NumericalParameterConfig; // initial value comes from model
|
width: NumericalParameterConfig; // initial value comes from model
|
||||||
|
@ -33,28 +33,23 @@ type IAINoImageFallbackProps = FlexProps & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
export const IAINoContentFallback = memo((props: IAINoImageFallbackProps) => {
|
||||||
const { icon = PiImageBold, boxSize = 16, sx, ...rest } = props;
|
const { icon = PiImageBold, boxSize = 16, ...rest } = props;
|
||||||
|
|
||||||
const styles = useMemo(
|
|
||||||
() => ({
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
flexDir: 'column',
|
|
||||||
gap: 2,
|
|
||||||
userSelect: 'none',
|
|
||||||
opacity: 0.7,
|
|
||||||
color: 'base.500',
|
|
||||||
fontSize: 'md',
|
|
||||||
...sx,
|
|
||||||
}),
|
|
||||||
[sx]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={styles} {...rest}>
|
<Flex
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
borderRadius="base"
|
||||||
|
flexDir="column"
|
||||||
|
gap={2}
|
||||||
|
userSelect="none"
|
||||||
|
opacity={0.7}
|
||||||
|
color="base.500"
|
||||||
|
fontSize="md"
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
{icon && <Icon as={icon} boxSize={boxSize} opacity={0.7} />}
|
||||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -13,8 +13,9 @@ import {
|
|||||||
Spacer,
|
Spacer,
|
||||||
Text,
|
Text,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
import { selectSystemSlice, setShouldEnableInformationalPopovers } from 'features/system/store/systemSlice';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { merge, omit } from 'lodash-es';
|
import { merge, omit } from 'lodash-es';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
@ -31,8 +32,13 @@ type Props = {
|
|||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectShouldEnableInformationalPopovers = createSelector(
|
||||||
|
selectSystemSlice,
|
||||||
|
(system) => system.shouldEnableInformationalPopovers
|
||||||
|
);
|
||||||
|
|
||||||
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
||||||
const shouldEnableInformationalPopovers = useAppSelector((s) => s.system.shouldEnableInformationalPopovers);
|
const shouldEnableInformationalPopovers = useAppSelector(selectShouldEnableInformationalPopovers);
|
||||||
|
|
||||||
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
||||||
|
|
||||||
|
158
invokeai/frontend/web/src/common/hooks/interactionScopes.ts
Normal file
158
invokeai/frontend/web/src/common/hooks/interactionScopes.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import { logger } from 'app/logging/logger';
|
||||||
|
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
|
||||||
|
import { objectKeys } from 'common/util/objectKeys';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
import type { Atom } from 'nanostores';
|
||||||
|
import { atom, computed } from 'nanostores';
|
||||||
|
import type { RefObject } from 'react';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
const log = logger('system');
|
||||||
|
|
||||||
|
const _INTERACTION_SCOPES = ['gallery', 'canvas', 'stagingArea', 'workflows', 'imageViewer'] as const;
|
||||||
|
|
||||||
|
type InteractionScope = (typeof _INTERACTION_SCOPES)[number];
|
||||||
|
|
||||||
|
export const $activeScopes = atom<Set<InteractionScope>>(new Set());
|
||||||
|
|
||||||
|
type InteractionScopeData = {
|
||||||
|
targets: Set<HTMLElement>;
|
||||||
|
$isActive: Atom<boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const INTERACTION_SCOPES: Record<InteractionScope, InteractionScopeData> = _INTERACTION_SCOPES.reduce(
|
||||||
|
(acc, region) => {
|
||||||
|
acc[region] = {
|
||||||
|
targets: new Set(),
|
||||||
|
$isActive: computed($activeScopes, (activeScopes) => activeScopes.has(region)),
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<InteractionScope, InteractionScopeData>
|
||||||
|
);
|
||||||
|
|
||||||
|
const formatScopes = (interactionScopes: Set<InteractionScope>) => {
|
||||||
|
if (interactionScopes.size === 0) {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
return Array.from(interactionScopes).join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addScope = (scope: InteractionScope) => {
|
||||||
|
const currentScopes = $activeScopes.get();
|
||||||
|
if (currentScopes.has(scope)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newScopes = new Set(currentScopes);
|
||||||
|
newScopes.add(scope);
|
||||||
|
$activeScopes.set(newScopes);
|
||||||
|
log.trace(`Added scope ${scope}: ${formatScopes($activeScopes.get())}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeScope = (scope: InteractionScope) => {
|
||||||
|
const currentScopes = $activeScopes.get();
|
||||||
|
if (!currentScopes.has(scope)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newScopes = new Set(currentScopes);
|
||||||
|
newScopes.delete(scope);
|
||||||
|
$activeScopes.set(newScopes);
|
||||||
|
log.trace(`Removed scope ${scope}: ${formatScopes($activeScopes.get())}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setScopes = (scopes: InteractionScope[]) => {
|
||||||
|
const newScopes = new Set(scopes);
|
||||||
|
$activeScopes.set(newScopes);
|
||||||
|
log.trace(`Set scopes: ${formatScopes($activeScopes.get())}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScopeOnFocus = (scope: InteractionScope, ref: RefObject<HTMLElement>) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const element = ref.current;
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERACTION_SCOPES[scope].targets.add(element);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
INTERACTION_SCOPES[scope].targets.delete(element);
|
||||||
|
};
|
||||||
|
}, [ref, scope]);
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseScopeOnMountOptions = {
|
||||||
|
mount?: boolean;
|
||||||
|
unmount?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultUseScopeOnMountOptions: UseScopeOnMountOptions = {
|
||||||
|
mount: true,
|
||||||
|
unmount: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScopeOnMount = (scope: InteractionScope, options?: UseScopeOnMountOptions) => {
|
||||||
|
useEffect(() => {
|
||||||
|
const { mount, unmount } = { ...defaultUseScopeOnMountOptions, ...options };
|
||||||
|
|
||||||
|
if (mount) {
|
||||||
|
addScope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (unmount) {
|
||||||
|
removeScope(scope);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [options, scope]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScopeImperativeApi = (scope: InteractionScope) => {
|
||||||
|
const api = useMemo(() => {
|
||||||
|
return {
|
||||||
|
add: () => {
|
||||||
|
addScope(scope);
|
||||||
|
},
|
||||||
|
remove: () => {
|
||||||
|
removeScope(scope);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}, [scope]);
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocusEvent = (_event: FocusEvent) => {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
if (!(activeElement instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newActiveScopes = new Set<InteractionScope>();
|
||||||
|
|
||||||
|
for (const scope of objectKeys(INTERACTION_SCOPES)) {
|
||||||
|
for (const element of INTERACTION_SCOPES[scope].targets) {
|
||||||
|
if (element.contains(activeElement)) {
|
||||||
|
newActiveScopes.add(scope);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldActiveScopes = $activeScopes.get();
|
||||||
|
if (!isEqual(oldActiveScopes, newActiveScopes)) {
|
||||||
|
$activeScopes.set(newActiveScopes);
|
||||||
|
log.trace(`Scopes changed: ${formatScopes($activeScopes.get())}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useScopeFocusWatcher = () => {
|
||||||
|
useAssertSingleton('useScopeFocusWatcher');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('focus', handleFocusEvent, true);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('focus', handleFocusEvent, true);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { WritableAtom } from 'nanostores';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
|
||||||
export const useBoolean = (initialValue: boolean) => {
|
export const useBoolean = (initialValue: boolean) => {
|
||||||
@ -19,3 +20,33 @@ export const useBoolean = (initialValue: boolean) => {
|
|||||||
|
|
||||||
return api;
|
return api;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const buildUseBoolean = ($boolean: WritableAtom<boolean>) => {
|
||||||
|
return () => {
|
||||||
|
const setTrue = useCallback(() => {
|
||||||
|
$boolean.set(true);
|
||||||
|
}, []);
|
||||||
|
const setFalse = useCallback(() => {
|
||||||
|
$boolean.set(false);
|
||||||
|
}, []);
|
||||||
|
const set = useCallback((value: boolean) => {
|
||||||
|
$boolean.set(value);
|
||||||
|
}, []);
|
||||||
|
const toggle = useCallback(() => {
|
||||||
|
$boolean.set(!$boolean.get());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const api = useMemo(
|
||||||
|
() => ({
|
||||||
|
setTrue,
|
||||||
|
setFalse,
|
||||||
|
set,
|
||||||
|
toggle,
|
||||||
|
$boolean,
|
||||||
|
}),
|
||||||
|
[set, setFalse, setTrue, toggle]
|
||||||
|
);
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
import { toast } from 'features/toast/toast';
|
import { toast } from 'features/toast/toast';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { Accept, FileRejection } from 'react-dropzone';
|
import type { Accept, FileRejection } from 'react-dropzone';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
@ -14,13 +15,9 @@ const accept: Accept = {
|
|||||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
const selectPostUploadAction = createMemoizedSelector(selectActiveTab, (activeTabName) => {
|
||||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||||
|
|
||||||
if (activeTabName === 'canvas') {
|
|
||||||
postUploadAction = { type: 'SET_CANVAS_INITIAL_IMAGE' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTabName === 'upscaling') {
|
if (activeTabName === 'upscaling') {
|
||||||
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
postUploadAction = { type: 'SET_UPSCALE_INITIAL_IMAGE' };
|
||||||
}
|
}
|
||||||
@ -30,10 +27,9 @@ const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (ac
|
|||||||
|
|
||||||
export const useFullscreenDropzone = () => {
|
export const useFullscreenDropzone = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const postUploadAction = useAppSelector(selectPostUploadAction);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
|
||||||
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
const [isHandlingUpload, setIsHandlingUpload] = useState<boolean>(false);
|
||||||
|
const postUploadAction = useAppSelector(selectPostUploadAction);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
|
|
||||||
const fileRejectionCallback = useCallback(
|
const fileRejectionCallback = useCallback(
|
||||||
@ -51,7 +47,7 @@ export const useFullscreenDropzone = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const fileAcceptedCallback = useCallback(
|
const fileAcceptedCallback = useCallback(
|
||||||
async (file: File) => {
|
(file: File) => {
|
||||||
uploadImage({
|
uploadImage({
|
||||||
file,
|
file,
|
||||||
image_category: 'user',
|
image_category: 'user',
|
||||||
@ -101,7 +97,7 @@ export const useFullscreenDropzone = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This is a hack to allow pasting images into the uploader
|
// This is a hack to allow pasting images into the uploader
|
||||||
const handlePaste = async (e: ClipboardEvent) => {
|
const handlePaste = (e: ClipboardEvent) => {
|
||||||
if (!dropzone.inputRef.current) {
|
if (!dropzone.inputRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { addScope, removeScope, setScopes } from 'common/hooks/interactionScopes';
|
||||||
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
import { useCancelCurrentQueueItem } from 'features/queue/hooks/useCancelCurrentQueueItem';
|
||||||
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
import { useClearQueue } from 'features/queue/hooks/useClearQueue';
|
||||||
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
|
||||||
@ -16,7 +17,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+enter', 'meta+enter'],
|
['ctrl+enter', 'meta+enter'],
|
||||||
queueBack,
|
queueBack,
|
||||||
{
|
{
|
||||||
enabled: () => !isDisabledQueueBack && !isLoadingQueueBack,
|
enabled: !isDisabledQueueBack && !isLoadingQueueBack,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
enableOnFormTags: ['input', 'textarea', 'select'],
|
enableOnFormTags: ['input', 'textarea', 'select'],
|
||||||
},
|
},
|
||||||
@ -29,7 +30,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+shift+enter', 'meta+shift+enter'],
|
['ctrl+shift+enter', 'meta+shift+enter'],
|
||||||
queueFront,
|
queueFront,
|
||||||
{
|
{
|
||||||
enabled: () => !isDisabledQueueFront && !isLoadingQueueFront,
|
enabled: !isDisabledQueueFront && !isLoadingQueueFront,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
enableOnFormTags: ['input', 'textarea', 'select'],
|
enableOnFormTags: ['input', 'textarea', 'select'],
|
||||||
},
|
},
|
||||||
@ -46,7 +47,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['shift+x'],
|
['shift+x'],
|
||||||
cancelQueueItem,
|
cancelQueueItem,
|
||||||
{
|
{
|
||||||
enabled: () => !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
|
enabled: !isDisabledCancelQueueItem && !isLoadingCancelQueueItem,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
||||||
@ -58,7 +59,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
['ctrl+shift+x', 'meta+shift+x'],
|
['ctrl+shift+x', 'meta+shift+x'],
|
||||||
clearQueue,
|
clearQueue,
|
||||||
{
|
{
|
||||||
enabled: () => !isDisabledClearQueue && !isLoadingClearQueue,
|
enabled: !isDisabledClearQueue && !isLoadingClearQueue,
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
},
|
},
|
||||||
[clearQueue, isDisabledClearQueue, isLoadingClearQueue]
|
[clearQueue, isDisabledClearQueue, isLoadingClearQueue]
|
||||||
@ -68,6 +69,8 @@ export const useGlobalHotkeys = () => {
|
|||||||
'1',
|
'1',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('generation'));
|
dispatch(setActiveTab('generation'));
|
||||||
|
addScope('canvas');
|
||||||
|
removeScope('workflows');
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -75,7 +78,9 @@ export const useGlobalHotkeys = () => {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
'2',
|
'2',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('canvas'));
|
dispatch(setActiveTab('upscaling'));
|
||||||
|
removeScope('canvas');
|
||||||
|
removeScope('workflows');
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -84,6 +89,8 @@ export const useGlobalHotkeys = () => {
|
|||||||
'3',
|
'3',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('workflows'));
|
dispatch(setActiveTab('workflows'));
|
||||||
|
removeScope('canvas');
|
||||||
|
addScope('workflows');
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
@ -93,6 +100,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
() => {
|
() => {
|
||||||
if (isModelManagerEnabled) {
|
if (isModelManagerEnabled) {
|
||||||
dispatch(setActiveTab('models'));
|
dispatch(setActiveTab('models'));
|
||||||
|
setScopes([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, isModelManagerEnabled]
|
[dispatch, isModelManagerEnabled]
|
||||||
@ -102,6 +110,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
isModelManagerEnabled ? '5' : '4',
|
isModelManagerEnabled ? '5' : '4',
|
||||||
() => {
|
() => {
|
||||||
dispatch(setActiveTab('queue'));
|
dispatch(setActiveTab('queue'));
|
||||||
|
setScopes([]);
|
||||||
},
|
},
|
||||||
[dispatch, isModelManagerEnabled]
|
[dispatch, isModelManagerEnabled]
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import type { GroupBase } from 'chakra-react-select';
|
import type { GroupBase } from 'chakra-react-select';
|
||||||
|
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
import type { ModelIdentifierField } from 'features/nodes/types/common';
|
||||||
import { groupBy, reduce } from 'lodash-es';
|
import { groupBy, reduce } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
@ -28,11 +30,13 @@ const groupByBaseFunc = <T extends AnyModelConfig>(model: T) => model.base.toUpp
|
|||||||
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
const groupByBaseAndTypeFunc = <T extends AnyModelConfig>(model: T) =>
|
||||||
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
`${model.base.toUpperCase()} / ${model.type.replaceAll('_', ' ').toUpperCase()}`;
|
||||||
|
|
||||||
|
const selectBaseWithSDXLFallback = createSelector(selectParamsSlice, (params) => params.model?.base ?? 'sdxl');
|
||||||
|
|
||||||
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
||||||
arg: UseGroupedModelComboboxArg<T>
|
arg: UseGroupedModelComboboxArg<T>
|
||||||
): UseGroupedModelComboboxReturn => {
|
): UseGroupedModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const base_model = useAppSelector((s) => s.generation.model?.base ?? 'sdxl');
|
const base = useAppSelector(selectBaseWithSDXLFallback);
|
||||||
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
const { modelConfigs, selectedModel, getIsDisabled, onChange, isLoading, groupByType = false } = arg;
|
||||||
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
||||||
if (!modelConfigs) {
|
if (!modelConfigs) {
|
||||||
@ -54,9 +58,9 @@ export const useGroupedModelCombobox = <T extends AnyModelConfig>(
|
|||||||
},
|
},
|
||||||
[] as GroupBase<ComboboxOption>[]
|
[] as GroupBase<ComboboxOption>[]
|
||||||
);
|
);
|
||||||
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base_model) ? -1 : 1));
|
_options.sort((a) => (a.label?.split('/')[0]?.toLowerCase().includes(base) ? -1 : 1));
|
||||||
return _options;
|
return _options;
|
||||||
}, [modelConfigs, groupByType, getIsDisabled, base_model]);
|
}, [modelConfigs, groupByType, getIsDisabled, base]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
import { useUploadImageMutation } from 'services/api/endpoints/images';
|
||||||
@ -29,7 +30,7 @@ type UseImageUploadButtonArgs = {
|
|||||||
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
||||||
*/
|
*/
|
||||||
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const onDropAccepted = useCallback(
|
const onDropAccepted = useCallback(
|
||||||
(files: File[]) => {
|
(files: File[]) => {
|
||||||
|
@ -1,67 +1,49 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { $isConnected } from 'app/hooks/useSocketIO';
|
||||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { selectParamsSlice } from 'features/controlLayers/store/paramsSlice';
|
||||||
selectControlAdapterAll,
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
selectControlAdaptersSlice,
|
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
||||||
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
||||||
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
|
||||||
import type { Layer } from 'features/controlLayers/store/types';
|
|
||||||
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
||||||
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
import { $templates } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||||
import type { Templates } from 'features/nodes/store/types';
|
import type { Templates } from 'features/nodes/store/types';
|
||||||
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
||||||
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
||||||
import { selectConfigSlice } from 'features/system/store/configSlice';
|
import { selectConfigSlice } from 'features/system/store/configSlice';
|
||||||
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import { forEach, upperFirst } from 'lodash-es';
|
import { forEach, upperFirst } from 'lodash-es';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { getConnectedEdges } from 'reactflow';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
|
|
||||||
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
|
const LAYER_TYPE_TO_TKEY = {
|
||||||
initial_image_layer: 'controlLayers.globalInitialImage',
|
ip_adapter: 'controlLayers.ipAdapter',
|
||||||
control_adapter_layer: 'controlLayers.globalControlAdapter',
|
inpaint_mask: 'controlLayers.inpaintMask',
|
||||||
ip_adapter_layer: 'controlLayers.globalIPAdapter',
|
regional_guidance: 'controlLayers.regionalGuidance',
|
||||||
regional_guidance_layer: 'controlLayers.regionalGuidance',
|
raster_layer: 'controlLayers.raster',
|
||||||
};
|
control_layer: 'controlLayers.globalControlAdapter',
|
||||||
|
} as const;
|
||||||
|
|
||||||
const createSelector = (templates: Templates) =>
|
const createSelector = (templates: Templates, isConnected: boolean) =>
|
||||||
createMemoizedSelector(
|
createMemoizedSelector(
|
||||||
[
|
[
|
||||||
selectControlAdaptersSlice,
|
|
||||||
selectGenerationSlice,
|
|
||||||
selectSystemSlice,
|
selectSystemSlice,
|
||||||
selectNodesSlice,
|
selectNodesSlice,
|
||||||
selectWorkflowSettingsSlice,
|
selectWorkflowSettingsSlice,
|
||||||
selectDynamicPromptsSlice,
|
selectDynamicPromptsSlice,
|
||||||
selectControlLayersSlice,
|
selectCanvasSlice,
|
||||||
activeTabNameSelector,
|
selectParamsSlice,
|
||||||
selectUpscalelice,
|
selectUpscalelice,
|
||||||
selectConfigSlice,
|
selectConfigSlice,
|
||||||
|
selectActiveTab,
|
||||||
],
|
],
|
||||||
(
|
(system, nodes, workflowSettings, dynamicPrompts, canvas, params, upscale, config, activeTabName) => {
|
||||||
controlAdapters,
|
const { bbox } = canvas;
|
||||||
generation,
|
const { model, positivePrompt } = params;
|
||||||
system,
|
|
||||||
nodes,
|
|
||||||
workflowSettings,
|
|
||||||
dynamicPrompts,
|
|
||||||
controlLayers,
|
|
||||||
activeTabName,
|
|
||||||
upscale,
|
|
||||||
config
|
|
||||||
) => {
|
|
||||||
const { model } = generation;
|
|
||||||
const { size } = controlLayers.present;
|
|
||||||
const { positivePrompt } = controlLayers.present;
|
|
||||||
|
|
||||||
const { isConnected } = system;
|
|
||||||
|
|
||||||
const reasons: { prefix?: string; content: string }[] = [];
|
const reasons: { prefix?: string; content: string }[] = [];
|
||||||
|
|
||||||
@ -114,6 +96,26 @@ const createSelector = (templates: Templates) =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (activeTabName === 'upscaling') {
|
||||||
|
if (!upscale.upscaleInitialImage) {
|
||||||
|
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
||||||
|
} else if (config.maxUpscaleDimension) {
|
||||||
|
const { width, height } = upscale.upscaleInitialImage;
|
||||||
|
const { scale } = upscale;
|
||||||
|
|
||||||
|
const maxPixels = config.maxUpscaleDimension ** 2;
|
||||||
|
const upscaledPixels = width * scale * height * scale;
|
||||||
|
|
||||||
|
if (upscaledPixels > maxPixels) {
|
||||||
|
reasons.push({ content: i18n.t('upscaling.exceedsMaxSize') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!upscale.upscaleModel) {
|
||||||
|
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
||||||
|
}
|
||||||
|
if (!upscale.tileControlnetModel) {
|
||||||
|
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
|
reasons.push({ content: i18n.t('parameters.invoke.noPrompts') });
|
||||||
@ -123,140 +125,115 @@ const createSelector = (templates: Templates) =>
|
|||||||
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeTabName === 'generation') {
|
canvas.controlLayers.entities
|
||||||
// Handling for generation tab
|
.filter((controlLayer) => controlLayer.isEnabled)
|
||||||
controlLayers.present.layers
|
.forEach((controlLayer, i) => {
|
||||||
.filter((l) => l.isEnabled)
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
.forEach((l, i) => {
|
const layerNumber = i + 1;
|
||||||
const layerLiteral = i18n.t('controlLayers.layers_one');
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY['control_layer']);
|
||||||
const layerNumber = i + 1;
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
|
const problems: string[] = [];
|
||||||
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
// Must have model
|
||||||
const problems: string[] = [];
|
if (!controlLayer.controlAdapter.model) {
|
||||||
if (l.type === 'control_adapter_layer') {
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
||||||
// Must have model
|
|
||||||
if (!l.controlAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (l.controlAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have a control image OR, if it has a processor, it must have a processed image
|
|
||||||
if (!l.controlAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
|
||||||
} else if (l.controlAdapter.processorConfig && !l.controlAdapter.processedImage) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
|
||||||
}
|
|
||||||
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
|
||||||
if (l.controlAdapter.type === 't2i_adapter') {
|
|
||||||
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
|
||||||
if (size.width % multiple !== 0 || size.height % multiple !== 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'ip_adapter_layer') {
|
|
||||||
// Must have model
|
|
||||||
if (!l.ipAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (l.ipAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have an image
|
|
||||||
if (!l.ipAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'initial_image_layer') {
|
|
||||||
// Must have an image
|
|
||||||
if (!l.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (l.type === 'regional_guidance_layer') {
|
|
||||||
// Must have a region
|
|
||||||
if (l.maskObjects.length === 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
|
||||||
}
|
|
||||||
// Must have at least 1 prompt or IP Adapter
|
|
||||||
if (l.positivePrompt === null && l.negativePrompt === null && l.ipAdapters.length === 0) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
|
||||||
}
|
|
||||||
l.ipAdapters.forEach((ipAdapter) => {
|
|
||||||
// Must have model
|
|
||||||
if (!ipAdapter.model) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
||||||
}
|
|
||||||
// Model base must match
|
|
||||||
if (ipAdapter.model?.base !== model?.base) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
||||||
}
|
|
||||||
// Must have an image
|
|
||||||
if (!ipAdapter.image) {
|
|
||||||
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (problems.length) {
|
|
||||||
const content = upperFirst(problems.join(', '));
|
|
||||||
reasons.push({ prefix, content });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (activeTabName === 'upscaling') {
|
|
||||||
if (!upscale.upscaleInitialImage) {
|
|
||||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleInitialImage') });
|
|
||||||
} else if (config.maxUpscaleDimension) {
|
|
||||||
const { width, height } = upscale.upscaleInitialImage;
|
|
||||||
const { scale } = upscale;
|
|
||||||
|
|
||||||
const maxPixels = config.maxUpscaleDimension ** 2;
|
|
||||||
const upscaledPixels = width * scale * height * scale;
|
|
||||||
|
|
||||||
if (upscaledPixels > maxPixels) {
|
|
||||||
reasons.push({ content: i18n.t('upscaling.exceedsMaxSize') });
|
|
||||||
}
|
}
|
||||||
}
|
// Model base must match
|
||||||
if (!upscale.upscaleModel) {
|
if (controlLayer.controlAdapter.model?.base !== model?.base) {
|
||||||
reasons.push({ content: i18n.t('upscaling.missingUpscaleModel') });
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
||||||
}
|
}
|
||||||
if (!upscale.tileControlnetModel) {
|
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
||||||
reasons.push({ content: i18n.t('upscaling.missingTileControlNetModel') });
|
if (controlLayer.controlAdapter.type === 't2i_adapter') {
|
||||||
}
|
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
||||||
} else {
|
if (bbox.rect.width % multiple !== 0 || bbox.rect.height % multiple !== 0) {
|
||||||
// Handling for all other tabs
|
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
||||||
selectControlAdapterAll(controlAdapters)
|
|
||||||
.filter((ca) => ca.isEnabled)
|
|
||||||
.forEach((ca, i) => {
|
|
||||||
if (!ca.isEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ca.model) {
|
if (problems.length) {
|
||||||
reasons.push({ content: i18n.t('parameters.invoke.noModelForControlAdapter', { number: i + 1 }) });
|
const content = upperFirst(problems.join(', '));
|
||||||
} else if (ca.model.base !== model?.base) {
|
reasons.push({ prefix, content });
|
||||||
// This should never happen, just a sanity check
|
}
|
||||||
reasons.push({
|
});
|
||||||
content: i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { number: i + 1 }),
|
|
||||||
});
|
canvas.ipAdapters.entities
|
||||||
|
.filter((entity) => entity.isEnabled)
|
||||||
|
.forEach((entity, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
// Must have model
|
||||||
|
if (!entity.ipAdapter.model) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
|
}
|
||||||
|
// Model base must match
|
||||||
|
if (entity.ipAdapter.model?.base !== model?.base) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
|
}
|
||||||
|
// Must have an image
|
||||||
|
if (!entity.ipAdapter.image) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.regions.entities
|
||||||
|
.filter((entity) => entity.isEnabled)
|
||||||
|
.forEach((entity, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
// Must have a region
|
||||||
|
if (entity.objects.length === 0) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
||||||
|
}
|
||||||
|
// Must have at least 1 prompt or IP Adapter
|
||||||
|
if (entity.positivePrompt === null && entity.negativePrompt === null && entity.ipAdapters.length === 0) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
||||||
|
}
|
||||||
|
entity.ipAdapters.forEach((ipAdapter) => {
|
||||||
|
// Must have model
|
||||||
|
if (!ipAdapter.model) {
|
||||||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
||||||
}
|
}
|
||||||
|
// Model base must match
|
||||||
if (
|
if (ipAdapter.model?.base !== model?.base) {
|
||||||
!ca.controlImage ||
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
||||||
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
}
|
||||||
) {
|
// Must have an image
|
||||||
reasons.push({
|
if (!ipAdapter.image) {
|
||||||
content: i18n.t('parameters.invoke.noControlImageForControlAdapter', { number: i + 1 }),
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.rasterLayers.entities
|
||||||
|
.filter((entity) => entity.isEnabled)
|
||||||
|
.forEach((entity, i) => {
|
||||||
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
||||||
|
const layerNumber = i + 1;
|
||||||
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[entity.type]);
|
||||||
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
||||||
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
if (problems.length) {
|
||||||
|
const content = upperFirst(problems.join(', '));
|
||||||
|
reasons.push({ prefix, content });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isReady: !reasons.length, reasons };
|
return { isReady: !reasons.length, reasons };
|
||||||
@ -265,7 +242,8 @@ const createSelector = (templates: Templates) =>
|
|||||||
|
|
||||||
export const useIsReadyToEnqueue = () => {
|
export const useIsReadyToEnqueue = () => {
|
||||||
const templates = useStore($templates);
|
const templates = useStore($templates);
|
||||||
const selector = useMemo(() => createSelector(templates), [templates]);
|
const isConnected = useStore($isConnected);
|
||||||
|
const selector = useMemo(() => createSelector(templates, isConnected), [templates, isConnected]);
|
||||||
const value = useAppSelector(selector);
|
const value = useAppSelector(selector);
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
// https://stackoverflow.com/a/73731908
|
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
type UseSingleAndDoubleClickOptions = {
|
|
||||||
onSingleClick: () => void;
|
|
||||||
onDoubleClick: () => void;
|
|
||||||
latency?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useSingleAndDoubleClick({
|
|
||||||
onSingleClick,
|
|
||||||
onDoubleClick,
|
|
||||||
latency = 250,
|
|
||||||
}: UseSingleAndDoubleClickOptions): () => void {
|
|
||||||
const [click, setClick] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
if (click === 1) {
|
|
||||||
onSingleClick();
|
|
||||||
}
|
|
||||||
setClick(0);
|
|
||||||
}, latency);
|
|
||||||
|
|
||||||
if (click === 2) {
|
|
||||||
onDoubleClick();
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [click, onDoubleClick, latency, onSingleClick]);
|
|
||||||
|
|
||||||
const onClick = useCallback(() => setClick((prev) => prev + 1), []);
|
|
||||||
|
|
||||||
return onClick;
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
type SerializableValue = string | number | boolean | null | undefined | SerializableValue[] | SerializableObject;
|
||||||
|
export type SerializableObject = {
|
||||||
export interface JSONObject {
|
[k: string | number]: SerializableValue;
|
||||||
[k: string]: JSONValue;
|
};
|
||||||
}
|
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { SyncableMap } from './SyncableMap';
|
||||||
|
|
||||||
|
describe('SyncableMap', () => {
|
||||||
|
it('should initialize with entries', () => {
|
||||||
|
const initialEntries = [
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
] as const;
|
||||||
|
const map = new SyncableMap(initialEntries);
|
||||||
|
expect(map.size).toBe(2);
|
||||||
|
expect(map.get('key1')).toBe('value1');
|
||||||
|
expect(map.get('key2')).toBe('value2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify subscribers when a key is set', () => {
|
||||||
|
const map = new SyncableMap<string, string>();
|
||||||
|
const subscriber = vi.fn();
|
||||||
|
map.subscribe(subscriber);
|
||||||
|
|
||||||
|
map.set('key1', 'value1');
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||||
|
expect(map.get('key1')).toBe('value1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify subscribers when a key is deleted', () => {
|
||||||
|
const map = new SyncableMap<string, string>([['key1', 'value1']]);
|
||||||
|
const subscriber = vi.fn();
|
||||||
|
map.subscribe(subscriber);
|
||||||
|
|
||||||
|
map.delete('key1');
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||||
|
expect(map.get('key1')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify subscribers when the map is cleared', () => {
|
||||||
|
const map = new SyncableMap<string, string>([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]);
|
||||||
|
const subscriber = vi.fn();
|
||||||
|
map.subscribe(subscriber);
|
||||||
|
|
||||||
|
map.clear();
|
||||||
|
|
||||||
|
expect(subscriber).toHaveBeenCalledTimes(1);
|
||||||
|
expect(map.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not notify unsubscribed callbacks', () => {
|
||||||
|
const map = new SyncableMap<string, string>();
|
||||||
|
const subscriber = vi.fn();
|
||||||
|
const unsubscribe = map.subscribe(subscriber);
|
||||||
|
|
||||||
|
unsubscribe();
|
||||||
|
|
||||||
|
map.set('key1', 'value1');
|
||||||
|
|
||||||
|
expect(subscriber).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a snapshot of the current state', () => {
|
||||||
|
const map = new SyncableMap<string, string>([['key1', 'value1']]);
|
||||||
|
|
||||||
|
const snapshot = map.getSnapshot();
|
||||||
|
|
||||||
|
expect(snapshot.size).toBe(1);
|
||||||
|
expect(snapshot.get('key1')).toBe('value1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the same snapshot if there were no changes', () => {
|
||||||
|
const map = new SyncableMap<string, string>([['key1', 'value1']]);
|
||||||
|
|
||||||
|
const firstSnapshot = map.getSnapshot();
|
||||||
|
const secondSnapshot = map.getSnapshot();
|
||||||
|
|
||||||
|
expect(firstSnapshot).toBe(secondSnapshot);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a new snapshot if changes were made', () => {
|
||||||
|
const map = new SyncableMap<string, string>([['key1', 'value1']]);
|
||||||
|
|
||||||
|
const firstSnapshot = map.getSnapshot();
|
||||||
|
map.set('key2', 'value2');
|
||||||
|
const secondSnapshot = map.getSnapshot();
|
||||||
|
|
||||||
|
expect(firstSnapshot).not.toBe(secondSnapshot);
|
||||||
|
expect(secondSnapshot.size).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consider different snapshots unequal', () => {
|
||||||
|
const map = new SyncableMap<string, string>([['key1', 'value1']]);
|
||||||
|
|
||||||
|
const firstSnapshot = map.getSnapshot();
|
||||||
|
map.set('key2', 'value2');
|
||||||
|
const secondSnapshot = map.getSnapshot();
|
||||||
|
|
||||||
|
expect(map['areSnapshotsEqual'](firstSnapshot, secondSnapshot)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should consider identical snapshots equal', () => {
|
||||||
|
const map = new SyncableMap<string, string>([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const firstSnapshot = map.getSnapshot();
|
||||||
|
const secondSnapshot = map.getSnapshot();
|
||||||
|
|
||||||
|
expect(map['areSnapshotsEqual'](firstSnapshot, secondSnapshot)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* A Map that allows for subscribing to changes and getting a snapshot of the current state.
|
||||||
|
*
|
||||||
|
* It can be used with the `useSyncExternalStore` hook to sync the state of the map with a React component.
|
||||||
|
*
|
||||||
|
* Reactivity is shallow, so changes to nested objects will not trigger a re-render.
|
||||||
|
*/
|
||||||
|
export class SyncableMap<K, V> extends Map<K, V> {
|
||||||
|
private subscriptions = new Set<() => void>();
|
||||||
|
private lastSnapshot: Map<K, V> | null = null;
|
||||||
|
|
||||||
|
constructor(entries?: readonly (readonly [K, V])[] | null) {
|
||||||
|
super(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
set = (key: K, value: V): this => {
|
||||||
|
super.set(key, value);
|
||||||
|
this.notifySubscribers();
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
delete = (key: K): boolean => {
|
||||||
|
const result = super.delete(key);
|
||||||
|
this.notifySubscribers();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
clear = (): void => {
|
||||||
|
super.clear();
|
||||||
|
this.notifySubscribers();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify all subscribers that the map has changed.
|
||||||
|
*/
|
||||||
|
private notifySubscribers = () => {
|
||||||
|
for (const callback of this.subscriptions) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to changes to the map.
|
||||||
|
* @param callback A function to call when the map changes
|
||||||
|
* @returns A function to unsubscribe from changes
|
||||||
|
*/
|
||||||
|
subscribe = (callback: () => void): (() => void) => {
|
||||||
|
this.subscriptions.add(callback);
|
||||||
|
return () => {
|
||||||
|
this.subscriptions.delete(callback);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a snapshot of the current state of the map.
|
||||||
|
* @returns A snapshot of the current state of the map
|
||||||
|
*/
|
||||||
|
getSnapshot = (): Map<K, V> => {
|
||||||
|
const currentSnapshot = new Map(this);
|
||||||
|
if (!this.lastSnapshot || !this.areSnapshotsEqual(this.lastSnapshot, currentSnapshot)) {
|
||||||
|
this.lastSnapshot = currentSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lastSnapshot;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two snapshots to determine if they are equal.
|
||||||
|
* @param snapshotA The first snapshot to compare
|
||||||
|
* @param snapshotB The second snapshot to compare
|
||||||
|
* @returns Whether the two snapshots are equal
|
||||||
|
*/
|
||||||
|
private areSnapshotsEqual = (snapshotA: Map<K, V>, snapshotB: Map<K, V>): boolean => {
|
||||||
|
if (snapshotA.size !== snapshotB.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of snapshotA) {
|
||||||
|
if (!Object.is(value, snapshotB.get(key))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
export const getImageDataTransparency = (pixels: Uint8ClampedArray) => {
|
|
||||||
let isFullyTransparent = true;
|
|
||||||
let isPartiallyTransparent = false;
|
|
||||||
const len = pixels.length;
|
|
||||||
let i = 3;
|
|
||||||
for (i; i < len; i += 4) {
|
|
||||||
if (pixels[i] === 255) {
|
|
||||||
isFullyTransparent = false;
|
|
||||||
} else {
|
|
||||||
isPartiallyTransparent = true;
|
|
||||||
}
|
|
||||||
if (!isFullyTransparent && isPartiallyTransparent) {
|
|
||||||
return { isFullyTransparent, isPartiallyTransparent };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { isFullyTransparent, isPartiallyTransparent };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const areAnyPixelsBlack = (pixels: Uint8ClampedArray) => {
|
|
||||||
const len = pixels.length;
|
|
||||||
let i = 0;
|
|
||||||
for (i; i < len; ) {
|
|
||||||
if (pixels[i++] === 0 && pixels[i++] === 0 && pixels[i++] === 0 && pixels[i++] === 255) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
@ -1,85 +1,170 @@
|
|||||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
import { moveOneToEnd, moveOneToStart, moveToEnd, moveToStart } from 'common/util/arrayUtils';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
describe('Array Manipulation Functions', () => {
|
describe('Array Manipulation Functions', () => {
|
||||||
const originalArray = ['a', 'b', 'c', 'd'];
|
const originalArray = ['a', 'b', 'c', 'd'];
|
||||||
describe('moveForwardOne', () => {
|
|
||||||
it('should move an item forward by one position', () => {
|
|
||||||
const array = [...originalArray];
|
|
||||||
const result = moveForward(array, (item) => item === 'b');
|
|
||||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing if the item is at the end', () => {
|
describe('moveOneToEnd', () => {
|
||||||
const array = [...originalArray];
|
describe('with callback', () => {
|
||||||
const result = moveForward(array, (item) => item === 'd');
|
it('should move an item forward by one position', () => {
|
||||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
const array = [...originalArray];
|
||||||
});
|
const result = moveOneToEnd(array, (item) => item === 'b');
|
||||||
|
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
it('should do nothing if the item is at the end', () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveForward(array, (item) => item === 'z');
|
const result = moveOneToEnd(array, (item) => item === 'd');
|
||||||
expect(result).toEqual(originalArray);
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToEnd(array, (item) => item === 'z');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('with item', () => {
|
||||||
|
it('should move an item forward by one position', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToEnd(array, (item) => item === 'b');
|
||||||
|
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if the item is at the end', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToEnd(array, (item) => item === 'd');
|
||||||
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToEnd(array, (item) => item === 'z');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('moveToFront', () => {
|
describe('moveToStart', () => {
|
||||||
it('should move an item to the front', () => {
|
describe('with callback', () => {
|
||||||
const array = [...originalArray];
|
it('should move an item to the front', () => {
|
||||||
const result = moveToFront(array, (item) => item === 'c');
|
const array = [...originalArray];
|
||||||
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
const result = moveToStart(array, (item) => item === 'c');
|
||||||
});
|
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should do nothing if the item is already at the front', () => {
|
it('should do nothing if the item is already at the front', () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveToFront(array, (item) => item === 'a');
|
const result = moveToStart(array, (item) => item === 'a');
|
||||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveToFront(array, (item) => item === 'z');
|
const result = moveToStart(array, (item) => item === 'z');
|
||||||
expect(result).toEqual(originalArray);
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('with item', () => {
|
||||||
|
it('should move an item to the front', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToStart(array, 'c');
|
||||||
|
expect(result).toEqual(['c', 'a', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if the item is already at the front', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToStart(array, 'a');
|
||||||
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToStart(array, 'z');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('moveBackwardsOne', () => {
|
describe('moveOneToStart', () => {
|
||||||
it('should move an item backward by one position', () => {
|
describe('with callback', () => {
|
||||||
const array = [...originalArray];
|
it('should move an item backward by one position', () => {
|
||||||
const result = moveBackward(array, (item) => item === 'c');
|
const array = [...originalArray];
|
||||||
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
const result = moveOneToStart(array, (item) => item === 'c');
|
||||||
});
|
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should do nothing if the item is at the beginning', () => {
|
it('should do nothing if the item is at the beginning', () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveBackward(array, (item) => item === 'a');
|
const result = moveOneToStart(array, (item) => item === 'a');
|
||||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveBackward(array, (item) => item === 'z');
|
const result = moveOneToStart(array, (item) => item === 'z');
|
||||||
expect(result).toEqual(originalArray);
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('with item', () => {
|
||||||
|
it('should move an item backward by one position', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToStart(array, 'c');
|
||||||
|
expect(result).toEqual(['a', 'c', 'b', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if the item is at the beginning', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToStart(array, 'a');
|
||||||
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveOneToStart(array, 'z');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('moveToBack', () => {
|
describe('moveToEnd', () => {
|
||||||
it('should move an item to the back', () => {
|
describe('with callback', () => {
|
||||||
const array = [...originalArray];
|
it('should move an item to the back', () => {
|
||||||
const result = moveToBack(array, (item) => item === 'b');
|
const array = [...originalArray];
|
||||||
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
const result = moveToEnd(array, (item) => item === 'b');
|
||||||
});
|
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||||
|
});
|
||||||
|
|
||||||
it('should do nothing if the item is already at the back', () => {
|
it('should do nothing if the item is already at the back', () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveToBack(array, (item) => item === 'd');
|
const result = moveToEnd(array, (item) => item === 'd');
|
||||||
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should leave the array unchanged if the item isn't in the array", () => {
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
const array = [...originalArray];
|
const array = [...originalArray];
|
||||||
const result = moveToBack(array, (item) => item === 'z');
|
const result = moveToEnd(array, (item) => item === 'z');
|
||||||
expect(result).toEqual(originalArray);
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('with item', () => {
|
||||||
|
it('should move an item to the back', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToEnd(array, 'b');
|
||||||
|
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should do nothing if the item is already at the back', () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToEnd(array, 'd');
|
||||||
|
expect(result).toEqual(['a', 'b', 'c', 'd']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should leave the array unchanged if the item isn't in the array", () => {
|
||||||
|
const array = [...originalArray];
|
||||||
|
const result = moveToEnd(array, 'z');
|
||||||
|
expect(result).toEqual(originalArray);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,37 +1,45 @@
|
|||||||
export const moveForward = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
export function moveToStart<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||||
const index = array.findIndex(callback);
|
export function moveToStart<T>(array: T[], item: T): T[];
|
||||||
if (index >= 0 && index < array.length - 1) {
|
export function moveToStart<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||||
//@ts-expect-error - These indicies are safe per the previous check
|
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||||
[array[index], array[index + 1]] = [array[index + 1], array[index]];
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const moveToFront = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
|
||||||
const index = array.findIndex(callback);
|
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
const [item] = array.splice(index, 1);
|
const [item] = array.splice(index, 1);
|
||||||
//@ts-expect-error - These indicies are safe per the previous check
|
//@ts-expect-error - These indicies are safe per the previous check
|
||||||
array.unshift(item);
|
array.unshift(item);
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const moveBackward = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
export function moveOneToStart<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||||
const index = array.findIndex(callback);
|
export function moveOneToStart<T>(array: T[], item: T): T[];
|
||||||
|
export function moveOneToStart<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||||
|
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
//@ts-expect-error - These indicies are safe per the previous check
|
//@ts-expect-error - These indicies are safe per the previous check
|
||||||
[array[index], array[index - 1]] = [array[index - 1], array[index]];
|
[array[index], array[index - 1]] = [array[index - 1], array[index]];
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
};
|
}
|
||||||
|
|
||||||
export const moveToBack = <T>(array: T[], callback: (item: T) => boolean): T[] => {
|
export function moveToEnd<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||||
const index = array.findIndex(callback);
|
export function moveToEnd<T>(array: T[], item: T): T[];
|
||||||
|
export function moveToEnd<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||||
|
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||||
if (index >= 0 && index < array.length - 1) {
|
if (index >= 0 && index < array.length - 1) {
|
||||||
const [item] = array.splice(index, 1);
|
const [item] = array.splice(index, 1);
|
||||||
//@ts-expect-error - These indicies are safe per the previous check
|
//@ts-expect-error - These indicies are safe per the previous check
|
||||||
array.push(item);
|
array.push(item);
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export function moveOneToEnd<T>(array: T[], selectItemCallback: (item: T) => boolean): T[];
|
||||||
|
export function moveOneToEnd<T>(array: T[], item: T): T[];
|
||||||
|
export function moveOneToEnd<T>(array: T[], arg1: T | ((item: T) => boolean)): T[] {
|
||||||
|
const index = arg1 instanceof Function ? array.findIndex(arg1) : array.indexOf(arg1);
|
||||||
|
if (index >= 0 && index < array.length - 1) {
|
||||||
|
//@ts-expect-error - These indicies are safe per the previous check
|
||||||
|
[array[index], array[index + 1]] = [array[index + 1], array[index]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { RgbaColor } from 'react-colorful';
|
import type { RgbaColor, RgbColor } from 'react-colorful';
|
||||||
|
|
||||||
export function rgbaToHex(color: RgbaColor, alpha: boolean = false): string {
|
export function rgbaToHex(color: RgbaColor, alpha: boolean = false): string {
|
||||||
const hex = ((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1);
|
const hex = ((1 << 24) + (color.r << 16) + (color.g << 8) + color.b).toString(16).slice(1);
|
||||||
@ -15,3 +15,13 @@ export function hexToRGBA(hex: string, alpha: number) {
|
|||||||
const b = parseInt(hex.substring(4, 6), 16);
|
const b = parseInt(hex.substring(4, 6), 16);
|
||||||
return { r, g, b, a: alpha };
|
return { r, g, b, a: alpha };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const rgbaColorToString = (color: RgbaColor): string => {
|
||||||
|
const { r, g, b, a } = color;
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rgbColorToString = (color: RgbColor): string => {
|
||||||
|
const { r, g, b } = color;
|
||||||
|
return `rgba(${r}, ${g}, ${b})`;
|
||||||
|
};
|
||||||
|
@ -7,7 +7,7 @@ import { $authToken } from 'app/store/nanostores/authToken';
|
|||||||
* @returns A function that takes a URL and returns a Promise that resolves with a Blob
|
* @returns A function that takes a URL and returns a Promise that resolves with a Blob
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const convertImageUrlToBlob = async (url: string) =>
|
export const convertImageUrlToBlob = (url: string) =>
|
||||||
new Promise<Blob | null>((resolve, reject) => {
|
new Promise<Blob | null>((resolve, reject) => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
export const isInputElement = (el: HTMLElement) => {
|
|
||||||
return (
|
|
||||||
el.tagName.toLowerCase() === 'input' ||
|
|
||||||
el.tagName.toLowerCase() === 'textarea' ||
|
|
||||||
el.tagName.toLowerCase() === 'select'
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,23 +0,0 @@
|
|||||||
type Base64AndCaption = {
|
|
||||||
base64: string;
|
|
||||||
caption: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const openBase64ImageInTab = (images: Base64AndCaption[]) => {
|
|
||||||
const w = window.open('');
|
|
||||||
if (!w) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
images.forEach((i) => {
|
|
||||||
const image = new Image();
|
|
||||||
image.src = i.base64;
|
|
||||||
|
|
||||||
w.document.write(i.caption);
|
|
||||||
w.document.write('</br>');
|
|
||||||
w.document.write(image.outerHTML);
|
|
||||||
w.document.write('</br></br>');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default openBase64ImageInTab;
|
|
72
invokeai/frontend/web/src/common/util/result.test.ts
Normal file
72
invokeai/frontend/web/src/common/util/result.test.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import type { Equals } from 'tsafe';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import type { ErrResult, OkResult } from './result';
|
||||||
|
import { Err, isErr, isOk, Ok, withResult, withResultAsync } from './result'; // Adjust import as needed
|
||||||
|
|
||||||
|
const promiseify = <T>(fn: () => T): (() => Promise<T>) => {
|
||||||
|
return () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolve(fn());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Result Utility Functions', () => {
|
||||||
|
it('Ok() should create an OkResult', () => {
|
||||||
|
const result = Ok(42);
|
||||||
|
expect(result).toEqual({ type: 'Ok', value: 42 });
|
||||||
|
expect(isOk(result)).toBe(true);
|
||||||
|
expect(isErr(result)).toBe(false);
|
||||||
|
assert<Equals<OkResult<number>, typeof result>>(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Err() should create an ErrResult', () => {
|
||||||
|
const error = new Error('Something went wrong');
|
||||||
|
const result = Err(error);
|
||||||
|
expect(result).toEqual({ type: 'Err', error });
|
||||||
|
expect(isOk(result)).toBe(false);
|
||||||
|
expect(isErr(result)).toBe(true);
|
||||||
|
assert<Equals<ErrResult<Error>, typeof result>>(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withResult() should return Ok on success', () => {
|
||||||
|
const fn = () => 42;
|
||||||
|
const result = withResult(fn);
|
||||||
|
expect(isOk(result)).toBe(true);
|
||||||
|
if (isOk(result)) {
|
||||||
|
expect(result.value).toBe(42);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withResult() should return Err on exception', () => {
|
||||||
|
const fn = () => {
|
||||||
|
throw new Error('Failure');
|
||||||
|
};
|
||||||
|
const result = withResult(fn);
|
||||||
|
expect(isErr(result)).toBe(true);
|
||||||
|
if (isErr(result)) {
|
||||||
|
expect(result.error.message).toBe('Failure');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withResultAsync() should return Ok on success', async () => {
|
||||||
|
const fn = promiseify(() => 42);
|
||||||
|
const result = await withResultAsync(fn);
|
||||||
|
expect(isOk(result)).toBe(true);
|
||||||
|
if (isOk(result)) {
|
||||||
|
expect(result.value).toBe(42);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('withResultAsync() should return Err on exception', async () => {
|
||||||
|
const fn = promiseify(() => {
|
||||||
|
throw new Error('Async failure');
|
||||||
|
});
|
||||||
|
const result = await withResultAsync(fn);
|
||||||
|
expect(isErr(result)).toBe(true);
|
||||||
|
if (isErr(result)) {
|
||||||
|
expect(result.error.message).toBe('Async failure');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user