LinHanjiang commited on
Commit
74aacd5
1 Parent(s): daca9fd

Upload 259 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lama_cleaner/__init__.py +18 -0
  2. lama_cleaner/__pycache__/__init__.cpython-38.pyc +0 -0
  3. lama_cleaner/__pycache__/const.cpython-38.pyc +0 -0
  4. lama_cleaner/__pycache__/helper.cpython-38.pyc +0 -0
  5. lama_cleaner/__pycache__/model_manager.cpython-38.pyc +0 -0
  6. lama_cleaner/__pycache__/parse_args.cpython-38.pyc +0 -0
  7. lama_cleaner/__pycache__/runtime.cpython-38.pyc +0 -0
  8. lama_cleaner/__pycache__/schema.cpython-38.pyc +0 -0
  9. lama_cleaner/__pycache__/server.cpython-38.pyc +0 -0
  10. lama_cleaner/app/.env +1 -0
  11. lama_cleaner/app/.eslintrc.json +52 -0
  12. lama_cleaner/app/.gitignore +27 -0
  13. lama_cleaner/app/.prettierrc +6 -0
  14. lama_cleaner/app/LICENSE +201 -0
  15. lama_cleaner/app/build/asset-manifest.json +16 -0
  16. lama_cleaner/app/build/index.html +1 -0
  17. lama_cleaner/app/build/static/css/main.6ca672e8.css +1 -0
  18. lama_cleaner/app/build/static/js/main.44aac645.js +0 -0
  19. lama_cleaner/app/build/static/js/main.44aac645.js.LICENSE.txt +69 -0
  20. lama_cleaner/app/build/static/media/WorkSans-Black.67c2c5a144333953880b.ttf +0 -0
  21. lama_cleaner/app/build/static/media/WorkSans-Bold.2bea7a7f7d052c74da25.ttf +0 -0
  22. lama_cleaner/app/build/static/media/WorkSans-Regular.bb287b894b27372d8ea7.ttf +0 -0
  23. lama_cleaner/app/build/static/media/WorkSans-SemiBold.1e98db4eb705b586728e.ttf +0 -0
  24. lama_cleaner/app/build/static/media/coffee-machine-lineal.ee32631219cc3986f861.gif +0 -0
  25. lama_cleaner/app/config/env.js +104 -0
  26. lama_cleaner/app/config/getHttpsConfig.js +66 -0
  27. lama_cleaner/app/config/jest/babelTransform.js +29 -0
  28. lama_cleaner/app/config/jest/cssTransform.js +14 -0
  29. lama_cleaner/app/config/jest/fileTransform.js +40 -0
  30. lama_cleaner/app/config/modules.js +134 -0
  31. lama_cleaner/app/config/paths.js +77 -0
  32. lama_cleaner/app/config/webpack.config.js +761 -0
  33. lama_cleaner/app/config/webpack/persistentCache/createEnvironmentHash.js +9 -0
  34. lama_cleaner/app/config/webpackDevServer.config.js +127 -0
  35. lama_cleaner/app/package.json +181 -0
  36. lama_cleaner/app/pnpm-lock.yaml +0 -0
  37. lama_cleaner/app/public/index.html +33 -0
  38. lama_cleaner/app/scripts/build.js +217 -0
  39. lama_cleaner/app/scripts/start.js +154 -0
  40. lama_cleaner/app/scripts/test.js +52 -0
  41. lama_cleaner/app/src/App.tsx +186 -0
  42. lama_cleaner/app/src/adapters/inpainting.ts +264 -0
  43. lama_cleaner/app/src/components/CoffeeIcon/CoffeeIcon.tsx +82 -0
  44. lama_cleaner/app/src/components/Croper/Croper.scss +167 -0
  45. lama_cleaner/app/src/components/Croper/Croper.tsx +408 -0
  46. lama_cleaner/app/src/components/Editor/Editor.scss +108 -0
  47. lama_cleaner/app/src/components/Editor/Editor.tsx +1744 -0
  48. lama_cleaner/app/src/components/Editor/MakeGIF.tsx +125 -0
  49. lama_cleaner/app/src/components/Editor/Slider.tsx +46 -0
  50. lama_cleaner/app/src/components/FileManager/FileManager.scss +164 -0
lama_cleaner/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
4
+
5
+ import warnings
6
+
7
+ warnings.simplefilter("ignore", UserWarning)
8
+
9
+ from lama_cleaner.parse_args import parse_args
10
+
11
+
12
+ def entry_point():
13
+ args = parse_args()
14
+ # To make os.environ["XDG_CACHE_HOME"] = args.model_cache_dir works for diffusers
15
+ # https://github.com/huggingface/diffusers/blob/be99201a567c1ccd841dc16fb24e88f7f239c187/src/diffusers/utils/constants.py#L18
16
+ from lama_cleaner.server import main
17
+
18
+ main(args)
lama_cleaner/__pycache__/__init__.cpython-38.pyc ADDED
Binary file (539 Bytes). View file
 
lama_cleaner/__pycache__/const.cpython-38.pyc ADDED
Binary file (5.18 kB). View file
 
lama_cleaner/__pycache__/helper.cpython-38.pyc ADDED
Binary file (7.63 kB). View file
 
lama_cleaner/__pycache__/model_manager.cpython-38.pyc ADDED
Binary file (2.79 kB). View file
 
lama_cleaner/__pycache__/parse_args.cpython-38.pyc ADDED
Binary file (5.61 kB). View file
 
lama_cleaner/__pycache__/runtime.cpython-38.pyc ADDED
Binary file (1.4 kB). View file
 
lama_cleaner/__pycache__/schema.cpython-38.pyc ADDED
Binary file (2.61 kB). View file
 
lama_cleaner/__pycache__/server.cpython-38.pyc ADDED
Binary file (13.5 kB). View file
 
lama_cleaner/app/.env ADDED
@@ -0,0 +1 @@
 
 
1
+ REACT_APP_INPAINTING_URL=""
lama_cleaner/app/.eslintrc.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": [
3
+ "airbnb",
4
+ "airbnb/hooks",
5
+ "plugin:@typescript-eslint/recommended",
6
+ "prettier",
7
+ "plugin:prettier/recommended"
8
+ ],
9
+ "plugins": ["@typescript-eslint", "react", "react-hooks", "prettier"],
10
+ "parser": "@typescript-eslint/parser",
11
+ "parserOptions": {
12
+ "ecmaFeatures": {
13
+ "jsx": true
14
+ },
15
+ "ecmaVersion": 2018,
16
+ "sourceType": "module",
17
+ "project": "./tsconfig.json"
18
+ },
19
+ "rules": {
20
+ "jsx-a11y/click-events-have-key-events": 0,
21
+ "react/jsx-props-no-spreading": 0,
22
+ "import/no-unresolved": 0,
23
+ "react/jsx-no-bind": "off",
24
+ "react/jsx-filename-extension": [
25
+ 1,
26
+ {
27
+ "extensions": [".ts", ".tsx"]
28
+ }
29
+ ],
30
+ "prettier/prettier": [
31
+ "error",
32
+ {
33
+ "singleQuote": true,
34
+ "arrowParens": "avoid",
35
+ "endOfLine": "auto"
36
+ }
37
+ ],
38
+ "consistent-return": "off",
39
+ "no-use-before-define": "off",
40
+ "import/extensions": "off",
41
+ "react/prop-types": 0,
42
+ "react/require-default-props": "off",
43
+ "no-shadow": "off",
44
+ "@typescript-eslint/ban-ts-comment": "off",
45
+ "@typescript-eslint/no-shadow": ["error"],
46
+ "@typescript-eslint/no-explicit-any": "off",
47
+ "@typescript-eslint/explicit-function-return-type": "off",
48
+ "@typescript-eslint/explicit-module-boundary-types": "off",
49
+ "react-hooks/rules-of-hooks": "error",
50
+ "react-hooks/exhaustive-deps": "warn"
51
+ }
52
+ }
lama_cleaner/app/.gitignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # production
12
+
13
+ # misc
14
+ .DS_Store
15
+ .env.local
16
+ .env.development.local
17
+ .env.test.local
18
+ .env.production.local
19
+
20
+ npm-debug.log*
21
+ yarn-debug.log*
22
+ yarn-error.log*
23
+
24
+ # Tailwind processed CSS
25
+ index.css
26
+
27
+ .firebase
lama_cleaner/app/.prettierrc ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "singleQuote": true,
3
+ "semi": false,
4
+ "trailingComma": "es5",
5
+ "arrowParens": "avoid"
6
+ }
lama_cleaner/app/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
lama_cleaner/app/build/asset-manifest.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": {
3
+ "main.css": "/static/css/main.6ca672e8.css",
4
+ "main.js": "/static/js/main.44aac645.js",
5
+ "static/media/coffee-machine-lineal.gif": "/static/media/coffee-machine-lineal.ee32631219cc3986f861.gif",
6
+ "static/media/WorkSans-SemiBold.ttf": "/static/media/WorkSans-SemiBold.1e98db4eb705b586728e.ttf",
7
+ "static/media/WorkSans-Bold.ttf": "/static/media/WorkSans-Bold.2bea7a7f7d052c74da25.ttf",
8
+ "static/media/WorkSans-Regular.ttf": "/static/media/WorkSans-Regular.bb287b894b27372d8ea7.ttf",
9
+ "static/media/WorkSans-Black.ttf": "/static/media/WorkSans-Black.67c2c5a144333953880b.ttf",
10
+ "index.html": "/index.html"
11
+ },
12
+ "entrypoints": [
13
+ "static/css/main.6ca672e8.css",
14
+ "static/js/main.44aac645.js"
15
+ ]
16
+ }
lama_cleaner/app/build/index.html ADDED
@@ -0,0 +1 @@
 
 
1
+ <!doctype html><html lang="en"><head><meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/><meta http-equiv="Pragma" content="no-cache"/><meta http-equiv="Expires" content="0"/><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by SOTA AI model</title><script defer="defer" src="/static/js/main.44aac645.js"></script><link href="/static/css/main.6ca672e8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
lama_cleaner/app/build/static/css/main.6ca672e8.css ADDED
@@ -0,0 +1 @@
 
 
1
+ :root{--blackA1:rgba(0,0,0,.012);--blackA2:rgba(0,0,0,.027);--blackA3:rgba(0,0,0,.047);--blackA4:rgba(0,0,0,.071);--blackA5:rgba(0,0,0,.09);--blackA6:rgba(0,0,0,.114);--blackA7:rgba(0,0,0,.141);--blackA8:rgba(0,0,0,.22);--blackA9:rgba(0,0,0,.439);--blackA10:rgba(0,0,0,.478);--blackA11:rgba(0,0,0,.565);--blackA12:rgba(0,0,0,.91);--mauve1:#fdfcfd;--mauve2:#f9f8f9;--mauve3:#f4f2f4;--mauve4:#eeedef;--mauve5:#e9e8ea;--mauve6:#e4e2e4;--mauve7:#dcdbdd;--mauve8:#c8c7cb;--mauve9:#908e96;--mauve10:#86848d;--mauve11:#6f6e77;--mauve12:#1a1523;--violet1:#fdfcfe;--violet2:#fbfaff;--violet3:#f5f2ff;--violet4:#ede9fe;--violet5:#e4defc;--violet6:#d7cff9;--violet7:#c4b8f3;--violet8:#aa99ec;--violet9:#6e56cf;--violet10:#644fc1;--violet11:#5746af;--violet12:#20134b;--page-bg:#fff;--page-bg-light:hsla(0,0%,100%,.5);--page-text-color:#040404;--yellow-accent:#fc0;--yellow-accent-light:#ffcc0055;--link-color:#000;--border-color:#eff1f4;--border-color-light:hsla(240,9%,43%,.5);--tooltip-bg:#e6e6ea;--tooltip-text-color:#000;--error-color:#ef4444;--success-color:#10b981;--editor-toolkit-bg:hsla(0,0%,100%,.5);--editor-options-bg:#e6e6ea;--options-text-color:var(--page-text-color);--editor-size-border-color:var(--border-color);--editor-toolkit-panel-border:0;--modal-bg:var(--page-bg);--modal-text-color:#000;--modal-hotkey-border-color:#000;--model-mask-bg:rgba(209,213,219,.4);--text-color:#040404;--text-color-gray:#6b6f76;--text-color-disabled:#6b6f76;--btn-text-color:var(--text-color);--btn-text-hover-color:#040404;--btn-border-color:#646478;--btn-primary-hover-bg:var(--yellow-accent);--animation-pulsing-bg:hsla(0,0%,100%,.5);--switch-root-background-color:#dfe1e4;--switch-thumb-color:var(--page-bg);--switch-thumb-checked-color:var(--page-bg);--slider-background-color:var(--switch-root-background-color);--tooltip-bg:var(--page-bg);--badge-background-color:#f1f3f5;--badge-color:#687076;--box-shadow:inset 0 0.5px hsla(0,0%,100%,.1),inset 0 1px 5px #f8f9fa,0px 0px 0px 0.5px #c1c8cd,0px 2px 1px -1px #c1c8cd,0 1px #c1c8cd;--croper-bg:rgba(0,0,0,.5);--tabs-active-color:#f0f3f9}@font-face{font-family:WorkSans;src:url(/static/media/WorkSans-Regular.bb287b894b27372d8ea7.ttf)}@font-face{font-family:WorkSans-Semibold;src:url(/static/media/WorkSans-SemiBold.1e98db4eb705b586728e.ttf)}@font-face{font-family:WorkSans-Bold;src:url(/static/media/WorkSans-Bold.2bea7a7f7d052c74da25.ttf)}@font-face{font-family:WorkSans-Black;src:url(/static/media/WorkSans-Black.67c2c5a144333953880b.ttf)}[data-theme=dark]{--page-bg:#040404;--page-bg-light:#04040488;--page-text-color:#f9f9f9;--yellow-accent:#fc0;--yellow-accent-light:#ffcc0055;--link-color:var(--yellow-accent);--border-color:#1e1e1e;--border-color-light:#666;--tooltip-bg:#212121;--tooltip-text-color:#d2d2d2;--editor-toolkit-bg:rgba(0,0,0,.5);--editor-options-bg:#212121;--options-text-color:var(--page-text-color);--editor-size-border-color:var(--yellow-accent);--editor-toolkit-panel-border:1px solid hsla(240,9%,43%,.4);--modal-bg:var(--page-bg);--modal-text-color:var(--page-text-color);--modal-hotkey-border-color:var(--page-text-color);--model-mask-bg:rgba(76,76,87,.4);--text-color:#fff;--text-color-gray:#c3c4c6;--text-color-disabled:#6b6f76;--btn-text-color:var(--text-color);--btn-text-hover-color:var(--page-bg);--btn-border-color:var(--yellow-accent);--btn-primary-hover-bg:var(--yellow-accent);--animation-pulsing-bg:#f0f0ff;--switch-root-background-color:#3c3f44;--switch-thumb-color:#1f2023;--switch-thumb-checked-color:#fff;--slider-background-color:var(--switch-root-background-color);--badge-background-color:#202425;--badge-color:#9ba1a6;--box-shadow:inset 0 0.5px hsla(0,0%,100%,.1),inset 0 1px 5px #1a1d1e,0px 0px 0px 0.5px #4c5155,0px 2px 1px -1px #4c5155,0 1px #4c5155;--croper-bg:rgba(0,0,0,.5);--tabs-active-color:#272831}@supports (color:hsl(0 0% 0%/0)){[data-theme=dark]{--tooltip-bg:#202425}}@-webkit-keyframes pulsing{0%{opacity:1}50%{background-color:hsla(0,0%,100%,.5);background-color:var(--animation-pulsing-bg);opacity:.75}to{opacity:1}}@keyframes pulsing{0%{opacity:1}50%{background-color:hsla(0,0%,100%,.5);background-color:var(--animation-pulsing-bg);opacity:.75}to{opacity:1}}@-webkit-keyframes opacityReveal{0%{opacity:0}to{opacity:1}}@keyframes opacityReveal{0%{opacity:0}to{opacity:1}}@-webkit-keyframes slideDown{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes slideDown{0%{-webkit-transform:translateY(-100%);transform:translateY(-100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes slideUp{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes slideUp{0%{-webkit-transform:translateY(100%);transform:translateY(100%)}to{-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes slideIn{0%{-webkit-transform:translateX(calc(100% + 25px));transform:translateX(calc(100% + 25px))}to{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideIn{0%{-webkit-transform:translateX(calc(100% + 25px));transform:translateX(calc(100% + 25px))}to{-webkit-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes slideUpAndFade{0%{opacity:0;-webkit-transform:translateY(2px);transform:translateY(2px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes slideDownAndFade{0%{opacity:0;-webkit-transform:translateY(-2px);transform:translateY(-2px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.lama-cleaner{background-color:#fff;background-color:var(--page-bg);color:#040404;color:var(--page-text-color);display:grid;grid-template-areas:"main-content";height:100vh;transition-duration:.2s;transition-property:background-color,color;transition-timing-function:repeat(2,ease-out);width:100vw}a{color:inherit;text-decoration:inherit}input:disabled{color:#6b6f76;color:var(--text-color-gray)}.editor-container{align-items:center;display:flex;height:100vh;justify-content:center;width:100vw}.react-transform-wrapper{display:grid!important;height:100%!important;width:100%!important}.editor-canvas-container{grid-row-gap:1rem;display:grid;grid-template-areas:"editor-content";row-gap:1rem}.editor-canvas{grid-area:editor-content;z-index:2}.original-image-container{display:grid;grid-area:editor-content;grid-template-areas:"original-image-content";pointer-events:none}.original-image-container img{grid-area:original-image-content}.original-image-container .editor-slider{background-color:#fc0;background-color:var(--yellow-accent);grid-area:original-image-content;height:100%;justify-self:end;transition:all .3s cubic-bezier(.4,0,.2,1);width:6px;z-index:2}.editor-canvas-loading{-webkit-animation:pulsing .75s infinite;animation:pulsing .75s infinite;pointer-events:none}.editor-toolkit-panel{align-items:center;-webkit-animation:slideUp .2s ease-out;animation:slideUp .2s ease-out;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.5);background-color:var(--page-bg-light);border:0;border:var(--editor-toolkit-panel-border);border-radius:3rem;bottom:.5rem;box-shadow:0 0 0 1px rgba(0,0,0,.102),0 3px 16px rgba(0,0,0,.078),0 2px 6px 1px rgba(0,0,0,.09);display:flex;gap:16px;justify-content:center;padding:.4rem 24px;position:fixed}@media screen and (max-width:767px){.editor-toolkit-panel{grid-template-areas:"toolkit-size-selector toolkit-size-selector" "toolkit-brush-slider toolkit-brush-slider" "toolkit-btns toolkit-btns";justify-items:center;padding:1rem 2rem;row-gap:2rem}}.editor-toolkit-panel .eyeicon-active{background-color:#fc0;background-color:var(--yellow-accent);color:#040404;color:var(--btn-text-hover-color)}.editor-brush-slider{grid-column-gap:1rem;align-items:center;-webkit-column-gap:1rem;column-gap:1rem;display:grid;grid-area:toolkit-brush-slider;grid-template-columns:repeat(2,-webkit-max-content);grid-template-columns:repeat(2,max-content);height:-webkit-max-content;height:max-content;-webkit-user-select:none;user-select:none}.editor-brush-slider input[type=range]{-webkit-appearance:none;appearance:none;background:transparent;border-color:transparent;color:transparent;cursor:pointer;width:100%}.editor-brush-slider input[type=range]:focus{outline:none}.editor-brush-slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:#fc0;background:var(--yellow-accent);border:1px solid #000;border-radius:50%;height:1.2rem;margin-top:-.5rem;width:1.2rem;z-index:2}.editor-brush-slider input[type=range]::-webkit-slider-runnable-track{background:#dfe1e4;background:var(--slider-background-color);border-radius:2rem;height:.2rem}.editor-brush-slider input[type=range]::-moz-range-track{background:#dfe1e4;background:var(--slider-background-color);border-radius:2rem}.editor-brush-slider input[type=range]::-moz-range-progress{background:#fc0;background:var(--yellow-accent)}.editor-toolkit-btns{display:flex;gap:12px}.brush-shape{background-color:rgba(255,204,0,.733);border:1px solid #fc0;border:1px solid var(--yellow-accent);border-radius:50%;pointer-events:none;position:absolute}.file-manager-modal{color:#040404;color:var(--text-color);height:90%;width:80%}.react-photo-album.react-photo-album--columns{height:80vh}.react-photo-album--photo{border:1px solid transparent;border-radius:8px;transition:visibility .25s ease-in,-webkit-transform .25s;transition:transform .25s,visibility .25s ease-in;transition:transform .25s,visibility .25s ease-in,-webkit-transform .25s;-webkit-user-select:none;user-select:none}.react-photo-album--photo:hover{border:1px solid #eff1f4;border:1px solid var(--border-color);-webkit-transform:scale(1.03);transform:scale(1.03)}.ScrollAreaRoot{--scrollbar-size:10px;border-radius:4px;overflow:hidden}.ScrollAreaViewport{border-radius:inherit;height:100%;width:100%}.ScrollAreaScrollbar{display:flex;padding:2px;touch-action:none;transition:background .16s ease-out;-webkit-user-select:none;user-select:none}.ScrollAreaScrollbar:hover{background:var(--blackA8)}.ScrollAreaScrollbar[data-orientation=vertical]{width:var(--scrollbar-size)}.ScrollAreaScrollbar[data-orientation=horizontal]{flex-direction:column;height:var(--scrollbar-size)}.ScrollAreaThumb{background:var(--mauve10);border-radius:var(--scrollbar-size);flex:1 1;position:relative}.ScrollAreaThumb:before{content:"";height:100%;left:50%;min-height:44px;min-width:44px;position:absolute;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:100%}.ScrollAreaCorner{background:var(--blackA8)}.file-search-input{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:12px;height:32px;padding-left:30px;width:250px}.sort-btn-inactive svg{opacity:.5}button,fieldset,input{all:unset}.TabsRoot{align-self:flex-start;background-color:#fff;background-color:var(--page-bg);display:flex;flex-direction:column;gap:8px}.TabsList{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:12px;flex-direction:row;gap:6px;padding:4px}.TabsList,.TabsTrigger{background-color:#fff;background-color:var(--page-bg);display:flex;justify-content:flex-start}.TabsTrigger{align-items:center;border-radius:8px;color:#000;color:var(--modal-text-color);font-family:inherit;font-size:15px;line-height:1;padding:8px;-webkit-user-select:none;user-select:none}.TabsTrigger:hover,.TabsTrigger[data-state=active]{background-color:#f0f3f9;background-color:var(--tabs-active-color)}.TabsTrigger:focus{position:relative}.TabsContent{background-color:#fff;background-color:var(--page-bg);outline:none;width:100%}.TabsContent[data-state=active]{display:flex;flex-direction:column;gap:14px}.landing-page{grid-row-gap:2rem;display:grid;grid-auto-rows:-webkit-max-content;grid-auto-rows:max-content;justify-items:center;place-self:center;row-gap:2rem}@media screen and (max-width:767px){.landing-page{padding:1rem}}.landing-page h1{font-size:1.4rem;text-align:center}@media screen and (max-width:767px){.landing-page h1{font-size:1.2rem}}.landing-page a{color:#000;color:var(--link-color)}.landing-file-selector{display:grid}header{align-items:center;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background-color:hsla(0,0%,100%,.5);background-color:var(--page-bg-light);border-bottom:1px solid hsla(240,9%,43%,.2);display:flex;height:60px;justify-content:space-between;padding:1rem 1.5rem;position:absolute;top:0;width:100%;z-index:20}.shortcuts{z-index:1}.header-icons-wrapper{gap:12px}.header-icons,.header-icons-wrapper{align-items:center;display:flex;justify-content:center;justify-self:end}.header-icons{gap:6px}.mask-preview{margin-left:20px;margin-top:30px;max-height:400px;max-width:400px}.imageSize,.mask-preview{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:8px}.imageSize{background-color:#fff;background-color:var(--page-bg);padding:8px;z-index:4}.prompt-wrapper{display:flex;gap:12px}.prompt-wrapper input{all:unset;border-radius:.5rem;border-width:0;min-width:600px;outline:1px solid #eff1f4;outline:1px solid var(--border-color);padding:0 .8rem}.prompt-wrapper input:focus-visible{border-width:0;outline:1px solid #fc0;outline:1px solid var(--yellow-accent)}.theme-toggle-ui{transition:all .2s ease-in;-webkit-user-select:none;user-select:none;z-index:10}.theme-toggle-ui .theme-btn{align-items:center;cursor:pointer;display:flex;justify-content:center;outline:none}.theme-toggle-ui .theme-btn svg{height:22px;width:22px}.modal-shortcuts{background-color:#fff;background-color:var(--modal-bg);box-shadow:0 0 20px rgba(0,0,40,.2);color:#000;color:var(--modal-text-color);grid-area:main-content}@media screen and (max-width:767px){.modal-shortcuts{-webkit-animation:slideDown .2s ease-out;animation:slideDown .2s ease-out;display:grid;height:auto;width:100%}}.shortcut-options{display:flex;flex-direction:row;gap:48px}.shortcut-options .shortcut-option{grid-column-gap:2rem;align-items:center;-webkit-column-gap:2rem;column-gap:2rem;display:grid;grid-template-columns:repeat(2,auto)}@media screen and (max-width:767px){.shortcut-options .shortcut-option{-webkit-column-gap:0;column-gap:0;row-gap:.6rem}}.shortcut-options .shortcut-key{background-color:#fff;background-color:var(--page-bg);border-radius:6px;box-shadow:inset 0 .5px hsla(0,0%,100%,.1),inset 0 1px 5px #f8f9fa,0 0 0 .5px #c1c8cd,0 2px 1px -1px #c1c8cd,0 1px #c1c8cd;box-shadow:var(--box-shadow);box-sizing:border-box;color:#000;color:var(--modal-text-color);font-family:inherit;font-weight:400;justify-self:end;line-height:1.5;padding-left:.5rem;padding-right:.5rem;text-shadow:0 0 1px hsla(0,0%,100%,.5);-webkit-user-select:none;user-select:none;white-space:nowrap;width:-webkit-max-content;width:max-content}@media screen and (max-width:767px){.shortcut-options .shortcut-key{padding:.2rem .4rem}}.shortcut-options .shortcut-description{font-size:.95rem;justify-self:start;text-align:left}@media screen and (max-width:767px){.shortcut-options .shortcut-description{justify-self:start;text-align:left;width:auto}}.shortcut-options-column{gap:12px;width:320px}.setting-block,.setting-block .option-desc,.shortcut-options-column{display:flex;flex-direction:column}.setting-block .option-desc{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:.3rem;color:#6b6f76;color:var(--text-color-gray);gap:8px;margin-top:12px;padding:1rem}.setting-block .option-desc .sub-setting-block{color:#040404;color:var(--text-color)}.setting-block .option-desc svg{color:#6b6f76;color:var(--text-color-gray)}.setting-block-content{align-items:center;display:flex;gap:12rem;justify-content:space-between}.setting-block-content-v{align-items:flex-start;display:flex;flex-direction:column;gap:1rem;justify-content:flex-start}.setting-block-content-title{align-items:center;display:flex;flex-direction:row;gap:8px;justify-content:center}.setting-block-desc{color:#6b6f76;color:var(--text-color-gray);font-size:1rem;margin-top:8px}.hd-setting-block .inline-tip{color:#040404;color:var(--text-color);cursor:pointer;display:inline}.model-desc-link{border-radius:999px;color:#687076;color:var(--badge-color);display:flex;justify-items:center;padding-left:5px;padding-right:5px;text-decoration:none}.modal-setting{background-color:#fff;background-color:var(--modal-bg);box-shadow:0 0 20px rgba(0,0,40,.2);color:#000;color:var(--modal-text-color);width:680px}@media screen and (max-width:767px){.modal-setting{-webkit-animation:slideDown .2s ease-out;animation:slideDown .2s ease-out;display:grid;height:auto;margin-top:-11rem;width:100%}}.folder-path-block{display:flex;flex-direction:column;gap:12px}.folder-path{border-radius:6px;border-width:0;outline:1px solid #eff1f4;outline:1px solid var(--border-color);padding:.3rem .5rem;width:95%}.folder-path:focus-visible{border-width:0;outline:1px solid #fc0;outline:1px solid var(--yellow-accent)}.side-panel{border-color:#eff1f4;border-color:var(--border-color);border-radius:.8rem;border-style:solid;border-width:1px;padding:.1rem .3rem;position:absolute;right:1.5rem;top:68px;z-index:4}.side-panel-trigger{border:0;font-family:WorkSans,sans-serif;font-size:16px}.side-panel-content{background-color:#fff;background-color:var(--page-bg);border-color:#eff1f4;border-color:var(--border-color);border-radius:.8rem;border-style:solid;border-width:1px;color:#040404;color:var(--text-color);display:flex;flex-direction:column;font-family:WorkSans,sans-serif;font-size:14px;gap:12px;outline:none;padding:1rem;position:relative;right:1.5rem;top:8px;z-index:9}.side-panel-content .setting-block-content{gap:1rem}.negative-prompt{all:unset;border-radius:.5rem;border-width:0;max-width:200px;min-height:150px;outline:1px solid #eff1f4;outline:1px solid var(--border-color);padding:12px .8rem;width:100%}.negative-prompt:focus-visible{border-width:0;outline:1px solid #fc0;outline:1px solid var(--yellow-accent)}.negative-prompt:-webkit-input-placeholder{padding-top:10px}.negative-prompt:-moz-input-placeholder{padding-top:10px}.negative-prompt:-ms-input-placeholder{padding-top:10px}.resize-title-tile{color:#6b6f76;color:var(--text-color-gray);font-size:.5rem;width:86px}.plugins{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:8px;z-index:4}.DropdownMenuContent,.DropdownMenuSubContent{-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-timing-function:cubic-bezier(.16,1,.3,1);animation-timing-function:cubic-bezier(.16,1,.3,1);background-color:#fff;background-color:var(--page-bg);border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:6px;box-shadow:0 10px 38px -10px rgba(22,23,24,.35),0 10px 20px -15px rgba(22,23,24,.2);min-width:80px;padding:5px;will-change:transform,opacity}.DropdownMenuContent[data-side=top],.DropdownMenuSubContent[data-side=top]{-webkit-animation-name:slideDownAndFade;animation-name:slideDownAndFade}.DropdownMenuContent[data-side=right],.DropdownMenuSubContent[data-side=right]{-webkit-animation-name:slideLeftAndFade;animation-name:slideLeftAndFade}.DropdownMenuContent[data-side=bottom],.DropdownMenuSubContent[data-side=bottom]{-webkit-animation-name:slideUpAndFade;animation-name:slideUpAndFade}.DropdownMenuContent[data-side=left],.DropdownMenuSubContent[data-side=left]{-webkit-animation-name:slideRightAndFade;animation-name:slideRightAndFade}.DropdownMenuCheckboxItem,.DropdownMenuItem,.DropdownMenuRadioItem,.DropdownMenuSubTrigger{align-items:center;border-radius:3px;color:#040404;color:var(--btn-text-color);display:flex;font-size:13px;gap:8px;height:32px;line-height:1;outline:none;padding:0 5px;position:relative;-webkit-user-select:none;user-select:none}.DropdownMenuSubTrigger[data-state=open]{background-color:#fff;background-color:var(--page-bg);color:#040404;color:var(--btn-text-color)}.DropdownMenuCheckboxItem[data-disabled],.DropdownMenuItem[data-disabled],.DropdownMenuRadioItem[data-disabled],.DropdownMenuSubTrigger[data-disabled]{color:#6b6f76;color:var(--text-color-disabled);pointer-events:none}.DropdownMenuCheckboxItem[data-highlighted],.DropdownMenuItem[data-highlighted],.DropdownMenuRadioItem[data-highlighted],.DropdownMenuSubTrigger[data-highlighted]{background-color:#fc0;background-color:var(--yellow-accent);color:#040404;color:var(--btn-text-hover-color)}.RightSlot{align-items:center;color:#040404;color:var(--btn-text-color);display:flex;margin-left:auto;padding-left:10px}[data-highlighted]>.RightSlot{color:#040404;color:var(--btn-text-hover-color)}[data-disabled] .RightSlot{color:#6b6f76;color:var(--text-color-gray)}@keyframes slideUpAndFade{0%{opacity:0;-webkit-transform:translateY(2px);transform:translateY(2px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes slideRightAndFade{0%{opacity:0;-webkit-transform:translateX(-2px);transform:translateX(-2px)}to{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideRightAndFade{0%{opacity:0;-webkit-transform:translateX(-2px);transform:translateX(-2px)}to{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideDownAndFade{0%{opacity:0;-webkit-transform:translateY(-2px);transform:translateY(-2px)}to{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes slideLeftAndFade{0%{opacity:0;-webkit-transform:translateX(2px);transform:translateX(2px)}to{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes slideLeftAndFade{0%{opacity:0;-webkit-transform:translateX(2px);transform:translateX(2px)}to{opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}}.crop-border{outline-color:#fc0;outline-color:var(--yellow-accent);outline-style:dashed}.info-bar{align-items:center;background-color:#fff;background-color:var(--page-bg);border:0;border:var(--editor-toolkit-panel-border);border-radius:9999px;box-shadow:0 0 0 1px rgba(0,0,0,.102),0 3px 16px rgba(0,0,0,.078),0 2px 6px 1px rgba(0,0,0,.09);color:#040404;color:var(--text-color);display:flex;font-size:1rem;gap:12px;justify-content:center;padding:.2rem .8rem;pointer-events:auto;position:absolute}.info-bar:hover{cursor:move}.croper-wrapper{height:100%;overflow:hidden;position:absolute;width:100%}.croper,.croper-wrapper{pointer-events:none;z-index:2}.croper{bottom:0;box-shadow:0 0 0 9999px rgba(0,0,0,.5);left:0;position:relative;right:0;top:0}.drag-bar{pointer-events:auto;position:absolute}.drag-bar.ord-top{cursor:ns-resize;height:12px;left:0;margin-top:-6px;top:0;width:100%}.drag-bar.ord-right{cursor:ew-resize;height:100%;margin-right:-6px;right:0;top:0;width:12px}.drag-bar.ord-bottom{bottom:0;cursor:ns-resize;height:12px;left:0;margin-bottom:-6px;width:100%}.drag-bar.ord-left{cursor:ew-resize;height:100%;left:0;margin-left:-6px;top:0;width:12px}.drag-handle{background-color:#ffcc0055;background-color:var(--yellow-accent-light);border:2px solid #fc0;border:2px solid var(--yellow-accent);content:"";display:block;height:12px;pointer-events:auto;position:absolute;width:12px;z-index:4}.drag-handle:hover{background-color:#fc0;background-color:var(--yellow-accent)}.drag-handle.ord-topleft{cursor:nw-resize;left:-7px;top:-7px}.drag-handle.ord-topright{cursor:ne-resize;right:-7px;top:-7px}.drag-handle.ord-bottomright{bottom:-7px;cursor:se-resize;right:-7px}.drag-handle.ord-bottomleft{bottom:-7px;cursor:sw-resize;left:-7px}.drag-handle.ord-bottom,.drag-handle.ord-top{cursor:ns-resize;left:calc(50% - 6px)}.drag-handle.ord-top{top:-7px}.drag-handle.ord-bottom{bottom:-7px}.drag-handle.ord-left,.drag-handle.ord-right{cursor:ew-resize;top:calc(50% - 6px)}.drag-handle.ord-left{left:-7px}.drag-handle.ord-right{right:-7px}.interactive-seg-wrapper{height:100%;overflow:hidden;pointer-events:none;position:absolute;width:100%;z-index:2}.interactive-seg-wrapper .click-item{border-radius:50%;height:8px;position:absolute;width:8px}.interactive-seg-wrapper .click-item-positive{background-color:rgba(21,215,121,.936);outline:6px solid rgba(98,255,179,.31)}.interactive-seg-wrapper .click-item-negative{background-color:rgba(237,49,55,.942);outline:6px solid rgba(255,89,95,.31)}.interactive-seg-confirm-actions{background-color:#fff;background-color:var(--page-bg);border-color:#eff1f4;border-color:var(--border-color);border-radius:16px;border-style:solid;border-width:1px;padding:8px;position:absolute;top:68px;z-index:5}.interactive-seg-confirm-actions .action-buttons{align-items:center;display:flex;gap:8px;justify-content:center}@-webkit-keyframes pulse{to{box-shadow:0 0 0 14px rgba(21,215,121,0)}}@keyframes pulse{to{box-shadow:0 0 0 14px rgba(21,215,121,0)}}.interactive-seg-cursor{-webkit-animation:pulse 1.5s cubic-bezier(.66,0,0,1) infinite;animation:pulse 1.5s cubic-bezier(.66,0,0,1) infinite;background-color:rgba(21,215,121,.936);border-radius:50%;box-shadow:0 0 0 0 rgba(21,215,121,.936);color:rgba(234,255,240,.98);height:20px;pointer-events:none;position:absolute;width:20px}.file-select-label{border:2px dashed #eff1f4;border:2px dashed var(--border-color);border-radius:.5rem;cursor:pointer;display:grid;min-width:600px}@media screen and (max-width:767px){.file-select-label{min-width:300px}}.file-select-label .file-select-label-hover,.file-select-label:hover{background-color:#fc0;background-color:var(--yellow-accent);color:#000}.file-select-container{display:grid;height:100%;padding:4rem;width:100%}.file-select-container input{display:none}.file-select-message{font-family:WorkSans;text-align:center}.btn-primary{grid-column-gap:1rem;background-color:#fff;background-color:var(--page-bg);border-radius:.5rem;color:#040404;color:var(--btn-text-color);-webkit-column-gap:1rem;column-gap:1rem;cursor:pointer;display:grid;font-family:WorkSans,sans-serif;grid-auto-flow:column;padding:.5rem;place-items:center;width:-webkit-max-content;width:max-content;z-index:1}.btn-primary:hover{background-color:#fc0;background-color:var(--btn-primary-hover-bg);color:#040404;color:var(--btn-text-hover-color)}.btn-primary svg{height:auto;width:20px}.btn-primary-disabled{background-color:#fff;background-color:var(--page-bg);opacity:.5;pointer-events:none}.btn-border{border-color:#646478;border-color:var(--btn-border-color);border-style:solid;border-width:1px}.modal-mask{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);background-color:rgba(209,213,219,.4);background-color:var(--model-mask-bg);inset:0;position:fixed;z-index:9998}@media(prefers-reduced-motion:no-preference){.modal-mask{-webkit-animation:opacityReveal .15s cubic-bezier(.16,1,.3,1) forwards;animation:opacityReveal .15s cubic-bezier(.16,1,.3,1) forwards}}@-webkit-keyframes contentShow{0%{opacity:0;-webkit-transform:translate(-50%,-48%) scale(.96);transform:translate(-50%,-48%) scale(.96)}to{opacity:1;-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}}@keyframes contentShow{0%{opacity:0;-webkit-transform:translate(-50%,-48%) scale(.96);transform:translate(-50%,-48%) scale(.96)}to{opacity:1;-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1)}}.modal{background-color:#fff;background-color:var(--page-bg);border-radius:.95rem;display:flex;flex-direction:column;gap:16px;left:50%;padding:25px;place-self:center;position:fixed;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);z-index:9999}.modal:focus{outline:none}.modal .modal-header{align-items:center;display:grid;grid-template-columns:repeat(2,auto)}.modal .modal-header .btn-primary{justify-self:end}@media(prefers-reduced-motion:no-preference){.modal{-webkit-animation:contentShow .15s cubic-bezier(.16,1,.3,1) forwards;animation:contentShow .15s cubic-bezier(.16,1,.3,1) forwards}}.select-trigger{all:unset;align-items:center;background-color:#fff;background-color:var(--page-bg);border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:.5rem;color:#040404;color:var(--options-text-color);display:inline-flex;gap:8px;height:32px;justify-content:space-between;padding:0 .8rem}.select-trigger svg{height:1rem;margin-top:.25rem;width:1rem}.select-trigger:hover{border-color:#fc0;border-color:var(--yellow-accent)}.select-trigger:disabled{border-color:#eff1f4;border-color:var(--border-color);color:#eff1f4;color:var(--border-color)}.select-content{background-color:#fff;background-color:var(--page-bg);border-radius:.5rem;overflow:hidden}.select-viewport{border:1px solid #eff1f4;border:1px solid var(--border-color);border-radius:.5rem;padding:5px}.select-item{all:unset;align-items:center;background-color:#fff;background-color:var(--page-bg);border-radius:.5rem;color:#040404;color:var(--options-text-color);display:flex;padding:6px 6px 6px 25px;position:relative;-webkit-user-select:none;user-select:none}.select-item:focus{background-color:#fc0;background-color:var(--yellow-accent);color:#040404;color:var(--btn-text-hover-color)}.select-item-indicator{align-items:center;display:inline-flex;justify-content:center;left:0;padding-right:4px;position:absolute;width:25px}.switch-root{-webkit-tap-highlight-color:rgba(0,0,0,0);all:"unset";background-color:#dfe1e4;background-color:var(--switch-root-background-color);border:none;border-radius:9999px;height:25px;position:relative;transition:background-color .1s;width:42px}.switch-root:focus-visible{outline:none}.switch-root[data-state=checked]{background-color:#fc0;background-color:var(--yellow-accent)}.switch-thumb{background-color:#fff;background-color:var(--switch-thumb-color);border-radius:9999px;display:block;height:17px;-webkit-transform:translateX(4px);transform:translateX(4px);transition:-webkit-transform .1s;transition:transform .1s;transition:transform .1s,-webkit-transform .1s;width:17px;will-change:transform}.switch-thumb[data-state=checked]{background-color:#fff;background-color:var(--switch-thumb-checked-color);outline:1px solid hsla(240,9%,43%,.5);-webkit-transform:translateX(21px);transform:translateX(21px)}.number-input{all:unset;border-radius:.5rem;flex:1 0 auto;height:32px;outline:1px solid #eff1f4;outline:1px solid var(--border-color);padding:0 .8rem;text-align:right}.number-input:focus-visible{outline:1px solid #fc0;outline:1px solid var(--yellow-accent)}.number-input:disabled{color:#eff1f4;color:var(--border-color)}.toast-viewpoint{bottom:48px;display:flex;flex-direction:row;gap:10px;margin:0;max-width:100vw;padding:25px;position:fixed;right:1.5rem;z-index:999999}.toast-viewpoint:focus-visible{outline:none}.toast-root{align-items:center;background-color:#fff;background-color:var(--page-bg);border:1px solid hsla(240,9%,43%,.5);border:1px solid var(--border-color-light);border-radius:.6rem;display:flex;gap:12px;padding:15px}.toast-root[data-state=open]{-webkit-animation:slideIn .15s cubic-bezier(.16,1,.3,1);animation:slideIn .15s cubic-bezier(.16,1,.3,1)}.toast-root[data-state=close]{-webkit-animation:opacityReveal .1s ease-in forwards;animation:opacityReveal .1s ease-in forwards}.toast-root[data-state=cancel]{-webkit-animation:transform .1s ease-out;animation:transform .1s ease-out;-webkit-transform:translateX(0);transform:translateX(0)}.toast-root.error{border:1px solid #ef4444;border:1px solid var(--error-color)}.toast-root.success{border:1px solid #10b981;border:1px solid var(--success-color)}.error-icon{color:#ef4444;color:var(--error-color);height:24px;width:24px}.success-icon{color:#10b981;color:var(--success-color);height:24px;width:24px}.loading-icon{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:spin;animation-name:spin;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-transform-origin:center center;transform-origin:center center}.loading-icon,.toast-desc,.toast-icon{align-items:center;display:flex}.toast-desc{color:#040404;color:var(--text-color);margin:0;min-width:240px}.tooltip-trigger{align-items:center;display:flex;justify-content:center}.tooltip-content{background-color:#fff;background-color:var(--tooltip-bg);border-radius:4px;box-shadow:0 10px 38px -10px rgba(14,18,22,.35),0 10px 20px -15px rgba(14,18,22,.2);color:#000;color:var(--tooltip-text-color);padding:10px 15px}@media(prefers-reduced-motion:no-preference){.tooltip-content{-webkit-animation-duration:.4s;animation-duration:.4s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:cubic-bezier(.16,1,.3,1);animation-timing-function:cubic-bezier(.16,1,.3,1);will-change:transform,opacity}.tooltip-content[data-state=delayed-open][data-side=top]{-webkit-animation-name:slideDownAndFade;animation-name:slideDownAndFade}.tooltip-content[data-state=delayed-open][data-side=bottom]{-webkit-animation-name:slideUpAndFade;animation-name:slideUpAndFade}}.tooltip-arrow{fill:#fff;fill:var(--tooltip-bg)}*,:after,:before{box-sizing:border-box;margin:0;padding:0}body,html{font-family:WorkSans,sans-serif}
lama_cleaner/app/build/static/js/main.44aac645.js ADDED
The diff for this file is too large to render. See raw diff
 
lama_cleaner/app/build/static/js/main.44aac645.js.LICENSE.txt ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ object-assign
3
+ (c) Sindre Sorhus
4
+ @license MIT
5
+ */
6
+
7
+ /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
8
+
9
+ /**
10
+ * @license
11
+ * Lodash <https://lodash.com/>
12
+ * Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
13
+ * Released under MIT license <https://lodash.com/license>
14
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
15
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
16
+ */
17
+
18
+ /** @license React v0.20.2
19
+ * scheduler.production.min.js
20
+ *
21
+ * Copyright (c) Facebook, Inc. and its affiliates.
22
+ *
23
+ * This source code is licensed under the MIT license found in the
24
+ * LICENSE file in the root directory of this source tree.
25
+ */
26
+
27
+ /** @license React v17.0.2
28
+ * react-dom.production.min.js
29
+ *
30
+ * Copyright (c) Facebook, Inc. and its affiliates.
31
+ *
32
+ * This source code is licensed under the MIT license found in the
33
+ * LICENSE file in the root directory of this source tree.
34
+ */
35
+
36
+ /** @license React v17.0.2
37
+ * react-jsx-runtime.production.min.js
38
+ *
39
+ * Copyright (c) Facebook, Inc. and its affiliates.
40
+ *
41
+ * This source code is licensed under the MIT license found in the
42
+ * LICENSE file in the root directory of this source tree.
43
+ */
44
+
45
+ /** @license React v17.0.2
46
+ * react.production.min.js
47
+ *
48
+ * Copyright (c) Facebook, Inc. and its affiliates.
49
+ *
50
+ * This source code is licensed under the MIT license found in the
51
+ * LICENSE file in the root directory of this source tree.
52
+ */
53
+
54
+ /**!
55
+ * FlexSearch.js v0.7.21 (Bundle)
56
+ * Copyright 2018-2021 Nextapps GmbH
57
+ * Author: Thomas Wilkerling
58
+ * Licence: Apache-2.0
59
+ * https://github.com/nextapps-de/flexsearch
60
+ */
61
+
62
+ /**!
63
+ * hotkeys-js v3.9.4
64
+ * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies.
65
+ *
66
+ * Copyright (c) 2022 kenny wong <wowohoo@qq.com>
67
+ * http://jaywcjlove.github.io/hotkeys
68
+ * Licensed under the MIT license
69
+ */
lama_cleaner/app/build/static/media/WorkSans-Black.67c2c5a144333953880b.ttf ADDED
Binary file (192 kB). View file
 
lama_cleaner/app/build/static/media/WorkSans-Bold.2bea7a7f7d052c74da25.ttf ADDED
Binary file (193 kB). View file
 
lama_cleaner/app/build/static/media/WorkSans-Regular.bb287b894b27372d8ea7.ttf ADDED
Binary file (192 kB). View file
 
lama_cleaner/app/build/static/media/WorkSans-SemiBold.1e98db4eb705b586728e.ttf ADDED
Binary file (193 kB). View file
 
lama_cleaner/app/build/static/media/coffee-machine-lineal.ee32631219cc3986f861.gif ADDED
lama_cleaner/app/config/env.js ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const paths = require('./paths');
6
+
7
+ // Make sure that including paths.js after env.js will read .env variables.
8
+ delete require.cache[require.resolve('./paths')];
9
+
10
+ const NODE_ENV = process.env.NODE_ENV;
11
+ if (!NODE_ENV) {
12
+ throw new Error(
13
+ 'The NODE_ENV environment variable is required but was not specified.'
14
+ );
15
+ }
16
+
17
+ // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
18
+ const dotenvFiles = [
19
+ `${paths.dotenv}.${NODE_ENV}.local`,
20
+ // Don't include `.env.local` for `test` environment
21
+ // since normally you expect tests to produce the same
22
+ // results for everyone
23
+ NODE_ENV !== 'test' && `${paths.dotenv}.local`,
24
+ `${paths.dotenv}.${NODE_ENV}`,
25
+ paths.dotenv,
26
+ ].filter(Boolean);
27
+
28
+ // Load environment variables from .env* files. Suppress warnings using silent
29
+ // if this file is missing. dotenv will never modify any environment variables
30
+ // that have already been set. Variable expansion is supported in .env files.
31
+ // https://github.com/motdotla/dotenv
32
+ // https://github.com/motdotla/dotenv-expand
33
+ dotenvFiles.forEach(dotenvFile => {
34
+ if (fs.existsSync(dotenvFile)) {
35
+ require('dotenv-expand')(
36
+ require('dotenv').config({
37
+ path: dotenvFile,
38
+ })
39
+ );
40
+ }
41
+ });
42
+
43
+ // We support resolving modules according to `NODE_PATH`.
44
+ // This lets you use absolute paths in imports inside large monorepos:
45
+ // https://github.com/facebook/create-react-app/issues/253.
46
+ // It works similar to `NODE_PATH` in Node itself:
47
+ // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders
48
+ // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored.
49
+ // Otherwise, we risk importing Node.js core modules into an app instead of webpack shims.
50
+ // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421
51
+ // We also resolve them to make sure all tools using them work consistently.
52
+ const appDirectory = fs.realpathSync(process.cwd());
53
+ process.env.NODE_PATH = (process.env.NODE_PATH || '')
54
+ .split(path.delimiter)
55
+ .filter(folder => folder && !path.isAbsolute(folder))
56
+ .map(folder => path.resolve(appDirectory, folder))
57
+ .join(path.delimiter);
58
+
59
+ // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
60
+ // injected into the application via DefinePlugin in webpack configuration.
61
+ const REACT_APP = /^REACT_APP_/i;
62
+
63
+ function getClientEnvironment(publicUrl) {
64
+ const raw = Object.keys(process.env)
65
+ .filter(key => REACT_APP.test(key))
66
+ .reduce(
67
+ (env, key) => {
68
+ env[key] = process.env[key];
69
+ return env;
70
+ },
71
+ {
72
+ // Useful for determining whether we’re running in production mode.
73
+ // Most importantly, it switches React into the correct mode.
74
+ NODE_ENV: process.env.NODE_ENV || 'development',
75
+ // Useful for resolving the correct path to static assets in `public`.
76
+ // For example, <img src={process.env.PUBLIC_URL + '/img/logo.png'} />.
77
+ // This should only be used as an escape hatch. Normally you would put
78
+ // images into the `src` and `import` them in code to get their paths.
79
+ PUBLIC_URL: publicUrl,
80
+ // We support configuring the sockjs pathname during development.
81
+ // These settings let a developer run multiple simultaneous projects.
82
+ // They are used as the connection `hostname`, `pathname` and `port`
83
+ // in webpackHotDevClient. They are used as the `sockHost`, `sockPath`
84
+ // and `sockPort` options in webpack-dev-server.
85
+ WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST,
86
+ WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH,
87
+ WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT,
88
+ // Whether or not react-refresh is enabled.
89
+ // It is defined here so it is available in the webpackHotDevClient.
90
+ FAST_REFRESH: process.env.FAST_REFRESH !== 'false',
91
+ }
92
+ );
93
+ // Stringify all values so we can feed into webpack DefinePlugin
94
+ const stringified = {
95
+ 'process.env': Object.keys(raw).reduce((env, key) => {
96
+ env[key] = JSON.stringify(raw[key]);
97
+ return env;
98
+ }, {}),
99
+ };
100
+
101
+ return { raw, stringified };
102
+ }
103
+
104
+ module.exports = getClientEnvironment;
lama_cleaner/app/config/getHttpsConfig.js ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const chalk = require('react-dev-utils/chalk');
7
+ const paths = require('./paths');
8
+
9
+ // Ensure the certificate and key provided are valid and if not
10
+ // throw an easy to debug error
11
+ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
12
+ let encrypted;
13
+ try {
14
+ // publicEncrypt will throw an error with an invalid cert
15
+ encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
16
+ } catch (err) {
17
+ throw new Error(
18
+ `The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
19
+ );
20
+ }
21
+
22
+ try {
23
+ // privateDecrypt will throw an error with an invalid key
24
+ crypto.privateDecrypt(key, encrypted);
25
+ } catch (err) {
26
+ throw new Error(
27
+ `The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
28
+ err.message
29
+ }`
30
+ );
31
+ }
32
+ }
33
+
34
+ // Read file and throw an error if it doesn't exist
35
+ function readEnvFile(file, type) {
36
+ if (!fs.existsSync(file)) {
37
+ throw new Error(
38
+ `You specified ${chalk.cyan(
39
+ type
40
+ )} in your env, but the file "${chalk.yellow(file)}" can't be found.`
41
+ );
42
+ }
43
+ return fs.readFileSync(file);
44
+ }
45
+
46
+ // Get the https config
47
+ // Return cert files if provided in env, otherwise just true or false
48
+ function getHttpsConfig() {
49
+ const { SSL_CRT_FILE, SSL_KEY_FILE, HTTPS } = process.env;
50
+ const isHttps = HTTPS === 'true';
51
+
52
+ if (isHttps && SSL_CRT_FILE && SSL_KEY_FILE) {
53
+ const crtFile = path.resolve(paths.appPath, SSL_CRT_FILE);
54
+ const keyFile = path.resolve(paths.appPath, SSL_KEY_FILE);
55
+ const config = {
56
+ cert: readEnvFile(crtFile, 'SSL_CRT_FILE'),
57
+ key: readEnvFile(keyFile, 'SSL_KEY_FILE'),
58
+ };
59
+
60
+ validateKeyAndCerts({ ...config, keyFile, crtFile });
61
+ return config;
62
+ }
63
+ return isHttps;
64
+ }
65
+
66
+ module.exports = getHttpsConfig;
lama_cleaner/app/config/jest/babelTransform.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const babelJest = require('babel-jest').default;
4
+
5
+ const hasJsxRuntime = (() => {
6
+ if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
7
+ return false;
8
+ }
9
+
10
+ try {
11
+ require.resolve('react/jsx-runtime');
12
+ return true;
13
+ } catch (e) {
14
+ return false;
15
+ }
16
+ })();
17
+
18
+ module.exports = babelJest.createTransformer({
19
+ presets: [
20
+ [
21
+ require.resolve('babel-preset-react-app'),
22
+ {
23
+ runtime: hasJsxRuntime ? 'automatic' : 'classic',
24
+ },
25
+ ],
26
+ ],
27
+ babelrc: false,
28
+ configFile: false,
29
+ });
lama_cleaner/app/config/jest/cssTransform.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ // This is a custom Jest transformer turning style imports into empty objects.
4
+ // http://facebook.github.io/jest/docs/en/webpack.html
5
+
6
+ module.exports = {
7
+ process() {
8
+ return 'module.exports = {};';
9
+ },
10
+ getCacheKey() {
11
+ // The output is always the same.
12
+ return 'cssTransform';
13
+ },
14
+ };
lama_cleaner/app/config/jest/fileTransform.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const camelcase = require('camelcase');
5
+
6
+ // This is a custom Jest transformer turning file imports into filenames.
7
+ // http://facebook.github.io/jest/docs/en/webpack.html
8
+
9
+ module.exports = {
10
+ process(src, filename) {
11
+ const assetFilename = JSON.stringify(path.basename(filename));
12
+
13
+ if (filename.match(/\.svg$/)) {
14
+ // Based on how SVGR generates a component name:
15
+ // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16
+ const pascalCaseFilename = camelcase(path.parse(filename).name, {
17
+ pascalCase: true,
18
+ });
19
+ const componentName = `Svg${pascalCaseFilename}`;
20
+ return `const React = require('react');
21
+ module.exports = {
22
+ __esModule: true,
23
+ default: ${assetFilename},
24
+ ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25
+ return {
26
+ $$typeof: Symbol.for('react.element'),
27
+ type: 'svg',
28
+ ref: ref,
29
+ key: null,
30
+ props: Object.assign({}, props, {
31
+ children: ${assetFilename}
32
+ })
33
+ };
34
+ }),
35
+ };`;
36
+ }
37
+
38
+ return `module.exports = ${assetFilename};`;
39
+ },
40
+ };
lama_cleaner/app/config/modules.js ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const paths = require('./paths');
6
+ const chalk = require('react-dev-utils/chalk');
7
+ const resolve = require('resolve');
8
+
9
+ /**
10
+ * Get additional module paths based on the baseUrl of a compilerOptions object.
11
+ *
12
+ * @param {Object} options
13
+ */
14
+ function getAdditionalModulePaths(options = {}) {
15
+ const baseUrl = options.baseUrl;
16
+
17
+ if (!baseUrl) {
18
+ return '';
19
+ }
20
+
21
+ const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
22
+
23
+ // We don't need to do anything if `baseUrl` is set to `node_modules`. This is
24
+ // the default behavior.
25
+ if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
26
+ return null;
27
+ }
28
+
29
+ // Allow the user set the `baseUrl` to `appSrc`.
30
+ if (path.relative(paths.appSrc, baseUrlResolved) === '') {
31
+ return [paths.appSrc];
32
+ }
33
+
34
+ // If the path is equal to the root directory we ignore it here.
35
+ // We don't want to allow importing from the root directly as source files are
36
+ // not transpiled outside of `src`. We do allow importing them with the
37
+ // absolute path (e.g. `src/Components/Button.js`) but we set that up with
38
+ // an alias.
39
+ if (path.relative(paths.appPath, baseUrlResolved) === '') {
40
+ return null;
41
+ }
42
+
43
+ // Otherwise, throw an error.
44
+ throw new Error(
45
+ chalk.red.bold(
46
+ "Your project's `baseUrl` can only be set to `src` or `node_modules`." +
47
+ ' Create React App does not support other values at this time.'
48
+ )
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Get webpack aliases based on the baseUrl of a compilerOptions object.
54
+ *
55
+ * @param {*} options
56
+ */
57
+ function getWebpackAliases(options = {}) {
58
+ const baseUrl = options.baseUrl;
59
+
60
+ if (!baseUrl) {
61
+ return {};
62
+ }
63
+
64
+ const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
65
+
66
+ if (path.relative(paths.appPath, baseUrlResolved) === '') {
67
+ return {
68
+ src: paths.appSrc,
69
+ };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get jest aliases based on the baseUrl of a compilerOptions object.
75
+ *
76
+ * @param {*} options
77
+ */
78
+ function getJestAliases(options = {}) {
79
+ const baseUrl = options.baseUrl;
80
+
81
+ if (!baseUrl) {
82
+ return {};
83
+ }
84
+
85
+ const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
86
+
87
+ if (path.relative(paths.appPath, baseUrlResolved) === '') {
88
+ return {
89
+ '^src/(.*)$': '<rootDir>/src/$1',
90
+ };
91
+ }
92
+ }
93
+
94
+ function getModules() {
95
+ // Check if TypeScript is setup
96
+ const hasTsConfig = fs.existsSync(paths.appTsConfig);
97
+ const hasJsConfig = fs.existsSync(paths.appJsConfig);
98
+
99
+ if (hasTsConfig && hasJsConfig) {
100
+ throw new Error(
101
+ 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
102
+ );
103
+ }
104
+
105
+ let config;
106
+
107
+ // If there's a tsconfig.json we assume it's a
108
+ // TypeScript project and set up the config
109
+ // based on tsconfig.json
110
+ if (hasTsConfig) {
111
+ const ts = require(resolve.sync('typescript', {
112
+ basedir: paths.appNodeModules,
113
+ }));
114
+ config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config;
115
+ // Otherwise we'll check if there is jsconfig.json
116
+ // for non TS projects.
117
+ } else if (hasJsConfig) {
118
+ config = require(paths.appJsConfig);
119
+ }
120
+
121
+ config = config || {};
122
+ const options = config.compilerOptions || {};
123
+
124
+ const additionalModulePaths = getAdditionalModulePaths(options);
125
+
126
+ return {
127
+ additionalModulePaths: additionalModulePaths,
128
+ webpackAliases: getWebpackAliases(options),
129
+ jestAliases: getJestAliases(options),
130
+ hasTsConfig,
131
+ };
132
+ }
133
+
134
+ module.exports = getModules();
lama_cleaner/app/config/paths.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const getPublicUrlOrPath = require('react-dev-utils/getPublicUrlOrPath');
6
+
7
+ // Make sure any symlinks in the project folder are resolved:
8
+ // https://github.com/facebook/create-react-app/issues/637
9
+ const appDirectory = fs.realpathSync(process.cwd());
10
+ const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11
+
12
+ // We use `PUBLIC_URL` environment variable or "homepage" field to infer
13
+ // "public path" at which the app is served.
14
+ // webpack needs to know it to put the right <script> hrefs into HTML even in
15
+ // single-page apps that may serve index.html for nested URLs like /todos/42.
16
+ // We can't use a relative path in HTML because we don't want to load something
17
+ // like /todos/42/static/js/bundle.7289d.js. We have to know the root.
18
+ const publicUrlOrPath = getPublicUrlOrPath(
19
+ process.env.NODE_ENV === 'development',
20
+ require(resolveApp('package.json')).homepage,
21
+ process.env.PUBLIC_URL
22
+ );
23
+
24
+ const buildPath = process.env.BUILD_PATH || 'build';
25
+
26
+ const moduleFileExtensions = [
27
+ 'web.mjs',
28
+ 'mjs',
29
+ 'web.js',
30
+ 'js',
31
+ 'web.ts',
32
+ 'ts',
33
+ 'web.tsx',
34
+ 'tsx',
35
+ 'json',
36
+ 'web.jsx',
37
+ 'jsx',
38
+ ];
39
+
40
+ // Resolve file paths in the same order as webpack
41
+ const resolveModule = (resolveFn, filePath) => {
42
+ const extension = moduleFileExtensions.find(extension =>
43
+ fs.existsSync(resolveFn(`${filePath}.${extension}`))
44
+ );
45
+
46
+ if (extension) {
47
+ return resolveFn(`${filePath}.${extension}`);
48
+ }
49
+
50
+ return resolveFn(`${filePath}.js`);
51
+ };
52
+
53
+ // config after eject: we're in ./config/
54
+ module.exports = {
55
+ dotenv: resolveApp('.env'),
56
+ appPath: resolveApp('.'),
57
+ appBuild: resolveApp(buildPath),
58
+ appPublic: resolveApp('public'),
59
+ appHtml: resolveApp('public/index.html'),
60
+ appIndexJs: resolveModule(resolveApp, 'src/index'),
61
+ appPackageJson: resolveApp('package.json'),
62
+ appSrc: resolveApp('src'),
63
+ appTsConfig: resolveApp('tsconfig.json'),
64
+ appJsConfig: resolveApp('jsconfig.json'),
65
+ yarnLockFile: resolveApp('yarn.lock'),
66
+ testsSetup: resolveModule(resolveApp, 'src/setupTests'),
67
+ proxySetup: resolveApp('src/setupProxy.js'),
68
+ appNodeModules: resolveApp('node_modules'),
69
+ appWebpackCache: resolveApp('node_modules/.cache'),
70
+ appTsBuildInfoFile: resolveApp('node_modules/.cache/tsconfig.tsbuildinfo'),
71
+ swSrc: resolveModule(resolveApp, 'src/service-worker'),
72
+ publicUrlOrPath,
73
+ };
74
+
75
+
76
+
77
+ module.exports.moduleFileExtensions = moduleFileExtensions;
lama_cleaner/app/config/webpack.config.js ADDED
@@ -0,0 +1,761 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const webpack = require('webpack')
6
+ const resolve = require('resolve')
7
+ const HtmlWebpackPlugin = require('html-webpack-plugin')
8
+ const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
9
+ const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
10
+ const TerserPlugin = require('terser-webpack-plugin')
11
+ const MiniCssExtractPlugin = require('mini-css-extract-plugin')
12
+ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
13
+ const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
14
+ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin')
15
+ const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
16
+ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
17
+ const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent')
18
+ const ESLintPlugin = require('eslint-webpack-plugin')
19
+ const paths = require('./paths')
20
+ const modules = require('./modules')
21
+ const getClientEnvironment = require('./env')
22
+ const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin')
23
+ const ForkTsCheckerWebpackPlugin =
24
+ process.env.TSC_COMPILE_ON_ERROR === 'true'
25
+ ? require('react-dev-utils/ForkTsCheckerWarningWebpackPlugin')
26
+ : require('react-dev-utils/ForkTsCheckerWebpackPlugin')
27
+ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
28
+
29
+ const createEnvironmentHash = require('./webpack/persistentCache/createEnvironmentHash')
30
+
31
+ // Source maps are resource heavy and can cause out of memory issue for large source files.
32
+ const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'
33
+
34
+ const reactRefreshRuntimeEntry = require.resolve('react-refresh/runtime')
35
+ const reactRefreshWebpackPluginRuntimeEntry = require.resolve(
36
+ '@pmmmwh/react-refresh-webpack-plugin'
37
+ )
38
+ const babelRuntimeEntry = require.resolve('babel-preset-react-app')
39
+ const babelRuntimeEntryHelpers = require.resolve(
40
+ '@babel/runtime/helpers/esm/assertThisInitialized',
41
+ { paths: [babelRuntimeEntry] }
42
+ )
43
+ const babelRuntimeRegenerator = require.resolve('@babel/runtime/regenerator', {
44
+ paths: [babelRuntimeEntry],
45
+ })
46
+
47
+ // Some apps do not need the benefits of saving a web request, so not inlining the chunk
48
+ // makes for a smoother build process.
49
+ const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'
50
+
51
+ const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true'
52
+ const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true'
53
+
54
+ const imageInlineSizeLimit = parseInt(
55
+ process.env.IMAGE_INLINE_SIZE_LIMIT || '10000'
56
+ )
57
+
58
+ // Check if TypeScript is setup
59
+ const useTypeScript = fs.existsSync(paths.appTsConfig)
60
+
61
+ // Check if Tailwind config exists
62
+ const useTailwind = fs.existsSync(
63
+ path.join(paths.appPath, 'tailwind.config.js')
64
+ )
65
+
66
+ // Get the path to the uncompiled service worker (if it exists).
67
+ const swSrc = paths.swSrc
68
+
69
+ // style files regexes
70
+ const cssRegex = /\.css$/
71
+ const cssModuleRegex = /\.module\.css$/
72
+ const sassRegex = /\.(scss|sass)$/
73
+ const sassModuleRegex = /\.module\.(scss|sass)$/
74
+
75
+ const hasJsxRuntime = (() => {
76
+ if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') {
77
+ return false
78
+ }
79
+
80
+ try {
81
+ require.resolve('react/jsx-runtime')
82
+ return true
83
+ } catch (e) {
84
+ return false
85
+ }
86
+ })()
87
+
88
+ // This is the production and development configuration.
89
+ // It is focused on developer experience, fast rebuilds, and a minimal bundle.
90
+ module.exports = function (webpackEnv) {
91
+ const isEnvDevelopment = webpackEnv === 'development'
92
+ const isEnvProduction = webpackEnv === 'production'
93
+
94
+ // Variable used for enabling profiling in Production
95
+ // passed into alias object. Uses a flag if passed into the build command
96
+ const isEnvProductionProfile =
97
+ isEnvProduction && process.argv.includes('--profile')
98
+
99
+ // We will provide `paths.publicUrlOrPath` to our app
100
+ // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
101
+ // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
102
+ // Get environment variables to inject into our app.
103
+ const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1))
104
+
105
+ const shouldUseReactRefresh = env.raw.FAST_REFRESH
106
+
107
+ // common function to get style loaders
108
+ const getStyleLoaders = (cssOptions, preProcessor) => {
109
+ const loaders = [
110
+ isEnvDevelopment && require.resolve('style-loader'),
111
+ isEnvProduction && {
112
+ loader: MiniCssExtractPlugin.loader,
113
+ // css is located in `static/css`, use '../../' to locate index.html folder
114
+ // in production `paths.publicUrlOrPath` can be a relative path
115
+ options: paths.publicUrlOrPath.startsWith('.')
116
+ ? { publicPath: '../../' }
117
+ : {},
118
+ },
119
+ {
120
+ loader: require.resolve('css-loader'),
121
+ options: cssOptions,
122
+ },
123
+ {
124
+ // Options for PostCSS as we reference these options twice
125
+ // Adds vendor prefixing based on your specified browser support in
126
+ // package.json
127
+ loader: require.resolve('postcss-loader'),
128
+ options: {
129
+ postcssOptions: {
130
+ // Necessary for external CSS imports to work
131
+ // https://github.com/facebook/create-react-app/issues/2677
132
+ ident: 'postcss',
133
+ config: false,
134
+ plugins: !useTailwind
135
+ ? [
136
+ 'postcss-flexbugs-fixes',
137
+ [
138
+ 'postcss-preset-env',
139
+ {
140
+ autoprefixer: {
141
+ flexbox: 'no-2009',
142
+ },
143
+ stage: 3,
144
+ },
145
+ ],
146
+ // Adds PostCSS Normalize as the reset css with default options,
147
+ // so that it honors browserslist config in package.json
148
+ // which in turn let's users customize the target behavior as per their needs.
149
+ 'postcss-normalize',
150
+ ]
151
+ : [
152
+ 'tailwindcss',
153
+ 'postcss-flexbugs-fixes',
154
+ [
155
+ 'postcss-preset-env',
156
+ {
157
+ autoprefixer: {
158
+ flexbox: 'no-2009',
159
+ },
160
+ stage: 3,
161
+ },
162
+ ],
163
+ ],
164
+ },
165
+ sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
166
+ },
167
+ },
168
+ ].filter(Boolean)
169
+ if (preProcessor) {
170
+ loaders.push(
171
+ {
172
+ loader: require.resolve('resolve-url-loader'),
173
+ options: {
174
+ sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment,
175
+ root: paths.appSrc,
176
+ },
177
+ },
178
+ {
179
+ loader: require.resolve(preProcessor),
180
+ options: {
181
+ sourceMap: true,
182
+ },
183
+ }
184
+ )
185
+ }
186
+ return loaders
187
+ }
188
+
189
+ return {
190
+ target: ['browserslist'],
191
+ // Webpack noise constrained to errors and warnings
192
+ stats: 'errors-warnings',
193
+ mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development',
194
+ // Stop compilation early in production
195
+ bail: isEnvProduction,
196
+ devtool: isEnvProduction
197
+ ? shouldUseSourceMap
198
+ ? 'source-map'
199
+ : false
200
+ : isEnvDevelopment && 'cheap-module-source-map',
201
+ // These are the "entry points" to our application.
202
+ // This means they will be the "root" imports that are included in JS bundle.
203
+ entry: paths.appIndexJs,
204
+ output: {
205
+ // The build folder.
206
+ path: paths.appBuild,
207
+ // Add /* filename */ comments to generated require()s in the output.
208
+ pathinfo: isEnvDevelopment,
209
+ // There will be one main bundle, and one file per asynchronous chunk.
210
+ // In development, it does not produce real files.
211
+ filename: isEnvProduction
212
+ ? 'static/js/[name].[contenthash:8].js'
213
+ : isEnvDevelopment && 'static/js/bundle.js',
214
+ // There are also additional JS chunk files if you use code splitting.
215
+ chunkFilename: isEnvProduction
216
+ ? 'static/js/[name].[contenthash:8].chunk.js'
217
+ : isEnvDevelopment && 'static/js/[name].chunk.js',
218
+ assetModuleFilename: 'static/media/[name].[hash][ext]',
219
+ // webpack uses `publicPath` to determine where the app is being served from.
220
+ // It requires a trailing slash, or the file assets will get an incorrect path.
221
+ // We inferred the "public path" (such as / or /my-project) from homepage.
222
+ publicPath: paths.publicUrlOrPath,
223
+ // Point sourcemap entries to original disk location (format as URL on Windows)
224
+ devtoolModuleFilenameTemplate: isEnvProduction
225
+ ? info =>
226
+ path
227
+ .relative(paths.appSrc, info.absoluteResourcePath)
228
+ .replace(/\\/g, '/')
229
+ : isEnvDevelopment &&
230
+ (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
231
+ },
232
+ cache: {
233
+ type: 'filesystem',
234
+ version: createEnvironmentHash(env.raw),
235
+ cacheDirectory: paths.appWebpackCache,
236
+ store: 'pack',
237
+ buildDependencies: {
238
+ defaultWebpack: ['webpack/lib/'],
239
+ config: [__filename],
240
+ tsconfig: [paths.appTsConfig, paths.appJsConfig].filter(f =>
241
+ fs.existsSync(f)
242
+ ),
243
+ },
244
+ },
245
+ infrastructureLogging: {
246
+ level: 'none',
247
+ },
248
+ optimization: {
249
+ minimize: isEnvProduction,
250
+ minimizer: [
251
+ // This is only used in production mode
252
+ new TerserPlugin({
253
+ terserOptions: {
254
+ parse: {
255
+ // We want terser to parse ecma 8 code. However, we don't want it
256
+ // to apply any minification steps that turns valid ecma 5 code
257
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
258
+ // sections only apply transformations that are ecma 5 safe
259
+ // https://github.com/facebook/create-react-app/pull/4234
260
+ ecma: 8,
261
+ },
262
+ compress: {
263
+ ecma: 5,
264
+ warnings: false,
265
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
266
+ // https://github.com/facebook/create-react-app/issues/2376
267
+ // Pending further investigation:
268
+ // https://github.com/mishoo/UglifyJS2/issues/2011
269
+ comparisons: false,
270
+ // Disabled because of an issue with Terser breaking valid code:
271
+ // https://github.com/facebook/create-react-app/issues/5250
272
+ // Pending further investigation:
273
+ // https://github.com/terser-js/terser/issues/120
274
+ inline: 2,
275
+ },
276
+ mangle: {
277
+ safari10: true,
278
+ },
279
+ // Added for profiling in devtools
280
+ keep_classnames: isEnvProductionProfile,
281
+ keep_fnames: isEnvProductionProfile,
282
+ output: {
283
+ ecma: 5,
284
+ comments: false,
285
+ // Turned on because emoji and regex is not minified properly using default
286
+ // https://github.com/facebook/create-react-app/issues/2488
287
+ ascii_only: true,
288
+ },
289
+ },
290
+ }),
291
+ // This is only used in production mode
292
+ new CssMinimizerPlugin(),
293
+ ],
294
+ },
295
+ resolve: {
296
+ // This allows you to set a fallback for where webpack should look for modules.
297
+ // We placed these paths second because we want `node_modules` to "win"
298
+ // if there are any conflicts. This matches Node resolution mechanism.
299
+ // https://github.com/facebook/create-react-app/issues/253
300
+ modules: ['node_modules', paths.appNodeModules].concat(
301
+ modules.additionalModulePaths || []
302
+ ),
303
+ // These are the reasonable defaults supported by the Node ecosystem.
304
+ // We also include JSX as a common component filename extension to support
305
+ // some tools, although we do not recommend using it, see:
306
+ // https://github.com/facebook/create-react-app/issues/290
307
+ // `web` extension prefixes have been added for better support
308
+ // for React Native Web.
309
+ extensions: paths.moduleFileExtensions
310
+ .map(ext => `.${ext}`)
311
+ .filter(ext => useTypeScript || !ext.includes('ts')),
312
+ alias: {
313
+ // Support React Native Web
314
+ // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
315
+ 'react-native': 'react-native-web',
316
+ // Allows for better profiling with ReactDevTools
317
+ ...(isEnvProductionProfile && {
318
+ 'react-dom$': 'react-dom/profiling',
319
+ 'scheduler/tracing': 'scheduler/tracing-profiling',
320
+ }),
321
+ ...(modules.webpackAliases || {}),
322
+ },
323
+ plugins: [
324
+ // Prevents users from importing files from outside of src/ (or node_modules/).
325
+ // This often causes confusion because we only process files within src/ with babel.
326
+ // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
327
+ // please link the files into your node_modules/ and let module-resolution kick in.
328
+ // Make sure your source files are compiled, as they will not be processed in any way.
329
+ new ModuleScopePlugin(paths.appSrc, [
330
+ paths.appPackageJson,
331
+ reactRefreshRuntimeEntry,
332
+ reactRefreshWebpackPluginRuntimeEntry,
333
+ babelRuntimeEntry,
334
+ babelRuntimeEntryHelpers,
335
+ babelRuntimeRegenerator,
336
+ ]),
337
+ ],
338
+ },
339
+ module: {
340
+ strictExportPresence: true,
341
+ rules: [
342
+ {
343
+ test: /\.m?js/,
344
+ resolve: {
345
+ fullySpecified: false,
346
+ },
347
+ },
348
+ // Handle node_modules packages that contain sourcemaps
349
+ shouldUseSourceMap && {
350
+ enforce: 'pre',
351
+ exclude: /@babel(?:\/|\\{1,2})runtime/,
352
+ test: /\.(js|mjs|jsx|ts|tsx|css)$/,
353
+ loader: require.resolve('source-map-loader'),
354
+ },
355
+ {
356
+ // "oneOf" will traverse all following loaders until one will
357
+ // match the requirements. When no loader matches it will fall
358
+ // back to the "file" loader at the end of the loader list.
359
+ oneOf: [
360
+ // TODO: Merge this config once `image/avif` is in the mime-db
361
+ // https://github.com/jshttp/mime-db
362
+ {
363
+ test: [/\.avif$/],
364
+ type: 'asset',
365
+ mimetype: 'image/avif',
366
+ parser: {
367
+ dataUrlCondition: {
368
+ maxSize: imageInlineSizeLimit,
369
+ },
370
+ },
371
+ },
372
+ // "url" loader works like "file" loader except that it embeds assets
373
+ // smaller than specified limit in bytes as data URLs to avoid requests.
374
+ // A missing `test` is equivalent to a match.
375
+ {
376
+ test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
377
+ type: 'asset',
378
+ parser: {
379
+ dataUrlCondition: {
380
+ maxSize: imageInlineSizeLimit,
381
+ },
382
+ },
383
+ },
384
+ {
385
+ test: /\.svg$/,
386
+ use: [
387
+ {
388
+ loader: require.resolve('@svgr/webpack'),
389
+ options: {
390
+ prettier: false,
391
+ svgo: false,
392
+ svgoConfig: {
393
+ plugins: [{ removeViewBox: false }],
394
+ },
395
+ titleProp: true,
396
+ ref: true,
397
+ },
398
+ },
399
+ {
400
+ loader: require.resolve('file-loader'),
401
+ options: {
402
+ name: 'static/media/[name].[hash].[ext]',
403
+ },
404
+ },
405
+ ],
406
+ issuer: {
407
+ and: [/\.(ts|tsx|js|jsx|md|mdx)$/],
408
+ },
409
+ },
410
+ // Process application JS with Babel.
411
+ // The preset includes JSX, Flow, TypeScript, and some ESnext features.
412
+ {
413
+ test: /\.(js|mjs|jsx|ts|tsx)$/,
414
+ include: paths.appSrc,
415
+ loader: require.resolve('babel-loader'),
416
+ options: {
417
+ customize: require.resolve(
418
+ 'babel-preset-react-app/webpack-overrides'
419
+ ),
420
+ presets: [
421
+ [
422
+ require.resolve('babel-preset-react-app'),
423
+ {
424
+ runtime: hasJsxRuntime ? 'automatic' : 'classic',
425
+ },
426
+ ],
427
+ ],
428
+
429
+ plugins: [
430
+ isEnvDevelopment &&
431
+ shouldUseReactRefresh &&
432
+ require.resolve('react-refresh/babel'),
433
+ ].filter(Boolean),
434
+ // This is a feature of `babel-loader` for webpack (not Babel itself).
435
+ // It enables caching results in ./node_modules/.cache/babel-loader/
436
+ // directory for faster rebuilds.
437
+ cacheDirectory: true,
438
+ // See #6846 for context on why cacheCompression is disabled
439
+ cacheCompression: false,
440
+ compact: isEnvProduction,
441
+ },
442
+ },
443
+ // Process any JS outside of the app with Babel.
444
+ // Unlike the application JS, we only compile the standard ES features.
445
+ {
446
+ test: /\.(js|mjs)$/,
447
+ exclude: /@babel(?:\/|\\{1,2})runtime/,
448
+ loader: require.resolve('babel-loader'),
449
+ options: {
450
+ babelrc: false,
451
+ configFile: false,
452
+ compact: false,
453
+ presets: [
454
+ [
455
+ require.resolve('babel-preset-react-app/dependencies'),
456
+ { helpers: true },
457
+ ],
458
+ ],
459
+ cacheDirectory: true,
460
+ // See #6846 for context on why cacheCompression is disabled
461
+ cacheCompression: false,
462
+
463
+ // Babel sourcemaps are needed for debugging into node_modules
464
+ // code. Without the options below, debuggers like VSCode
465
+ // show incorrect code and set breakpoints on the wrong lines.
466
+ sourceMaps: shouldUseSourceMap,
467
+ inputSourceMap: shouldUseSourceMap,
468
+ },
469
+ },
470
+ // "postcss" loader applies autoprefixer to our CSS.
471
+ // "css" loader resolves paths in CSS and adds assets as dependencies.
472
+ // "style" loader turns CSS into JS modules that inject <style> tags.
473
+ // In production, we use MiniCSSExtractPlugin to extract that CSS
474
+ // to a file, but in development "style" loader enables hot editing
475
+ // of CSS.
476
+ // By default we support CSS Modules with the extension .module.css
477
+ {
478
+ test: cssRegex,
479
+ exclude: cssModuleRegex,
480
+ use: getStyleLoaders({
481
+ importLoaders: 1,
482
+ sourceMap: isEnvProduction
483
+ ? shouldUseSourceMap
484
+ : isEnvDevelopment,
485
+ modules: {
486
+ mode: 'icss',
487
+ },
488
+ }),
489
+ // Don't consider CSS imports dead code even if the
490
+ // containing package claims to have no side effects.
491
+ // Remove this when webpack adds a warning or an error for this.
492
+ // See https://github.com/webpack/webpack/issues/6571
493
+ sideEffects: true,
494
+ },
495
+ // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
496
+ // using the extension .module.css
497
+ {
498
+ test: cssModuleRegex,
499
+ use: getStyleLoaders({
500
+ importLoaders: 1,
501
+ sourceMap: isEnvProduction
502
+ ? shouldUseSourceMap
503
+ : isEnvDevelopment,
504
+ modules: {
505
+ mode: 'local',
506
+ getLocalIdent: getCSSModuleLocalIdent,
507
+ },
508
+ }),
509
+ },
510
+ // Opt-in support for SASS (using .scss or .sass extensions).
511
+ // By default we support SASS Modules with the
512
+ // extensions .module.scss or .module.sass
513
+ {
514
+ test: sassRegex,
515
+ exclude: sassModuleRegex,
516
+ use: getStyleLoaders(
517
+ {
518
+ importLoaders: 3,
519
+ sourceMap: isEnvProduction
520
+ ? shouldUseSourceMap
521
+ : isEnvDevelopment,
522
+ modules: {
523
+ mode: 'icss',
524
+ },
525
+ },
526
+ 'sass-loader'
527
+ ),
528
+ // Don't consider CSS imports dead code even if the
529
+ // containing package claims to have no side effects.
530
+ // Remove this when webpack adds a warning or an error for this.
531
+ // See https://github.com/webpack/webpack/issues/6571
532
+ sideEffects: true,
533
+ },
534
+ // Adds support for CSS Modules, but using SASS
535
+ // using the extension .module.scss or .module.sass
536
+ {
537
+ test: sassModuleRegex,
538
+ use: getStyleLoaders(
539
+ {
540
+ importLoaders: 3,
541
+ sourceMap: isEnvProduction
542
+ ? shouldUseSourceMap
543
+ : isEnvDevelopment,
544
+ modules: {
545
+ mode: 'local',
546
+ getLocalIdent: getCSSModuleLocalIdent,
547
+ },
548
+ },
549
+ 'sass-loader'
550
+ ),
551
+ },
552
+ // "file" loader makes sure those assets get served by WebpackDevServer.
553
+ // When you `import` an asset, you get its (virtual) filename.
554
+ // In production, they would get copied to the `build` folder.
555
+ // This loader doesn't use a "test" so it will catch all modules
556
+ // that fall through the other loaders.
557
+ {
558
+ // Exclude `js` files to keep "css" loader working as it injects
559
+ // its runtime that would otherwise be processed through "file" loader.
560
+ // Also exclude `html` and `json` extensions so they get processed
561
+ // by webpacks internal loaders.
562
+ exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
563
+ type: 'asset/resource',
564
+ },
565
+ // ** STOP ** Are you adding a new loader?
566
+ // Make sure to add the new loader(s) before the "file" loader.
567
+ ],
568
+ },
569
+ ].filter(Boolean),
570
+ },
571
+ plugins: [
572
+ // Generates an `index.html` file with the <script> injected.
573
+ new HtmlWebpackPlugin(
574
+ Object.assign(
575
+ {},
576
+ {
577
+ inject: true,
578
+ template: paths.appHtml,
579
+ },
580
+ isEnvProduction
581
+ ? {
582
+ minify: {
583
+ removeComments: true,
584
+ collapseWhitespace: true,
585
+ removeRedundantAttributes: true,
586
+ useShortDoctype: true,
587
+ removeEmptyAttributes: true,
588
+ removeStyleLinkTypeAttributes: true,
589
+ keepClosingSlash: true,
590
+ minifyJS: true,
591
+ minifyCSS: true,
592
+ minifyURLs: true,
593
+ },
594
+ }
595
+ : undefined
596
+ )
597
+ ),
598
+ // Inlines the webpack runtime script. This script is too small to warrant
599
+ // a network request.
600
+ // https://github.com/facebook/create-react-app/issues/5358
601
+ isEnvProduction &&
602
+ shouldInlineRuntimeChunk &&
603
+ new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]),
604
+ // Makes some environment variables available in index.html.
605
+ // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
606
+ // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
607
+ // It will be an empty string unless you specify "homepage"
608
+ // in `package.json`, in which case it will be the pathname of that URL.
609
+ new InterpolateHtmlPlugin(HtmlWebpackPlugin, env.raw),
610
+ // This gives some necessary context to module not found errors, such as
611
+ // the requesting resource.
612
+ new ModuleNotFoundPlugin(paths.appPath),
613
+ // Makes some environment variables available to the JS code, for example:
614
+ // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
615
+ // It is absolutely essential that NODE_ENV is set to production
616
+ // during a production build.
617
+ // Otherwise React will be compiled in the very slow development mode.
618
+ new webpack.DefinePlugin(env.stringified),
619
+ // Experimental hot reloading for React .
620
+ // https://github.com/facebook/react/tree/main/packages/react-refresh
621
+ isEnvDevelopment &&
622
+ shouldUseReactRefresh &&
623
+ new ReactRefreshWebpackPlugin({
624
+ overlay: false,
625
+ }),
626
+ // Watcher doesn't work well if you mistype casing in a path so we use
627
+ // a plugin that prints an error when you attempt to do this.
628
+ // See https://github.com/facebook/create-react-app/issues/240
629
+ isEnvDevelopment && new CaseSensitivePathsPlugin(),
630
+ isEnvProduction &&
631
+ new MiniCssExtractPlugin({
632
+ // Options similar to the same options in webpackOptions.output
633
+ // both options are optional
634
+ filename: 'static/css/[name].[contenthash:8].css',
635
+ chunkFilename: 'static/css/[name].[contenthash:8].chunk.css',
636
+ }),
637
+ // Generate an asset manifest file with the following content:
638
+ // - "files" key: Mapping of all asset filenames to their corresponding
639
+ // output file so that tools can pick it up without having to parse
640
+ // `index.html`
641
+ // - "entrypoints" key: Array of files which are included in `index.html`,
642
+ // can be used to reconstruct the HTML if necessary
643
+ new WebpackManifestPlugin({
644
+ fileName: 'asset-manifest.json',
645
+ publicPath: paths.publicUrlOrPath,
646
+ generate: (seed, files, entrypoints) => {
647
+ const manifestFiles = files.reduce((manifest, file) => {
648
+ manifest[file.name] = file.path
649
+ return manifest
650
+ }, seed)
651
+ const entrypointFiles = entrypoints.main.filter(
652
+ fileName => !fileName.endsWith('.map')
653
+ )
654
+
655
+ return {
656
+ files: manifestFiles,
657
+ entrypoints: entrypointFiles,
658
+ }
659
+ },
660
+ }),
661
+ // Moment.js is an extremely popular library that bundles large locale files
662
+ // by default due to how webpack interprets its code. This is a practical
663
+ // solution that requires the user to opt into importing specific locales.
664
+ // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
665
+ // You can remove this if you don't use Moment.js:
666
+ new webpack.IgnorePlugin({
667
+ resourceRegExp: /^\.\/locale$/,
668
+ contextRegExp: /moment$/,
669
+ }),
670
+ // Generate a service worker script that will precache, and keep up to date,
671
+ // the HTML & assets that are part of the webpack build.
672
+ isEnvProduction &&
673
+ fs.existsSync(swSrc) &&
674
+ new WorkboxWebpackPlugin.InjectManifest({
675
+ swSrc,
676
+ dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./,
677
+ exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/],
678
+ // Bump up the default maximum size (2mb) that's precached,
679
+ // to make lazy-loading failure scenarios less likely.
680
+ // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
681
+ maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
682
+ }),
683
+ // TypeScript type checking
684
+ useTypeScript &&
685
+ new ForkTsCheckerWebpackPlugin({
686
+ async: isEnvDevelopment,
687
+ typescript: {
688
+ typescriptPath: resolve.sync('typescript', {
689
+ basedir: paths.appNodeModules,
690
+ }),
691
+ configOverwrite: {
692
+ compilerOptions: {
693
+ sourceMap: isEnvProduction
694
+ ? shouldUseSourceMap
695
+ : isEnvDevelopment,
696
+ skipLibCheck: true,
697
+ inlineSourceMap: false,
698
+ declarationMap: false,
699
+ noEmit: true,
700
+ incremental: true,
701
+ tsBuildInfoFile: paths.appTsBuildInfoFile,
702
+ },
703
+ },
704
+ context: paths.appPath,
705
+ diagnosticOptions: {
706
+ syntactic: true,
707
+ },
708
+ mode: 'write-references',
709
+ // profile: true,
710
+ },
711
+ issue: {
712
+ // This one is specifically to match during CI tests,
713
+ // as micromatch doesn't match
714
+ // '../cra-template-typescript/template/src/App.tsx'
715
+ // otherwise.
716
+ include: [
717
+ { file: '../**/src/**/*.{ts,tsx}' },
718
+ { file: '**/src/**/*.{ts,tsx}' },
719
+ ],
720
+ exclude: [
721
+ { file: '**/src/**/__tests__/**' },
722
+ { file: '**/src/**/?(*.){spec|test}.*' },
723
+ { file: '**/src/setupProxy.*' },
724
+ { file: '**/src/setupTests.*' },
725
+ ],
726
+ },
727
+ logger: {
728
+ infrastructure: 'silent',
729
+ },
730
+ }),
731
+ !disableESLintPlugin &&
732
+ new ESLintPlugin({
733
+ // Plugin options
734
+ extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
735
+ formatter: require.resolve('react-dev-utils/eslintFormatter'),
736
+ eslintPath: require.resolve('eslint'),
737
+ failOnError: !(isEnvDevelopment && emitErrorsAsWarnings),
738
+ context: paths.appSrc,
739
+ cache: true,
740
+ cacheLocation: path.resolve(
741
+ paths.appNodeModules,
742
+ '.cache/.eslintcache'
743
+ ),
744
+ // ESLint class options
745
+ cwd: paths.appPath,
746
+ resolvePluginsRelativeTo: __dirname,
747
+ baseConfig: {
748
+ extends: [require.resolve('eslint-config-react-app/base')],
749
+ rules: {
750
+ ...(!hasJsxRuntime && {
751
+ 'react/react-in-jsx-scope': 'error',
752
+ }),
753
+ },
754
+ },
755
+ }),
756
+ ].filter(Boolean),
757
+ // Turn off performance processing because we utilize
758
+ // our own hints via the FileSizeReporter
759
+ performance: false,
760
+ }
761
+ }
lama_cleaner/app/config/webpack/persistentCache/createEnvironmentHash.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+ const { createHash } = require('crypto');
3
+
4
+ module.exports = env => {
5
+ const hash = createHash('md5');
6
+ hash.update(JSON.stringify(env));
7
+
8
+ return hash.digest('hex');
9
+ };
lama_cleaner/app/config/webpackDevServer.config.js ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const evalSourceMapMiddleware = require('react-dev-utils/evalSourceMapMiddleware');
5
+ const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
6
+ const ignoredFiles = require('react-dev-utils/ignoredFiles');
7
+ const redirectServedPath = require('react-dev-utils/redirectServedPathMiddleware');
8
+ const paths = require('./paths');
9
+ const getHttpsConfig = require('./getHttpsConfig');
10
+
11
+ const host = process.env.HOST || '0.0.0.0';
12
+ const sockHost = process.env.WDS_SOCKET_HOST;
13
+ const sockPath = process.env.WDS_SOCKET_PATH; // default: '/ws'
14
+ const sockPort = process.env.WDS_SOCKET_PORT;
15
+
16
+ module.exports = function (proxy, allowedHost) {
17
+ const disableFirewall =
18
+ !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true';
19
+ return {
20
+ // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
21
+ // websites from potentially accessing local content through DNS rebinding:
22
+ // https://github.com/webpack/webpack-dev-server/issues/887
23
+ // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
24
+ // However, it made several existing use cases such as development in cloud
25
+ // environment or subdomains in development significantly more complicated:
26
+ // https://github.com/facebook/create-react-app/issues/2271
27
+ // https://github.com/facebook/create-react-app/issues/2233
28
+ // While we're investigating better solutions, for now we will take a
29
+ // compromise. Since our WDS configuration only serves files in the `public`
30
+ // folder we won't consider accessing them a vulnerability. However, if you
31
+ // use the `proxy` feature, it gets more dangerous because it can expose
32
+ // remote code execution vulnerabilities in backends like Django and Rails.
33
+ // So we will disable the host check normally, but enable it if you have
34
+ // specified the `proxy` setting. Finally, we let you override it if you
35
+ // really know what you're doing with a special environment variable.
36
+ // Note: ["localhost", ".localhost"] will support subdomains - but we might
37
+ // want to allow setting the allowedHosts manually for more complex setups
38
+ allowedHosts: disableFirewall ? 'all' : [allowedHost],
39
+ headers: {
40
+ 'Access-Control-Allow-Origin': '*',
41
+ 'Access-Control-Allow-Methods': '*',
42
+ 'Access-Control-Allow-Headers': '*',
43
+ },
44
+ // Enable gzip compression of generated files.
45
+ compress: true,
46
+ static: {
47
+ // By default WebpackDevServer serves physical files from current directory
48
+ // in addition to all the virtual build products that it serves from memory.
49
+ // This is confusing because those files won’t automatically be available in
50
+ // production build folder unless we copy them. However, copying the whole
51
+ // project directory is dangerous because we may expose sensitive files.
52
+ // Instead, we establish a convention that only files in `public` directory
53
+ // get served. Our build script will copy `public` into the `build` folder.
54
+ // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
55
+ // <link rel="icon" href="%PUBLIC_URL%/favicon.ico">
56
+ // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
57
+ // Note that we only recommend to use `public` folder as an escape hatch
58
+ // for files like `favicon.ico`, `manifest.json`, and libraries that are
59
+ // for some reason broken when imported through webpack. If you just want to
60
+ // use an image, put it in `src` and `import` it from JavaScript instead.
61
+ directory: paths.appPublic,
62
+ publicPath: [paths.publicUrlOrPath],
63
+ // By default files from `contentBase` will not trigger a page reload.
64
+ watch: {
65
+ // Reportedly, this avoids CPU overload on some systems.
66
+ // https://github.com/facebook/create-react-app/issues/293
67
+ // src/node_modules is not ignored to support absolute imports
68
+ // https://github.com/facebook/create-react-app/issues/1065
69
+ ignored: ignoredFiles(paths.appSrc),
70
+ },
71
+ },
72
+ client: {
73
+ webSocketURL: {
74
+ // Enable custom sockjs pathname for websocket connection to hot reloading server.
75
+ // Enable custom sockjs hostname, pathname and port for websocket connection
76
+ // to hot reloading server.
77
+ hostname: sockHost,
78
+ pathname: sockPath,
79
+ port: sockPort,
80
+ },
81
+ overlay: {
82
+ errors: true,
83
+ warnings: false,
84
+ },
85
+ },
86
+ devMiddleware: {
87
+ // It is important to tell WebpackDevServer to use the same "publicPath" path as
88
+ // we specified in the webpack config. When homepage is '.', default to serving
89
+ // from the root.
90
+ // remove last slash so user can land on `/test` instead of `/test/`
91
+ publicPath: paths.publicUrlOrPath.slice(0, -1),
92
+ },
93
+
94
+ https: getHttpsConfig(),
95
+ host,
96
+ historyApiFallback: {
97
+ // Paths with dots should still use the history fallback.
98
+ // See https://github.com/facebook/create-react-app/issues/387.
99
+ disableDotRule: true,
100
+ index: paths.publicUrlOrPath,
101
+ },
102
+ // `proxy` is run between `before` and `after` `webpack-dev-server` hooks
103
+ proxy,
104
+ onBeforeSetupMiddleware(devServer) {
105
+ // Keep `evalSourceMapMiddleware`
106
+ // middlewares before `redirectServedPath` otherwise will not have any effect
107
+ // This lets us fetch source contents from webpack for the error overlay
108
+ devServer.app.use(evalSourceMapMiddleware(devServer));
109
+
110
+ if (fs.existsSync(paths.proxySetup)) {
111
+ // This registers user provided middleware for proxy reasons
112
+ require(paths.proxySetup)(devServer.app);
113
+ }
114
+ },
115
+ onAfterSetupMiddleware(devServer) {
116
+ // Redirect to `PUBLIC_URL` or `homepage` from `package.json` if url not match
117
+ devServer.app.use(redirectServedPath(paths.publicUrlOrPath));
118
+
119
+ // This service worker file is effectively a 'no-op' that will reset any
120
+ // previous service worker registered for the same host:port combination.
121
+ // We do this in development to avoid hitting the production cache if
122
+ // it used the same host and port.
123
+ // https://github.com/facebook/create-react-app/issues/2272#issuecomment-302832432
124
+ devServer.app.use(noopServiceWorkerMiddleware(paths.publicUrlOrPath));
125
+ },
126
+ };
127
+ };
lama_cleaner/app/package.json ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "lama-cleaner",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "proxy": "http://127.0.0.1:8080",
6
+ "dependencies": {
7
+ "@babel/core": "^7.16.0",
8
+ "@heroicons/react": "^2.0.0",
9
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
10
+ "@radix-ui/colors": "^0.1.8",
11
+ "@radix-ui/react-dialog": "0.1.8-rc.25",
12
+ "@radix-ui/react-dropdown-menu": "^2.0.4",
13
+ "@radix-ui/react-icons": "^1.1.1",
14
+ "@radix-ui/react-popover": "^1.0.0",
15
+ "@radix-ui/react-scroll-area": "^1.0.2",
16
+ "@radix-ui/react-select": "0.1.2-rc.27",
17
+ "@radix-ui/react-switch": "^0.1.5",
18
+ "@radix-ui/react-tabs": "^1.0.1",
19
+ "@radix-ui/react-toast": "^0.1.1",
20
+ "@radix-ui/react-tooltip": "^0.1.7",
21
+ "@svgr/webpack": "^5.5.0",
22
+ "@testing-library/jest-dom": "^5.14.1",
23
+ "@testing-library/react": "^12.1.2",
24
+ "@testing-library/user-event": "^13.5.0",
25
+ "@types/flexsearch": "^0.7.3",
26
+ "@types/jest": "^27.0.2",
27
+ "@types/lodash": "^4.14.182",
28
+ "@types/node": "^16.11.1",
29
+ "@types/react": "^17.0.30",
30
+ "@types/react-dom": "^17.0.9",
31
+ "babel-jest": "^27.4.2",
32
+ "babel-loader": "^8.2.3",
33
+ "babel-plugin-named-asset-import": "^0.3.8",
34
+ "babel-preset-react-app": "^10.0.1",
35
+ "bfj": "^7.0.2",
36
+ "browserslist": "^4.18.1",
37
+ "camelcase": "^6.2.1",
38
+ "case-sensitive-paths-webpack-plugin": "^2.4.0",
39
+ "cross-env": "7.x",
40
+ "css-loader": "^6.5.1",
41
+ "css-minimizer-webpack-plugin": "^3.2.0",
42
+ "dotenv": "^10.0.0",
43
+ "dotenv-expand": "^5.1.0",
44
+ "eslint": "^8.3.0",
45
+ "eslint-config-react-app": "^7.0.1",
46
+ "eslint-webpack-plugin": "^3.1.1",
47
+ "file-loader": "^6.2.0",
48
+ "flexsearch": "0.7.21",
49
+ "fs-extra": "^10.0.0",
50
+ "hacktimer": "^1.1.3",
51
+ "html-webpack-plugin": "^5.5.0",
52
+ "identity-obj-proxy": "^3.0.0",
53
+ "jest": "^27.4.3",
54
+ "jest-resolve": "^27.4.2",
55
+ "jest-watch-typeahead": "^1.0.0",
56
+ "lodash": "^4.17.21",
57
+ "mini-css-extract-plugin": "^2.4.5",
58
+ "mitt": "^3.0.0",
59
+ "nanoid": "^4.0.0",
60
+ "npm-run-all": "4.x",
61
+ "postcss": "^8.4.4",
62
+ "postcss-flexbugs-fixes": "^5.0.2",
63
+ "postcss-loader": "^6.2.1",
64
+ "postcss-normalize": "^10.0.1",
65
+ "postcss-preset-env": "^7.0.1",
66
+ "prompts": "^2.4.2",
67
+ "react": "^17.0.2",
68
+ "react-app-polyfill": "^3.0.0",
69
+ "react-dev-utils": "^12.0.1",
70
+ "react-dom": "^17.0.2",
71
+ "react-feather": "^2.0.10",
72
+ "react-hotkeys-hook": "^3.4.7",
73
+ "react-photo-album": "^2.0.0",
74
+ "react-refresh": "^0.11.0",
75
+ "react-use": "^17.3.1",
76
+ "react-zoom-pan-pinch": "^2.1.3",
77
+ "recoil": "^0.6.1",
78
+ "resolve": "^1.20.0",
79
+ "resolve-url-loader": "^4.0.0",
80
+ "sass-loader": "^12.3.0",
81
+ "semver": "^7.3.5",
82
+ "socket.io-client": "^4.5.2",
83
+ "source-map-loader": "^3.0.0",
84
+ "style-loader": "^3.3.1",
85
+ "tailwindcss": "^3.0.2",
86
+ "terser-webpack-plugin": "^5.2.5",
87
+ "typescript": "4.x",
88
+ "webpack": "^5.64.4",
89
+ "webpack-dev-server": "^4.6.0",
90
+ "webpack-manifest-plugin": "^4.0.2",
91
+ "workbox-webpack-plugin": "^6.4.1"
92
+ },
93
+ "scripts": {
94
+ "start": "cross-env GENERATE_SOURCEMAP=false node scripts/start.js",
95
+ "build": "cross-env GENERATE_SOURCEMAP=false node scripts/build.js",
96
+ "test": "node scripts/test.js"
97
+ },
98
+ "eslintConfig": {
99
+ "extends": "react-app"
100
+ },
101
+ "browserslist": {
102
+ "production": [
103
+ ">0.2%",
104
+ "not dead",
105
+ "not op_mini all"
106
+ ],
107
+ "development": [
108
+ "last 1 chrome version",
109
+ "last 1 firefox version",
110
+ "last 1 safari version"
111
+ ]
112
+ },
113
+ "devDependencies": {
114
+ "@typescript-eslint/eslint-plugin": "^5.1.0",
115
+ "eslint-config-airbnb": "^18.2.1",
116
+ "eslint-config-prettier": "^8.3.0",
117
+ "eslint-plugin-import": "^2.25.2",
118
+ "eslint-plugin-jsx-a11y": "^6.4.1",
119
+ "eslint-plugin-prettier": "^4.0.0",
120
+ "eslint-plugin-react": "^7.27.1",
121
+ "eslint-plugin-react-hooks": "^4.3.0",
122
+ "prettier": "^2.4.1",
123
+ "sass": "^1.49.9"
124
+ },
125
+ "jest": {
126
+ "roots": [
127
+ "<rootDir>/src"
128
+ ],
129
+ "collectCoverageFrom": [
130
+ "src/**/*.{js,jsx,ts,tsx}",
131
+ "!src/**/*.d.ts"
132
+ ],
133
+ "setupFiles": [
134
+ "react-app-polyfill/jsdom"
135
+ ],
136
+ "setupFilesAfterEnv": [
137
+ "<rootDir>/src/setupTests.ts"
138
+ ],
139
+ "testMatch": [
140
+ "<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
141
+ "<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}"
142
+ ],
143
+ "testEnvironment": "jsdom",
144
+ "transform": {
145
+ "^.+\\.(js|jsx|mjs|cjs|ts|tsx)$": "<rootDir>/config/jest/babelTransform.js",
146
+ "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
147
+ "^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
148
+ },
149
+ "transformIgnorePatterns": [
150
+ "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$",
151
+ "^.+\\.module\\.(css|sass|scss)$"
152
+ ],
153
+ "modulePaths": [],
154
+ "moduleNameMapper": {
155
+ "^react-native$": "react-native-web",
156
+ "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
157
+ },
158
+ "moduleFileExtensions": [
159
+ "web.js",
160
+ "js",
161
+ "web.ts",
162
+ "ts",
163
+ "web.tsx",
164
+ "tsx",
165
+ "json",
166
+ "web.jsx",
167
+ "jsx",
168
+ "node"
169
+ ],
170
+ "watchPlugins": [
171
+ "jest-watch-typeahead/filename",
172
+ "jest-watch-typeahead/testname"
173
+ ],
174
+ "resetMocks": true
175
+ },
176
+ "babel": {
177
+ "presets": [
178
+ "react-app"
179
+ ]
180
+ }
181
+ }
lama_cleaner/app/pnpm-lock.yaml ADDED
The diff for this file is too large to render. See raw diff
 
lama_cleaner/app/public/index.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta
5
+ http-equiv="Cache-Control"
6
+ content="no-cache, no-store, must-revalidate"
7
+ />
8
+ <meta http-equiv="Pragma" content="no-cache" />
9
+ <meta http-equiv="Expires" content="0" />
10
+
11
+ <meta charset="utf-8" />
12
+ <meta
13
+ name="viewport"
14
+ content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
15
+ />
16
+
17
+ <meta name="theme-color" content="#ffffff" />
18
+ <!--
19
+ Notice the use of %PUBLIC_URL% in the tags above.
20
+ It will be replaced with the URL of the `public` folder during the build.
21
+ Only files inside the `public` folder can be referenced from the HTML.
22
+
23
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24
+ work correctly both with client-side routing and a non-root public URL.
25
+ Learn how to configure a non-root public URL by running `npm run build`.
26
+ -->
27
+ <title>lama-cleaner - Image inpainting powered by SOTA AI model</title>
28
+ </head>
29
+ <body>
30
+ <noscript>You need to enable JavaScript to run this app.</noscript>
31
+ <div id="root"></div>
32
+ </body>
33
+ </html>
lama_cleaner/app/scripts/build.js ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ // Do this as the first thing so that any code reading it knows the right env.
4
+ process.env.BABEL_ENV = 'production';
5
+ process.env.NODE_ENV = 'production';
6
+
7
+ // Makes the script crash on unhandled rejections instead of silently
8
+ // ignoring them. In the future, promise rejections that are not handled will
9
+ // terminate the Node.js process with a non-zero exit code.
10
+ process.on('unhandledRejection', err => {
11
+ throw err;
12
+ });
13
+
14
+ // Ensure environment variables are read.
15
+ require('../config/env');
16
+
17
+ const path = require('path');
18
+ const chalk = require('react-dev-utils/chalk');
19
+ const fs = require('fs-extra');
20
+ const bfj = require('bfj');
21
+ const webpack = require('webpack');
22
+ const configFactory = require('../config/webpack.config');
23
+ const paths = require('../config/paths');
24
+ const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
25
+ const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
26
+ const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
27
+ const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
28
+ const printBuildError = require('react-dev-utils/printBuildError');
29
+
30
+ const measureFileSizesBeforeBuild =
31
+ FileSizeReporter.measureFileSizesBeforeBuild;
32
+ const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
33
+ const useYarn = fs.existsSync(paths.yarnLockFile);
34
+
35
+ // These sizes are pretty large. We'll warn for bundles exceeding them.
36
+ const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
37
+ const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
38
+
39
+ const isInteractive = process.stdout.isTTY;
40
+
41
+ // Warn and crash if required files are missing
42
+ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
43
+ process.exit(1);
44
+ }
45
+
46
+ const argv = process.argv.slice(2);
47
+ const writeStatsJson = argv.indexOf('--stats') !== -1;
48
+
49
+ // Generate configuration
50
+ const config = configFactory('production');
51
+
52
+ // We require that you explicitly set browsers and do not fall back to
53
+ // browserslist defaults.
54
+ const { checkBrowsers } = require('react-dev-utils/browsersHelper');
55
+ checkBrowsers(paths.appPath, isInteractive)
56
+ .then(() => {
57
+ // First, read the current file sizes in build directory.
58
+ // This lets us display how much they changed later.
59
+ return measureFileSizesBeforeBuild(paths.appBuild);
60
+ })
61
+ .then(previousFileSizes => {
62
+ // Remove all content but keep the directory so that
63
+ // if you're in it, you don't end up in Trash
64
+ fs.emptyDirSync(paths.appBuild);
65
+ // Merge with the public folder
66
+ copyPublicFolder();
67
+ // Start the webpack build
68
+ return build(previousFileSizes);
69
+ })
70
+ .then(
71
+ ({ stats, previousFileSizes, warnings }) => {
72
+ if (warnings.length) {
73
+ console.log(chalk.yellow('Compiled with warnings.\n'));
74
+ console.log(warnings.join('\n\n'));
75
+ console.log(
76
+ '\nSearch for the ' +
77
+ chalk.underline(chalk.yellow('keywords')) +
78
+ ' to learn more about each warning.'
79
+ );
80
+ console.log(
81
+ 'To ignore, add ' +
82
+ chalk.cyan('// eslint-disable-next-line') +
83
+ ' to the line before.\n'
84
+ );
85
+ } else {
86
+ console.log(chalk.green('Compiled successfully.\n'));
87
+ }
88
+
89
+ console.log('File sizes after gzip:\n');
90
+ printFileSizesAfterBuild(
91
+ stats,
92
+ previousFileSizes,
93
+ paths.appBuild,
94
+ WARN_AFTER_BUNDLE_GZIP_SIZE,
95
+ WARN_AFTER_CHUNK_GZIP_SIZE
96
+ );
97
+ console.log();
98
+
99
+ const appPackage = require(paths.appPackageJson);
100
+ const publicUrl = paths.publicUrlOrPath;
101
+ const publicPath = config.output.publicPath;
102
+ const buildFolder = path.relative(process.cwd(), paths.appBuild);
103
+ printHostingInstructions(
104
+ appPackage,
105
+ publicUrl,
106
+ publicPath,
107
+ buildFolder,
108
+ useYarn
109
+ );
110
+ },
111
+ err => {
112
+ const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true';
113
+ if (tscCompileOnError) {
114
+ console.log(
115
+ chalk.yellow(
116
+ 'Compiled with the following type errors (you may want to check these before deploying your app):\n'
117
+ )
118
+ );
119
+ printBuildError(err);
120
+ } else {
121
+ console.log(chalk.red('Failed to compile.\n'));
122
+ printBuildError(err);
123
+ process.exit(1);
124
+ }
125
+ }
126
+ )
127
+ .catch(err => {
128
+ if (err && err.message) {
129
+ console.log(err.message);
130
+ }
131
+ process.exit(1);
132
+ });
133
+
134
+ // Create the production build and print the deployment instructions.
135
+ function build(previousFileSizes) {
136
+ console.log('Creating an optimized production build...');
137
+
138
+ const compiler = webpack(config);
139
+ return new Promise((resolve, reject) => {
140
+ compiler.run((err, stats) => {
141
+ let messages;
142
+ if (err) {
143
+ if (!err.message) {
144
+ return reject(err);
145
+ }
146
+
147
+ let errMessage = err.message;
148
+
149
+ // Add additional information for postcss errors
150
+ if (Object.prototype.hasOwnProperty.call(err, 'postcssNode')) {
151
+ errMessage +=
152
+ '\nCompileError: Begins at CSS selector ' +
153
+ err['postcssNode'].selector;
154
+ }
155
+
156
+ messages = formatWebpackMessages({
157
+ errors: [errMessage],
158
+ warnings: [],
159
+ });
160
+ } else {
161
+ messages = formatWebpackMessages(
162
+ stats.toJson({ all: false, warnings: true, errors: true })
163
+ );
164
+ }
165
+ if (messages.errors.length) {
166
+ // Only keep the first error. Others are often indicative
167
+ // of the same problem, but confuse the reader with noise.
168
+ if (messages.errors.length > 1) {
169
+ messages.errors.length = 1;
170
+ }
171
+ return reject(new Error(messages.errors.join('\n\n')));
172
+ }
173
+ if (
174
+ process.env.CI &&
175
+ (typeof process.env.CI !== 'string' ||
176
+ process.env.CI.toLowerCase() !== 'false') &&
177
+ messages.warnings.length
178
+ ) {
179
+ // Ignore sourcemap warnings in CI builds. See #8227 for more info.
180
+ const filteredWarnings = messages.warnings.filter(
181
+ w => !/Failed to parse source map/.test(w)
182
+ );
183
+ if (filteredWarnings.length) {
184
+ console.log(
185
+ chalk.yellow(
186
+ '\nTreating warnings as errors because process.env.CI = true.\n' +
187
+ 'Most CI servers set it automatically.\n'
188
+ )
189
+ );
190
+ return reject(new Error(filteredWarnings.join('\n\n')));
191
+ }
192
+ }
193
+
194
+ const resolveArgs = {
195
+ stats,
196
+ previousFileSizes,
197
+ warnings: messages.warnings,
198
+ };
199
+
200
+ if (writeStatsJson) {
201
+ return bfj
202
+ .write(paths.appBuild + '/bundle-stats.json', stats.toJson())
203
+ .then(() => resolve(resolveArgs))
204
+ .catch(error => reject(new Error(error)));
205
+ }
206
+
207
+ return resolve(resolveArgs);
208
+ });
209
+ });
210
+ }
211
+
212
+ function copyPublicFolder() {
213
+ fs.copySync(paths.appPublic, paths.appBuild, {
214
+ dereference: true,
215
+ filter: file => file !== paths.appHtml,
216
+ });
217
+ }
lama_cleaner/app/scripts/start.js ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ // Do this as the first thing so that any code reading it knows the right env.
4
+ process.env.BABEL_ENV = 'development';
5
+ process.env.NODE_ENV = 'development';
6
+
7
+ // Makes the script crash on unhandled rejections instead of silently
8
+ // ignoring them. In the future, promise rejections that are not handled will
9
+ // terminate the Node.js process with a non-zero exit code.
10
+ process.on('unhandledRejection', err => {
11
+ throw err;
12
+ });
13
+
14
+ // Ensure environment variables are read.
15
+ require('../config/env');
16
+
17
+ const fs = require('fs');
18
+ const chalk = require('react-dev-utils/chalk');
19
+ const webpack = require('webpack');
20
+ const WebpackDevServer = require('webpack-dev-server');
21
+ const clearConsole = require('react-dev-utils/clearConsole');
22
+ const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
23
+ const {
24
+ choosePort,
25
+ createCompiler,
26
+ prepareProxy,
27
+ prepareUrls,
28
+ } = require('react-dev-utils/WebpackDevServerUtils');
29
+ const openBrowser = require('react-dev-utils/openBrowser');
30
+ const semver = require('semver');
31
+ const paths = require('../config/paths');
32
+ const configFactory = require('../config/webpack.config');
33
+ const createDevServerConfig = require('../config/webpackDevServer.config');
34
+ const getClientEnvironment = require('../config/env');
35
+ const react = require(require.resolve('react', { paths: [paths.appPath] }));
36
+
37
+ const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
38
+ const useYarn = fs.existsSync(paths.yarnLockFile);
39
+ const isInteractive = process.stdout.isTTY;
40
+
41
+ // Warn and crash if required files are missing
42
+ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
43
+ process.exit(1);
44
+ }
45
+
46
+ // Tools like Cloud9 rely on this.
47
+ const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
48
+ const HOST = process.env.HOST || '0.0.0.0';
49
+
50
+ if (process.env.HOST) {
51
+ console.log(
52
+ chalk.cyan(
53
+ `Attempting to bind to HOST environment variable: ${chalk.yellow(
54
+ chalk.bold(process.env.HOST)
55
+ )}`
56
+ )
57
+ );
58
+ console.log(
59
+ `If this was unintentional, check that you haven't mistakenly set it in your shell.`
60
+ );
61
+ console.log(
62
+ `Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}`
63
+ );
64
+ console.log();
65
+ }
66
+
67
+ // We require that you explicitly set browsers and do not fall back to
68
+ // browserslist defaults.
69
+ const { checkBrowsers } = require('react-dev-utils/browsersHelper');
70
+ checkBrowsers(paths.appPath, isInteractive)
71
+ .then(() => {
72
+ // We attempt to use the default port but if it is busy, we offer the user to
73
+ // run on a different port. `choosePort()` Promise resolves to the next free port.
74
+ return choosePort(HOST, DEFAULT_PORT);
75
+ })
76
+ .then(port => {
77
+ if (port == null) {
78
+ // We have not found a port.
79
+ return;
80
+ }
81
+
82
+ const config = configFactory('development');
83
+ const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
84
+ const appName = require(paths.appPackageJson).name;
85
+
86
+ const useTypeScript = fs.existsSync(paths.appTsConfig);
87
+ const urls = prepareUrls(
88
+ protocol,
89
+ HOST,
90
+ port,
91
+ paths.publicUrlOrPath.slice(0, -1)
92
+ );
93
+ // Create a webpack compiler that is configured with custom messages.
94
+ const compiler = createCompiler({
95
+ appName,
96
+ config,
97
+ urls,
98
+ useYarn,
99
+ useTypeScript,
100
+ webpack,
101
+ });
102
+ // Load proxy config
103
+ const proxySetting = require(paths.appPackageJson).proxy;
104
+ const proxyConfig = prepareProxy(
105
+ proxySetting,
106
+ paths.appPublic,
107
+ paths.publicUrlOrPath
108
+ );
109
+ // Serve webpack assets generated by the compiler over a web server.
110
+ const serverConfig = {
111
+ ...createDevServerConfig(proxyConfig, urls.lanUrlForConfig),
112
+ host: HOST,
113
+ port,
114
+ };
115
+ const devServer = new WebpackDevServer(serverConfig, compiler);
116
+ // Launch WebpackDevServer.
117
+ devServer.startCallback(() => {
118
+ if (isInteractive) {
119
+ clearConsole();
120
+ }
121
+
122
+ if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) {
123
+ console.log(
124
+ chalk.yellow(
125
+ `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.`
126
+ )
127
+ );
128
+ }
129
+
130
+ console.log(chalk.cyan('Starting the development server...\n'));
131
+ openBrowser(urls.localUrlForBrowser);
132
+ });
133
+
134
+ ['SIGINT', 'SIGTERM'].forEach(function (sig) {
135
+ process.on(sig, function () {
136
+ devServer.close();
137
+ process.exit();
138
+ });
139
+ });
140
+
141
+ if (process.env.CI !== 'true') {
142
+ // Gracefully exit when stdin ends
143
+ process.stdin.on('end', function () {
144
+ devServer.close();
145
+ process.exit();
146
+ });
147
+ }
148
+ })
149
+ .catch(err => {
150
+ if (err && err.message) {
151
+ console.log(err.message);
152
+ }
153
+ process.exit(1);
154
+ });
lama_cleaner/app/scripts/test.js ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+
3
+ // Do this as the first thing so that any code reading it knows the right env.
4
+ process.env.BABEL_ENV = 'test';
5
+ process.env.NODE_ENV = 'test';
6
+ process.env.PUBLIC_URL = '';
7
+
8
+ // Makes the script crash on unhandled rejections instead of silently
9
+ // ignoring them. In the future, promise rejections that are not handled will
10
+ // terminate the Node.js process with a non-zero exit code.
11
+ process.on('unhandledRejection', err => {
12
+ throw err;
13
+ });
14
+
15
+ // Ensure environment variables are read.
16
+ require('../config/env');
17
+
18
+ const jest = require('jest');
19
+ const execSync = require('child_process').execSync;
20
+ let argv = process.argv.slice(2);
21
+
22
+ function isInGitRepository() {
23
+ try {
24
+ execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
25
+ return true;
26
+ } catch (e) {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ function isInMercurialRepository() {
32
+ try {
33
+ execSync('hg --cwd . root', { stdio: 'ignore' });
34
+ return true;
35
+ } catch (e) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // Watch unless on CI or explicitly running all tests
41
+ if (
42
+ !process.env.CI &&
43
+ argv.indexOf('--watchAll') === -1 &&
44
+ argv.indexOf('--watchAll=false') === -1
45
+ ) {
46
+ // https://github.com/facebook/create-react-app/issues/5210
47
+ const hasSourceControl = isInGitRepository() || isInMercurialRepository();
48
+ argv.push(hasSourceControl ? '--watch' : '--watchAll');
49
+ }
50
+
51
+
52
+ jest.run(argv);
lama_cleaner/app/src/App.tsx ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useCallback, useEffect, useMemo } from 'react'
2
+ import { useRecoilState, useSetRecoilState } from 'recoil'
3
+ import { nanoid } from 'nanoid'
4
+ import useInputImage from './hooks/useInputImage'
5
+ import { themeState } from './components/Header/ThemeChanger'
6
+ import Workspace from './components/Workspace'
7
+ import { fileState, serverConfigState, toastState } from './store/Atoms'
8
+ import { keepGUIAlive } from './utils'
9
+ import Header from './components/Header/Header'
10
+ import useHotKey from './hooks/useHotkey'
11
+ import { getServerConfig, isDesktop } from './adapters/inpainting'
12
+
13
+ const SUPPORTED_FILE_TYPE = [
14
+ 'image/jpeg',
15
+ 'image/png',
16
+ 'image/webp',
17
+ 'image/bmp',
18
+ 'image/tiff',
19
+ ]
20
+
21
+ function App() {
22
+ const [file, setFile] = useRecoilState(fileState)
23
+ const [theme, setTheme] = useRecoilState(themeState)
24
+ const setToastState = useSetRecoilState(toastState)
25
+ const userInputImage = useInputImage()
26
+ const setServerConfigState = useSetRecoilState(serverConfigState)
27
+
28
+ // Set Input Image
29
+ useEffect(() => {
30
+ setFile(userInputImage)
31
+ }, [userInputImage, setFile])
32
+
33
+ // Keeping GUI Window Open
34
+ useEffect(() => {
35
+ const fetchData = async () => {
36
+ const isRunDesktop = await isDesktop().then(res => res.text())
37
+ if (isRunDesktop === 'True') {
38
+ keepGUIAlive()
39
+ }
40
+ }
41
+ fetchData()
42
+ }, [])
43
+
44
+ useEffect(() => {
45
+ const fetchServerConfig = async () => {
46
+ const serverConfig = await getServerConfig().then(res => res.json())
47
+ console.log(serverConfig)
48
+ setServerConfigState(serverConfig)
49
+ }
50
+ fetchServerConfig()
51
+ }, [])
52
+
53
+ // Dark Mode Hotkey
54
+ useHotKey(
55
+ 'shift+d',
56
+ () => {
57
+ const newTheme = theme === 'light' ? 'dark' : 'light'
58
+ setTheme(newTheme)
59
+ },
60
+ {},
61
+ [theme]
62
+ )
63
+
64
+ useEffect(() => {
65
+ document.body.setAttribute('data-theme', theme)
66
+ }, [theme])
67
+
68
+ const workspaceId = useMemo(() => {
69
+ return nanoid()
70
+ }, [file])
71
+
72
+ ///
73
+
74
+ const [isDragging, setIsDragging] = React.useState(false)
75
+ const dragCounter = React.useRef(0)
76
+
77
+ const handleDrag = React.useCallback(event => {
78
+ event.preventDefault()
79
+ event.stopPropagation()
80
+ }, [])
81
+
82
+ const handleDragIn = React.useCallback(event => {
83
+ event.preventDefault()
84
+ event.stopPropagation()
85
+ dragCounter.current += 1
86
+ if (event.dataTransfer.items && event.dataTransfer.items.length > 0) {
87
+ setIsDragging(true)
88
+ }
89
+ }, [])
90
+
91
+ const handleDragOut = React.useCallback(event => {
92
+ event.preventDefault()
93
+ event.stopPropagation()
94
+ dragCounter.current -= 1
95
+ if (dragCounter.current > 0) return
96
+ setIsDragging(false)
97
+ }, [])
98
+
99
+ const handleDrop = React.useCallback(
100
+ event => {
101
+ event.preventDefault()
102
+ event.stopPropagation()
103
+ setIsDragging(false)
104
+ if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
105
+ if (event.dataTransfer.files.length > 1) {
106
+ setToastState({
107
+ open: true,
108
+ desc: 'Please drag and drop only one file',
109
+ state: 'error',
110
+ duration: 3000,
111
+ })
112
+ } else {
113
+ const dragFile = event.dataTransfer.files[0]
114
+ const fileType = dragFile.type
115
+ if (SUPPORTED_FILE_TYPE.includes(fileType)) {
116
+ setFile(dragFile)
117
+ } else {
118
+ setToastState({
119
+ open: true,
120
+ desc: 'Please drag and drop an image file',
121
+ state: 'error',
122
+ duration: 3000,
123
+ })
124
+ }
125
+ }
126
+ event.dataTransfer.clearData()
127
+ }
128
+ },
129
+ [setToastState, setFile]
130
+ )
131
+
132
+ const onPaste = useCallback((event: any) => {
133
+ // TODO: when sd side panel open, ctrl+v not work
134
+ // https://htmldom.dev/paste-an-image-from-the-clipboard/
135
+ if (!event.clipboardData) {
136
+ return
137
+ }
138
+ const clipboardItems = event.clipboardData.items
139
+ const items: DataTransferItem[] = [].slice
140
+ .call(clipboardItems)
141
+ .filter((item: DataTransferItem) => {
142
+ // Filter the image items only
143
+ return item.type.indexOf('image') !== -1
144
+ })
145
+
146
+ if (items.length === 0) {
147
+ return
148
+ }
149
+
150
+ event.preventDefault()
151
+ event.stopPropagation()
152
+
153
+ // TODO: add confirm dialog
154
+
155
+ const item = items[0]
156
+ // Get the blob of image
157
+ const blob = item.getAsFile()
158
+ if (blob) {
159
+ setFile(blob)
160
+ }
161
+ }, [])
162
+
163
+ React.useEffect(() => {
164
+ window.addEventListener('dragenter', handleDragIn)
165
+ window.addEventListener('dragleave', handleDragOut)
166
+ window.addEventListener('dragover', handleDrag)
167
+ window.addEventListener('drop', handleDrop)
168
+ window.addEventListener('paste', onPaste)
169
+ return function cleanUp() {
170
+ window.removeEventListener('dragenter', handleDragIn)
171
+ window.removeEventListener('dragleave', handleDragOut)
172
+ window.removeEventListener('dragover', handleDrag)
173
+ window.removeEventListener('drop', handleDrop)
174
+ window.removeEventListener('paste', onPaste)
175
+ }
176
+ })
177
+
178
+ return (
179
+ <div className="lama-cleaner">
180
+ <Header />
181
+ <Workspace key={workspaceId} />
182
+ </div>
183
+ )
184
+ }
185
+
186
+ export default App
lama_cleaner/app/src/adapters/inpainting.ts ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PluginName } from '../components/Plugins/Plugins'
2
+ import { Rect, Settings } from '../store/Atoms'
3
+ import { dataURItoBlob, loadImage, srcToFile } from '../utils'
4
+
5
+ export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}`
6
+
7
+ export default async function inpaint(
8
+ imageFile: File,
9
+ settings: Settings,
10
+ croperRect: Rect,
11
+ prompt?: string,
12
+ negativePrompt?: string,
13
+ seed?: number,
14
+ maskBase64?: string,
15
+ customMask?: File,
16
+ paintByExampleImage?: File
17
+ ) {
18
+ // 1080, 2000, Original
19
+ const fd = new FormData()
20
+ fd.append('image', imageFile)
21
+ if (maskBase64 !== undefined) {
22
+ fd.append('mask', dataURItoBlob(maskBase64))
23
+ } else if (customMask !== undefined) {
24
+ fd.append('mask', customMask)
25
+ }
26
+
27
+ const hdSettings = settings.hdSettings[settings.model]
28
+ fd.append('ldmSteps', settings.ldmSteps.toString())
29
+ fd.append('ldmSampler', settings.ldmSampler.toString())
30
+ fd.append('zitsWireframe', settings.zitsWireframe.toString())
31
+ fd.append('hdStrategy', hdSettings.hdStrategy)
32
+ fd.append('hdStrategyCropMargin', hdSettings.hdStrategyCropMargin.toString())
33
+ fd.append(
34
+ 'hdStrategyCropTrigerSize',
35
+ hdSettings.hdStrategyCropTrigerSize.toString()
36
+ )
37
+ fd.append(
38
+ 'hdStrategyResizeLimit',
39
+ hdSettings.hdStrategyResizeLimit.toString()
40
+ )
41
+
42
+ fd.append('prompt', prompt === undefined ? '' : prompt)
43
+ fd.append(
44
+ 'negativePrompt',
45
+ negativePrompt === undefined ? '' : negativePrompt
46
+ )
47
+ fd.append('croperX', croperRect.x.toString())
48
+ fd.append('croperY', croperRect.y.toString())
49
+ fd.append('croperHeight', croperRect.height.toString())
50
+ fd.append('croperWidth', croperRect.width.toString())
51
+ fd.append('useCroper', settings.showCroper ? 'true' : 'false')
52
+
53
+ fd.append('sdMaskBlur', settings.sdMaskBlur.toString())
54
+ fd.append('sdStrength', settings.sdStrength.toString())
55
+ fd.append('sdSteps', settings.sdSteps.toString())
56
+ fd.append('sdGuidanceScale', settings.sdGuidanceScale.toString())
57
+ fd.append('sdSampler', settings.sdSampler.toString())
58
+ fd.append('sdSeed', seed ? seed.toString() : '-1')
59
+ fd.append('sdMatchHistograms', settings.sdMatchHistograms ? 'true' : 'false')
60
+ fd.append('sdScale', (settings.sdScale / 100).toString())
61
+
62
+ fd.append('cv2Radius', settings.cv2Radius.toString())
63
+ fd.append('cv2Flag', settings.cv2Flag.toString())
64
+
65
+ fd.append('paintByExampleSteps', settings.paintByExampleSteps.toString())
66
+ fd.append(
67
+ 'paintByExampleGuidanceScale',
68
+ settings.paintByExampleGuidanceScale.toString()
69
+ )
70
+ fd.append('paintByExampleSeed', seed ? seed.toString() : '-1')
71
+ fd.append(
72
+ 'paintByExampleMaskBlur',
73
+ settings.paintByExampleMaskBlur.toString()
74
+ )
75
+ fd.append(
76
+ 'paintByExampleMatchHistograms',
77
+ settings.paintByExampleMatchHistograms ? 'true' : 'false'
78
+ )
79
+ // TODO: resize image's shortest_edge to 224 before pass to backend, save network time?
80
+ // https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPImageProcessor
81
+ if (paintByExampleImage) {
82
+ fd.append('paintByExampleImage', paintByExampleImage)
83
+ }
84
+
85
+ // InstructPix2Pix
86
+ fd.append('p2pSteps', settings.p2pSteps.toString())
87
+ fd.append('p2pImageGuidanceScale', settings.p2pImageGuidanceScale.toString())
88
+ fd.append('p2pGuidanceScale', settings.p2pGuidanceScale.toString())
89
+
90
+ // ControlNet
91
+ fd.append(
92
+ 'controlnet_conditioning_scale',
93
+ settings.controlnetConditioningScale.toString()
94
+ )
95
+
96
+ try {
97
+ const res = await fetch(`${API_ENDPOINT}/inpaint`, {
98
+ method: 'POST',
99
+ body: fd,
100
+ })
101
+ if (res.ok) {
102
+ const blob = await res.blob()
103
+ const newSeed = res.headers.get('x-seed')
104
+ return { blob: URL.createObjectURL(blob), seed: newSeed }
105
+ }
106
+ const errMsg = await res.text()
107
+ throw new Error(errMsg)
108
+ } catch (error) {
109
+ throw new Error(`Something went wrong: ${error}`)
110
+ }
111
+ }
112
+
113
+ export function getServerConfig() {
114
+ return fetch(`${API_ENDPOINT}/server_config`, {
115
+ method: 'GET',
116
+ })
117
+ }
118
+
119
+ export function switchModel(name: string) {
120
+ const fd = new FormData()
121
+ fd.append('name', name)
122
+ return fetch(`${API_ENDPOINT}/model`, {
123
+ method: 'POST',
124
+ body: fd,
125
+ })
126
+ }
127
+
128
+ export function currentModel() {
129
+ return fetch(`${API_ENDPOINT}/model`, {
130
+ method: 'GET',
131
+ })
132
+ }
133
+
134
+ export function isDesktop() {
135
+ return fetch(`${API_ENDPOINT}/is_desktop`, {
136
+ method: 'GET',
137
+ })
138
+ }
139
+
140
+ export function modelDownloaded(name: string) {
141
+ return fetch(`${API_ENDPOINT}/model_downloaded/${name}`, {
142
+ method: 'GET',
143
+ })
144
+ }
145
+
146
+ export async function runPlugin(
147
+ name: string,
148
+ imageFile: File,
149
+ upscale?: number,
150
+ maskFile?: File | null,
151
+ clicks?: number[][]
152
+ ) {
153
+ const fd = new FormData()
154
+ fd.append('name', name)
155
+ fd.append('image', imageFile)
156
+ if (upscale) {
157
+ fd.append('upscale', upscale.toString())
158
+ }
159
+ if (clicks) {
160
+ fd.append('clicks', JSON.stringify(clicks))
161
+ }
162
+ if (maskFile) {
163
+ fd.append('mask', maskFile)
164
+ }
165
+
166
+ try {
167
+ const res = await fetch(`${API_ENDPOINT}/run_plugin`, {
168
+ method: 'POST',
169
+ body: fd,
170
+ })
171
+ if (res.ok) {
172
+ const blob = await res.blob()
173
+ return { blob: URL.createObjectURL(blob) }
174
+ }
175
+ const errMsg = await res.text()
176
+ throw new Error(errMsg)
177
+ } catch (error) {
178
+ throw new Error(`Something went wrong: ${error}`)
179
+ }
180
+ }
181
+
182
+ export async function getMediaFile(tab: string, filename: string) {
183
+ const res = await fetch(
184
+ `${API_ENDPOINT}/media/${tab}/${encodeURIComponent(filename)}`,
185
+ {
186
+ method: 'GET',
187
+ }
188
+ )
189
+ if (res.ok) {
190
+ const blob = await res.blob()
191
+ const file = new File([blob], filename)
192
+ return file
193
+ }
194
+ const errMsg = await res.text()
195
+ throw new Error(errMsg)
196
+ }
197
+
198
+ export async function getMedias(tab: string) {
199
+ const res = await fetch(`${API_ENDPOINT}/medias/${tab}`, {
200
+ method: 'GET',
201
+ })
202
+ if (res.ok) {
203
+ const filenames = await res.json()
204
+ return filenames
205
+ }
206
+ const errMsg = await res.text()
207
+ throw new Error(errMsg)
208
+ }
209
+
210
+ export async function downloadToOutput(
211
+ image: HTMLImageElement,
212
+ filename: string,
213
+ mimeType: string
214
+ ) {
215
+ const file = await srcToFile(image.src, filename, mimeType)
216
+ const fd = new FormData()
217
+ fd.append('image', file)
218
+ fd.append('filename', filename)
219
+
220
+ try {
221
+ const res = await fetch(`${API_ENDPOINT}/save_image`, {
222
+ method: 'POST',
223
+ body: fd,
224
+ })
225
+ if (!res.ok) {
226
+ const errMsg = await res.text()
227
+ throw new Error(errMsg)
228
+ }
229
+ } catch (error) {
230
+ throw new Error(`Something went wrong: ${error}`)
231
+ }
232
+ }
233
+
234
+ export async function makeGif(
235
+ originFile: File,
236
+ cleanImage: HTMLImageElement,
237
+ filename: string,
238
+ mimeType: string
239
+ ) {
240
+ const cleanFile = await srcToFile(cleanImage.src, filename, mimeType)
241
+ const fd = new FormData()
242
+ fd.append('name', PluginName.MakeGIF)
243
+ fd.append('image', originFile)
244
+ fd.append('clean_img', cleanFile)
245
+ fd.append('filename', filename)
246
+
247
+ try {
248
+ const res = await fetch(`${API_ENDPOINT}/run_plugin`, {
249
+ method: 'POST',
250
+ body: fd,
251
+ })
252
+ if (!res.ok) {
253
+ const errMsg = await res.text()
254
+ throw new Error(errMsg)
255
+ }
256
+
257
+ const blob = await res.blob()
258
+ const newImage = new Image()
259
+ await loadImage(newImage, URL.createObjectURL(blob))
260
+ return newImage
261
+ } catch (error) {
262
+ throw new Error(`Something went wrong: ${error}`)
263
+ }
264
+ }
lama_cleaner/app/src/components/CoffeeIcon/CoffeeIcon.tsx ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react'
2
+ import { Coffee } from 'react-feather'
3
+ import Button from '../shared/Button'
4
+ import Modal from '../shared/Modal'
5
+ import CoffeeMachineGif from '../../media/coffee-machine-lineal.gif'
6
+
7
+ const CoffeeIcon = () => {
8
+ const [show, setShow] = useState(false)
9
+ const onClick = () => {
10
+ setShow(true)
11
+ }
12
+
13
+ return (
14
+ <div>
15
+ <Button
16
+ onClick={onClick}
17
+ toolTip="Buy me a coffee"
18
+ style={{ border: 0 }}
19
+ icon={<Coffee />}
20
+ />
21
+ <Modal
22
+ onClose={() => setShow(false)}
23
+ title="Buy Me a Coffee"
24
+ className="modal-setting"
25
+ show={show}
26
+ showCloseIcon={false}
27
+ >
28
+ <div
29
+ style={{
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ }}
33
+ >
34
+ <h4 style={{ lineHeight: '24px' }}>
35
+ Hi, if you found my project is useful, please conside buy me a
36
+ coffee to support my work. Thanks!
37
+ </h4>
38
+ <img
39
+ src={CoffeeMachineGif}
40
+ alt="coffee machine"
41
+ style={{
42
+ height: 150,
43
+ objectFit: 'contain',
44
+ }}
45
+ />
46
+ </div>
47
+
48
+ <div
49
+ style={{
50
+ display: 'flex',
51
+ width: '100%',
52
+ justifyContent: 'flex-end',
53
+ alignItems: 'center',
54
+ gap: '12px',
55
+ }}
56
+ >
57
+ <Button onClick={() => setShow(false)}> No thanks </Button>
58
+ <a
59
+ href="https://ko-fi.com/Z8Z1CZJGY"
60
+ target="_blank"
61
+ rel="noreferrer"
62
+ >
63
+ <Button border onClick={() => setShow(false)}>
64
+ <div
65
+ style={{
66
+ display: 'flex',
67
+ justifyContent: 'center',
68
+ alignItems: 'center',
69
+ gap: '8px',
70
+ }}
71
+ >
72
+ Sure
73
+ </div>
74
+ </Button>
75
+ </a>
76
+ </div>
77
+ </Modal>
78
+ </div>
79
+ )
80
+ }
81
+
82
+ export default CoffeeIcon
lama_cleaner/app/src/components/Croper/Croper.scss ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @use 'sass:math';
2
+
3
+ $drag-handle-shortside: 12px;
4
+ $drag-handle-longside: 40px;
5
+ $drag-bar-size: 12px;
6
+
7
+ $half-handle-shortside: math.div($drag-handle-shortside, 2);
8
+ $half-handle-longside: math.div($drag-handle-longside, 2);
9
+ $half-drag-bar-size: math.div($drag-bar-size, 2);
10
+
11
+ .crop-border {
12
+ outline-color: var(--yellow-accent);
13
+ outline-style: dashed;
14
+ }
15
+
16
+ .info-bar {
17
+ position: absolute;
18
+ pointer-events: auto;
19
+ font-size: 1rem;
20
+ padding: 0.2rem 0.8rem;
21
+ display: flex;
22
+ align-items: center;
23
+ justify-content: center;
24
+ gap: 12px;
25
+ color: var(--text-color);
26
+ background-color: var(--page-bg);
27
+ border-radius: 9999px;
28
+
29
+ border: var(--editor-toolkit-panel-border);
30
+ box-shadow: 0 0 0 1px #0000001a, 0 3px 16px #00000014, 0 2px 6px 1px #00000017;
31
+
32
+ &:hover {
33
+ cursor: move;
34
+ }
35
+ }
36
+
37
+ .croper-wrapper {
38
+ position: absolute;
39
+ height: 100%;
40
+ width: 100%;
41
+ z-index: 2;
42
+ overflow: hidden;
43
+ pointer-events: none;
44
+ }
45
+
46
+ .croper {
47
+ position: relative;
48
+ top: 0;
49
+ bottom: 0;
50
+ left: 0;
51
+ right: 0;
52
+ z-index: 2;
53
+ pointer-events: none;
54
+
55
+ // display: flex;
56
+ // flex-direction: column;
57
+ // align-items: center;
58
+
59
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
60
+ }
61
+
62
+ .drag-bar {
63
+ position: absolute;
64
+ pointer-events: auto;
65
+ // display: none;
66
+
67
+ &.ord-top {
68
+ top: 0;
69
+ left: 0;
70
+ width: 100%;
71
+ height: $drag-bar-size;
72
+ margin-top: -$half-drag-bar-size;
73
+ cursor: ns-resize;
74
+ }
75
+ &.ord-right {
76
+ right: 0;
77
+ top: 0;
78
+ width: $drag-bar-size;
79
+ height: 100%;
80
+ margin-right: -$half-drag-bar-size;
81
+ cursor: ew-resize;
82
+ }
83
+ &.ord-bottom {
84
+ bottom: 0;
85
+ left: 0;
86
+ width: 100%;
87
+ height: $drag-bar-size;
88
+ margin-bottom: -$half-drag-bar-size;
89
+ cursor: ns-resize;
90
+ }
91
+ &.ord-left {
92
+ top: 0;
93
+ left: 0;
94
+ width: $drag-bar-size;
95
+ height: 100%;
96
+ margin-left: -$half-drag-bar-size;
97
+ cursor: ew-resize;
98
+ }
99
+ }
100
+
101
+ .drag-handle {
102
+ width: $drag-handle-shortside;
103
+ height: $drag-handle-shortside;
104
+ z-index: 4;
105
+ position: absolute;
106
+ display: block;
107
+ content: '';
108
+ border: 2px solid var(--yellow-accent);
109
+ background-color: var(--yellow-accent-light);
110
+ pointer-events: auto;
111
+
112
+ &:hover {
113
+ background-color: var(--yellow-accent);
114
+ }
115
+
116
+ &.ord-topleft {
117
+ cursor: nw-resize;
118
+ top: (-$half-handle-shortside)-1px;
119
+ left: (-$half-handle-shortside)-1px;
120
+ }
121
+
122
+ &.ord-topright {
123
+ cursor: ne-resize;
124
+ top: -($half-handle-shortside)-1px;
125
+ right: -($half-handle-shortside)-1px;
126
+ }
127
+
128
+ &.ord-bottomright {
129
+ cursor: se-resize;
130
+ bottom: -($half-handle-shortside)-1px;
131
+ right: -($half-handle-shortside)-1px;
132
+ }
133
+
134
+ &.ord-bottomleft {
135
+ cursor: sw-resize;
136
+ bottom: -($half-handle-shortside)-1px;
137
+ left: -($half-handle-shortside)-1px;
138
+ }
139
+
140
+ &.ord-top,
141
+ &.ord-bottom {
142
+ left: calc(50% - $half-handle-shortside);
143
+ cursor: ns-resize;
144
+ }
145
+
146
+ &.ord-top {
147
+ top: (-$half-handle-shortside)-1px;
148
+ }
149
+
150
+ &.ord-bottom {
151
+ bottom: -($half-handle-shortside)-1px;
152
+ }
153
+
154
+ &.ord-left,
155
+ &.ord-right {
156
+ top: calc(50% - $half-handle-shortside);
157
+ cursor: ew-resize;
158
+ }
159
+
160
+ &.ord-left {
161
+ left: (-$half-handle-shortside)-1px;
162
+ }
163
+
164
+ &.ord-right {
165
+ right: -($half-handle-shortside)-1px;
166
+ }
167
+ }
lama_cleaner/app/src/components/Croper/Croper.tsx ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState } from 'react'
2
+ import { useRecoilState, useRecoilValue } from 'recoil'
3
+ import {
4
+ croperHeight,
5
+ croperWidth,
6
+ croperX,
7
+ croperY,
8
+ isInpaintingState,
9
+ } from '../../store/Atoms'
10
+
11
+ const DOC_MOVE_OPTS = { capture: true, passive: false }
12
+
13
+ const DRAG_HANDLE_BORDER = 2
14
+ const DRAG_HANDLE_SHORT = 12
15
+ const DRAG_HANDLE_LONG = 40
16
+
17
+ interface EVData {
18
+ initX: number
19
+ initY: number
20
+ initHeight: number
21
+ initWidth: number
22
+ startResizeX: number
23
+ startResizeY: number
24
+ ord: string // top/right/bottom/left
25
+ }
26
+
27
+ interface Props {
28
+ maxHeight: number
29
+ maxWidth: number
30
+ scale: number
31
+ minHeight: number
32
+ minWidth: number
33
+ show: boolean
34
+ }
35
+
36
+ const clamp = (
37
+ newPos: number,
38
+ newLength: number,
39
+ oldPos: number,
40
+ oldLength: number,
41
+ minLength: number,
42
+ maxLength: number
43
+ ) => {
44
+ if (newPos !== oldPos && newLength === oldLength) {
45
+ if (newPos < 0) {
46
+ return [0, oldLength]
47
+ }
48
+ if (newPos + newLength > maxLength) {
49
+ return [maxLength - oldLength, oldLength]
50
+ }
51
+ } else {
52
+ if (newLength < minLength) {
53
+ if (newPos === oldPos) {
54
+ return [newPos, minLength]
55
+ }
56
+ return [newPos + newLength - minLength, minLength]
57
+ }
58
+ if (newPos < 0) {
59
+ return [0, newPos + newLength]
60
+ }
61
+ if (newPos + newLength > maxLength) {
62
+ return [newPos, maxLength - newPos]
63
+ }
64
+ }
65
+
66
+ return [newPos, newLength]
67
+ }
68
+
69
+ const Croper = (props: Props) => {
70
+ const { minHeight, minWidth, maxHeight, maxWidth, scale, show } = props
71
+ const [x, setX] = useRecoilState(croperX)
72
+ const [y, setY] = useRecoilState(croperY)
73
+ const [height, setHeight] = useRecoilState(croperHeight)
74
+ const [width, setWidth] = useRecoilState(croperWidth)
75
+ const isInpainting = useRecoilValue(isInpaintingState)
76
+
77
+ const [isResizing, setIsResizing] = useState(false)
78
+ const [isMoving, setIsMoving] = useState(false)
79
+
80
+ useEffect(() => {
81
+ setX(Math.round((maxWidth - 512) / 2))
82
+ setY(Math.round((maxHeight - 512) / 2))
83
+ }, [maxHeight, maxWidth])
84
+
85
+ const [evData, setEVData] = useState<EVData>({
86
+ initX: 0,
87
+ initY: 0,
88
+ initHeight: 0,
89
+ initWidth: 0,
90
+ startResizeX: 0,
91
+ startResizeY: 0,
92
+ ord: 'top',
93
+ })
94
+
95
+ const onDragFocus = () => {
96
+ console.log('focus')
97
+ }
98
+
99
+ const clampLeftRight = (newX: number, newWidth: number) => {
100
+ return clamp(newX, newWidth, x, width, minWidth, maxWidth)
101
+ }
102
+
103
+ const clampTopBottom = (newY: number, newHeight: number) => {
104
+ return clamp(newY, newHeight, y, height, minHeight, maxHeight)
105
+ }
106
+
107
+ const onPointerMove = (e: PointerEvent) => {
108
+ if (isInpainting) {
109
+ return
110
+ }
111
+ const curX = e.clientX
112
+ const curY = e.clientY
113
+
114
+ const offsetY = Math.round((curY - evData.startResizeY) / scale)
115
+ const offsetX = Math.round((curX - evData.startResizeX) / scale)
116
+
117
+ const moveTop = () => {
118
+ const newHeight = evData.initHeight - offsetY
119
+ const newY = evData.initY + offsetY
120
+ const [clampedY, clampedHeight] = clampTopBottom(newY, newHeight)
121
+ setHeight(clampedHeight)
122
+ setY(clampedY)
123
+ }
124
+
125
+ const moveBottom = () => {
126
+ const newHeight = evData.initHeight + offsetY
127
+ const [clampedY, clampedHeight] = clampTopBottom(evData.initY, newHeight)
128
+ setHeight(clampedHeight)
129
+ setY(clampedY)
130
+ }
131
+
132
+ const moveLeft = () => {
133
+ const newWidth = evData.initWidth - offsetX
134
+ const newX = evData.initX + offsetX
135
+ const [clampedX, clampedWidth] = clampLeftRight(newX, newWidth)
136
+ setWidth(clampedWidth)
137
+ setX(clampedX)
138
+ }
139
+
140
+ const moveRight = () => {
141
+ const newWidth = evData.initWidth + offsetX
142
+ const [clampedX, clampedWidth] = clampLeftRight(evData.initX, newWidth)
143
+ setWidth(clampedWidth)
144
+ setX(clampedX)
145
+ }
146
+
147
+ if (isResizing) {
148
+ switch (evData.ord) {
149
+ case 'topleft': {
150
+ moveTop()
151
+ moveLeft()
152
+ break
153
+ }
154
+ case 'topright': {
155
+ moveTop()
156
+ moveRight()
157
+ break
158
+ }
159
+ case 'bottomleft': {
160
+ moveBottom()
161
+ moveLeft()
162
+ break
163
+ }
164
+ case 'bottomright': {
165
+ moveBottom()
166
+ moveRight()
167
+ break
168
+ }
169
+ case 'top': {
170
+ moveTop()
171
+ break
172
+ }
173
+ case 'right': {
174
+ moveRight()
175
+ break
176
+ }
177
+ case 'bottom': {
178
+ moveBottom()
179
+ break
180
+ }
181
+ case 'left': {
182
+ moveLeft()
183
+ break
184
+ }
185
+
186
+ default:
187
+ break
188
+ }
189
+ }
190
+
191
+ if (isMoving) {
192
+ const newX = evData.initX + offsetX
193
+ const newY = evData.initY + offsetY
194
+ const [clampedX, clampedWidth] = clampLeftRight(newX, evData.initWidth)
195
+ const [clampedY, clampedHeight] = clampTopBottom(newY, evData.initHeight)
196
+ setWidth(clampedWidth)
197
+ setHeight(clampedHeight)
198
+ setX(clampedX)
199
+ setY(clampedY)
200
+ }
201
+ }
202
+
203
+ const onPointerDone = (e: PointerEvent) => {
204
+ if (isResizing) {
205
+ setIsResizing(false)
206
+ }
207
+
208
+ if (isMoving) {
209
+ setIsMoving(false)
210
+ }
211
+ }
212
+
213
+ useEffect(() => {
214
+ if (isResizing || isMoving) {
215
+ document.addEventListener('pointermove', onPointerMove, DOC_MOVE_OPTS)
216
+ document.addEventListener('pointerup', onPointerDone, DOC_MOVE_OPTS)
217
+ document.addEventListener('pointercancel', onPointerDone, DOC_MOVE_OPTS)
218
+ return () => {
219
+ document.removeEventListener(
220
+ 'pointermove',
221
+ onPointerMove,
222
+ DOC_MOVE_OPTS
223
+ )
224
+ document.removeEventListener('pointerup', onPointerDone, DOC_MOVE_OPTS)
225
+ document.removeEventListener(
226
+ 'pointercancel',
227
+ onPointerDone,
228
+ DOC_MOVE_OPTS
229
+ )
230
+ }
231
+ }
232
+ }, [isResizing, isMoving, width, height, evData])
233
+
234
+ const onCropPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
235
+ const { ord } = (e.target as HTMLElement).dataset
236
+ if (ord) {
237
+ setIsResizing(true)
238
+ setEVData({
239
+ initX: x,
240
+ initY: y,
241
+ initHeight: height,
242
+ initWidth: width,
243
+ startResizeX: e.clientX,
244
+ startResizeY: e.clientY,
245
+ ord,
246
+ })
247
+ }
248
+ }
249
+
250
+ const createCropSelection = () => {
251
+ return (
252
+ <div
253
+ className="drag-elements"
254
+ onFocus={onDragFocus}
255
+ onPointerDown={onCropPointerDown}
256
+ >
257
+ <div
258
+ className="drag-bar ord-top"
259
+ data-ord="top"
260
+ style={{ transform: `scale(${1 / scale})` }}
261
+ />
262
+ <div
263
+ className="drag-bar ord-right"
264
+ data-ord="right"
265
+ style={{ transform: `scale(${1 / scale})` }}
266
+ />
267
+ <div
268
+ className="drag-bar ord-bottom"
269
+ data-ord="bottom"
270
+ style={{ transform: `scale(${1 / scale})` }}
271
+ />
272
+ <div
273
+ className="drag-bar ord-left"
274
+ data-ord="left"
275
+ style={{ transform: `scale(${1 / scale})` }}
276
+ />
277
+
278
+ <div
279
+ className="drag-handle ord-topleft"
280
+ data-ord="topleft"
281
+ aria-label="topleft"
282
+ tabIndex={0}
283
+ role="button"
284
+ style={{ transform: `scale(${1 / scale})` }}
285
+ />
286
+
287
+ <div
288
+ className="drag-handle ord-topright"
289
+ data-ord="topright"
290
+ aria-label="topright"
291
+ tabIndex={0}
292
+ role="button"
293
+ style={{ transform: `scale(${1 / scale})` }}
294
+ />
295
+
296
+ <div
297
+ className="drag-handle ord-bottomleft"
298
+ data-ord="bottomleft"
299
+ aria-label="bottomleft"
300
+ tabIndex={0}
301
+ role="button"
302
+ style={{ transform: `scale(${1 / scale})` }}
303
+ />
304
+
305
+ <div
306
+ className="drag-handle ord-bottomright"
307
+ data-ord="bottomright"
308
+ aria-label="bottomright"
309
+ tabIndex={0}
310
+ role="button"
311
+ style={{ transform: `scale(${1 / scale})` }}
312
+ />
313
+
314
+ <div
315
+ className="drag-handle ord-top"
316
+ data-ord="top"
317
+ aria-label="top"
318
+ tabIndex={0}
319
+ role="button"
320
+ style={{ transform: `scale(${1 / scale})` }}
321
+ />
322
+ <div
323
+ className="drag-handle ord-right"
324
+ data-ord="right"
325
+ aria-label="right"
326
+ tabIndex={0}
327
+ role="button"
328
+ style={{ transform: `scale(${1 / scale})` }}
329
+ />
330
+ <div
331
+ className="drag-handle ord-bottom"
332
+ data-ord="bottom"
333
+ aria-label="bottom"
334
+ tabIndex={0}
335
+ role="button"
336
+ style={{ transform: `scale(${1 / scale})` }}
337
+ />
338
+ <div
339
+ className="drag-handle ord-left"
340
+ data-ord="left"
341
+ aria-label="left"
342
+ tabIndex={0}
343
+ role="button"
344
+ style={{ transform: `scale(${1 / scale})` }}
345
+ />
346
+ </div>
347
+ )
348
+ }
349
+
350
+ const onInfoBarPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
351
+ setIsMoving(true)
352
+ setEVData({
353
+ initX: x,
354
+ initY: y,
355
+ initHeight: height,
356
+ initWidth: width,
357
+ startResizeX: e.clientX,
358
+ startResizeY: e.clientY,
359
+ ord: '',
360
+ })
361
+ }
362
+
363
+ const createInfoBar = () => {
364
+ return (
365
+ <div
366
+ className="info-bar"
367
+ onPointerDown={onInfoBarPointerDown}
368
+ style={{
369
+ transform: `scale(${1 / scale})`,
370
+ top: `${10 / scale}px`,
371
+ left: `${10 / scale}px`,
372
+ }}
373
+ >
374
+ <div className="crop-size">
375
+ {width} x {height}
376
+ </div>
377
+ </div>
378
+ )
379
+ }
380
+
381
+ const createBorder = () => {
382
+ return (
383
+ <div
384
+ className="crop-border"
385
+ style={{
386
+ height,
387
+ width,
388
+ outlineWidth: `${DRAG_HANDLE_BORDER / scale}px`,
389
+ }}
390
+ />
391
+ )
392
+ }
393
+
394
+ return (
395
+ <div
396
+ className="croper-wrapper"
397
+ style={{ visibility: show ? 'visible' : 'hidden' }}
398
+ >
399
+ <div className="croper" style={{ height, width, left: x, top: y }}>
400
+ {createBorder()}
401
+ {createInfoBar()}
402
+ {createCropSelection()}
403
+ </div>
404
+ </div>
405
+ )
406
+ }
407
+
408
+ export default Croper
lama_cleaner/app/src/components/Editor/Editor.scss ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @use '../../styles/Mixins' as *;
2
+
3
+ .editor-container {
4
+ display: flex;
5
+ width: 100vw;
6
+ height: 100vh;
7
+ justify-content: center;
8
+ align-items: center;
9
+ }
10
+
11
+ .react-transform-wrapper {
12
+ display: grid !important;
13
+ width: 100% !important;
14
+ height: 100% !important;
15
+ }
16
+
17
+ .editor-canvas-container {
18
+ display: grid;
19
+ grid-template-areas: 'editor-content';
20
+ row-gap: 1rem;
21
+ }
22
+
23
+ .editor-canvas {
24
+ grid-area: editor-content;
25
+ z-index: 2;
26
+ }
27
+
28
+ .original-image-container {
29
+ grid-area: editor-content;
30
+ pointer-events: none;
31
+ display: grid;
32
+ grid-template-areas: 'original-image-content';
33
+
34
+ img {
35
+ grid-area: original-image-content;
36
+ }
37
+
38
+ .editor-slider {
39
+ grid-area: original-image-content;
40
+ height: 100%;
41
+ width: 6px;
42
+ justify-self: end;
43
+ background-color: var(--yellow-accent);
44
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
45
+ z-index: 2;
46
+ }
47
+ }
48
+
49
+ .editor-canvas-loading {
50
+ pointer-events: none;
51
+ animation: pulsing 750ms infinite;
52
+ }
53
+
54
+ .editor-toolkit-panel {
55
+ position: fixed;
56
+ bottom: 0.5rem;
57
+ border-radius: 3rem;
58
+ padding: 0.4rem 24px;
59
+ display: flex;
60
+ gap: 16px;
61
+ align-items: center;
62
+ justify-content: center;
63
+ backdrop-filter: blur(12px);
64
+ background-color: var(--page-bg-light);
65
+ animation: slideUp 0.2s ease-out;
66
+ border: var(--editor-toolkit-panel-border);
67
+ box-shadow: 0 0 0 1px #0000001a, 0 3px 16px #00000014, 0 2px 6px 1px #00000017;
68
+
69
+ @include mobile {
70
+ padding: 1rem 2rem;
71
+ grid-template-areas:
72
+ 'toolkit-size-selector toolkit-size-selector'
73
+ 'toolkit-brush-slider toolkit-brush-slider'
74
+ 'toolkit-btns toolkit-btns';
75
+ row-gap: 2rem;
76
+ justify-items: center;
77
+ }
78
+
79
+ .eyeicon-active {
80
+ background-color: var(--yellow-accent);
81
+ color: var(--btn-text-hover-color);
82
+ }
83
+ }
84
+
85
+ .editor-brush-slider {
86
+ grid-area: toolkit-brush-slider;
87
+ user-select: none;
88
+ display: grid;
89
+ grid-template-columns: repeat(2, max-content);
90
+ height: max-content;
91
+ column-gap: 1rem;
92
+ align-items: center;
93
+
94
+ @include slider-bar;
95
+ }
96
+
97
+ .editor-toolkit-btns {
98
+ display: flex;
99
+ gap: 12px;
100
+ }
101
+
102
+ .brush-shape {
103
+ position: absolute;
104
+ border-radius: 50%;
105
+ background-color: #ffcc00bb;
106
+ border: 1px solid var(--yellow-accent);
107
+ pointer-events: none;
108
+ }
lama_cleaner/app/src/components/Editor/Editor.tsx ADDED
@@ -0,0 +1,1744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, {
2
+ SyntheticEvent,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from 'react'
8
+ import {
9
+ CursorArrowRaysIcon,
10
+ EyeIcon,
11
+ ArrowsPointingOutIcon,
12
+ ArrowDownTrayIcon,
13
+ } from '@heroicons/react/24/outline'
14
+ import {
15
+ ReactZoomPanPinchRef,
16
+ TransformComponent,
17
+ TransformWrapper,
18
+ } from 'react-zoom-pan-pinch'
19
+ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
20
+ import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
21
+ import inpaint, { downloadToOutput, runPlugin } from '../../adapters/inpainting'
22
+ import Button from '../shared/Button'
23
+ import Slider from './Slider'
24
+ import {
25
+ askWritePermission,
26
+ copyCanvasImage,
27
+ downloadImage,
28
+ isMidClick,
29
+ isRightClick,
30
+ loadImage,
31
+ srcToFile,
32
+ useImage,
33
+ } from '../../utils'
34
+ import {
35
+ appState,
36
+ brushSizeState,
37
+ croperState,
38
+ enableFileManagerState,
39
+ fileState,
40
+ imageHeightState,
41
+ imageWidthState,
42
+ interactiveSegClicksState,
43
+ isDiffusionModelsState,
44
+ isEnableAutoSavingState,
45
+ isInpaintingState,
46
+ isInteractiveSegRunningState,
47
+ isInteractiveSegState,
48
+ isPix2PixState,
49
+ isPluginRunningState,
50
+ isProcessingState,
51
+ negativePropmtState,
52
+ propmtState,
53
+ runManuallyState,
54
+ seedState,
55
+ settingState,
56
+ toastState,
57
+ } from '../../store/Atoms'
58
+ import useHotKey from '../../hooks/useHotkey'
59
+ import Croper from '../Croper/Croper'
60
+ import emitter, {
61
+ EVENT_PROMPT,
62
+ EVENT_CUSTOM_MASK,
63
+ EVENT_PAINT_BY_EXAMPLE,
64
+ RERUN_LAST_MASK,
65
+ } from '../../event'
66
+ import FileSelect from '../FileSelect/FileSelect'
67
+ import InteractiveSeg from '../InteractiveSeg/InteractiveSeg'
68
+ import InteractiveSegConfirmActions from '../InteractiveSeg/ConfirmActions'
69
+ import InteractiveSegReplaceModal from '../InteractiveSeg/ReplaceModal'
70
+ import { PluginName } from '../Plugins/Plugins'
71
+ import MakeGIF from './MakeGIF'
72
+
73
+ const TOOLBAR_SIZE = 200
74
+ const MIN_BRUSH_SIZE = 10
75
+ const MAX_BRUSH_SIZE = 200
76
+ const BRUSH_COLOR = '#ffcc00bb'
77
+
78
+ interface Line {
79
+ size?: number
80
+ pts: { x: number; y: number }[]
81
+ }
82
+
83
+ type LineGroup = Array<Line>
84
+
85
+ function drawLines(
86
+ ctx: CanvasRenderingContext2D,
87
+ lines: LineGroup,
88
+ color = BRUSH_COLOR
89
+ ) {
90
+ ctx.strokeStyle = color
91
+ ctx.lineCap = 'round'
92
+ ctx.lineJoin = 'round'
93
+
94
+ lines.forEach(line => {
95
+ if (!line?.pts.length || !line.size) {
96
+ return
97
+ }
98
+ ctx.lineWidth = line.size
99
+ ctx.beginPath()
100
+ ctx.moveTo(line.pts[0].x, line.pts[0].y)
101
+ line.pts.forEach(pt => ctx.lineTo(pt.x, pt.y))
102
+ ctx.stroke()
103
+ })
104
+ }
105
+
106
+ function mouseXY(ev: SyntheticEvent) {
107
+ const mouseEvent = ev.nativeEvent as MouseEvent
108
+ return { x: mouseEvent.offsetX, y: mouseEvent.offsetY }
109
+ }
110
+
111
+ export default function Editor() {
112
+ const [file, setFile] = useRecoilState(fileState)
113
+ const promptVal = useRecoilValue(propmtState)
114
+ const negativePromptVal = useRecoilValue(negativePropmtState)
115
+ const settings = useRecoilValue(settingState)
116
+ const [seedVal, setSeed] = useRecoilState(seedState)
117
+ const croperRect = useRecoilValue(croperState)
118
+ const setToastState = useSetRecoilState(toastState)
119
+ const [isInpainting, setIsInpainting] = useRecoilState(isInpaintingState)
120
+ const setIsPluginRunning = useSetRecoilState(isPluginRunningState)
121
+ const isProcessing = useRecoilValue(isProcessingState)
122
+ const runMannually = useRecoilValue(runManuallyState)
123
+ const isDiffusionModels = useRecoilValue(isDiffusionModelsState)
124
+ const isPix2Pix = useRecoilValue(isPix2PixState)
125
+ const [isInteractiveSeg, setIsInteractiveSeg] = useRecoilState(
126
+ isInteractiveSegState
127
+ )
128
+ const setIsInteractiveSegRunning = useSetRecoilState(
129
+ isInteractiveSegRunningState
130
+ )
131
+
132
+ const [showInteractiveSegModal, setShowInteractiveSegModal] = useState(false)
133
+ const [interactiveSegMask, setInteractiveSegMask] =
134
+ useState<HTMLImageElement | null>(null)
135
+ // only used while interactive segmentation is on
136
+ const [tmpInteractiveSegMask, setTmpInteractiveSegMask] =
137
+ useState<HTMLImageElement | null>(null)
138
+ const [prevInteractiveSegMask, setPrevInteractiveSegMask] = useState<
139
+ HTMLImageElement | null | undefined
140
+ >(null)
141
+
142
+ const [clicks, setClicks] = useRecoilState(interactiveSegClicksState)
143
+
144
+ const [brushSize, setBrushSize] = useRecoilState(brushSizeState)
145
+
146
+ const [original, isOriginalLoaded] = useImage(file)
147
+ const [renders, setRenders] = useState<HTMLImageElement[]>([])
148
+ const [context, setContext] = useState<CanvasRenderingContext2D>()
149
+ const [maskCanvas] = useState<HTMLCanvasElement>(() => {
150
+ return document.createElement('canvas')
151
+ })
152
+ const [lineGroups, setLineGroups] = useState<LineGroup[]>([])
153
+ const [lastLineGroup, setLastLineGroup] = useState<LineGroup>([])
154
+ const [curLineGroup, setCurLineGroup] = useState<LineGroup>([])
155
+ const [{ x, y }, setCoords] = useState({ x: -1, y: -1 })
156
+ const [showBrush, setShowBrush] = useState(false)
157
+ const [showRefBrush, setShowRefBrush] = useState(false)
158
+ const [isPanning, setIsPanning] = useState<boolean>(false)
159
+ const [isChangingBrushSizeByMouse, setIsChangingBrushSizeByMouse] =
160
+ useState<boolean>(false)
161
+ const [changeBrushSizeByMouseInit, setChangeBrushSizeByMouseInit] = useState({
162
+ x: -1,
163
+ y: -1,
164
+ brushSize: 20,
165
+ })
166
+
167
+ const [showOriginal, setShowOriginal] = useState(false)
168
+ const [scale, setScale] = useState<number>(1)
169
+ const [panned, setPanned] = useState<boolean>(false)
170
+ const [minScale, setMinScale] = useState<number>(1.0)
171
+ const windowSize = useWindowSize()
172
+ const windowCenterX = windowSize.width / 2
173
+ const windowCenterY = windowSize.height / 2
174
+ const viewportRef = useRef<ReactZoomPanPinchRef | undefined | null>()
175
+ // Indicates that the image has been loaded and is centered on first load
176
+ const [initialCentered, setInitialCentered] = useState(false)
177
+
178
+ const [isDraging, setIsDraging] = useState(false)
179
+ const [isMultiStrokeKeyPressed, setIsMultiStrokeKeyPressed] = useState(false)
180
+
181
+ const [sliderPos, setSliderPos] = useState<number>(0)
182
+
183
+ // redo 相关
184
+ const [redoRenders, setRedoRenders] = useState<HTMLImageElement[]>([])
185
+ const [redoCurLines, setRedoCurLines] = useState<Line[]>([])
186
+ const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([])
187
+ const enableFileManager = useRecoilValue(enableFileManagerState)
188
+ const isEnableAutoSaving = useRecoilValue(isEnableAutoSavingState)
189
+
190
+ const [imageWidth, setImageWidth] = useRecoilState(imageWidthState)
191
+ const [imageHeight, setImageHeight] = useRecoilState(imageHeightState)
192
+ const app = useRecoilValue(appState)
193
+
194
+ const draw = useCallback(
195
+ (render: HTMLImageElement, lineGroup: LineGroup) => {
196
+ if (!context) {
197
+ return
198
+ }
199
+ console.log(
200
+ `[draw] render size: ${render.width}x${render.height} image size: ${imageWidth}x${imageHeight} canvas size: ${context.canvas.width}x${context.canvas.height}`
201
+ )
202
+
203
+ context.clearRect(0, 0, context.canvas.width, context.canvas.height)
204
+ context.drawImage(render, 0, 0, imageWidth, imageHeight)
205
+ if (isInteractiveSeg && tmpInteractiveSegMask !== null) {
206
+ context.drawImage(tmpInteractiveSegMask, 0, 0, imageWidth, imageHeight)
207
+ }
208
+ if (!isInteractiveSeg && interactiveSegMask !== null) {
209
+ context.drawImage(interactiveSegMask, 0, 0, imageWidth, imageHeight)
210
+ }
211
+ drawLines(context, lineGroup)
212
+ },
213
+ [
214
+ context,
215
+ isInteractiveSeg,
216
+ tmpInteractiveSegMask,
217
+ interactiveSegMask,
218
+ imageHeight,
219
+ imageWidth,
220
+ ]
221
+ )
222
+
223
+ const drawLinesOnMask = useCallback(
224
+ (_lineGroups: LineGroup[], maskImage?: HTMLImageElement | null) => {
225
+ if (!context?.canvas.width || !context?.canvas.height) {
226
+ throw new Error('canvas has invalid size')
227
+ }
228
+ maskCanvas.width = context?.canvas.width
229
+ maskCanvas.height = context?.canvas.height
230
+ const ctx = maskCanvas.getContext('2d')
231
+ if (!ctx) {
232
+ throw new Error('could not retrieve mask canvas')
233
+ }
234
+
235
+ if (maskImage !== undefined && maskImage !== null) {
236
+ // TODO: check whether draw yellow mask works on backend
237
+ ctx.drawImage(maskImage, 0, 0, imageWidth, imageHeight)
238
+ }
239
+
240
+ _lineGroups.forEach(lineGroup => {
241
+ drawLines(ctx, lineGroup, 'white')
242
+ })
243
+
244
+ if (
245
+ (maskImage === undefined || maskImage === null) &&
246
+ _lineGroups.length === 1 &&
247
+ _lineGroups[0].length === 0 &&
248
+ isPix2Pix
249
+ ) {
250
+ // For InstructPix2Pix without mask
251
+ drawLines(
252
+ ctx,
253
+ [
254
+ {
255
+ size: 9999999999,
256
+ pts: [
257
+ { x: 0, y: 0 },
258
+ { x: imageWidth, y: 0 },
259
+ { x: imageWidth, y: imageHeight },
260
+ { x: 0, y: imageHeight },
261
+ ],
262
+ },
263
+ ],
264
+ 'white'
265
+ )
266
+ }
267
+ },
268
+ [context, maskCanvas, isPix2Pix, imageWidth, imageHeight]
269
+ )
270
+
271
+ const hadDrawSomething = useCallback(() => {
272
+ if (isPix2Pix) {
273
+ return true
274
+ }
275
+ return curLineGroup.length !== 0
276
+ }, [curLineGroup, isPix2Pix])
277
+
278
+ const drawOnCurrentRender = useCallback(
279
+ (lineGroup: LineGroup) => {
280
+ if (renders.length === 0) {
281
+ draw(original, lineGroup)
282
+ } else {
283
+ draw(renders[renders.length - 1], lineGroup)
284
+ }
285
+ },
286
+ [original, renders, draw]
287
+ )
288
+
289
+ const runInpainting = useCallback(
290
+ async (
291
+ useLastLineGroup?: boolean,
292
+ customMask?: File,
293
+ maskImage?: HTMLImageElement | null,
294
+ paintByExampleImage?: File
295
+ ) => {
296
+ // customMask: mask uploaded by user
297
+ // maskImage: mask from interactive segmentation
298
+ if (file === undefined) {
299
+ return
300
+ }
301
+ const useCustomMask = customMask !== undefined && customMask !== null
302
+ const useMaskImage = maskImage !== undefined && maskImage !== null
303
+ // useLastLineGroup 的影响
304
+ // 1. 使用上一次的 mask
305
+ // 2. 结果替换当前 render
306
+ console.log('runInpainting')
307
+ console.log({
308
+ useCustomMask,
309
+ useMaskImage,
310
+ })
311
+
312
+ let maskLineGroup: LineGroup = []
313
+ if (useLastLineGroup === true) {
314
+ if (lastLineGroup.length === 0) {
315
+ return
316
+ }
317
+ maskLineGroup = lastLineGroup
318
+ } else if (!useCustomMask) {
319
+ if (!hadDrawSomething() && !useMaskImage) {
320
+ return
321
+ }
322
+
323
+ setLastLineGroup(curLineGroup)
324
+ maskLineGroup = curLineGroup
325
+ }
326
+
327
+ const newLineGroups = [...lineGroups, maskLineGroup]
328
+
329
+ setCurLineGroup([])
330
+ setIsDraging(false)
331
+ setIsInpainting(true)
332
+ if (settings.graduallyInpainting) {
333
+ drawLinesOnMask([maskLineGroup], maskImage)
334
+ } else {
335
+ drawLinesOnMask(newLineGroups)
336
+ }
337
+
338
+ let targetFile = file
339
+ if (settings.graduallyInpainting === true) {
340
+ if (useLastLineGroup === true) {
341
+ // renders.length == 1 还是用原来的
342
+ if (renders.length > 1) {
343
+ const lastRender = renders[renders.length - 2]
344
+ targetFile = await srcToFile(
345
+ lastRender.currentSrc,
346
+ file.name,
347
+ file.type
348
+ )
349
+ }
350
+ } else if (renders.length > 0) {
351
+ console.info('gradually inpainting on last result')
352
+
353
+ const lastRender = renders[renders.length - 1]
354
+ targetFile = await srcToFile(
355
+ lastRender.currentSrc,
356
+ file.name,
357
+ file.type
358
+ )
359
+ }
360
+ }
361
+
362
+ try {
363
+ const res = await inpaint(
364
+ targetFile,
365
+ settings,
366
+ croperRect,
367
+ promptVal,
368
+ negativePromptVal,
369
+ seedVal,
370
+ useCustomMask ? undefined : maskCanvas.toDataURL(),
371
+ useCustomMask ? customMask : undefined,
372
+ paintByExampleImage
373
+ )
374
+ if (!res) {
375
+ throw new Error('Something went wrong on server side.')
376
+ }
377
+ const { blob, seed } = res
378
+ if (seed) {
379
+ setSeed(parseInt(seed, 10))
380
+ }
381
+ const newRender = new Image()
382
+ await loadImage(newRender, blob)
383
+
384
+ if (useLastLineGroup === true) {
385
+ const prevRenders = renders.slice(0, -1)
386
+ const newRenders = [...prevRenders, newRender]
387
+ setRenders(newRenders)
388
+ } else {
389
+ const newRenders = [...renders, newRender]
390
+ setRenders(newRenders)
391
+ }
392
+
393
+ draw(newRender, [])
394
+ // Only append new LineGroup after inpainting success
395
+ setLineGroups(newLineGroups)
396
+
397
+ // clear redo stack
398
+ resetRedoState()
399
+ } catch (e: any) {
400
+ setToastState({
401
+ open: true,
402
+ desc: e.message ? e.message : e.toString(),
403
+ state: 'error',
404
+ duration: 4000,
405
+ })
406
+ drawOnCurrentRender([])
407
+ }
408
+ setIsInpainting(false)
409
+ setPrevInteractiveSegMask(maskImage)
410
+ setTmpInteractiveSegMask(null)
411
+ setInteractiveSegMask(null)
412
+ },
413
+ [
414
+ lineGroups,
415
+ curLineGroup,
416
+ maskCanvas,
417
+ settings.graduallyInpainting,
418
+ settings,
419
+ croperRect,
420
+ promptVal,
421
+ negativePromptVal,
422
+ drawOnCurrentRender,
423
+ hadDrawSomething,
424
+ drawLinesOnMask,
425
+ seedVal,
426
+ ]
427
+ )
428
+
429
+ useEffect(() => {
430
+ emitter.on(EVENT_PROMPT, () => {
431
+ if (hadDrawSomething() || interactiveSegMask) {
432
+ runInpainting(false, undefined, interactiveSegMask)
433
+ } else if (lastLineGroup.length !== 0) {
434
+ // 使用上一次手绘的 mask 生成
435
+ runInpainting(true, undefined, prevInteractiveSegMask)
436
+ } else if (prevInteractiveSegMask) {
437
+ // 使用上一次 IS 的 mask 生成
438
+ runInpainting(false, undefined, prevInteractiveSegMask)
439
+ } else if (isPix2Pix) {
440
+ runInpainting(false, undefined, null)
441
+ } else {
442
+ setToastState({
443
+ open: true,
444
+ desc: 'Please draw mask on picture',
445
+ state: 'error',
446
+ duration: 1500,
447
+ })
448
+ }
449
+ })
450
+
451
+ return () => {
452
+ emitter.off(EVENT_PROMPT)
453
+ }
454
+ }, [
455
+ hadDrawSomething,
456
+ runInpainting,
457
+ promptVal,
458
+ interactiveSegMask,
459
+ prevInteractiveSegMask,
460
+ ])
461
+
462
+ useEffect(() => {
463
+ emitter.on(EVENT_CUSTOM_MASK, (data: any) => {
464
+ // TODO: not work with paint by example
465
+ runInpainting(false, data.mask)
466
+ })
467
+
468
+ return () => {
469
+ emitter.off(EVENT_CUSTOM_MASK)
470
+ }
471
+ }, [runInpainting])
472
+
473
+ useEffect(() => {
474
+ emitter.on(EVENT_PAINT_BY_EXAMPLE, (data: any) => {
475
+ if (hadDrawSomething() || interactiveSegMask) {
476
+ runInpainting(false, undefined, interactiveSegMask, data.image)
477
+ } else if (lastLineGroup.length !== 0) {
478
+ // 使用上一次手绘的 mask 生成
479
+ runInpainting(true, undefined, prevInteractiveSegMask, data.image)
480
+ } else if (prevInteractiveSegMask) {
481
+ // 使用上一次 IS 的 mask 生成
482
+ runInpainting(false, undefined, prevInteractiveSegMask, data.image)
483
+ } else {
484
+ setToastState({
485
+ open: true,
486
+ desc: 'Please draw mask on picture',
487
+ state: 'error',
488
+ duration: 1500,
489
+ })
490
+ }
491
+ })
492
+
493
+ return () => {
494
+ emitter.off(EVENT_PAINT_BY_EXAMPLE)
495
+ }
496
+ }, [runInpainting])
497
+
498
+ useEffect(() => {
499
+ emitter.on(RERUN_LAST_MASK, () => {
500
+ if (lastLineGroup.length !== 0) {
501
+ // 使用上一次手绘的 mask 生成
502
+ runInpainting(true, undefined, prevInteractiveSegMask)
503
+ } else if (prevInteractiveSegMask) {
504
+ // 使用上一次 IS 的 mask 生成
505
+ runInpainting(false, undefined, prevInteractiveSegMask)
506
+ } else {
507
+ setToastState({
508
+ open: true,
509
+ desc: 'No mask to reuse',
510
+ state: 'error',
511
+ duration: 1500,
512
+ })
513
+ }
514
+ })
515
+ return () => {
516
+ emitter.off(RERUN_LAST_MASK)
517
+ }
518
+ }, [runInpainting])
519
+
520
+ const getCurrentRender = useCallback(async () => {
521
+ let targetFile = file
522
+ if (renders.length > 0) {
523
+ const lastRender = renders[renders.length - 1]
524
+ targetFile = await srcToFile(lastRender.currentSrc, file.name, file.type)
525
+ }
526
+ return targetFile
527
+ }, [file, renders])
528
+
529
+ useEffect(() => {
530
+ emitter.on(PluginName.InteractiveSeg, () => {
531
+ setIsInteractiveSeg(true)
532
+ if (interactiveSegMask !== null) {
533
+ setShowInteractiveSegModal(true)
534
+ }
535
+ })
536
+ return () => {
537
+ emitter.off(PluginName.InteractiveSeg)
538
+ }
539
+ })
540
+
541
+ const runRenderablePlugin = useCallback(
542
+ async (name: string, data?: any) => {
543
+ if (isProcessing) {
544
+ return
545
+ }
546
+ try {
547
+ // TODO 要不要加 undoCurrentLine??
548
+ const start = new Date()
549
+ setIsPluginRunning(true)
550
+ const targetFile = await getCurrentRender()
551
+ const res = await runPlugin(name, targetFile, data?.upscale)
552
+ if (!res) {
553
+ throw new Error('Something went wrong on server side.')
554
+ }
555
+ const { blob } = res
556
+ const newRender = new Image()
557
+ await loadImage(newRender, blob)
558
+ setImageHeight(newRender.height)
559
+ setImageWidth(newRender.width)
560
+ const newRenders = [...renders, newRender]
561
+ setRenders(newRenders)
562
+ const newLineGroups = [...lineGroups, []]
563
+ setLineGroups(newLineGroups)
564
+
565
+ const end = new Date()
566
+ const time = end.getTime() - start.getTime()
567
+
568
+ setToastState({
569
+ open: true,
570
+ desc: `Run ${name} successfully in ${time / 1000}s`,
571
+ state: 'success',
572
+ duration: 3000,
573
+ })
574
+
575
+ const rW = windowSize.width / newRender.width
576
+ const rH = (windowSize.height - TOOLBAR_SIZE) / newRender.height
577
+ let s = 1.0
578
+ if (rW < 1 || rH < 1) {
579
+ s = Math.min(rW, rH)
580
+ }
581
+ setMinScale(s)
582
+ setScale(s)
583
+ viewportRef.current?.centerView(s, 1)
584
+ } catch (e: any) {
585
+ setToastState({
586
+ open: true,
587
+ desc: e.message ? e.message : e.toString(),
588
+ state: 'error',
589
+ duration: 3000,
590
+ })
591
+ } finally {
592
+ setIsPluginRunning(false)
593
+ }
594
+ },
595
+ [
596
+ renders,
597
+ setRenders,
598
+ getCurrentRender,
599
+ setIsPluginRunning,
600
+ isProcessing,
601
+ setImageHeight,
602
+ setImageWidth,
603
+ lineGroups,
604
+ viewportRef,
605
+ windowSize,
606
+ setLineGroups,
607
+ ]
608
+ )
609
+
610
+ useEffect(() => {
611
+ emitter.on(PluginName.RemoveBG, () => {
612
+ runRenderablePlugin(PluginName.RemoveBG)
613
+ })
614
+ return () => {
615
+ emitter.off(PluginName.RemoveBG)
616
+ }
617
+ }, [runRenderablePlugin])
618
+
619
+ useEffect(() => {
620
+ emitter.on(PluginName.GFPGAN, () => {
621
+ runRenderablePlugin(PluginName.GFPGAN)
622
+ })
623
+ return () => {
624
+ emitter.off(PluginName.GFPGAN)
625
+ }
626
+ }, [runRenderablePlugin])
627
+
628
+ useEffect(() => {
629
+ emitter.on(PluginName.RestoreFormer, () => {
630
+ runRenderablePlugin(PluginName.RestoreFormer)
631
+ })
632
+ return () => {
633
+ emitter.off(PluginName.RestoreFormer)
634
+ }
635
+ }, [runRenderablePlugin])
636
+
637
+ useEffect(() => {
638
+ emitter.on(PluginName.RealESRGAN, (data: any) => {
639
+ runRenderablePlugin(PluginName.RealESRGAN, data)
640
+ })
641
+ return () => {
642
+ emitter.off(PluginName.RealESRGAN)
643
+ }
644
+ }, [runRenderablePlugin])
645
+
646
+ const hadRunInpainting = () => {
647
+ return renders.length !== 0
648
+ }
649
+
650
+ const handleMultiStrokeKeyDown = () => {
651
+ if (isInpainting) {
652
+ return
653
+ }
654
+ setIsMultiStrokeKeyPressed(true)
655
+ }
656
+
657
+ const handleMultiStrokeKeyup = () => {
658
+ if (!isMultiStrokeKeyPressed) {
659
+ return
660
+ }
661
+ if (isInpainting) {
662
+ return
663
+ }
664
+
665
+ setIsMultiStrokeKeyPressed(false)
666
+
667
+ if (!runMannually) {
668
+ runInpainting()
669
+ }
670
+ }
671
+
672
+ const predicate = (event: KeyboardEvent) => {
673
+ return event.key === 'Control' || event.key === 'Meta'
674
+ }
675
+
676
+ useKey(predicate, handleMultiStrokeKeyup, { event: 'keyup' }, [
677
+ isInpainting,
678
+ isMultiStrokeKeyPressed,
679
+ hadDrawSomething,
680
+ ])
681
+
682
+ useKey(
683
+ predicate,
684
+ handleMultiStrokeKeyDown,
685
+ {
686
+ event: 'keydown',
687
+ },
688
+ [isInpainting]
689
+ )
690
+
691
+ const getCurrentWidthHeight = useCallback(() => {
692
+ let width = 512
693
+ let height = 512
694
+ if (!isOriginalLoaded) {
695
+ return [width, height]
696
+ }
697
+ if (renders.length === 0) {
698
+ width = original.naturalWidth
699
+ height = original.naturalHeight
700
+ } else if (renders.length !== 0) {
701
+ width = renders[renders.length - 1].width
702
+ height = renders[renders.length - 1].height
703
+ }
704
+
705
+ return [width, height]
706
+ }, [original, isOriginalLoaded, renders])
707
+
708
+ // Draw once the original image is loaded
709
+ useEffect(() => {
710
+ if (!isOriginalLoaded) {
711
+ return
712
+ }
713
+
714
+ const [width, height] = getCurrentWidthHeight()
715
+ setImageWidth(width)
716
+ setImageHeight(height)
717
+
718
+ const rW = windowSize.width / width
719
+ const rH = (windowSize.height - TOOLBAR_SIZE) / height
720
+
721
+ let s = 1.0
722
+ if (rW < 1 || rH < 1) {
723
+ s = Math.min(rW, rH)
724
+ }
725
+ setMinScale(s)
726
+ setScale(s)
727
+
728
+ console.log(
729
+ `[on file load] image size: ${width}x${height}, canvas size: ${context?.canvas.width}x${context?.canvas.height} scale: ${s}, initialCentered: ${initialCentered}`
730
+ )
731
+
732
+ if (context?.canvas) {
733
+ context.canvas.width = width
734
+ context.canvas.height = height
735
+ console.log('[on file load] set canvas size && drawOnCurrentRender')
736
+ drawOnCurrentRender([])
737
+ }
738
+
739
+ if (!initialCentered) {
740
+ // 防止每次擦除以后图片 zoom 还原
741
+ viewportRef.current?.centerView(s, 1)
742
+ console.log('[on file load] centerView')
743
+ setInitialCentered(true)
744
+ }
745
+ }, [
746
+ context?.canvas,
747
+ viewportRef,
748
+ original,
749
+ isOriginalLoaded,
750
+ windowSize,
751
+ initialCentered,
752
+ drawOnCurrentRender,
753
+ getCurrentWidthHeight,
754
+ ])
755
+
756
+ useEffect(() => {
757
+ console.log('[useEffect] centerView')
758
+ // render 改变尺寸以后,undo/redo 重新 center
759
+ viewportRef?.current?.centerView(minScale, 1)
760
+ }, [context?.canvas.height, context?.canvas.width, viewportRef, minScale])
761
+
762
+ // Zoom reset
763
+ const resetZoom = useCallback(() => {
764
+ if (!minScale || !windowSize) {
765
+ return
766
+ }
767
+ const viewport = viewportRef.current
768
+ if (!viewport) {
769
+ return
770
+ }
771
+ const offsetX = (windowSize.width - imageWidth * minScale) / 2
772
+ const offsetY = (windowSize.height - imageHeight * minScale) / 2
773
+ viewport.setTransform(offsetX, offsetY, minScale, 200, 'easeOutQuad')
774
+ viewport.state.scale = minScale
775
+
776
+ setScale(minScale)
777
+ setPanned(false)
778
+ }, [
779
+ viewportRef,
780
+ windowSize,
781
+ imageHeight,
782
+ imageWidth,
783
+ windowSize.height,
784
+ minScale,
785
+ ])
786
+
787
+ const resetRedoState = () => {
788
+ setRedoCurLines([])
789
+ setRedoLineGroups([])
790
+ setRedoRenders([])
791
+ }
792
+
793
+ useEffect(() => {
794
+ window.addEventListener('resize', () => {
795
+ resetZoom()
796
+ })
797
+ return () => {
798
+ window.removeEventListener('resize', () => {
799
+ resetZoom()
800
+ })
801
+ }
802
+ }, [windowSize, resetZoom])
803
+
804
+ useEffect(() => {
805
+ window.addEventListener('blur', () => {
806
+ setIsChangingBrushSizeByMouse(false)
807
+ })
808
+ return () => {
809
+ window.removeEventListener('blur', () => {
810
+ setIsChangingBrushSizeByMouse(false)
811
+ })
812
+ }
813
+ }, [])
814
+
815
+ const onInteractiveCancel = useCallback(() => {
816
+ setIsInteractiveSeg(false)
817
+ setIsInteractiveSegRunning(false)
818
+ setClicks([])
819
+ setTmpInteractiveSegMask(null)
820
+ }, [])
821
+
822
+ const handleEscPressed = () => {
823
+ if (isProcessing) {
824
+ return
825
+ }
826
+
827
+ if (isInteractiveSeg) {
828
+ onInteractiveCancel()
829
+ return
830
+ }
831
+
832
+ if (isDraging || isMultiStrokeKeyPressed) {
833
+ setIsDraging(false)
834
+ setCurLineGroup([])
835
+ drawOnCurrentRender([])
836
+ } else {
837
+ resetZoom()
838
+ }
839
+ }
840
+
841
+ useKey(
842
+ 'Escape',
843
+ handleEscPressed,
844
+ {
845
+ event: 'keydown',
846
+ },
847
+ [
848
+ isDraging,
849
+ isInpainting,
850
+ isMultiStrokeKeyPressed,
851
+ isInteractiveSeg,
852
+ onInteractiveCancel,
853
+ resetZoom,
854
+ drawOnCurrentRender,
855
+ ]
856
+ )
857
+
858
+ const onMouseMove = (ev: SyntheticEvent) => {
859
+ const mouseEvent = ev.nativeEvent as MouseEvent
860
+ setCoords({ x: mouseEvent.pageX, y: mouseEvent.pageY })
861
+ }
862
+
863
+ const onMouseDrag = (ev: SyntheticEvent) => {
864
+ if (isChangingBrushSizeByMouse) {
865
+ const initX = changeBrushSizeByMouseInit.x
866
+ // move right: increase brush size
867
+ const newSize = changeBrushSizeByMouseInit.brushSize + (x - initX)
868
+ if (newSize <= MAX_BRUSH_SIZE && newSize >= MIN_BRUSH_SIZE) {
869
+ setBrushSize(newSize)
870
+ }
871
+ return
872
+ }
873
+ if (isInteractiveSeg) {
874
+ return
875
+ }
876
+ if (isPanning) {
877
+ return
878
+ }
879
+ if (!isDraging) {
880
+ return
881
+ }
882
+ if (curLineGroup.length === 0) {
883
+ return
884
+ }
885
+ const lineGroup = [...curLineGroup]
886
+ lineGroup[lineGroup.length - 1].pts.push(mouseXY(ev))
887
+ setCurLineGroup(lineGroup)
888
+ drawOnCurrentRender(lineGroup)
889
+ }
890
+
891
+ const runInteractiveSeg = async (newClicks: number[][]) => {
892
+ if (!file) {
893
+ return
894
+ }
895
+
896
+ setIsInteractiveSegRunning(true)
897
+ const targetFile = await getCurrentRender()
898
+ const prevMask = null
899
+ // prev_mask seems to be not working better
900
+ // if (tmpInteractiveSegMask !== null) {
901
+ // prevMask = await srcToFile(
902
+ // tmpInteractiveSegMask.currentSrc,
903
+ // 'prev_mask.jpg',
904
+ // 'image/jpeg'
905
+ // )
906
+ // }
907
+
908
+ try {
909
+ const res = await runPlugin(
910
+ PluginName.InteractiveSeg,
911
+ targetFile,
912
+ undefined,
913
+ prevMask,
914
+ newClicks
915
+ )
916
+ if (!res) {
917
+ throw new Error('Something went wrong on server side.')
918
+ }
919
+ const { blob } = res
920
+ const img = new Image()
921
+ img.onload = () => {
922
+ setTmpInteractiveSegMask(img)
923
+ }
924
+ img.src = blob
925
+ } catch (e: any) {
926
+ setToastState({
927
+ open: true,
928
+ desc: e.message ? e.message : e.toString(),
929
+ state: 'error',
930
+ duration: 4000,
931
+ })
932
+ }
933
+ setIsInteractiveSegRunning(false)
934
+ }
935
+
936
+ const onPointerUp = (ev: SyntheticEvent) => {
937
+ if (isMidClick(ev)) {
938
+ setIsPanning(false)
939
+ }
940
+ if (isInteractiveSeg) {
941
+ return
942
+ }
943
+
944
+ if (isPanning) {
945
+ return
946
+ }
947
+ if (!original.src) {
948
+ return
949
+ }
950
+ const canvas = context?.canvas
951
+ if (!canvas) {
952
+ return
953
+ }
954
+ if (isInpainting) {
955
+ return
956
+ }
957
+ if (!isDraging) {
958
+ return
959
+ }
960
+
961
+ if (isMultiStrokeKeyPressed) {
962
+ setIsDraging(false)
963
+ return
964
+ }
965
+
966
+ if (runMannually) {
967
+ setIsDraging(false)
968
+ } else {
969
+ runInpainting()
970
+ }
971
+ }
972
+
973
+ const isOutsideCroper = (clickPnt: { x: number; y: number }) => {
974
+ if (clickPnt.x < croperRect.x) {
975
+ return true
976
+ }
977
+ if (clickPnt.y < croperRect.y) {
978
+ return true
979
+ }
980
+ if (clickPnt.x > croperRect.x + croperRect.width) {
981
+ return true
982
+ }
983
+ if (clickPnt.y > croperRect.y + croperRect.height) {
984
+ return true
985
+ }
986
+ return false
987
+ }
988
+
989
+ const onCanvasMouseUp = (ev: SyntheticEvent) => {
990
+ if (isInteractiveSeg) {
991
+ const xy = mouseXY(ev)
992
+ const newClicks: number[][] = [...clicks]
993
+ if (isRightClick(ev)) {
994
+ newClicks.push([xy.x, xy.y, 0, newClicks.length])
995
+ } else {
996
+ newClicks.push([xy.x, xy.y, 1, newClicks.length])
997
+ }
998
+ runInteractiveSeg(newClicks)
999
+ setClicks(newClicks)
1000
+ }
1001
+ }
1002
+
1003
+ const onMouseDown = (ev: SyntheticEvent) => {
1004
+ if (isProcessing) {
1005
+ return
1006
+ }
1007
+ if (isInteractiveSeg) {
1008
+ return
1009
+ }
1010
+ if (isChangingBrushSizeByMouse) {
1011
+ return
1012
+ }
1013
+ if (isPanning) {
1014
+ return
1015
+ }
1016
+ if (!original.src) {
1017
+ return
1018
+ }
1019
+ const canvas = context?.canvas
1020
+ if (!canvas) {
1021
+ return
1022
+ }
1023
+
1024
+ if (isRightClick(ev)) {
1025
+ return
1026
+ }
1027
+
1028
+ if (isMidClick(ev)) {
1029
+ setIsPanning(true)
1030
+ return
1031
+ }
1032
+
1033
+ if (
1034
+ isDiffusionModels &&
1035
+ settings.showCroper &&
1036
+ isOutsideCroper(mouseXY(ev))
1037
+ ) {
1038
+ return
1039
+ }
1040
+
1041
+ setIsDraging(true)
1042
+
1043
+ let lineGroup: LineGroup = []
1044
+ if (isMultiStrokeKeyPressed || runMannually) {
1045
+ lineGroup = [...curLineGroup]
1046
+ }
1047
+ lineGroup.push({ size: brushSize, pts: [mouseXY(ev)] })
1048
+ setCurLineGroup(lineGroup)
1049
+ drawOnCurrentRender(lineGroup)
1050
+ }
1051
+
1052
+ const undoStroke = useCallback(() => {
1053
+ if (curLineGroup.length === 0) {
1054
+ return
1055
+ }
1056
+ setLastLineGroup([])
1057
+
1058
+ const lastLine = curLineGroup.pop()!
1059
+ const newRedoCurLines = [...redoCurLines, lastLine]
1060
+ setRedoCurLines(newRedoCurLines)
1061
+
1062
+ const newLineGroup = [...curLineGroup]
1063
+ setCurLineGroup(newLineGroup)
1064
+ drawOnCurrentRender(newLineGroup)
1065
+ }, [curLineGroup, redoCurLines, drawOnCurrentRender])
1066
+
1067
+ const undoRender = useCallback(() => {
1068
+ if (!renders.length) {
1069
+ return
1070
+ }
1071
+
1072
+ // save line Group
1073
+ const latestLineGroup = lineGroups.pop()!
1074
+ setRedoLineGroups([...redoLineGroups, latestLineGroup])
1075
+ // If render is undo, clear strokes
1076
+ setRedoCurLines([])
1077
+
1078
+ setLineGroups([...lineGroups])
1079
+ setCurLineGroup([])
1080
+ setIsDraging(false)
1081
+
1082
+ // save render
1083
+ const lastRender = renders.pop()!
1084
+ setRedoRenders([...redoRenders, lastRender])
1085
+
1086
+ const newRenders = [...renders]
1087
+ setRenders(newRenders)
1088
+ // if (newRenders.length === 0) {
1089
+ // draw(original, [])
1090
+ // } else {
1091
+ // draw(newRenders[newRenders.length - 1], [])
1092
+ // }
1093
+ }, [
1094
+ draw,
1095
+ renders,
1096
+ redoRenders,
1097
+ redoLineGroups,
1098
+ lineGroups,
1099
+ original,
1100
+ context,
1101
+ ])
1102
+
1103
+ const undo = () => {
1104
+ if (runMannually && curLineGroup.length !== 0) {
1105
+ undoStroke()
1106
+ } else {
1107
+ undoRender()
1108
+ }
1109
+ }
1110
+
1111
+ // Handle Cmd+Z
1112
+ const undoPredicate = (event: KeyboardEvent) => {
1113
+ // TODO: fix prompt input ctrl+z
1114
+ const isCmdZ =
1115
+ (event.metaKey || event.ctrlKey) && !event.shiftKey && event.key === 'z'
1116
+ // Handle tab switch
1117
+ if (event.key === 'Tab') {
1118
+ event.preventDefault()
1119
+ }
1120
+ if (isCmdZ) {
1121
+ event.preventDefault()
1122
+ return true
1123
+ }
1124
+ return false
1125
+ }
1126
+
1127
+ useKey(undoPredicate, undo, undefined, [
1128
+ undoStroke,
1129
+ undoRender,
1130
+ runMannually,
1131
+ curLineGroup,
1132
+ context?.canvas,
1133
+ renders,
1134
+ ])
1135
+
1136
+ const disableUndo = () => {
1137
+ if (isProcessing) {
1138
+ return true
1139
+ }
1140
+ if (renders.length > 0) {
1141
+ return false
1142
+ }
1143
+
1144
+ if (runMannually) {
1145
+ if (curLineGroup.length === 0) {
1146
+ return true
1147
+ }
1148
+ } else if (renders.length === 0) {
1149
+ return true
1150
+ }
1151
+
1152
+ return false
1153
+ }
1154
+
1155
+ const redoStroke = useCallback(() => {
1156
+ if (redoCurLines.length === 0) {
1157
+ return
1158
+ }
1159
+ const line = redoCurLines.pop()!
1160
+ setRedoCurLines([...redoCurLines])
1161
+
1162
+ const newLineGroup = [...curLineGroup, line]
1163
+ setCurLineGroup(newLineGroup)
1164
+ drawOnCurrentRender(newLineGroup)
1165
+ }, [curLineGroup, redoCurLines, drawOnCurrentRender])
1166
+
1167
+ const redoRender = useCallback(() => {
1168
+ if (redoRenders.length === 0) {
1169
+ return
1170
+ }
1171
+ const lineGroup = redoLineGroups.pop()!
1172
+ setRedoLineGroups([...redoLineGroups])
1173
+
1174
+ setLineGroups([...lineGroups, lineGroup])
1175
+ setCurLineGroup([])
1176
+ setIsDraging(false)
1177
+
1178
+ const render = redoRenders.pop()!
1179
+ const newRenders = [...renders, render]
1180
+ setRenders(newRenders)
1181
+ // draw(newRenders[newRenders.length - 1], [])
1182
+ }, [draw, renders, redoRenders, redoLineGroups, lineGroups, original])
1183
+
1184
+ const redo = () => {
1185
+ if (runMannually && redoCurLines.length !== 0) {
1186
+ redoStroke()
1187
+ } else {
1188
+ redoRender()
1189
+ }
1190
+ }
1191
+
1192
+ // Handle Cmd+shift+Z
1193
+ const redoPredicate = (event: KeyboardEvent) => {
1194
+ const isCmdZ =
1195
+ (event.metaKey || event.ctrlKey) &&
1196
+ event.shiftKey &&
1197
+ event.key.toLowerCase() === 'z'
1198
+ // Handle tab switch
1199
+ if (event.key === 'Tab') {
1200
+ event.preventDefault()
1201
+ }
1202
+ if (isCmdZ) {
1203
+ event.preventDefault()
1204
+ return true
1205
+ }
1206
+ return false
1207
+ }
1208
+
1209
+ useKey(redoPredicate, redo, undefined, [
1210
+ redoStroke,
1211
+ redoRender,
1212
+ runMannually,
1213
+ redoCurLines,
1214
+ ])
1215
+
1216
+ const disableRedo = () => {
1217
+ if (isProcessing) {
1218
+ return true
1219
+ }
1220
+ if (redoRenders.length > 0) {
1221
+ return false
1222
+ }
1223
+
1224
+ if (runMannually) {
1225
+ if (redoCurLines.length === 0) {
1226
+ return true
1227
+ }
1228
+ } else if (redoRenders.length === 0) {
1229
+ return true
1230
+ }
1231
+
1232
+ return false
1233
+ }
1234
+
1235
+ useKeyPressEvent(
1236
+ 'Tab',
1237
+ ev => {
1238
+ ev?.preventDefault()
1239
+ ev?.stopPropagation()
1240
+ if (hadRunInpainting()) {
1241
+ setShowOriginal(() => {
1242
+ window.setTimeout(() => {
1243
+ setSliderPos(100)
1244
+ }, 10)
1245
+ return true
1246
+ })
1247
+ }
1248
+ },
1249
+ ev => {
1250
+ ev?.preventDefault()
1251
+ ev?.stopPropagation()
1252
+ if (hadRunInpainting()) {
1253
+ setSliderPos(0)
1254
+ window.setTimeout(() => {
1255
+ setShowOriginal(false)
1256
+ }, 350)
1257
+ }
1258
+ }
1259
+ )
1260
+
1261
+ function download() {
1262
+ if (file === undefined) {
1263
+ return
1264
+ }
1265
+ if ((enableFileManager || isEnableAutoSaving) && renders.length > 0) {
1266
+ try {
1267
+ downloadToOutput(renders[renders.length - 1], file.name, file.type)
1268
+ setToastState({
1269
+ open: true,
1270
+ desc: `Save image success`,
1271
+ state: 'success',
1272
+ duration: 2000,
1273
+ })
1274
+ } catch (e: any) {
1275
+ setToastState({
1276
+ open: true,
1277
+ desc: e.message ? e.message : e.toString(),
1278
+ state: 'error',
1279
+ duration: 2000,
1280
+ })
1281
+ }
1282
+ return
1283
+ }
1284
+
1285
+ // TODO: download to output directory
1286
+ const name = file.name.replace(/(\.[\w\d_-]+)$/i, '_cleanup$1')
1287
+ const curRender = renders[renders.length - 1]
1288
+ downloadImage(curRender.currentSrc, name)
1289
+ if (settings.downloadMask) {
1290
+ let maskFileName = file.name.replace(/(\.[\w\d_-]+)$/i, '_mask$1')
1291
+ maskFileName = maskFileName.replace(/\.[^/.]+$/, '.jpg')
1292
+
1293
+ drawLinesOnMask(lineGroups)
1294
+ // Create a link
1295
+ const aDownloadLink = document.createElement('a')
1296
+ // Add the name of the file to the link
1297
+ aDownloadLink.download = maskFileName
1298
+ // Attach the data to the link
1299
+ aDownloadLink.href = maskCanvas.toDataURL('image/jpeg')
1300
+ // Get the code to click the download link
1301
+ aDownloadLink.click()
1302
+ }
1303
+ }
1304
+
1305
+ const toggleShowBrush = (newState: boolean) => {
1306
+ if (newState !== showBrush && !isPanning) {
1307
+ setShowBrush(newState)
1308
+ }
1309
+ }
1310
+
1311
+ const getCursor = useCallback(() => {
1312
+ if (isPanning) {
1313
+ return 'grab'
1314
+ }
1315
+ if (showBrush) {
1316
+ return 'none'
1317
+ }
1318
+ return undefined
1319
+ }, [showBrush, isPanning])
1320
+
1321
+ // Standard Hotkeys for Brush Size
1322
+ useHotKey('[', () => {
1323
+ setBrushSize((currentBrushSize: number) => {
1324
+ if (currentBrushSize > 10) {
1325
+ return currentBrushSize - 10
1326
+ }
1327
+ if (currentBrushSize <= 10 && currentBrushSize > 0) {
1328
+ return currentBrushSize - 5
1329
+ }
1330
+ return currentBrushSize
1331
+ })
1332
+ })
1333
+
1334
+ useHotKey(']', () => {
1335
+ setBrushSize((currentBrushSize: number) => {
1336
+ return currentBrushSize + 10
1337
+ })
1338
+ })
1339
+
1340
+ // Manual Inpainting Hotkey
1341
+ useHotKey(
1342
+ 'shift+r',
1343
+ () => {
1344
+ if (runMannually && hadDrawSomething()) {
1345
+ runInpainting()
1346
+ }
1347
+ },
1348
+ {},
1349
+ [runMannually, runInpainting, hadDrawSomething]
1350
+ )
1351
+
1352
+ useHotKey(
1353
+ 'ctrl+c, cmd+c',
1354
+ async () => {
1355
+ const hasPermission = await askWritePermission()
1356
+ if (hasPermission && renders.length > 0) {
1357
+ if (context?.canvas) {
1358
+ await copyCanvasImage(context?.canvas)
1359
+ setToastState({
1360
+ open: true,
1361
+ desc: 'Copy inpainting result to clipboard',
1362
+ state: 'success',
1363
+ duration: 3000,
1364
+ })
1365
+ }
1366
+ }
1367
+ },
1368
+ {},
1369
+ [renders, context]
1370
+ )
1371
+
1372
+ // Toggle clean/zoom tool on spacebar.
1373
+ useKeyPressEvent(
1374
+ ' ',
1375
+ ev => {
1376
+ if (!app.disableShortCuts) {
1377
+ ev?.preventDefault()
1378
+ ev?.stopPropagation()
1379
+ setShowBrush(false)
1380
+ setIsPanning(true)
1381
+ }
1382
+ },
1383
+ ev => {
1384
+ if (!app.disableShortCuts) {
1385
+ ev?.preventDefault()
1386
+ ev?.stopPropagation()
1387
+ setShowBrush(true)
1388
+ setIsPanning(false)
1389
+ }
1390
+ }
1391
+ )
1392
+
1393
+ useKeyPressEvent(
1394
+ 'Alt',
1395
+ ev => {
1396
+ ev?.preventDefault()
1397
+ ev?.stopPropagation()
1398
+ setIsChangingBrushSizeByMouse(true)
1399
+ setChangeBrushSizeByMouseInit({ x, y, brushSize })
1400
+ },
1401
+ ev => {
1402
+ ev?.preventDefault()
1403
+ ev?.stopPropagation()
1404
+ setIsChangingBrushSizeByMouse(false)
1405
+ }
1406
+ )
1407
+
1408
+ const getCurScale = (): number => {
1409
+ let s = minScale
1410
+ if (viewportRef.current?.state.scale !== undefined) {
1411
+ s = viewportRef.current?.state.scale
1412
+ }
1413
+ return s!
1414
+ }
1415
+
1416
+ const getBrushStyle = (_x: number, _y: number) => {
1417
+ const curScale = getCurScale()
1418
+ return {
1419
+ width: `${brushSize * curScale}px`,
1420
+ height: `${brushSize * curScale}px`,
1421
+ left: `${_x}px`,
1422
+ top: `${_y}px`,
1423
+ transform: 'translate(-50%, -50%)',
1424
+ }
1425
+ }
1426
+
1427
+ const handleSliderChange = (value: number) => {
1428
+ setBrushSize(value)
1429
+
1430
+ if (!showRefBrush) {
1431
+ setShowRefBrush(true)
1432
+ window.setTimeout(() => {
1433
+ setShowRefBrush(false)
1434
+ }, 10000)
1435
+ }
1436
+ }
1437
+
1438
+ const renderFileSelect = () => {
1439
+ return (
1440
+ <div className="landing-file-selector">
1441
+ <FileSelect
1442
+ onSelection={async f => {
1443
+ setFile(f)
1444
+ }}
1445
+ />
1446
+ </div>
1447
+ )
1448
+ }
1449
+
1450
+ const renderInteractiveSegCursor = () => {
1451
+ return (
1452
+ <div
1453
+ className="interactive-seg-cursor"
1454
+ style={{
1455
+ left: `${x}px`,
1456
+ top: `${y}px`,
1457
+ // transform: 'translate(-50%, -50%)',
1458
+ }}
1459
+ >
1460
+ <CursorArrowRaysIcon />
1461
+ </div>
1462
+ )
1463
+ }
1464
+
1465
+ const renderCanvas = () => {
1466
+ return (
1467
+ <TransformWrapper
1468
+ ref={r => {
1469
+ if (r) {
1470
+ viewportRef.current = r
1471
+ }
1472
+ }}
1473
+ panning={{ disabled: !isPanning, velocityDisabled: true }}
1474
+ wheel={{ step: 0.05 }}
1475
+ centerZoomedOut
1476
+ alignmentAnimation={{ disabled: true }}
1477
+ // centerOnInit
1478
+ limitToBounds={false}
1479
+ doubleClick={{ disabled: true }}
1480
+ initialScale={minScale}
1481
+ minScale={minScale * 0.6}
1482
+ onPanning={ref => {
1483
+ if (!panned) {
1484
+ setPanned(true)
1485
+ }
1486
+ }}
1487
+ onZoom={ref => {
1488
+ setScale(ref.state.scale)
1489
+ }}
1490
+ >
1491
+ <TransformComponent
1492
+ contentClass={isProcessing ? 'editor-canvas-loading' : ''}
1493
+ contentStyle={{
1494
+ visibility: initialCentered ? 'visible' : 'hidden',
1495
+ }}
1496
+ >
1497
+ <div className="editor-canvas-container">
1498
+ <canvas
1499
+ className="editor-canvas"
1500
+ style={{
1501
+ cursor: getCursor(),
1502
+ clipPath: `inset(0 ${sliderPos}% 0 0)`,
1503
+ transition: 'clip-path 300ms cubic-bezier(0.4, 0, 0.2, 1)',
1504
+ }}
1505
+ onContextMenu={e => {
1506
+ e.preventDefault()
1507
+ }}
1508
+ onMouseOver={() => {
1509
+ toggleShowBrush(true)
1510
+ setShowRefBrush(false)
1511
+ }}
1512
+ onFocus={() => toggleShowBrush(true)}
1513
+ onMouseLeave={() => toggleShowBrush(false)}
1514
+ onMouseDown={onMouseDown}
1515
+ onMouseUp={onCanvasMouseUp}
1516
+ onMouseMove={onMouseDrag}
1517
+ ref={r => {
1518
+ if (r && !context) {
1519
+ const ctx = r.getContext('2d')
1520
+ if (ctx) {
1521
+ setContext(ctx)
1522
+ }
1523
+ }
1524
+ }}
1525
+ />
1526
+ <div
1527
+ className="original-image-container"
1528
+ style={{
1529
+ width: `${imageWidth}px`,
1530
+ height: `${imageHeight}px`,
1531
+ }}
1532
+ >
1533
+ {showOriginal && (
1534
+ <>
1535
+ <div
1536
+ className="editor-slider"
1537
+ style={{
1538
+ marginRight: `${sliderPos}%`,
1539
+ }}
1540
+ />
1541
+ <img
1542
+ className="original-image"
1543
+ src={original.src}
1544
+ alt="original"
1545
+ style={{
1546
+ width: `${imageWidth}px`,
1547
+ height: `${imageHeight}px`,
1548
+ }}
1549
+ />
1550
+ </>
1551
+ )}
1552
+ </div>
1553
+ </div>
1554
+
1555
+ <Croper
1556
+ maxHeight={imageHeight}
1557
+ maxWidth={imageWidth}
1558
+ minHeight={Math.min(256, imageHeight)}
1559
+ minWidth={Math.min(256, imageWidth)}
1560
+ scale={scale}
1561
+ show={isDiffusionModels && settings.showCroper}
1562
+ />
1563
+
1564
+ {isInteractiveSeg ? <InteractiveSeg /> : <></>}
1565
+ </TransformComponent>
1566
+ </TransformWrapper>
1567
+ )
1568
+ }
1569
+
1570
+ const onInteractiveAccept = () => {
1571
+ setInteractiveSegMask(tmpInteractiveSegMask)
1572
+ setTmpInteractiveSegMask(null)
1573
+
1574
+ if (!runMannually && tmpInteractiveSegMask) {
1575
+ runInpainting(false, undefined, tmpInteractiveSegMask)
1576
+ }
1577
+ }
1578
+
1579
+ return (
1580
+ <div
1581
+ className="editor-container"
1582
+ aria-hidden="true"
1583
+ onMouseMove={onMouseMove}
1584
+ onMouseUp={onPointerUp}
1585
+ >
1586
+ <MakeGIF renders={renders} />
1587
+ <InteractiveSegConfirmActions
1588
+ onAcceptClick={onInteractiveAccept}
1589
+ onCancelClick={onInteractiveCancel}
1590
+ />
1591
+ {file === undefined ? renderFileSelect() : renderCanvas()}
1592
+
1593
+ {showBrush &&
1594
+ !isInpainting &&
1595
+ !isPanning &&
1596
+ (isInteractiveSeg ? (
1597
+ renderInteractiveSegCursor()
1598
+ ) : (
1599
+ <div
1600
+ className="brush-shape"
1601
+ style={getBrushStyle(
1602
+ isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.x : x,
1603
+ isChangingBrushSizeByMouse ? changeBrushSizeByMouseInit.y : y
1604
+ )}
1605
+ />
1606
+ ))}
1607
+
1608
+ {showRefBrush && (
1609
+ <div
1610
+ className="brush-shape"
1611
+ style={getBrushStyle(windowCenterX, windowCenterY)}
1612
+ />
1613
+ )}
1614
+
1615
+ <div className="editor-toolkit-panel">
1616
+ <Slider
1617
+ label="Brush"
1618
+ min={MIN_BRUSH_SIZE}
1619
+ max={MAX_BRUSH_SIZE}
1620
+ value={brushSize}
1621
+ onChange={handleSliderChange}
1622
+ onClick={() => setShowRefBrush(false)}
1623
+ />
1624
+ <div className="editor-toolkit-btns">
1625
+ <Button
1626
+ toolTip="Reset Zoom & Pan"
1627
+ icon={<ArrowsPointingOutIcon />}
1628
+ disabled={scale === minScale && panned === false}
1629
+ onClick={resetZoom}
1630
+ />
1631
+ <Button
1632
+ toolTip="Undo"
1633
+ icon={
1634
+ <svg
1635
+ width="19"
1636
+ height="9"
1637
+ viewBox="0 0 19 9"
1638
+ fill="none"
1639
+ xmlns="http://www.w3.org/2000/svg"
1640
+ >
1641
+ <path
1642
+ d="M2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1H2ZM1 8H0V9H1V8ZM8 9C8.55228 9 9 8.55229 9 8C9 7.44771 8.55228 7 8 7V9ZM16.5963 7.42809C16.8327 7.92721 17.429 8.14016 17.9281 7.90374C18.4272 7.66731 18.6402 7.07103 18.4037 6.57191L16.5963 7.42809ZM16.9468 5.83205L17.8505 5.40396L16.9468 5.83205ZM0 1V8H2V1H0ZM1 9H8V7H1V9ZM1.66896 8.74329L6.66896 4.24329L5.33104 2.75671L0.331035 7.25671L1.66896 8.74329ZM16.043 6.26014L16.5963 7.42809L18.4037 6.57191L17.8505 5.40396L16.043 6.26014ZM6.65079 4.25926C9.67554 1.66661 14.3376 2.65979 16.043 6.26014L17.8505 5.40396C15.5805 0.61182 9.37523 -0.710131 5.34921 2.74074L6.65079 4.25926Z"
1643
+ fill="currentColor"
1644
+ />
1645
+ </svg>
1646
+ }
1647
+ onClick={undo}
1648
+ disabled={disableUndo()}
1649
+ />
1650
+ <Button
1651
+ toolTip="Redo"
1652
+ icon={
1653
+ <svg
1654
+ width="19"
1655
+ height="9"
1656
+ viewBox="0 0 19 9"
1657
+ fill="none"
1658
+ xmlns="http://www.w3.org/2000/svg"
1659
+ style={{ transform: 'scale(-1,1)' }}
1660
+ >
1661
+ <path
1662
+ d="M2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1H2ZM1 8H0V9H1V8ZM8 9C8.55228 9 9 8.55229 9 8C9 7.44771 8.55228 7 8 7V9ZM16.5963 7.42809C16.8327 7.92721 17.429 8.14016 17.9281 7.90374C18.4272 7.66731 18.6402 7.07103 18.4037 6.57191L16.5963 7.42809ZM16.9468 5.83205L17.8505 5.40396L16.9468 5.83205ZM0 1V8H2V1H0ZM1 9H8V7H1V9ZM1.66896 8.74329L6.66896 4.24329L5.33104 2.75671L0.331035 7.25671L1.66896 8.74329ZM16.043 6.26014L16.5963 7.42809L18.4037 6.57191L17.8505 5.40396L16.043 6.26014ZM6.65079 4.25926C9.67554 1.66661 14.3376 2.65979 16.043 6.26014L17.8505 5.40396C15.5805 0.61182 9.37523 -0.710131 5.34921 2.74074L6.65079 4.25926Z"
1663
+ fill="currentColor"
1664
+ />
1665
+ </svg>
1666
+ }
1667
+ onClick={redo}
1668
+ disabled={disableRedo()}
1669
+ />
1670
+ <Button
1671
+ toolTip="Show Original"
1672
+ icon={<EyeIcon />}
1673
+ className={showOriginal ? 'eyeicon-active' : ''}
1674
+ onDown={ev => {
1675
+ ev.preventDefault()
1676
+ setShowOriginal(() => {
1677
+ window.setTimeout(() => {
1678
+ setSliderPos(100)
1679
+ }, 10)
1680
+ return true
1681
+ })
1682
+ }}
1683
+ onUp={() => {
1684
+ setSliderPos(0)
1685
+ window.setTimeout(() => {
1686
+ setShowOriginal(false)
1687
+ }, 300)
1688
+ }}
1689
+ disabled={renders.length === 0}
1690
+ />
1691
+ <Button
1692
+ toolTip="Save Image"
1693
+ icon={<ArrowDownTrayIcon />}
1694
+ disabled={!renders.length}
1695
+ onClick={download}
1696
+ />
1697
+
1698
+ {settings.runInpaintingManually && !isDiffusionModels && (
1699
+ <Button
1700
+ toolTip="Run Inpainting"
1701
+ icon={
1702
+ <svg
1703
+ width="24"
1704
+ height="24"
1705
+ viewBox="0 0 24 24"
1706
+ fill="none"
1707
+ xmlns="http://www.w3.org/2000/svg"
1708
+ >
1709
+ <path
1710
+ d="M2 13L1.34921 12.2407C1.16773 12.3963 1.04797 12.6117 1.01163 12.8479L2 13ZM22.5 4L23.49 4.14142C23.5309 3.85444 23.4454 3.5638 23.2555 3.3448C23.0655 3.1258 22.7899 3 22.5 3V4ZM12.5 4V3C12.2613 3 12.0305 3.08539 11.8492 3.24074L12.5 4ZM1 19.5L0.0116283 19.3479C-0.0327373 19.6363 0.051055 19.9297 0.241035 20.1511C0.431014 20.3726 0.708231 20.5 1 20.5V19.5ZM11.5 19.5V20.5C11.7373 20.5 11.9668 20.4156 12.1476 20.2619L11.5 19.5ZM21.5 11L22.1476 11.7619C22.3337 11.6038 22.4554 11.3831 22.49 11.1414L21.5 11ZM2 14H12.5V12H2V14ZM13.169 13.7433L23.169 4.74329L21.831 3.25671L11.831 12.2567L13.169 13.7433ZM22.5 3H12.5V5H22.5V3ZM11.8492 3.24074L1.34921 12.2407L2.65079 13.7593L13.1508 4.75926L11.8492 3.24074ZM1.01163 12.8479L0.0116283 19.3479L1.98837 19.6521L2.98837 13.1521L1.01163 12.8479ZM1 20.5H11.5V18.5H1V20.5ZM12.4884 19.6521L13.4884 13.1521L11.5116 12.8479L10.5116 19.3479L12.4884 19.6521ZM21.51 3.85858L20.51 10.8586L22.49 11.1414L23.49 4.14142L21.51 3.85858ZM20.8524 10.2381L10.8524 18.7381L12.1476 20.2619L22.1476 11.7619L20.8524 10.2381Z"
1711
+ fill="currentColor"
1712
+ />
1713
+ </svg>
1714
+ }
1715
+ disabled={
1716
+ isProcessing ||
1717
+ (!hadDrawSomething() && interactiveSegMask === null)
1718
+ }
1719
+ onClick={() => {
1720
+ // ensured by disabled
1721
+ runInpainting(false, undefined, interactiveSegMask)
1722
+ }}
1723
+ />
1724
+ )}
1725
+ </div>
1726
+ </div>
1727
+ <InteractiveSegReplaceModal
1728
+ show={showInteractiveSegModal}
1729
+ onClose={() => {
1730
+ onInteractiveCancel()
1731
+ setShowInteractiveSegModal(false)
1732
+ }}
1733
+ onCleanClick={() => {
1734
+ onInteractiveCancel()
1735
+ setInteractiveSegMask(null)
1736
+ }}
1737
+ onReplaceClick={() => {
1738
+ setShowInteractiveSegModal(false)
1739
+ setIsInteractiveSeg(true)
1740
+ }}
1741
+ />
1742
+ </div>
1743
+ )
1744
+ }
lama_cleaner/app/src/components/Editor/MakeGIF.tsx ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useState } from 'react'
2
+ import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
3
+ import Button from '../shared/Button'
4
+ import { fileState, gifImageState, toastState } from '../../store/Atoms'
5
+ import { makeGif } from '../../adapters/inpainting'
6
+ import Modal from '../shared/Modal'
7
+ import { LoadingIcon } from '../shared/Toast'
8
+ import { downloadImage } from '../../utils'
9
+ import emitter from '../../event'
10
+ import { PluginName } from '../Plugins/Plugins'
11
+
12
+ interface Props {
13
+ renders: HTMLImageElement[]
14
+ }
15
+
16
+ const MakeGIF = (props: Props) => {
17
+ const { renders } = props
18
+ const [gifImg, setGifImg] = useRecoilState(gifImageState)
19
+ const file = useRecoilValue(fileState)
20
+ const setToastState = useSetRecoilState(toastState)
21
+ const [show, setShow] = useState(false)
22
+
23
+ const handleOnClose = () => {
24
+ setShow(false)
25
+ }
26
+
27
+ const handleDownload = () => {
28
+ if (gifImg) {
29
+ const name = file.name.replace(/\.[^/.]+$/, '.gif')
30
+ downloadImage(gifImg.src, name)
31
+ }
32
+ }
33
+
34
+ useEffect(() => {
35
+ emitter.on(PluginName.MakeGIF, async () => {
36
+ if (renders.length === 0) {
37
+ setToastState({
38
+ open: true,
39
+ desc: 'No render found',
40
+ state: 'error',
41
+ duration: 2000,
42
+ })
43
+ return
44
+ }
45
+
46
+ setShow(true)
47
+ setGifImg(null)
48
+ try {
49
+ const gif = await makeGif(
50
+ file,
51
+ renders[renders.length - 1],
52
+ file.name,
53
+ file.type
54
+ )
55
+ if (gif) {
56
+ setGifImg(gif)
57
+ }
58
+ } catch (e: any) {
59
+ setToastState({
60
+ open: true,
61
+ desc: e.message ? e.message : e.toString(),
62
+ state: 'error',
63
+ duration: 2000,
64
+ })
65
+ setShow(false)
66
+ }
67
+ })
68
+ return () => {
69
+ emitter.off(PluginName.MakeGIF)
70
+ }
71
+ }, [setGifImg, renders, file, setShow])
72
+
73
+ return (
74
+ <Modal
75
+ onClose={handleOnClose}
76
+ title="GIF"
77
+ className="modal-setting"
78
+ show={show}
79
+ >
80
+ <div
81
+ style={{
82
+ display: 'flex',
83
+ alignItems: 'center',
84
+ justifyContent: 'center',
85
+ flexDirection: 'column',
86
+ gap: 16,
87
+ }}
88
+ >
89
+ {gifImg ? (
90
+ <img src={gifImg.src} style={{ borderRadius: 8 }} alt="gif" />
91
+ ) : (
92
+ <div
93
+ style={{
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ gap: 8,
98
+ }}
99
+ >
100
+ <LoadingIcon />
101
+ Generating GIF...
102
+ </div>
103
+ )}
104
+
105
+ {gifImg && (
106
+ <div
107
+ style={{
108
+ display: 'flex',
109
+ width: '100%',
110
+ justifyContent: 'flex-end',
111
+ alignItems: 'center',
112
+ gap: '12px',
113
+ }}
114
+ >
115
+ <Button onClick={handleDownload} border>
116
+ Download
117
+ </Button>
118
+ </div>
119
+ )}
120
+ </div>
121
+ </Modal>
122
+ )
123
+ }
124
+
125
+ export default MakeGIF
lama_cleaner/app/src/components/Editor/Slider.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+
3
+ type SliderProps = {
4
+ label?: any
5
+ value?: number
6
+ min?: number
7
+ max?: number
8
+ onChange: (value: number) => void
9
+ onClick?: () => void
10
+ width?: number
11
+ }
12
+
13
+ export default function Slider(props: SliderProps) {
14
+ const { value, onChange, onClick, label, min, max, width } = props
15
+ const styles: any = {}
16
+ if (width !== undefined) {
17
+ styles.width = width
18
+ }
19
+
20
+ const step = ((max || 100) - (min || 0)) / 100
21
+
22
+ const onMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
23
+ e.currentTarget?.blur()
24
+ }
25
+
26
+ return (
27
+ <div className="editor-brush-slider">
28
+ <span>{label}</span>
29
+ <input
30
+ type="range"
31
+ step={step}
32
+ min={min}
33
+ max={max}
34
+ value={value}
35
+ onChange={ev => {
36
+ ev.preventDefault()
37
+ ev.stopPropagation()
38
+ onChange(parseInt(ev.currentTarget.value, 10))
39
+ }}
40
+ onClick={onClick}
41
+ style={styles}
42
+ onMouseUp={onMouseUp}
43
+ />
44
+ </div>
45
+ )
46
+ }
lama_cleaner/app/src/components/FileManager/FileManager.scss ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import '@radix-ui/colors/blackA.css';
2
+ @import '@radix-ui/colors/mauve.css';
3
+ @import '@radix-ui/colors/violet.css';
4
+
5
+ .file-manager-modal {
6
+ color: var(--text-color);
7
+ height: 90%;
8
+ width: 80%;
9
+ }
10
+
11
+ .react-photo-album.react-photo-album--columns {
12
+ height: 80vh;
13
+ }
14
+
15
+ .react-photo-album--photo {
16
+ -moz-user-select: none;
17
+ -webkit-user-select: none;
18
+ user-select: none;
19
+ border-radius: 8px;
20
+ border: 1px solid transparent;
21
+
22
+ // transform-origin: 0 0;
23
+ transition: transform 0.25s, visibility 0.25s ease-in;
24
+
25
+ &:hover {
26
+ border: 1px solid var(--border-color);
27
+ transform: scale(1.03);
28
+ }
29
+ }
30
+
31
+ .ScrollAreaRoot {
32
+ border-radius: 4px;
33
+ overflow: hidden;
34
+ // box-shadow: 0 2px 10px var(--blackA7);
35
+ --scrollbar-size: 10px;
36
+ }
37
+
38
+ .ScrollAreaViewport {
39
+ width: 100%;
40
+ height: 100%;
41
+ border-radius: inherit;
42
+ }
43
+
44
+ .ScrollAreaScrollbar {
45
+ display: flex;
46
+ /* ensures no selection */
47
+ user-select: none;
48
+ /* disable browser handling of all panning and zooming gestures on touch devices */
49
+ touch-action: none;
50
+ padding: 2px;
51
+ // background: var(--blackA6);
52
+ transition: background 160ms ease-out;
53
+ }
54
+
55
+ .ScrollAreaScrollbar:hover {
56
+ background: var(--blackA8);
57
+ }
58
+ .ScrollAreaScrollbar[data-orientation='vertical'] {
59
+ width: var(--scrollbar-size);
60
+ }
61
+ .ScrollAreaScrollbar[data-orientation='horizontal'] {
62
+ flex-direction: column;
63
+ height: var(--scrollbar-size);
64
+ }
65
+
66
+ .ScrollAreaThumb {
67
+ flex: 1;
68
+ background: var(--mauve10);
69
+ border-radius: var(--scrollbar-size);
70
+ position: relative;
71
+ }
72
+ /* increase target size for touch devices https://www.w3.org/WAI/WCAG21/Understanding/target-size.html */
73
+ .ScrollAreaThumb::before {
74
+ content: '';
75
+ position: absolute;
76
+ top: 50%;
77
+ left: 50%;
78
+ transform: translate(-50%, -50%);
79
+ width: 100%;
80
+ height: 100%;
81
+ min-width: 44px;
82
+ min-height: 44px;
83
+ }
84
+
85
+ .ScrollAreaCorner {
86
+ background: var(--blackA8);
87
+ }
88
+
89
+ .file-search-input {
90
+ width: 250px;
91
+ padding-left: 30px;
92
+ height: 32px;
93
+ border: 1px solid var(--border-color);
94
+ border-radius: 12px;
95
+ }
96
+
97
+ .sort-btn-inactive {
98
+ svg {
99
+ opacity: 0.5;
100
+ }
101
+ }
102
+
103
+ /* reset */
104
+ button,
105
+ fieldset,
106
+ input {
107
+ all: unset;
108
+ }
109
+
110
+ .TabsRoot {
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 8px;
114
+ background-color: var(--page-bg);
115
+ align-self: flex-start;
116
+ }
117
+
118
+ .TabsList {
119
+ display: flex;
120
+ flex-direction: row;
121
+ gap: 6px;
122
+ justify-content: flex-start;
123
+ border: 1px solid var(--border-color);
124
+ border-radius: 12px;
125
+ background-color: var(--page-bg);
126
+ padding: 4px;
127
+ }
128
+
129
+ .TabsTrigger {
130
+ font-family: inherit;
131
+ background-color: white;
132
+ padding: 8px;
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: flex-start;
136
+ font-size: 15px;
137
+ line-height: 1;
138
+ color: var(--modal-text-color);
139
+ user-select: none;
140
+ background-color: var(--page-bg);
141
+ border-radius: 8px;
142
+ }
143
+ .TabsTrigger:hover {
144
+ background-color: var(--tabs-active-color);
145
+ }
146
+ .TabsTrigger[data-state='active'] {
147
+ background-color: var(--tabs-active-color);
148
+ }
149
+ .TabsTrigger:focus {
150
+ position: relative;
151
+ }
152
+
153
+ .TabsContent {
154
+ background-color: white;
155
+ outline: none;
156
+ background-color: var(--page-bg);
157
+ width: 100%;
158
+ }
159
+
160
+ .TabsContent[data-state='active'] {
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 14px;
164
+ }