zhou20120904 commited on
Commit
a62d4c5
1 Parent(s): 5d78f81

Upload 48 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ media/cover.png filter=lfs diff=lfs merge=lfs -text
37
+ media/github.mp4 filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18 as builder
2
+
3
+ WORKDIR /usr/src/app
4
+
5
+ # Copy the package.json and package-lock.json files over
6
+ # We do this FIRST so that we don't copy the huge node_modules folder over from our local machine
7
+ # The node_modules can contain machine-specific libraries, so it should be created by the machine that's actually running the code
8
+ COPY . ./
9
+
10
+ # Now we run NPM install, which includes dev dependencies
11
+ RUN npm install
12
+
13
+ FROM alpine:latest as production
14
+ RUN apk --no-cache add nodejs ca-certificates
15
+ WORKDIR /root/
16
+ COPY --from=builder /usr/src/app ./
17
+ CMD [ "node", "node_modules/vite/bin/vite.js", "--host" ]
LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
index.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
8
+ />
9
+ <title>Inpaint-web</title>
10
+
11
+ </head>
12
+ <body class="h-screen">
13
+ <noscript>You need to enable JavaScript to run this app.</noscript>
14
+ <div id="root" class="h-full"></div>
15
+
16
+ <script type="module" src="/src/index.tsx"></script>
17
+ </body>
18
+
19
+ </html>
media/bar.png ADDED
media/cover.png ADDED

Git LFS Details

  • SHA256: 134bf50b4b4ac2399b188ee129bb88796bd1170818835e486dd3ab5565c8f1b4
  • Pointer size: 132 Bytes
  • Size of remote file: 1.03 MB
media/github.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8753cc51e46d3198d9023f7b5f197b28708a9dd1d1eb73d6d8a1d7cf43c788c6
3
+ size 1657419
media/super-resolution.jpg ADDED
media/wechat.jpg ADDED
media/wechatG.jpg ADDED
messages/en.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://inlang.com/schema/inlang-message-format",
3
+ "drop_zone": "Click or drag here",
4
+ "try_it_images": "Try it:",
5
+ "feedback": "About me",
6
+ "start_new": "Start new",
7
+ "bruch_size": "Brush Size",
8
+ "original": "Original",
9
+ "upscale": "4x-upscaling",
10
+ "download": "Download",
11
+ "undo": "Undo",
12
+ "inpaint_model_download_message": "Need to download a 30MB model file, please wait patiently...",
13
+ "upscaleing_model_download_message": "Need to download a 70MB model file, please wait patiently..."
14
+ }
messages/zh.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://inlang.com/schema/inlang-message-format",
3
+ "drop_zone": "点击 或者 拖拽到这里",
4
+ "try_it_images": "试一试:",
5
+ "feedback": "联系作者",
6
+ "start_new": "开始新的",
7
+ "bruch_size": "刷子大小",
8
+ "original": "原图",
9
+ "upscale": "4 倍放大",
10
+ "download": "下载",
11
+ "undo": "撤销",
12
+ "inpaint_model_download_message": "注意需要连接国际互联网,需要下载一次30MB大小模型文件,耐心等待。。。",
13
+ "upscaleing_model_download_message": "注意需要连接国际互联网,需要下载一次70MB大小模型文件,耐心等待。。。"
14
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "inpaint-web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@heroicons/react": "^1.0.4",
7
+ "@inlang/paraglide-js-adapter-vite": "^1.0.2",
8
+ "@vitejs/plugin-react-swc": "^3.5.0",
9
+ "localforage": "^1.10.0",
10
+ "opencv-ts": "^1.3.6",
11
+ "react": "^17.0.2",
12
+ "react-dom": "^17.0.2",
13
+ "react-use": "^17.3.1",
14
+ "react-zoom-pan-pinch": "^3.3.0",
15
+ "vite": "^5.0.2",
16
+ "vite-plugin-eslint": "^1.8.1",
17
+ "vite-plugin-svgr": "^4.2.0",
18
+ "vite-tsconfig-paths": "^4.2.1"
19
+ },
20
+ "scripts": {
21
+ "start": "vite --host",
22
+ "fast-build": "vite build",
23
+ "build": "tsc && vite build",
24
+ "serve": "vite preview",
25
+ "prepare": "husky install",
26
+ "format": "prettier --write .",
27
+ "postinstall": "paraglide-js compile --project ./project.inlang",
28
+ "paraglide": "paraglide-js compile --project ./project.inlang"
29
+ },
30
+ "lint-staged": {
31
+ "*.{vue,js,ts,jsx,tsx,css,sass,scss,md}": [
32
+ "prettier --write",
33
+ "echo '统一格式化完成🌸'"
34
+ ]
35
+ },
36
+ "eslintConfig": {
37
+ "extends": "react-app"
38
+ },
39
+ "browserslist": {
40
+ "production": [
41
+ ">0.2%",
42
+ "not dead",
43
+ "not op_mini all"
44
+ ],
45
+ "development": [
46
+ "last 1 chrome version",
47
+ "last 1 firefox version",
48
+ "last 1 safari version"
49
+ ]
50
+ },
51
+ "devDependencies": {
52
+ "@inlang/paraglide-js": "1.0.0-prerelease.19",
53
+ "@testing-library/jest-dom": "^5.14.1",
54
+ "@testing-library/react": "^12.1.2",
55
+ "@testing-library/user-event": "^13.5.0",
56
+ "@types/jest": "^27.0.2",
57
+ "@types/node": "^18.0.0",
58
+ "@types/react": "^17.0.30",
59
+ "@types/react-dom": "^17.0.9",
60
+ "@typescript-eslint/eslint-plugin": "^5.1.0",
61
+ "@vitest/coverage-v8": "^0.34.6",
62
+ "autoprefixer": "^10.4.16",
63
+ "cross-env": "7.x",
64
+ "delay-cli": "^1.1.0",
65
+ "eslint-config-airbnb": "^18.2.1",
66
+ "eslint-config-prettier": "^8.3.0",
67
+ "eslint-plugin-import": "^2.25.2",
68
+ "eslint-plugin-jsx-a11y": "^6.4.1",
69
+ "eslint-plugin-prettier": "^4.0.0",
70
+ "eslint-plugin-react": "^7.26.1",
71
+ "eslint-plugin-react-hooks": "^4.2.0",
72
+ "husky": "^8.0.3",
73
+ "jsdom": "^22.1.0",
74
+ "lint-staged": "^15.1.0",
75
+ "npm-run-all": "4.x",
76
+ "postcss": "^8.4.31",
77
+ "postcss-cli": "8.x",
78
+ "postcss-preset-env": "6.x",
79
+ "prettier": "^2.4.1",
80
+ "tailwind-scrollbar": "^2.0.0",
81
+ "tailwindcss": "^3.3.6",
82
+ "typescript": "4.x",
83
+ "vitest": "^0.34.6"
84
+ }
85
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
project.inlang/project_id ADDED
@@ -0,0 +1 @@
 
 
1
+ ddd85f36c36577d96de7533e96ddc7706cbb8776bb4cf8218a033a5c7bcc21f2
project.inlang/settings.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://inlang.com/schema/project-settings",
3
+ "sourceLanguageTag": "zh",
4
+ "languageTags": [
5
+ "en", "zh"
6
+ ],
7
+ "modules": [
8
+ "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
9
+ "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@latest/dist/index.js",
10
+ "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
11
+ "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
12
+ "https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js",
13
+ "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
14
+ "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
15
+ ],
16
+ "plugin.inlang.messageFormat": {
17
+ "pathPattern": "./messages/{languageTag}.json"
18
+ }
19
+ }
public/_headers ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ /*
2
+ Cross-Origin-Opener-Policy: same-origin
3
+ Cross-Origin-Embedder-Policy: require-corp
public/examples/bag.jpeg ADDED
public/examples/bird.jpeg ADDED
public/examples/car.jpeg ADDED
public/examples/dog.jpeg ADDED
public/examples/jacket.jpeg ADDED
public/examples/paris.jpeg ADDED
public/examples/shoe.jpeg ADDED
public/examples/table.jpeg ADDED
src/App.tsx ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ /* eslint-disable jsx-a11y/control-has-associated-label */
3
+ import { ArrowLeftIcon, InformationCircleIcon } from '@heroicons/react/outline'
4
+ import { useEffect, useRef, useState } from 'react'
5
+ import { useClickAway } from 'react-use'
6
+ import Button from './components/Button'
7
+ import FileSelect from './components/FileSelect'
8
+ import Modal from './components/Modal'
9
+ import Editor from './Editor'
10
+ import { resizeImageFile } from './utils'
11
+ import Progress from './components/Progress'
12
+ import { downloadModel } from './adapters/cache'
13
+ import * as m from './paraglide/messages'
14
+ import {
15
+ languageTag,
16
+ onSetLanguageTag,
17
+ setLanguageTag,
18
+ } from './paraglide/runtime'
19
+
20
+ function App() {
21
+ const [file, setFile] = useState<File>()
22
+ const [stateLanguageTag, setStateLanguageTag] = useState<'en' | 'zh'>('zh')
23
+
24
+ onSetLanguageTag(() => setStateLanguageTag(languageTag()))
25
+
26
+ const [showAbout, setShowAbout] = useState(false)
27
+ const modalRef = useRef(null)
28
+
29
+ const [downloadProgress, setDownloadProgress] = useState(100)
30
+
31
+ useEffect(() => {
32
+ downloadModel('inpaint', setDownloadProgress)
33
+ }, [])
34
+
35
+ useClickAway(modalRef, () => {
36
+ setShowAbout(false)
37
+ })
38
+
39
+ async function startWithDemoImage(img: string) {
40
+ const imgBlob = await fetch(`/examples/${img}.jpeg`).then(r => r.blob())
41
+ setFile(new File([imgBlob], `${img}.jpeg`, { type: 'image/jpeg' }))
42
+ }
43
+
44
+ return (
45
+ <div className="min-h-full flex flex-col">
46
+ <header className="z-10 shadow flex flex-row items-center md:justify-between h-14">
47
+ <Button
48
+ className={[
49
+ file ? '' : 'opacity-50 pointer-events-none',
50
+ 'pl-1 pr-1 mx-1 sm:mx-5',
51
+ ].join(' ')}
52
+ icon={<ArrowLeftIcon className="w-6 h-6" />}
53
+ onClick={() => {
54
+ setFile(undefined)
55
+ }}
56
+ >
57
+ <div className="md:w-[290px]">
58
+ <span className="hidden sm:inline select-none">
59
+ {m.start_new()}
60
+ </span>
61
+ </div>
62
+ </Button>
63
+ <div className="text-4xl font-bold text-blue-600 hover:text-blue-700 transition duration-300 ease-in-out">
64
+ Inpaint-web
65
+ </div>
66
+ <div className="hidden md:flex justify-end w-[300px] mx-1 sm:mx-5">
67
+ <Button
68
+ className="mr-5 flex"
69
+ onClick={() => {
70
+ if (languageTag() === 'zh') {
71
+ setLanguageTag('en')
72
+ } else {
73
+ setLanguageTag('zh')
74
+ }
75
+ }}
76
+ >
77
+ <p>{languageTag() === 'en' ? '切换到中文' : 'en'}</p>
78
+ </Button>
79
+ <Button
80
+ className="w-38 flex sm:visible"
81
+ icon={<InformationCircleIcon className="w-6 h-6" />}
82
+ onClick={() => {
83
+ setShowAbout(true)
84
+ }}
85
+ >
86
+ <p>{m.feedback()}</p>
87
+ </Button>
88
+ </div>
89
+ </header>
90
+
91
+ <main
92
+ style={{
93
+ height: 'calc(100vh - 56px)',
94
+ }}
95
+ className=" relative"
96
+ >
97
+ {file ? (
98
+ <Editor file={file} />
99
+ ) : (
100
+ <>
101
+ <div className="flex h-full flex-1 flex-col items-center justify-center overflow-hidden">
102
+ <div className="h-72 sm:w-1/2 max-w-5xl">
103
+ <FileSelect
104
+ onSelection={async f => {
105
+ const { file: resizedFile } = await resizeImageFile(
106
+ f,
107
+ 1024 * 4
108
+ )
109
+ setFile(resizedFile)
110
+ }}
111
+ />
112
+ </div>
113
+ <div className="flex flex-col sm:flex-row pt-10 items-center justify-center cursor-pointer">
114
+ <span className="text-gray-500">{m.try_it_images()}</span>
115
+ <div className="flex space-x-2 sm:space-x-4 px-4">
116
+ {['bag', 'dog', 'car', 'bird', 'jacket', 'shoe', 'paris'].map(
117
+ image => (
118
+ <div
119
+ key={image}
120
+ onClick={() => startWithDemoImage(image)}
121
+ role="button"
122
+ onKeyDown={() => startWithDemoImage(image)}
123
+ tabIndex={-1}
124
+ >
125
+ <img
126
+ className="rounded-md hover:opacity-75 w-auto h-25"
127
+ src={`examples/${image}.jpeg`}
128
+ alt={image}
129
+ style={{ height: '100px' }}
130
+ />
131
+ </div>
132
+ )
133
+ )}
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </>
138
+ )}
139
+ </main>
140
+
141
+ {showAbout && (
142
+ <Modal>
143
+ <div ref={modalRef} className="text-xl space-y-5">
144
+ <p>
145
+ {' '}
146
+ 任何问题到:{' '}
147
+ <a
148
+ href="https://github.com/lxfater/inpaint-web"
149
+ style={{ color: 'blue' }}
150
+ rel="noreferrer"
151
+ target="_blank"
152
+ >
153
+ Inpaint-web
154
+ </a>{' '}
155
+ 反馈
156
+ </p>
157
+ <p>
158
+ {' '}
159
+ For any questions, please go to:{' '}
160
+ <a
161
+ href="https://github.com/lxfater/inpaint-web"
162
+ style={{ color: 'blue' }}
163
+ rel="noreferrer"
164
+ target="_blank"
165
+ >
166
+ Inpaint-web
167
+ </a>{' '}
168
+ to provide feedback.
169
+ </p>
170
+ </div>
171
+ </Modal>
172
+ )}
173
+ {!(downloadProgress === 100) && (
174
+ <Modal>
175
+ <div className="text-xl space-y-5">
176
+ <p>{m.inpaint_model_download_message()}</p>
177
+ <Progress percent={downloadProgress} />
178
+ </div>
179
+ </Modal>
180
+ )}
181
+ </div>
182
+ )
183
+ }
184
+
185
+ export default App
src/Editor.tsx ADDED
@@ -0,0 +1,707 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable jsx-a11y/click-events-have-key-events */
2
+ /* eslint-disable jsx-a11y/no-static-element-interactions */
3
+ import { DownloadIcon, EyeIcon, ViewBoardsIcon } from '@heroicons/react/outline'
4
+ import { useCallback, useEffect, useState, useRef, useMemo } from 'react'
5
+ import { useWindowSize } from 'react-use'
6
+ import inpaint from './adapters/inpainting'
7
+ import superResolution from './adapters/superResolution'
8
+ import Button from './components/Button'
9
+ import Slider from './components/Slider'
10
+ import { downloadImage, loadImage, useImage } from './utils'
11
+ import Progress from './components/Progress'
12
+ import { modelExists, downloadModel } from './adapters/cache'
13
+ import Modal from './components/Modal'
14
+ import * as m from './paraglide/messages'
15
+
16
+ interface EditorProps {
17
+ file: File
18
+ }
19
+
20
+ interface Line {
21
+ size?: number
22
+ pts: { x: number; y: number }[]
23
+ src: string
24
+ }
25
+
26
+ function drawLines(
27
+ ctx: CanvasRenderingContext2D,
28
+ lines: Line[],
29
+ color = 'rgba(255, 0, 0, 0.5)'
30
+ ) {
31
+ ctx.strokeStyle = color
32
+ ctx.lineCap = 'round'
33
+ ctx.lineJoin = 'round'
34
+
35
+ lines.forEach(line => {
36
+ if (!line?.pts.length || !line.size) {
37
+ return
38
+ }
39
+ ctx.lineWidth = line.size
40
+ ctx.beginPath()
41
+ ctx.moveTo(line.pts[0].x, line.pts[0].y)
42
+ line.pts.forEach(pt => ctx.lineTo(pt.x, pt.y))
43
+ ctx.stroke()
44
+ })
45
+ }
46
+
47
+ const BRUSH_HIDE_ON_SLIDER_CHANGE_TIMEOUT = 2000
48
+ export default function Editor(props: EditorProps) {
49
+ const { file } = props
50
+ const [brushSize, setBrushSize] = useState(40)
51
+ const [original, isOriginalLoaded] = useImage(file)
52
+ const [renders, setRenders] = useState<HTMLImageElement[]>([])
53
+ const [context, setContext] = useState<CanvasRenderingContext2D>()
54
+ const [maskCanvas] = useState<HTMLCanvasElement>(() => {
55
+ return document.createElement('canvas')
56
+ })
57
+ const [lines, setLines] = useState<Line[]>([{ pts: [], src: '' }])
58
+ const brushRef = useRef<HTMLDivElement>(null)
59
+ const [showBrush, setShowBrush] = useState(false)
60
+ const [hideBrushTimeout, setHideBrushTimeout] = useState(0)
61
+ const [showOriginal, setShowOriginal] = useState(false)
62
+ const [isInpaintingLoading, setIsProcessingLoading] = useState(false)
63
+ const [generateProgress, setGenerateProgress] = useState(0)
64
+ const modalRef = useRef(null)
65
+ const [separator, setSeparator] = useState<HTMLDivElement>()
66
+ const [useSeparator, setUseSeparator] = useState(false)
67
+ const [originalImg, setOriginalImg] = useState<HTMLDivElement>()
68
+ const [separatorLeft, setSeparatorLeft] = useState(0)
69
+ const historyListRef = useRef<HTMLDivElement>(null)
70
+ const isBrushSizeChange = useRef<boolean>(false)
71
+ const scaledBrushSize = useMemo(() => brushSize, [brushSize])
72
+ const canvasDiv = useRef<HTMLDivElement>(null)
73
+ const [downloaded, setDownloaded] = useState(true)
74
+ const [downloadProgress, setDownloadProgress] = useState(0)
75
+ const windowSize = useWindowSize()
76
+
77
+ const draw = useCallback(
78
+ (index = -1) => {
79
+ if (!context) {
80
+ return
81
+ }
82
+ context.clearRect(0, 0, context.canvas.width, context.canvas.height)
83
+ const currRender =
84
+ renders[index === -1 ? renders.length - 1 : index] ?? original
85
+ const { canvas } = context
86
+
87
+ const divWidth = canvasDiv.current!.offsetWidth
88
+ const divHeight = canvasDiv.current!.offsetHeight
89
+
90
+ // 计算宽高比
91
+ const imgAspectRatio = currRender.width / currRender.height
92
+ const divAspectRatio = divWidth / divHeight
93
+
94
+ let canvasWidth
95
+ let canvasHeight
96
+
97
+ // 比较宽高比以决定如何缩放
98
+ if (divAspectRatio > imgAspectRatio) {
99
+ // div 较宽,基于高度缩放
100
+ canvasHeight = divHeight
101
+ canvasWidth = currRender.width * (divHeight / currRender.height)
102
+ } else {
103
+ // div 较窄,基于宽度缩放
104
+ canvasWidth = divWidth
105
+ canvasHeight = currRender.height * (divWidth / currRender.width)
106
+ }
107
+
108
+ canvas.width = canvasWidth
109
+ canvas.height = canvasHeight
110
+
111
+ if (currRender?.src) {
112
+ context.drawImage(currRender, 0, 0, canvas.width, canvas.height)
113
+ } else {
114
+ context.drawImage(original, 0, 0, canvas.width, canvas.height)
115
+ }
116
+ const currentLine = lines[lines.length - 1]
117
+ drawLines(context, [currentLine])
118
+ },
119
+ [context, lines, original, renders]
120
+ )
121
+
122
+ const refreshCanvasMask = useCallback(() => {
123
+ if (!context?.canvas.width || !context?.canvas.height) {
124
+ throw new Error('canvas has invalid size')
125
+ }
126
+ maskCanvas.width = context?.canvas.width
127
+ maskCanvas.height = context?.canvas.height
128
+ const ctx = maskCanvas.getContext('2d')
129
+ if (!ctx) {
130
+ throw new Error('could not retrieve mask canvas')
131
+ }
132
+ // Just need the finishing touch
133
+ const line = lines.slice(-1)[0]
134
+ if (line) drawLines(ctx, [line], 'white')
135
+ }, [context?.canvas.height, context?.canvas.width, lines, maskCanvas])
136
+
137
+ // Draw once the original image is loaded
138
+ useEffect(() => {
139
+ if (!context?.canvas) {
140
+ return
141
+ }
142
+ if (isOriginalLoaded) {
143
+ draw()
144
+ }
145
+ }, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
146
+
147
+ // Handle mouse interactions
148
+ useEffect(() => {
149
+ const canvas = context?.canvas
150
+ if (!canvas) {
151
+ return
152
+ }
153
+ const onMouseMove = (ev: MouseEvent) => {
154
+ if (brushRef.current) {
155
+ const x = ev.pageX - scaledBrushSize / 2
156
+ const y = ev.pageY - scaledBrushSize / 2
157
+
158
+ brushRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`
159
+ }
160
+ }
161
+ const onPaint = (px: number, py: number) => {
162
+ const currLine = lines[lines.length - 1]
163
+ currLine.pts.push({ x: px, y: py })
164
+ draw()
165
+ }
166
+ const onMouseDrag = (ev: MouseEvent) => {
167
+ const px = ev.offsetX - canvas.offsetLeft
168
+ const py = ev.offsetY - canvas.offsetTop
169
+ onPaint(px, py)
170
+ }
171
+
172
+ const onPointerUp = async () => {
173
+ if (!original.src || showOriginal) {
174
+ return
175
+ }
176
+ if (lines.slice(-1)[0]?.pts.length === 0) {
177
+ return
178
+ }
179
+ const loading = onloading()
180
+ canvas.removeEventListener('mousemove', onMouseDrag)
181
+ canvas.removeEventListener('mouseup', onPointerUp)
182
+ refreshCanvasMask()
183
+ try {
184
+ const start = Date.now()
185
+ console.log('inpaint_start')
186
+ // each time based on the last result, the first is the original
187
+ const newFile = renders.slice(-1)[0] ?? file
188
+ const res = await inpaint(newFile, maskCanvas.toDataURL())
189
+ if (!res) {
190
+ throw new Error('empty response')
191
+ }
192
+ // TODO: fix the render if it failed loading
193
+ const newRender = new Image()
194
+ newRender.dataset.id = Date.now().toString()
195
+ await loadImage(newRender, res)
196
+ renders.push(newRender)
197
+ lines.push({ pts: [], src: '' } as Line)
198
+ setRenders([...renders])
199
+ setLines([...lines])
200
+ console.log('inpaint_processed', {
201
+ duration: Date.now() - start,
202
+ })
203
+ } catch (e: any) {
204
+ console.log('inpaint_failed', {
205
+ error: e,
206
+ })
207
+ // eslint-disable-next-line
208
+ alert(e.message ? e.message : e.toString())
209
+ }
210
+ if (historyListRef.current) {
211
+ const { scrollWidth, clientWidth } = historyListRef.current
212
+ if (scrollWidth > clientWidth) {
213
+ historyListRef.current.scrollTo(scrollWidth, 0)
214
+ }
215
+ }
216
+ loading.close()
217
+ draw()
218
+ }
219
+ canvas.addEventListener('mousemove', onMouseMove)
220
+
221
+ const onTouchMove = (ev: TouchEvent) => {
222
+ ev.preventDefault()
223
+ ev.stopPropagation()
224
+ const currLine = lines[lines.length - 1]
225
+ const coords = canvas.getBoundingClientRect()
226
+ currLine.pts.push({
227
+ x: ev.touches[0].clientX - coords.x,
228
+ y: ev.touches[0].clientY - coords.y,
229
+ })
230
+ draw()
231
+ }
232
+ const onPointerStart = () => {
233
+ if (!original.src || showOriginal) {
234
+ return
235
+ }
236
+ const currLine = lines[lines.length - 1]
237
+ currLine.size = brushSize
238
+ canvas.addEventListener('mousemove', onMouseDrag)
239
+ canvas.addEventListener('mouseup', onPointerUp)
240
+ // onPaint(e)
241
+ }
242
+
243
+ canvas.addEventListener('touchstart', onPointerStart)
244
+ canvas.addEventListener('touchmove', onTouchMove)
245
+ canvas.addEventListener('touchend', onPointerUp)
246
+ canvas.onmouseenter = () => {
247
+ window.clearTimeout(hideBrushTimeout)
248
+ setShowBrush(true && !showOriginal)
249
+ }
250
+ canvas.onmouseleave = () => setShowBrush(false)
251
+ canvas.onmousedown = onPointerStart
252
+
253
+ return () => {
254
+ canvas.removeEventListener('mousemove', onMouseDrag)
255
+ canvas.removeEventListener('mousemove', onMouseMove)
256
+ canvas.removeEventListener('mouseup', onPointerUp)
257
+ canvas.removeEventListener('touchstart', onPointerStart)
258
+ canvas.removeEventListener('touchmove', onTouchMove)
259
+ canvas.removeEventListener('touchend', onPointerUp)
260
+ canvas.onmouseenter = null
261
+ canvas.onmouseleave = null
262
+ canvas.onmousedown = null
263
+ }
264
+ }, [
265
+ brushSize,
266
+ context,
267
+ file,
268
+ draw,
269
+ lines,
270
+ refreshCanvasMask,
271
+ maskCanvas,
272
+ original.src,
273
+ renders,
274
+ showOriginal,
275
+ hideBrushTimeout,
276
+ ])
277
+
278
+ useEffect(() => {
279
+ if (!separator || !originalImg) return
280
+
281
+ const separatorMove = (ev: MouseEvent) => {
282
+ ev.preventDefault()
283
+ ev.stopPropagation()
284
+ if (context?.canvas) {
285
+ const { width } = context?.canvas
286
+ const canvasRect = context?.canvas.getBoundingClientRect()
287
+ const separatorOffsetLeft = ev.pageX - canvasRect.left
288
+ if (separatorOffsetLeft <= width && separatorOffsetLeft >= 0) {
289
+ setSeparatorLeft(separatorOffsetLeft)
290
+ } else if (separatorOffsetLeft < 0) {
291
+ setSeparatorLeft(0)
292
+ } else if (separatorOffsetLeft > width) {
293
+ setSeparatorLeft(width)
294
+ }
295
+ }
296
+ }
297
+
298
+ const separatorDown = () => {
299
+ window.addEventListener('mousemove', separatorMove)
300
+ setUseSeparator(true)
301
+ }
302
+
303
+ const separatorUp = () => {
304
+ window.removeEventListener('mousemove', separatorMove)
305
+ setUseSeparator(false)
306
+ }
307
+
308
+ separator.addEventListener('mousedown', separatorDown)
309
+ window.addEventListener('mouseup', separatorUp)
310
+
311
+ return () => {
312
+ separator.removeEventListener('mousedown', separatorDown)
313
+ window.removeEventListener('mouseup', separatorUp)
314
+ }
315
+ }, [separator, context])
316
+
317
+ function download() {
318
+ const currRender = renders.at(-1) ?? original
319
+ downloadImage(currRender.currentSrc, 'IMG')
320
+ }
321
+
322
+ const undo = useCallback(async () => {
323
+ const l = lines
324
+ l.pop()
325
+ l.pop()
326
+ setLines([...l, { pts: [], src: '' }])
327
+ const r = renders
328
+ r.pop()
329
+ setRenders([...r])
330
+ }, [lines, renders])
331
+
332
+ useEffect(() => {
333
+ const handler = (event: KeyboardEvent) => {
334
+ if (!renders.length) {
335
+ return
336
+ }
337
+ const isCmdZ = (event.metaKey || event.ctrlKey) && event.key === 'z'
338
+ if (isCmdZ) {
339
+ event.preventDefault()
340
+ undo()
341
+ }
342
+ }
343
+ window.addEventListener('keydown', handler)
344
+ return () => {
345
+ window.removeEventListener('keydown', handler)
346
+ }
347
+ }, [renders, undo])
348
+
349
+ const backTo = useCallback(
350
+ (index: number) => {
351
+ lines.splice(index + 1)
352
+ setLines([...lines, { pts: [], src: '' }])
353
+ renders.splice(index + 1)
354
+ setRenders([...renders])
355
+ },
356
+ [renders, lines]
357
+ )
358
+
359
+ const History = useMemo(
360
+ () =>
361
+ renders.map((render, index) => {
362
+ return (
363
+ <div
364
+ key={render.dataset.id}
365
+ style={{
366
+ position: 'relative',
367
+ display: 'inline-block',
368
+ flexShrink: 0,
369
+ }}
370
+ >
371
+ <img
372
+ src={render.src}
373
+ alt="render"
374
+ className="rounded-sm"
375
+ style={{
376
+ height: '90px',
377
+ }}
378
+ />
379
+ <Button
380
+ className="hover:opacity-100 opacity-0 cursor-pointer rounded-sm"
381
+ style={{
382
+ position: 'absolute',
383
+ top: '0',
384
+ left: '0',
385
+ width: '100%',
386
+ height: '100%',
387
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
388
+ display: 'flex',
389
+ alignItems: 'center',
390
+ justifyContent: 'center',
391
+ }}
392
+ onClick={() => backTo(index)}
393
+ onEnter={() => draw(index)}
394
+ onLeave={draw}
395
+ >
396
+ <div
397
+ style={{
398
+ color: '#fff',
399
+ fontSize: '12px',
400
+ textAlign: 'center',
401
+ }}
402
+ >
403
+ 回到这
404
+ <br />
405
+ Back here
406
+ </div>
407
+ </Button>
408
+ </div>
409
+ )
410
+ }),
411
+ [renders, backTo]
412
+ )
413
+
414
+ const handleSliderStart = () => {
415
+ setShowBrush(true)
416
+ }
417
+ const handleSliderChange = (sliderValue: number) => {
418
+ if (!isBrushSizeChange.current) {
419
+ isBrushSizeChange.current = true
420
+ }
421
+ if (brushRef.current) {
422
+ const x = document.documentElement.clientWidth / 2 - scaledBrushSize / 2
423
+ const y = document.documentElement.clientHeight / 2 - scaledBrushSize / 2
424
+
425
+ brushRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`
426
+ }
427
+ setBrushSize(sliderValue)
428
+ window.clearTimeout(hideBrushTimeout)
429
+ setHideBrushTimeout(
430
+ window.setTimeout(() => {
431
+ setShowBrush(false)
432
+ }, BRUSH_HIDE_ON_SLIDER_CHANGE_TIMEOUT)
433
+ )
434
+ }
435
+
436
+ const onloading = useCallback(() => {
437
+ setIsProcessingLoading(true)
438
+ setGenerateProgress(0)
439
+ const progressTimer = window.setInterval(() => {
440
+ setGenerateProgress(p => {
441
+ if (p < 90) return p + 10 * Math.random()
442
+ if (p >= 90 && p < 99) return p + 1 * Math.random()
443
+ // Do not hide the progress bar after 99%,cause sometimes long time progress
444
+ // window.setTimeout(() => setIsInpaintingLoading(false), 500)
445
+ return p
446
+ })
447
+ }, 1000)
448
+ return {
449
+ close: () => {
450
+ clearInterval(progressTimer)
451
+ setGenerateProgress(100)
452
+ setIsProcessingLoading(false)
453
+ },
454
+ }
455
+ }, [])
456
+
457
+ const onSuperResolution = useCallback(async () => {
458
+ if (!(await modelExists('superResolution'))) {
459
+ setDownloaded(false)
460
+ await downloadModel('superResolution', setDownloadProgress)
461
+ setDownloaded(true)
462
+ }
463
+ setIsProcessingLoading(true)
464
+ try {
465
+ // 运行
466
+ const start = Date.now()
467
+ console.log('superResolution_start')
468
+ // each time based on the last result, the first is the original
469
+ const newFile = renders.at(-1) ?? file
470
+ const res = await superResolution(newFile, setGenerateProgress)
471
+ if (!res) {
472
+ throw new Error('empty response')
473
+ }
474
+ // TODO: fix the render if it failed loading
475
+ const newRender = new Image()
476
+ newRender.dataset.id = Date.now().toString()
477
+ await loadImage(newRender, res)
478
+ renders.push(newRender)
479
+ lines.push({ pts: [], src: '' } as Line)
480
+ setRenders([...renders])
481
+ setLines([...lines])
482
+ console.log('superResolution_processed', {
483
+ duration: Date.now() - start,
484
+ })
485
+
486
+ // 替换当前图片
487
+ } catch (error) {
488
+ console.error('superResolution', error)
489
+ } finally {
490
+ setIsProcessingLoading(false)
491
+ }
492
+ }, [file, lines, original.naturalHeight, original.naturalWidth, renders])
493
+
494
+ return (
495
+ <div
496
+ className={[
497
+ 'flex flex-col items-center h-full justify-between',
498
+ isInpaintingLoading ? 'animate-pulse-fast pointer-events-none' : '',
499
+ ].join(' ')}
500
+ >
501
+ {/* History */}
502
+ <div
503
+ ref={historyListRef}
504
+ style={{
505
+ height: '116px',
506
+ }}
507
+ className={[
508
+ 'flex-shrink-0',
509
+ 'mt-4 border p-3 rounded',
510
+ 'flex items-left w-full max-w-4xl',
511
+ 'space-y-0 flex-row space-x-5',
512
+ 'scrollbar-thin scrollbar-thumb-black scrollbar-track-primary overflow-x-scroll',
513
+ ].join(' ')}
514
+ >
515
+ {History}
516
+ </div>
517
+ {/* 画图 */}
518
+ <div
519
+ className={[
520
+ 'flex-grow',
521
+ 'flex justify-center',
522
+ 'my-2',
523
+ 'relative',
524
+ ].join(' ')}
525
+ style={{
526
+ width: '70vw',
527
+ }}
528
+ ref={canvasDiv}
529
+ >
530
+ <div className="relative">
531
+ <canvas
532
+ className="rounded-sm"
533
+ style={showBrush ? { cursor: 'none' } : {}}
534
+ ref={r => {
535
+ if (r && !context) {
536
+ const ctx = r.getContext('2d')
537
+ if (ctx) {
538
+ setContext(ctx)
539
+ }
540
+ }
541
+ }}
542
+ />
543
+ <div
544
+ className={[
545
+ 'absolute top-0 right-0 pointer-events-none',
546
+ showOriginal ? '' : 'overflow-hidden',
547
+ ].join(' ')}
548
+ style={{
549
+ width: showOriginal ? `${context?.canvas.width}px` : '0px',
550
+ height: context?.canvas.height,
551
+ transitionProperty: 'width, height',
552
+ transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
553
+ transitionDuration: '300ms',
554
+ }}
555
+ ref={r => {
556
+ if (r && !originalImg) {
557
+ setOriginalImg(r)
558
+ }
559
+ }}
560
+ >
561
+ <div
562
+ className={[
563
+ 'absolute top-0 right-0 pointer-events-none z-10',
564
+ useSeparator ? 'bg-black text-white' : 'bg-primary ',
565
+ 'w-1',
566
+ 'flex items-center justify-center',
567
+ 'separator',
568
+ ].join(' ')}
569
+ style={{
570
+ left: `${separatorLeft}px`,
571
+ height: context?.canvas.height,
572
+ transitionProperty: 'width, height',
573
+ transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
574
+ transitionDuration: '300ms',
575
+ }}
576
+ >
577
+ <span className="absolute left-1 bottom-0 p-1 bg-opacity-25 bg-black rounded text-white select-none">
578
+ original
579
+ </span>
580
+ <div
581
+ className={[
582
+ 'absolute py-2 px-1 rounded-md pointer-events-auto',
583
+ useSeparator ? 'bg-black' : 'bg-primary ',
584
+ ].join(' ')}
585
+ style={{ cursor: 'ew-resize' }}
586
+ ref={r => {
587
+ if (r && !separator) {
588
+ setSeparator(r)
589
+ }
590
+ }}
591
+ >
592
+ <ViewBoardsIcon
593
+ className="w-5 h-5"
594
+ style={{ cursor: 'ew-resize' }}
595
+ />
596
+ </div>
597
+ </div>
598
+ <img
599
+ className="absolute right-0"
600
+ src={original.src}
601
+ alt="original"
602
+ width={`${context?.canvas.width}px`}
603
+ height={`${context?.canvas.height}px`}
604
+ style={{
605
+ width: `${context?.canvas.width}px`,
606
+ height: `${context?.canvas.height}px`,
607
+ maxWidth: 'none',
608
+ clipPath: `inset(0 0 0 ${separatorLeft}px)`,
609
+ }}
610
+ />
611
+ </div>
612
+ {isInpaintingLoading && (
613
+ <div className="z-10 bg-white absolute bg-opacity-80 top-0 left-0 right-0 bottom-0 h-full w-full flex justify-center items-center">
614
+ <div ref={modalRef} className="text-xl space-y-5 w-4/5 sm:w-1/2">
615
+ <p>正在处理中,请耐心等待。。。</p>
616
+ <p>It is being processed, please be patient...</p>
617
+ <Progress percent={generateProgress} />
618
+ </div>
619
+ </div>
620
+ )}
621
+ </div>
622
+ </div>
623
+
624
+ {!downloaded && (
625
+ <Modal>
626
+ <div className="text-xl space-y-5">
627
+ <p>{m.upscaleing_model_download_message()}</p>
628
+ <Progress percent={downloadProgress} />
629
+ </div>
630
+ </Modal>
631
+ )}
632
+ {showBrush && (
633
+ <div
634
+ className="fixed rounded-full bg-red-500 bg-opacity-50 pointer-events-none left-0 top-0"
635
+ style={{
636
+ width: `${scaledBrushSize}px`,
637
+ height: `${scaledBrushSize}px`,
638
+ transform: `translate3d(-100px, -100px, 0)`,
639
+ }}
640
+ ref={brushRef}
641
+ />
642
+ )}
643
+ {/* 工具栏 */}
644
+ <div
645
+ className={[
646
+ 'flex-shrink-0',
647
+ 'bg-white rounded-md border border-gray-300 hover:border-gray-400 shadow-md hover:shadow-lg p-4 transition duration-200 ease-in-out',
648
+ 'flex items-center w-full max-w-4xl py-6 mb-4, justify-between',
649
+ 'flex-col space-y-2 sm:space-y-0 sm:flex-row sm:space-x-5',
650
+ ].join(' ')}
651
+ >
652
+ {renders.length > 0 && (
653
+ <Button
654
+ primary
655
+ onClick={undo}
656
+ icon={
657
+ <svg
658
+ className="w-6 h-6"
659
+ width="19"
660
+ height="9"
661
+ viewBox="0 0 19 9"
662
+ fill="none"
663
+ xmlns="http://www.w3.org/2000/svg"
664
+ >
665
+ <path
666
+ 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"
667
+ fill="currentColor"
668
+ />
669
+ </svg>
670
+ }
671
+ >
672
+ {m.undo()}
673
+ </Button>
674
+ )}
675
+ <Slider
676
+ label={m.bruch_size()}
677
+ min={10}
678
+ max={200}
679
+ value={brushSize}
680
+ onChange={handleSliderChange}
681
+ onStart={handleSliderStart}
682
+ />
683
+ <Button
684
+ primary={showOriginal}
685
+ icon={<EyeIcon className="w-6 h-6" />}
686
+ onUp={() => {
687
+ setShowOriginal(!showOriginal)
688
+ setTimeout(() => setSeparatorLeft(0), 300)
689
+ }}
690
+ >
691
+ {m.original()}
692
+ </Button>
693
+ {!showOriginal && (
694
+ <Button onUp={onSuperResolution}>{m.upscale()}</Button>
695
+ )}
696
+
697
+ <Button
698
+ primary
699
+ icon={<DownloadIcon className="w-6 h-6" />}
700
+ onClick={download}
701
+ >
702
+ {m.download()}
703
+ </Button>
704
+ </div>
705
+ </div>
706
+ )
707
+ }
src/adapters/cache.ts ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import localforage from 'localforage'
2
+
3
+ export type modelType = 'inpaint' | 'superResolution'
4
+
5
+ localforage.config({
6
+ name: 'modelCache',
7
+ })
8
+
9
+ export async function saveModel(modelType: modelType, modelBlob: ArrayBuffer) {
10
+ await localforage.setItem(getModel(modelType).name, modelBlob)
11
+ }
12
+
13
+ function getModel(modelType: modelType) {
14
+ if (modelType === 'inpaint') {
15
+ const modelList = [
16
+ {
17
+ name: 'model',
18
+ url: 'https://huggingface.co/lxfater/inpaint-web/resolve/main/migan.onnx',
19
+ backupUrl: '',
20
+ },
21
+ {
22
+ name: 'model-perf',
23
+ url: 'https://huggingface.co/andraniksargsyan/migan/resolve/main/migan.onnx',
24
+ backupUrl: '',
25
+ },
26
+ {
27
+ name: 'migan-pipeline-v2',
28
+ url: 'https://huggingface.co/andraniksargsyan/migan/resolve/main/migan_pipeline_v2.onnx',
29
+ backupUrl:
30
+ 'https://worker-share-proxy-01f5.lxfater.workers.dev/andraniksargsyan/migan/resolve/main/migan_pipeline_v2.onnx',
31
+ },
32
+ ]
33
+ const currentModel = modelList[2]
34
+ return currentModel
35
+ }
36
+ if (modelType === 'superResolution') {
37
+ const modelList = [
38
+ {
39
+ name: 'realesrgan-x4',
40
+ url: 'https://huggingface.co/lxfater/inpaint-web/resolve/main/realesrgan-x4.onnx',
41
+ backupUrl:
42
+ 'https://worker-share-proxy-01f5.lxfater.workers.dev/lxfater/inpaint-web/resolve/main/realesrgan-x4.onnx',
43
+ },
44
+ ]
45
+ const currentModel = modelList[0]
46
+ return currentModel
47
+ }
48
+ throw new Error('wrong modelType')
49
+ }
50
+
51
+ export async function loadModel(modelType: modelType): Promise<ArrayBuffer> {
52
+ const model = (await localforage.getItem(
53
+ getModel(modelType).name
54
+ )) as ArrayBuffer
55
+ return model
56
+ }
57
+
58
+ export async function modelExists(modelType: modelType) {
59
+ const model = await loadModel(modelType)
60
+ return model !== null && model !== undefined
61
+ }
62
+
63
+ export async function ensureModel(modelType: modelType) {
64
+ if (await modelExists(modelType)) {
65
+ return loadModel(modelType)
66
+ }
67
+ const model = getModel(modelType)
68
+ const response = await fetch(model.url)
69
+ const buffer = await response.arrayBuffer()
70
+ await saveModel(modelType, buffer)
71
+ return buffer
72
+ }
73
+
74
+ export async function downloadModel(
75
+ modelType: modelType,
76
+ setDownloadProgress: (arg0: number) => void
77
+ ) {
78
+ if (await modelExists(modelType)) {
79
+ return
80
+ }
81
+
82
+ async function downloadFromUrl(url: string) {
83
+ console.log('start download from', url)
84
+ setDownloadProgress(0)
85
+ const response = await fetch(url)
86
+ const fullSize = response.headers.get('content-length')
87
+ const reader = response.body!.getReader()
88
+ const total: Uint8Array[] = []
89
+ let downloaded = 0
90
+
91
+ while (true) {
92
+ const { done, value } = await reader.read()
93
+
94
+ if (done) {
95
+ break
96
+ }
97
+
98
+ downloaded += value?.length || 0
99
+
100
+ if (value) {
101
+ total.push(value)
102
+ }
103
+
104
+ setDownloadProgress((downloaded / Number(fullSize)) * 100)
105
+ }
106
+
107
+ const buffer = new Uint8Array(downloaded)
108
+ let offset = 0
109
+ for (const chunk of total) {
110
+ buffer.set(chunk, offset)
111
+ offset += chunk.length
112
+ }
113
+
114
+ await saveModel(modelType, buffer)
115
+ setDownloadProgress(100)
116
+ }
117
+
118
+ const model = getModel(modelType)
119
+ try {
120
+ await downloadFromUrl(model.url)
121
+ } catch (e) {
122
+ if (model.backupUrl) {
123
+ try {
124
+ await downloadFromUrl(model.backupUrl)
125
+ } catch (r) {
126
+ alert(`Failed to download the backup model: ${r}`)
127
+ }
128
+ }
129
+ alert(`Failed to download the model, network problem: ${e}`)
130
+ }
131
+ }
src/adapters/inpainting.ts ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // @ts-nocheck
2
+ /* eslint-disable camelcase */
3
+ /* eslint-disable no-plusplus */
4
+ import cv, { Mat } from 'opencv-ts'
5
+ import { ensureModel } from './cache'
6
+ import { getCapabilities } from './util'
7
+ import type { modelType } from './cache'
8
+ // ort.env.debug = true
9
+ // ort.env.logLevel = 'verbose'
10
+ // ort.env.webgpu.profilingMode = 'default'
11
+
12
+ function loadImage(url: string): Promise<HTMLImageElement> {
13
+ return new Promise((resolve, reject) => {
14
+ const img = new Image()
15
+ img.crossOrigin = 'Anonymous'
16
+ img.onload = () => resolve(img)
17
+ img.onerror = () => reject(new Error(`Failed to load image from ${url}`))
18
+ img.src = url
19
+ })
20
+ }
21
+ function imgProcess(img: Mat) {
22
+ const channels = new cv.MatVector()
23
+ cv.split(img, channels) // 分割通道
24
+
25
+ const C = channels.size() // 通道数
26
+ const H = img.rows // 图像高度
27
+ const W = img.cols // 图像宽度
28
+
29
+ const chwArray = new Uint8Array(C * H * W) // 创建新的数组来存储转换后的数据
30
+
31
+ for (let c = 0; c < C; c++) {
32
+ const channelData = channels.get(c).data // 获取单个通道的数据
33
+ for (let h = 0; h < H; h++) {
34
+ for (let w = 0; w < W; w++) {
35
+ chwArray[c * H * W + h * W + w] = channelData[h * W + w]
36
+ // chwArray[c * H * W + h * W + w] = channelData[h * W + w]
37
+ }
38
+ }
39
+ }
40
+
41
+ channels.delete() // 清理内存
42
+ return chwArray // 返回转换后的数据
43
+ }
44
+ function markProcess(img: Mat) {
45
+ const channels = new cv.MatVector()
46
+ cv.split(img, channels) // 分割通道
47
+
48
+ const C = 1 // 通道数
49
+ const H = img.rows // 图像高度
50
+ const W = img.cols // 图像宽度
51
+
52
+ const chwArray = new Uint8Array(C * H * W) // 创建新的数组来存储转换后的数据
53
+
54
+ for (let c = 0; c < C; c++) {
55
+ const channelData = channels.get(0).data // 获取单个通道的数据
56
+ for (let h = 0; h < H; h++) {
57
+ for (let w = 0; w < W; w++) {
58
+ chwArray[c * H * W + h * W + w] = (channelData[h * W + w] !== 255) * 255
59
+ }
60
+ }
61
+ }
62
+
63
+ channels.delete() // 清理内存
64
+ return chwArray // 返回转换后的数据
65
+ }
66
+ function processImage(
67
+ img: HTMLImageElement,
68
+ canvasId?: string
69
+ ): Promise<Uint8Array> {
70
+ return new Promise((resolve, reject) => {
71
+ try {
72
+ const src = cv.imread(img)
73
+ const src_rgb = new cv.Mat()
74
+ // 将图像从RGBA转换为RGB
75
+ cv.cvtColor(src, src_rgb, cv.COLOR_RGBA2RGB)
76
+ if (canvasId) {
77
+ cv.imshow(canvasId, src_rgb)
78
+ }
79
+ resolve(imgProcess(src_rgb))
80
+
81
+ src.delete()
82
+ src_rgb.delete()
83
+ } catch (error) {
84
+ reject(error)
85
+ }
86
+ })
87
+ }
88
+
89
+ function processMark(
90
+ img: HTMLImageElement,
91
+ canvasId?: string
92
+ ): Promise<Uint8Array> {
93
+ return new Promise((resolve, reject) => {
94
+ try {
95
+ const src = cv.imread(img)
96
+ const src_grey = new cv.Mat()
97
+
98
+ // 将图像从RGBA转换为二值化
99
+ cv.cvtColor(src, src_grey, cv.COLOR_BGR2GRAY)
100
+
101
+ if (canvasId) {
102
+ cv.imshow(canvasId, src_grey)
103
+ }
104
+
105
+ resolve(markProcess(src_grey))
106
+
107
+ src.delete()
108
+ } catch (error) {
109
+ reject(error)
110
+ }
111
+ })
112
+ }
113
+ function postProcess(uint8Data: Uint8Array, width: number, height: number) {
114
+ const chwToHwcData = []
115
+ const size = width * height
116
+
117
+ for (let h = 0; h < height; h++) {
118
+ for (let w = 0; w < width; w++) {
119
+ for (let c = 0; c < 3; c++) {
120
+ // RGB通道
121
+ const chwIndex = c * size + h * width + w
122
+ const pixelVal = uint8Data[chwIndex]
123
+ let newPiex = pixelVal
124
+ if (pixelVal > 255) {
125
+ newPiex = 255
126
+ } else if (pixelVal < 0) {
127
+ newPiex = 0
128
+ }
129
+ chwToHwcData.push(newPiex) // 归一化反转
130
+ }
131
+ chwToHwcData.push(255) // Alpha通道
132
+ }
133
+ }
134
+ return chwToHwcData
135
+ }
136
+
137
+ function imageDataToDataURL(imageData) {
138
+ // 创建 canvas
139
+ const canvas = document.createElement('canvas')
140
+ canvas.width = imageData.width
141
+ canvas.height = imageData.height
142
+
143
+ // 绘制 imageData 到 canvas
144
+ const ctx = canvas.getContext('2d')
145
+ ctx.putImageData(imageData, 0, 0)
146
+
147
+ // 导出为数据 URL
148
+ return canvas.toDataURL()
149
+ }
150
+
151
+ function configEnv(capabilities) {
152
+ ort.env.wasm.wasmPaths =
153
+ 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.16.3/dist/'
154
+ if (capabilities.webgpu) {
155
+ ort.env.wasm.numThreads = 1
156
+ } else {
157
+ if (capabilities.threads) {
158
+ ort.env.wasm.numThreads = navigator.hardwareConcurrency ?? 4
159
+ }
160
+ if (capabilities.simd) {
161
+ ort.env.wasm.simd = true
162
+ }
163
+ ort.env.wasm.proxy = true
164
+ }
165
+ console.log('env', ort.env.wasm)
166
+ }
167
+ const resizeMark = (
168
+ image: HTMLImageElement,
169
+ width: number,
170
+ height: number
171
+ ): Promise<HTMLImageElement> => {
172
+ return new Promise((resolve, reject) => {
173
+ const canvas = document.createElement('canvas')
174
+ canvas.width = width
175
+ canvas.height = height
176
+
177
+ // 将图片绘制到canvas上,并调整大小
178
+ const ctx = canvas.getContext('2d')
179
+ if (!ctx) {
180
+ reject(new Error('Unable to get canvas context'))
181
+ return
182
+ }
183
+ ctx.drawImage(image, 0, 0, width, height)
184
+
185
+ // 获取调整大小后的图片URL
186
+ const resizedImageUrl = canvas.toDataURL()
187
+
188
+ // 创建一个新的Image对象并设置其src为调整大小后的图片URL
189
+ const resizedImage = new Image()
190
+ resizedImage.onload = () => resolve(resizedImage)
191
+ resizedImage.onerror = () =>
192
+ reject(new Error('Failed to load resized image'))
193
+ resizedImage.src = resizedImageUrl
194
+ })
195
+ }
196
+ let model: ArrayBuffer | null = null
197
+ export default async function inpaint(
198
+ imageFile: File | HTMLImageElement,
199
+ maskBase64: string
200
+ ) {
201
+ console.time('sessionCreate')
202
+ if (!model) {
203
+ const capabilities = await getCapabilities()
204
+ configEnv(capabilities)
205
+ const modelBuffer = await ensureModel('inpaint')
206
+ model = await ort.InferenceSession.create(modelBuffer, {
207
+ executionProviders: [capabilities.webgpu ? 'webgpu' : 'wasm'],
208
+ })
209
+ }
210
+ console.timeEnd('sessionCreate')
211
+ console.time('preProcess')
212
+
213
+ const [originalImg, originalMark] = await Promise.all([
214
+ imageFile instanceof HTMLImageElement
215
+ ? imageFile
216
+ : loadImage(URL.createObjectURL(imageFile)),
217
+ loadImage(maskBase64),
218
+ ])
219
+
220
+ const [img, mark] = await Promise.all([
221
+ processImage(originalImg),
222
+ processMark(
223
+ await resizeMark(originalMark, originalImg.width, originalImg.height)
224
+ ),
225
+ ])
226
+
227
+ const imageTensor = new ort.Tensor('uint8', img, [
228
+ 1,
229
+ 3,
230
+ originalImg.height,
231
+ originalImg.width,
232
+ ])
233
+
234
+ const maskTensor = new ort.Tensor('uint8', mark, [
235
+ 1,
236
+ 1,
237
+ originalImg.height,
238
+ originalImg.width,
239
+ ])
240
+
241
+ const Feed: {
242
+ [key: string]: any
243
+ } = {
244
+ [model.inputNames[0]]: imageTensor,
245
+ [model.inputNames[1]]: maskTensor,
246
+ }
247
+
248
+ console.timeEnd('preProcess')
249
+
250
+ console.time('run')
251
+ const results = await model.run(Feed)
252
+ console.timeEnd('run')
253
+
254
+ console.time('postProcess')
255
+ const outsTensor = results[model.outputNames[0]]
256
+ const chwToHwcData = postProcess(
257
+ outsTensor.data,
258
+ originalImg.width,
259
+ originalImg.height
260
+ )
261
+ const imageData = new ImageData(
262
+ new Uint8ClampedArray(chwToHwcData),
263
+ originalImg.width,
264
+ originalImg.height
265
+ )
266
+ console.log(imageData, 'imageData')
267
+ const result = imageDataToDataURL(imageData)
268
+ console.timeEnd('postProcess')
269
+
270
+ return result
271
+ }
src/adapters/superResolution.ts ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-console */
2
+ /* eslint-disable no-plusplus */
3
+ import cv, { Mat } from 'opencv-ts'
4
+ import { getCapabilities } from './util'
5
+ import { ensureModel } from './cache'
6
+
7
+ function loadImage(url: string): Promise<HTMLImageElement> {
8
+ return new Promise((resolve, reject) => {
9
+ const img = new Image()
10
+ img.crossOrigin = 'Anonymous'
11
+ img.onload = () => resolve(img)
12
+ img.onerror = () => reject(new Error(`Failed to load image from ${url}`))
13
+ img.src = url
14
+ })
15
+ }
16
+ function imgProcess(img: Mat) {
17
+ const channels = new cv.MatVector()
18
+ cv.split(img, channels) // 分割通道
19
+
20
+ const C = channels.size() // 通道数
21
+ const H = img.rows // 图像高度
22
+ const W = img.cols // 图像宽度
23
+
24
+ const chwArray = new Float32Array(C * H * W) // 创建新的数组来存储转换后的数据
25
+
26
+ for (let c = 0; c < C; c++) {
27
+ const channelData = channels.get(c).data // 获取单个通道的数据
28
+ for (let h = 0; h < H; h++) {
29
+ for (let w = 0; w < W; w++) {
30
+ chwArray[c * H * W + h * W + w] = channelData[h * W + w] / 255.0
31
+ // chwArray[c * H * W + h * W + w] = channelData[h * W + w]
32
+ }
33
+ }
34
+ }
35
+
36
+ channels.delete() // 清理内存
37
+ return chwArray // 返回转换后的数据
38
+ }
39
+ async function tileProc(
40
+ inputTensor: ort.Tensor,
41
+ session: ort.InferenceSession,
42
+ callback: (progress: number) => void
43
+ ) {
44
+ const inputDims = inputTensor.dims
45
+ const imageW = inputDims[3]
46
+ const imageH = inputDims[2]
47
+
48
+ const rOffset = 0
49
+ const gOffset = imageW * imageH
50
+ const bOffset = imageW * imageH * 2
51
+
52
+ const outputDims = [
53
+ inputDims[0],
54
+ inputDims[1],
55
+ inputDims[2] * 4,
56
+ inputDims[3] * 4,
57
+ ]
58
+ const outputTensor = new ort.Tensor(
59
+ 'float32',
60
+ new Float32Array(
61
+ outputDims[0] * outputDims[1] * outputDims[2] * outputDims[3]
62
+ ),
63
+ outputDims
64
+ )
65
+
66
+ const outImageW = outputDims[3]
67
+ const outImageH = outputDims[2]
68
+ const outROffset = 0
69
+ const outGOffset = outImageW * outImageH
70
+ const outBOffset = outImageW * outImageH * 2
71
+
72
+ const tileSize = 64
73
+ const tilePadding = 6
74
+ const tileSizePre = tileSize - tilePadding * 2
75
+
76
+ const tilesx = Math.ceil(inputDims[3] / tileSizePre)
77
+ const tilesy = Math.ceil(inputDims[2] / tileSizePre)
78
+
79
+ const { data } = inputTensor
80
+
81
+ console.log(inputTensor)
82
+ const numTiles = tilesx * tilesy
83
+ let currentTile = 0
84
+
85
+ for (let i = 0; i < tilesx; i++) {
86
+ for (let j = 0; j < tilesy; j++) {
87
+ const ti = Date.now()
88
+ const tileW = Math.min(tileSizePre, imageW - i * tileSizePre)
89
+ const tileH = Math.min(tileSizePre, imageH - j * tileSizePre)
90
+ console.log(`tileW: ${tileW} tileH: ${tileH}`)
91
+ const tileROffset = 0
92
+ const tileGOffset = tileSize * tileSize
93
+ const tileBOffset = tileSize * tileSize * 2
94
+
95
+ // padding tile 转移到上面的数据上
96
+ const tileData = new Float32Array(tileSize * tileSize * 3)
97
+ for (let xp = -tilePadding; xp < tileSizePre + tilePadding; xp++) {
98
+ for (let yp = -tilePadding; yp < tileSizePre + tilePadding; yp++) {
99
+ // 计算在data中的一维坐标,防止边缘溢出
100
+ let xim = i * tileSizePre + xp
101
+ if (xim < 0) xim = 0
102
+ else if (xim >= imageW) xim = imageW - 1
103
+
104
+ // 计算在data中的一维坐标,防止边缘溢出
105
+ let yim = j * tileSizePre + yp
106
+ if (yim < 0) yim = 0
107
+ else if (yim >= imageH) yim = imageH - 1
108
+
109
+ const idx = xim + yim * imageW
110
+
111
+ const xt = xp + tilePadding
112
+ const yt = yp + tilePadding
113
+ // const idx = (i * tileSize + x) + (j * tileSize + y) * imageW;
114
+ // 主要转化到一维的坐标上,
115
+ tileData[xt + yt * tileSize + tileROffset] = data[idx + rOffset]
116
+ tileData[xt + yt * tileSize + tileGOffset] = data[idx + gOffset]
117
+ tileData[xt + yt * tileSize + tileBOffset] = data[idx + bOffset]
118
+ }
119
+ }
120
+
121
+ const tile = new ort.Tensor('float32', tileData, [
122
+ 1,
123
+ 3,
124
+ tileSize,
125
+ tileSize,
126
+ ])
127
+ const r = await session.run({ 'input.1': tile })
128
+ const results = {
129
+ output: r['1895'],
130
+ }
131
+ console.log(`pre dims:${results.output.dims}`)
132
+
133
+ const outTileW = tileW * 4
134
+ const outTileH = tileH * 4
135
+ const outTileSize = tileSize * 4
136
+ const outTileSizePre = tileSizePre * 4
137
+
138
+ const outTileROffset = 0
139
+ const outTileGOffset = outTileSize * outTileSize
140
+ const outTileBOffset = outTileSize * outTileSize * 2
141
+
142
+ // add tile to output,直接输出
143
+ for (let x = 0; x < outTileW; x++) {
144
+ for (let y = 0; y < outTileH; y++) {
145
+ const xim = i * outTileSizePre + x
146
+ const yim = j * outTileSizePre + y
147
+ const idx = xim + yim * outImageW
148
+ const xt = x + tilePadding * 4
149
+ const yt = y + tilePadding * 4
150
+ outputTensor.data[idx + outROffset] =
151
+ results.output.data[xt + yt * outTileSize + outTileROffset]
152
+ outputTensor.data[idx + outGOffset] =
153
+ results.output.data[xt + yt * outTileSize + outTileGOffset]
154
+ outputTensor.data[idx + outBOffset] =
155
+ results.output.data[xt + yt * outTileSize + outTileBOffset]
156
+ }
157
+ }
158
+ currentTile++
159
+ const dt = Date.now() - ti
160
+ const remTime = (numTiles - currentTile) * dt
161
+ console.log(
162
+ `tile ${currentTile} of ${numTiles} took ${dt} ms, remaining time: ${remTime} ms`
163
+ )
164
+ callback(Math.round(100 * (currentTile / numTiles)))
165
+ }
166
+ }
167
+ console.log(`output dims:${outputTensor.dims}`)
168
+ return outputTensor
169
+ }
170
+ function processImage(
171
+ img: HTMLImageElement,
172
+ canvasId?: string
173
+ ): Promise<Uint8Array> {
174
+ return new Promise((resolve, reject) => {
175
+ try {
176
+ const src = cv.imread(img)
177
+ // eslint-disable-next-line camelcase
178
+ const src_rgb = new cv.Mat()
179
+ // 将图像从RGBA转换为RGB
180
+ cv.cvtColor(src, src_rgb, cv.COLOR_RGBA2RGB)
181
+ if (canvasId) {
182
+ cv.imshow(canvasId, src_rgb)
183
+ }
184
+ resolve(imgProcess(src_rgb))
185
+
186
+ src.delete()
187
+ src_rgb.delete()
188
+ } catch (error) {
189
+ reject(error)
190
+ }
191
+ })
192
+ }
193
+ function configEnv(capabilities: {
194
+ webgpu: any
195
+ wasm?: boolean
196
+ simd: any
197
+ threads: any
198
+ }) {
199
+ ort.env.wasm.wasmPaths =
200
+ 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.16.3/dist/'
201
+ if (capabilities.webgpu) {
202
+ ort.env.wasm.numThreads = 1
203
+ } else {
204
+ if (capabilities.threads) {
205
+ ort.env.wasm.numThreads = navigator.hardwareConcurrency ?? 4
206
+ }
207
+ if (capabilities.simd) {
208
+ ort.env.wasm.simd = true
209
+ }
210
+ ort.env.wasm.proxy = true
211
+ }
212
+ console.log('env', ort.env.wasm)
213
+ }
214
+ function postProcess(floatData: Float32Array, width: number, height: number) {
215
+ const chwToHwcData = []
216
+ const size = width * height
217
+
218
+ for (let h = 0; h < height; h++) {
219
+ for (let w = 0; w < width; w++) {
220
+ for (let c = 0; c < 3; c++) {
221
+ // RGB通道
222
+ const chwIndex = c * size + h * width + w
223
+ const pixelVal = floatData[chwIndex]
224
+ let newPiex = pixelVal
225
+ if (pixelVal > 1) {
226
+ newPiex = 1
227
+ } else if (pixelVal < 0) {
228
+ newPiex = 0
229
+ }
230
+ chwToHwcData.push(newPiex * 255) // 归一化反转
231
+ }
232
+ chwToHwcData.push(255) // Alpha通道
233
+ }
234
+ }
235
+ return chwToHwcData
236
+ }
237
+
238
+ function imageDataToDataURL(imageData: ImageData) {
239
+ // 创建 canvas
240
+ const canvas = document.createElement('canvas')
241
+ canvas.width = imageData.width
242
+ canvas.height = imageData.height
243
+
244
+ // 绘制 imageData 到 canvas
245
+ const ctx = canvas.getContext('2d')
246
+ ctx.putImageData(imageData, 0, 0)
247
+
248
+ // 导出为数据 URL
249
+ return canvas.toDataURL()
250
+ }
251
+ let model: ArrayBuffer | null = null
252
+ export default async function superResolution(
253
+ imageFile: File | HTMLImageElement,
254
+ callback: (progress: number) => void
255
+ ) {
256
+ console.time('sessionCreate')
257
+ if (!model) {
258
+ const capabilities = await getCapabilities()
259
+ configEnv(capabilities)
260
+ const modelBuffer = await ensureModel('superResolution')
261
+ model = await ort.InferenceSession.create(modelBuffer, {
262
+ executionProviders: [capabilities.webgpu ? 'webgpu' : 'wasm'],
263
+ })
264
+ }
265
+ console.timeEnd('sessionCreate')
266
+
267
+ const img =
268
+ imageFile instanceof HTMLImageElement
269
+ ? imageFile
270
+ : await loadImage(URL.createObjectURL(imageFile))
271
+ const imageTersorData = await processImage(img)
272
+ const imageTensor = new ort.Tensor('float32', imageTersorData, [
273
+ 1,
274
+ 3,
275
+ img.height,
276
+ img.width,
277
+ ])
278
+
279
+ const result = await tileProc(imageTensor, model, callback)
280
+ console.time('postProcess')
281
+ const outsTensor = result
282
+ const chwToHwcData = postProcess(
283
+ outsTensor.data,
284
+ img.width * 4,
285
+ img.height * 4
286
+ )
287
+ const imageData = new ImageData(
288
+ new Uint8ClampedArray(chwToHwcData),
289
+ img.width * 4,
290
+ img.height * 4
291
+ )
292
+ console.log(imageData, 'imageData')
293
+ const url = imageDataToDataURL(imageData)
294
+ console.timeEnd('postProcess')
295
+
296
+ return url
297
+ }
src/adapters/util.ts ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export async function checkWebgpu() {
2
+ // @ts-ignore
3
+ if (!navigator.gpu) {
4
+ return false
5
+ }
6
+ // @ts-ignore
7
+ const adapter = await navigator.gpu.requestAdapter()
8
+ if (!adapter) {
9
+ return false
10
+ }
11
+ return true
12
+ }
13
+ export const wasm = () =>
14
+ typeof WebAssembly === 'object' &&
15
+ typeof WebAssembly.instantiate === 'function'
16
+ export const threads = () =>
17
+ (async e => {
18
+ try {
19
+ return (
20
+ typeof MessageChannel !== 'undefined' &&
21
+ new MessageChannel().port1.postMessage(new SharedArrayBuffer(1)),
22
+ WebAssembly.validate(e)
23
+ )
24
+ // eslint-disable-next-line @typescript-eslint/no-shadow
25
+ } catch (e) {
26
+ return !1
27
+ }
28
+ })(
29
+ new Uint8Array([
30
+ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 5, 4, 1, 3, 1,
31
+ 1, 10, 11, 1, 9, 0, 65, 0, 254, 16, 2, 0, 26, 11,
32
+ ])
33
+ )
34
+ export const simd = async () =>
35
+ WebAssembly.validate(
36
+ new Uint8Array([
37
+ 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10,
38
+ 1, 8, 0, 65, 0, 253, 15, 253, 98, 11,
39
+ ])
40
+ )
41
+
42
+ export const getCapabilities = async () => {
43
+ return {
44
+ webgpu: await checkWebgpu(),
45
+ wasm: wasm(),
46
+ simd: await simd(),
47
+ threads: await threads(),
48
+ }
49
+ }
50
+ const version = '1.16.3'
51
+ export const getTagSrc = async () => {
52
+ const prefix = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/`
53
+ const capablilities = await getCapabilities()
54
+ if (capablilities.webgpu) {
55
+ return `${prefix}ort.webgpu.min.js`
56
+ }
57
+ if (capablilities.wasm) {
58
+ if (capablilities.simd || capablilities.threads) {
59
+ return `${prefix}ort.wasm.min.js`
60
+ }
61
+ return `${prefix}ort.wasm-core.min.js`
62
+ }
63
+ return `${prefix}ort.min.js`
64
+ }
65
+
66
+ export const loadingOnnxruntime = async () => {
67
+ const script = document.createElement('script')
68
+
69
+ // 设置script标签的属性,例如src
70
+ script.src = await getTagSrc() // 替换为您要加载的脚本的URL
71
+
72
+ // 将script标签添加到文档的head部分
73
+ document.head.appendChild(script)
74
+ }
75
+
76
+ export async function checkGpu() {
77
+ return !navigator?.gpu && !(await navigator.gpu?.requestAdapter())
78
+ }
src/components/Button.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactNode, useState } from 'react'
2
+
3
+ interface ButtonProps {
4
+ children: ReactNode
5
+ className?: string
6
+ icon?: ReactNode
7
+ primary?: boolean
8
+ style?: {
9
+ [key: string]: string
10
+ }
11
+ onClick?: () => void
12
+ onDown?: () => void
13
+ onUp?: () => void
14
+ onEnter?: () => void
15
+ onLeave?: () => void
16
+ }
17
+
18
+ export default function Button(props: ButtonProps) {
19
+ const {
20
+ children,
21
+ className,
22
+ icon,
23
+ primary,
24
+ style,
25
+ onClick,
26
+ onDown,
27
+ onUp,
28
+ onEnter,
29
+ onLeave,
30
+ } = props
31
+ const [active, setActive] = useState(false)
32
+ let background = ''
33
+ if (primary) {
34
+ background = 'bg-primary hover:bg-black hover:text-white'
35
+ }
36
+ if (active) {
37
+ background = 'bg-black text-white'
38
+ }
39
+ if (!primary && !active) {
40
+ background = 'hover:bg-primary'
41
+ }
42
+ return (
43
+ <div
44
+ role="button"
45
+ onKeyDown={() => {
46
+ onDown?.()
47
+ }}
48
+ onClick={onClick}
49
+ onPointerDown={() => {
50
+ setActive(true)
51
+ onDown?.()
52
+ }}
53
+ onPointerUp={() => {
54
+ setActive(false)
55
+ onUp?.()
56
+ }}
57
+ onPointerEnter={() => {
58
+ onEnter?.()
59
+ }}
60
+ onPointerLeave={() => {
61
+ onLeave?.()
62
+ }}
63
+ tabIndex={-1}
64
+ className={[
65
+ 'inline-flex space-x-3 py-3 px-5 rounded-md cursor-pointer',
66
+ background,
67
+ className,
68
+ ].join(' ')}
69
+ style={style}
70
+ >
71
+ {icon}
72
+ <span className="whitespace-nowrap select-none">{children}</span>
73
+ </div>
74
+ )
75
+ }
src/components/FileSelect.tsx ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import * as m from '../paraglide/messages'
3
+
4
+ type FileSelectProps = {
5
+ onSelection: (file: File) => void
6
+ }
7
+
8
+ export default function FileSelect(props: FileSelectProps) {
9
+ const { onSelection } = props
10
+
11
+ const [dragHover, setDragHover] = useState(false)
12
+ const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
13
+
14
+ function onFileSelected(file: File) {
15
+ if (!file) {
16
+ return
17
+ }
18
+ // Skip non-image files
19
+ const isImage = file.type.match('image.*')
20
+ if (!isImage) {
21
+ return
22
+ }
23
+ try {
24
+ // Check if file is larger than 10mb
25
+ if (file.size > 10 * 1024 * 1024) {
26
+ throw new Error('file too large')
27
+ }
28
+ onSelection(file)
29
+ } catch (e) {
30
+ // eslint-disable-next-line
31
+ alert(`error: ${(e as any).message}`)
32
+ }
33
+ }
34
+
35
+ async function getFile(entry: any): Promise<File> {
36
+ return new Promise(resolve => {
37
+ entry.file((file: File) => resolve(file))
38
+ })
39
+ }
40
+
41
+ /* eslint-disable no-await-in-loop */
42
+
43
+ // Drop handler function to get all files
44
+ async function getAllFileEntries(items: DataTransferItemList) {
45
+ const fileEntries: Array<File> = []
46
+ // Use BFS to traverse entire directory/file structure
47
+ const queue = []
48
+ // Unfortunately items is not iterable i.e. no forEach
49
+ for (let i = 0; i < items.length; i += 1) {
50
+ queue.push(items[i].webkitGetAsEntry())
51
+ }
52
+ while (queue.length > 0) {
53
+ const entry = queue.shift()
54
+ if (entry?.isFile) {
55
+ // Only append images
56
+ const file = await getFile(entry)
57
+ fileEntries.push(file)
58
+ } else if (entry?.isDirectory) {
59
+ queue.push(
60
+ ...(await readAllDirectoryEntries((entry as any).createReader()))
61
+ )
62
+ }
63
+ }
64
+ return fileEntries
65
+ }
66
+
67
+ // Get all the entries (files or sub-directories) in a directory
68
+ // by calling readEntries until it returns empty array
69
+ async function readAllDirectoryEntries(directoryReader: any) {
70
+ const entries = []
71
+ let readEntries = await readEntriesPromise(directoryReader)
72
+ while (readEntries.length > 0) {
73
+ entries.push(...readEntries)
74
+ readEntries = await readEntriesPromise(directoryReader)
75
+ }
76
+ return entries
77
+ }
78
+
79
+ /* eslint-enable no-await-in-loop */
80
+
81
+ // Wrap readEntries in a promise to make working with readEntries easier
82
+ // readEntries will return only some of the entries in a directory
83
+ // e.g. Chrome returns at most 100 entries at a time
84
+ async function readEntriesPromise(directoryReader: any): Promise<any> {
85
+ return new Promise((resolve, reject) => {
86
+ directoryReader.readEntries(resolve, reject)
87
+ })
88
+ }
89
+
90
+ async function handleDrop(ev: React.DragEvent) {
91
+ ev.preventDefault()
92
+ const items = await getAllFileEntries(ev.dataTransfer.items)
93
+ setDragHover(false)
94
+ onFileSelected(items[0])
95
+ }
96
+
97
+ return (
98
+ <label
99
+ htmlFor={uploadElemId}
100
+ className="block w-full h-full group relative cursor-pointer rounded-md font-medium focus-within:outline-none"
101
+ >
102
+ <div
103
+ className={[
104
+ 'w-full h-full flex items-center justify-center px-6 pt-5 pb-6 text-xl',
105
+ 'border-4 border-dashed rounded-md',
106
+ 'hover:border-black hover:bg-primary',
107
+ 'text-center',
108
+ dragHover ? 'border-black bg-primary' : 'bg-gray-100 border-gray-300',
109
+ ].join(' ')}
110
+ onDrop={handleDrop}
111
+ onDragOver={ev => {
112
+ ev.stopPropagation()
113
+ ev.preventDefault()
114
+ setDragHover(true)
115
+ }}
116
+ onDragLeave={() => setDragHover(false)}
117
+ >
118
+ <input
119
+ id={uploadElemId}
120
+ name={uploadElemId}
121
+ type="file"
122
+ className="sr-only"
123
+ onChange={ev => {
124
+ const file = ev.currentTarget.files?.[0]
125
+ if (file) {
126
+ onFileSelected(file)
127
+ }
128
+ }}
129
+ accept="image/png, image/jpeg, image/webp"
130
+ />
131
+ <p>{m.drop_zone()}</p>
132
+ </div>
133
+ </label>
134
+ )
135
+ }
src/components/Link.tsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface LinkProps {
2
+ children: string
3
+ href: string
4
+ }
5
+
6
+ export default function Link(props: LinkProps) {
7
+ const { children, href } = props
8
+ return (
9
+ <a href={href} className="font-black underline">
10
+ {children}
11
+ </a>
12
+ )
13
+ }
src/components/Logo.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function Logo() {
2
+ return (
3
+ <svg
4
+ width="421"
5
+ height="78"
6
+ viewBox="0 0 421 78"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <path d="M102 61H414" stroke="#BDFF01" strokeWidth="9" />
11
+ <path
12
+ d="M127.767 43.728C127.335 45.576 126.591 47.064 125.535 48.192C124.503 49.32 123.243 50.136 121.755 50.64C120.267 51.12 118.611 51.36 116.787 51.36C114.291 51.36 112.131 50.88 110.307 49.92C108.507 48.96 107.115 47.568 106.131 45.744C105.171 43.92 104.691 41.712 104.691 39.12C104.691 36.528 105.171 34.32 106.131 32.496C107.115 30.672 108.507 29.28 110.307 28.32C112.131 27.36 114.291 26.88 116.787 26.88C118.539 26.88 120.171 27.132 121.683 27.636C123.195 28.116 124.467 28.884 125.499 29.94C126.555 30.996 127.251 32.376 127.587 34.08L120.387 36.888C120.195 35.328 119.799 34.32 119.199 33.864C118.623 33.408 117.891 33.18 117.003 33.18C115.755 33.18 114.819 33.66 114.195 34.62C113.571 35.556 113.259 37.056 113.259 39.12C113.259 41.16 113.547 42.66 114.123 43.62C114.723 44.58 115.743 45.06 117.183 45.06C118.047 45.06 118.755 44.808 119.307 44.304C119.859 43.776 120.207 42.948 120.351 41.82L127.767 43.728ZM137.694 25.8V43.836C137.694 44.556 137.802 45.084 138.018 45.42C138.234 45.756 138.618 45.924 139.17 45.924C139.434 45.924 139.662 45.912 139.854 45.888C140.07 45.84 140.274 45.792 140.466 45.744L140.106 50.208C139.65 50.544 138.99 50.82 138.126 51.036C137.286 51.252 136.458 51.36 135.642 51.36C133.41 51.36 131.814 50.88 130.854 49.92C129.894 48.96 129.414 47.352 129.414 45.096V25.8H137.694ZM151.295 51.36C148.031 51.36 145.499 50.544 143.699 48.912C141.899 47.28 140.999 44.976 140.999 42C140.999 40.008 141.407 38.316 142.223 36.924C143.063 35.532 144.227 34.476 145.715 33.756C147.227 33.012 148.979 32.64 150.971 32.64C153.011 32.64 154.739 33.012 156.155 33.756C157.595 34.476 158.687 35.496 159.431 36.816C160.175 38.136 160.547 39.672 160.547 41.424C160.547 41.808 160.523 42.204 160.475 42.612C160.451 43.02 160.415 43.344 160.367 43.584H148.919C149.039 44.52 149.303 45.132 149.711 45.42C150.119 45.708 150.683 45.852 151.403 45.852C152.579 45.852 153.299 45.336 153.563 44.304L160.223 46.104C159.959 47.28 159.371 48.264 158.459 49.056C157.547 49.824 156.455 50.4 155.183 50.784C153.935 51.168 152.639 51.36 151.295 51.36ZM151.007 38.112C150.383 38.112 149.903 38.292 149.567 38.652C149.255 38.988 149.039 39.612 148.919 40.524H152.807C152.735 39.732 152.567 39.132 152.303 38.724C152.039 38.316 151.607 38.112 151.007 38.112ZM172.322 39.768C172.322 38.592 171.806 38.004 170.774 38.004C170.27 38.004 169.85 38.148 169.514 38.436C169.178 38.724 168.986 39.3 168.938 40.164L161.954 39.156C162.122 37.164 162.986 35.58 164.546 34.404C166.106 33.228 168.374 32.64 171.35 32.64C174.47 32.64 176.774 33.276 178.262 34.548C179.774 35.796 180.53 37.452 180.53 39.516V45.096C180.53 46.08 180.998 46.572 181.934 46.572C182.198 46.572 182.402 46.536 182.546 46.464L182.186 50.712C181.298 51.144 180.23 51.36 178.982 51.36C177.59 51.36 176.402 51.12 175.418 50.64C174.434 50.16 173.774 49.428 173.438 48.444C172.958 49.308 172.202 50.016 171.17 50.568C170.138 51.096 168.962 51.36 167.642 51.36C165.746 51.36 164.282 50.94 163.25 50.1C162.242 49.236 161.738 48.06 161.738 46.572C161.738 45.252 162.158 44.184 162.998 43.368C163.838 42.528 165.05 41.952 166.634 41.64L172.322 40.452V39.768ZM169.514 45.348C169.514 45.732 169.634 46.044 169.874 46.284C170.114 46.524 170.45 46.644 170.882 46.644C171.29 46.644 171.626 46.5 171.89 46.212C172.178 45.924 172.322 45.516 172.322 44.988V43.404L170.918 43.728C169.982 43.968 169.514 44.508 169.514 45.348ZM183.73 51V33H191.182L191.362 36.924C191.89 35.556 192.682 34.5 193.738 33.756C194.794 33.012 196.09 32.64 197.626 32.64C199.402 32.64 200.782 33.168 201.766 34.224C202.774 35.28 203.278 36.768 203.278 38.688V51H194.998V40.74C194.998 39.972 194.89 39.456 194.674 39.192C194.458 38.904 194.134 38.76 193.702 38.76C193.102 38.76 192.67 38.976 192.406 39.408C192.142 39.816 192.01 40.512 192.01 41.496V51H183.73ZM228.95 27.24V40.776C228.95 47.832 225.122 51.36 217.466 51.36C209.81 51.36 205.982 47.832 205.982 40.776V27.24H214.406V41.424C214.406 43.848 215.426 45.06 217.466 45.06C219.53 45.06 220.562 43.848 220.562 41.424V27.24H228.95ZM231.683 58.56V33H239.207L239.387 36.6C239.891 35.4 240.623 34.44 241.583 33.72C242.543 33 243.707 32.64 245.075 32.64C247.283 32.64 248.975 33.456 250.151 35.088C251.351 36.696 251.951 38.964 251.951 41.892C251.951 44.892 251.351 47.22 250.151 48.876C248.951 50.532 247.247 51.36 245.039 51.36C243.791 51.36 242.747 51.06 241.907 50.46C241.067 49.836 240.419 49.02 239.963 48.012V58.56H231.683ZM241.727 45.528C242.303 45.528 242.759 45.3 243.095 44.844C243.431 44.364 243.599 43.416 243.599 42C243.599 40.584 243.431 39.636 243.095 39.156C242.759 38.676 242.303 38.436 241.727 38.436C241.175 38.436 240.743 38.724 240.431 39.3C240.119 39.852 239.963 40.752 239.963 42C239.963 44.352 240.551 45.528 241.727 45.528ZM257.901 43.584C259.317 43.584 260.409 43.92 261.177 44.592C261.945 45.264 262.329 46.212 262.329 47.436C262.329 48.66 261.945 49.608 261.177 50.28C260.409 50.952 259.317 51.288 257.901 51.288C256.509 51.288 255.417 50.952 254.625 50.28C253.857 49.608 253.473 48.66 253.473 47.436C253.473 46.212 253.857 45.264 254.625 44.592C255.417 43.92 256.509 43.584 257.901 43.584ZM264.801 58.56V33H272.325L272.505 36.6C273.009 35.4 273.741 34.44 274.701 33.72C275.661 33 276.825 32.64 278.193 32.64C280.401 32.64 282.093 33.456 283.269 35.088C284.469 36.696 285.069 38.964 285.069 41.892C285.069 44.892 284.469 47.22 283.269 48.876C282.069 50.532 280.365 51.36 278.157 51.36C276.909 51.36 275.865 51.06 275.025 50.46C274.185 49.836 273.537 49.02 273.081 48.012V58.56H264.801ZM274.845 45.528C275.421 45.528 275.877 45.3 276.213 44.844C276.549 44.364 276.717 43.416 276.717 42C276.717 40.584 276.549 39.636 276.213 39.156C275.877 38.676 275.421 38.436 274.845 38.436C274.293 38.436 273.861 38.724 273.549 39.3C273.237 39.852 273.081 40.752 273.081 42C273.081 44.352 273.669 45.528 274.845 45.528ZM291.511 31.848C289.879 31.848 288.715 31.584 288.019 31.056C287.347 30.528 287.011 29.64 287.011 28.392C287.011 27.12 287.347 26.232 288.019 25.728C288.715 25.2 289.879 24.936 291.511 24.936C293.143 24.936 294.295 25.2 294.967 25.728C295.663 26.232 296.011 27.12 296.011 28.392C296.011 29.64 295.663 30.528 294.967 31.056C294.295 31.584 293.143 31.848 291.511 31.848ZM295.651 33V51H287.371V33H295.651ZM307.733 32.64C309.941 32.64 311.741 32.952 313.133 33.576C314.525 34.176 315.545 34.944 316.193 35.88C316.865 36.816 317.213 37.776 317.237 38.76L309.605 40.992C309.605 40.032 309.485 39.336 309.245 38.904C309.029 38.448 308.645 38.22 308.093 38.22C307.373 38.22 306.857 38.496 306.545 39.048C306.233 39.6 306.077 40.62 306.077 42.108C306.077 43.14 306.161 43.92 306.329 44.448C306.521 44.976 306.773 45.336 307.085 45.528C307.397 45.696 307.745 45.78 308.129 45.78C309.089 45.78 309.629 44.988 309.749 43.404L317.165 45.708C317.141 46.716 316.757 47.652 316.013 48.516C315.269 49.356 314.225 50.04 312.881 50.568C311.561 51.096 309.989 51.36 308.165 51.36C304.901 51.36 302.345 50.544 300.497 48.912C298.673 47.28 297.761 44.976 297.761 42C297.761 40.008 298.169 38.316 298.985 36.924C299.825 35.532 300.989 34.476 302.477 33.756C303.989 33.012 305.741 32.64 307.733 32.64ZM333.303 33V38.58H328.767V43.656C328.767 44.472 328.935 45.06 329.271 45.42C329.631 45.756 330.183 45.924 330.927 45.924C331.815 45.924 332.571 45.768 333.195 45.456L333.555 50.136C332.955 50.52 332.139 50.82 331.107 51.036C330.075 51.252 329.103 51.36 328.191 51.36C325.671 51.36 323.751 50.88 322.431 49.92C321.135 48.936 320.487 47.28 320.487 44.952V38.58H317.715V33H320.487V29.184L328.767 26.88V33H333.303ZM340.979 51.36C339.107 51.36 337.667 50.88 336.659 49.92C335.651 48.936 335.147 47.352 335.147 45.168V33H343.427V43.26C343.427 43.956 343.535 44.46 343.751 44.772C343.991 45.084 344.339 45.24 344.795 45.24C345.875 45.24 346.415 44.34 346.415 42.54V33H354.695V51H347.243L347.063 47.184C346.007 49.968 343.979 51.36 340.979 51.36ZM357.508 51V33H364.96L365.14 37.5C365.548 36.012 366.172 34.836 367.012 33.972C367.876 33.084 369.016 32.64 370.432 32.64C370.984 32.64 371.404 32.688 371.692 32.784C371.98 32.88 372.184 32.976 372.304 33.072L371.62 39.732C371.404 39.636 371.092 39.552 370.684 39.48C370.276 39.408 369.832 39.372 369.352 39.372C368.272 39.372 367.408 39.624 366.76 40.128C366.112 40.632 365.788 41.376 365.788 42.36V51H357.508ZM383.116 51.36C379.852 51.36 377.32 50.544 375.52 48.912C373.72 47.28 372.82 44.976 372.82 42C372.82 40.008 373.228 38.316 374.044 36.924C374.884 35.532 376.048 34.476 377.536 33.756C379.048 33.012 380.8 32.64 382.792 32.64C384.832 32.64 386.56 33.012 387.976 33.756C389.416 34.476 390.508 35.496 391.252 36.816C391.996 38.136 392.368 39.672 392.368 41.424C392.368 41.808 392.344 42.204 392.296 42.612C392.272 43.02 392.236 43.344 392.188 43.584H380.74C380.86 44.52 381.124 45.132 381.532 45.42C381.94 45.708 382.504 45.852 383.224 45.852C384.4 45.852 385.12 45.336 385.384 44.304L392.044 46.104C391.78 47.28 391.192 48.264 390.28 49.056C389.368 49.824 388.276 50.4 387.004 50.784C385.756 51.168 384.46 51.36 383.116 51.36ZM382.828 38.112C382.204 38.112 381.724 38.292 381.388 38.652C381.076 38.988 380.86 39.612 380.74 40.524H384.628C384.556 39.732 384.388 39.132 384.124 38.724C383.86 38.316 383.428 38.112 382.828 38.112ZM402.666 51.36C400.914 51.36 399.222 51.12 397.59 50.64C395.982 50.16 394.578 49.44 393.378 48.48L396.33 44.196C396.978 44.772 397.83 45.288 398.886 45.744C399.942 46.176 400.998 46.392 402.054 46.392C402.462 46.392 402.822 46.344 403.134 46.248C403.47 46.152 403.638 45.984 403.638 45.744C403.638 45.552 403.542 45.408 403.35 45.312C403.182 45.216 402.798 45.12 402.198 45.024L400.902 44.772C398.286 44.268 396.438 43.548 395.358 42.612C394.278 41.676 393.738 40.416 393.738 38.832C393.738 37.848 394.05 36.888 394.674 35.952C395.322 34.992 396.342 34.2 397.734 33.576C399.126 32.952 400.962 32.64 403.242 32.64C405.114 32.64 406.794 32.88 408.282 33.36C409.77 33.816 410.982 34.452 411.918 35.268L409.074 39.372C408.402 38.844 407.538 38.424 406.482 38.112C405.45 37.776 404.502 37.608 403.638 37.608C403.038 37.608 402.618 37.668 402.378 37.788C402.138 37.884 402.018 38.016 402.018 38.184C402.018 38.352 402.138 38.508 402.378 38.652C402.618 38.796 403.086 38.928 403.782 39.048L406.338 39.48C408.282 39.792 409.698 40.416 410.586 41.352C411.498 42.288 411.954 43.44 411.954 44.808C411.954 45.984 411.63 47.076 410.982 48.084C410.358 49.068 409.362 49.86 407.994 50.46C406.65 51.06 404.874 51.36 402.666 51.36Z"
13
+ fill="black"
14
+ />
15
+ <path
16
+ d="M32.3784 6C24.8503 12.4687 17.1994 18.7776 9.57114 25.1287C9.21086 25.4287 3.51469 29.0986 7.28323 28.7435C11.1275 28.3813 14.9766 26.7755 18.5431 25.4487C26.9472 22.3224 34.9705 18.3498 43.231 14.8888C45.234 14.0496 47.5435 13.1384 49.6037 12.3525C50.3249 12.0774 51.0159 11.658 51.7838 11.5703C52.1318 11.5306 51.2979 12.0741 51.0171 12.2814C47.8543 14.6172 44.5735 16.8155 41.3384 19.0488C33.1146 24.726 24.783 30.251 16.5667 35.9375C15.472 36.6951 12.3341 38.7486 13.6678 38.6753C16.281 38.5316 19.1152 37.518 21.4899 36.542C29.0864 33.4195 35.8808 28.6199 42.8956 24.4413C46.0358 22.5708 49.2021 20.7546 52.4306 19.0369C55.0217 17.6584 57.9194 15.9401 60.7917 15.114C61.8487 14.81 61.15 15.3907 60.6959 15.8488C57.4898 19.0831 53.7876 22.0277 50.2266 24.8443C40.6129 32.448 30.4389 39.306 20.0644 45.8456C17.3593 47.5508 13.2575 50.0488 10.3138 51.7477C9.42941 52.2582 6.68112 53.6127 7.61863 53.2055C14.5146 50.2101 21.3676 47.122 28.114 43.8071C40.6576 37.6437 52.857 30.8108 65.691 25.2354C67.4153 24.4863 70.7591 23.0521 72.8422 22.3791C74.5883 21.815 73.5446 22.4481 72.5068 23.1732C61.8057 30.6501 50.5857 37.5569 39.4218 44.3286C31.8827 48.9015 24.2212 53.2329 16.1594 56.844C15.0664 57.3336 13.9891 57.8913 12.8293 58.1951C10.964 58.6836 16.4296 56.8193 18.2437 56.1684C24.6432 53.8723 30.8867 51.2692 37.1699 48.6781C49.9855 43.3932 62.8729 38.4374 76.1722 34.4679C78.1078 33.8902 80.0538 33.3477 81.9938 32.7849C82.8974 32.5228 83.4275 32.2167 82.0418 32.8798C79.515 34.0888 78.032 34.972 75.3337 36.5657C64.6559 42.8721 54.1878 49.5186 43.4946 55.801C33.8109 61.4904 23.6948 68.0711 12.9491 71.7535C12.4873 71.9118 11.0878 72.15 11.4877 71.872C14.746 69.6074 20.0543 68.1178 23.5142 66.7402C40.3001 60.0571 56.9851 53.0797 73.8723 46.6515C76.9827 45.4675 84.3981 42.7403 87.8394 41.6738C89.2613 41.2331 96.1589 38.9157 91.2773 42.776C82.9402 49.3687 72.7936 54.14 63.2234 58.6573C59.2228 60.5456 55.316 62.3148 51.1609 63.801C50.0671 64.1922 48.9511 64.5226 47.8308 64.8321C46.6536 65.1573 45.3402 65.0935 44.2612 65.6617C43.2596 66.1892 46.5326 65.6388 47.6631 65.5432C53.7394 65.0292 59.7854 64.2771 65.7868 63.1965C72.0428 62.0701 78.2017 60.5262 84.3417 58.9062"
17
+ stroke="#BDFF01"
18
+ strokeWidth="12"
19
+ strokeLinecap="round"
20
+ />
21
+ <path
22
+ d="M27.119 50.2256L47.4582 28.0373H74.5772L48.6909 65.6341H22.1882L27.119 50.2256Z"
23
+ fill="white"
24
+ stroke="black"
25
+ />
26
+ <path
27
+ d="M14.2947 52.6267L7.70887 53.7462C6.95087 53.8756 6.4398 54.5932 6.57013 55.3519C6.63106 55.7295 6.84414 56.0464 7.13358 56.2509C7.42443 56.4591 7.79561 56.5551 8.17544 56.4931L14.7619 55.3727C15.5192 55.2442 16.0316 54.5247 15.903 53.7674C15.7749 53.0076 15.0547 52.5001 14.2947 52.6267Z"
28
+ fill="black"
29
+ />
30
+ <path
31
+ d="M55.3193 20.5792C55.6276 20.3977 55.8663 20.0982 55.9623 19.7239L57.6224 13.2509C57.8124 12.5073 57.3623 11.7474 56.6197 11.5569C55.8747 11.3666 55.1151 11.8152 54.9257 12.5597L53.2656 19.0327C53.0738 19.7774 53.5247 20.5343 54.2684 20.7267C54.6391 20.821 55.0149 20.7585 55.3193 20.5792Z"
32
+ fill="black"
33
+ />
34
+ <path
35
+ d="M32.277 23.1873C32.6333 23.8673 33.4747 24.1301 34.1544 23.7727C34.8366 23.4159 35.0983 22.5747 34.7428 21.8934L31.6392 15.9762C31.2805 15.2956 30.4413 15.0324 29.7572 15.3907C29.0755 15.7497 28.8158 16.5893 29.1734 17.2701L32.277 23.1873Z"
36
+ fill="black"
37
+ />
38
+ <path
39
+ d="M13.4455 34.9526L19.7321 37.2162C20.4539 37.477 21.2501 37.1029 21.5146 36.3781C21.7756 35.6542 21.4004 34.8578 20.6742 34.5966L14.3901 32.331C13.6662 32.0689 12.8697 32.4452 12.6044 33.1676C12.3445 33.8917 12.7216 34.6915 13.4455 34.9526Z"
40
+ fill="black"
41
+ />
42
+ <path
43
+ d="M76.1485 26.0102C75.8931 25.6802 75.4948 25.4828 75.0771 25.4828H48.1771C47.9337 25.4828 47.7014 25.5502 47.4949 25.6774L47.4478 25.6377L24.7798 49.603L24.5382 49.875L24.5687 49.9064C24.518 49.9949 24.4802 50.0861 24.4553 50.1811L20.2501 65.8743C20.144 66.2772 20.2307 66.7142 20.4824 67.0452C20.7378 67.3753 21.1379 67.5726 21.5538 67.5726H48.4556C48.8862 67.5726 49.2679 67.391 49.6643 66.9945L71.7884 43.5575C71.8603 43.481 72.1074 43.1279 72.1756 42.8743L76.3808 27.1802C76.4887 26.7782 76.402 26.3431 76.1485 26.0102ZM47.4183 64.873H23.312L26.7944 51.8813H50.9007L47.4183 64.873ZM52.2929 49.1817H28.8928L48.7589 28.1824H72.1563L52.2929 49.1817Z"
44
+ fill="black"
45
+ />
46
+ </svg>
47
+ )
48
+ }
src/components/MadeWidthBadge.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function MadeWidthBadge() {
2
+ return (
3
+ <svg
4
+ width="101"
5
+ height="43"
6
+ viewBox="0 0 101 43"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <path
11
+ fillRule="evenodd"
12
+ clipRule="evenodd"
13
+ d="M0.158081 28.5402C0.158081 26.12 2.07738 24.1581 4.44495 24.1581H5.62173C6.08596 24.1581 6.46229 24.5428 6.46229 25.0173V25.7907C6.46229 26.2652 6.08596 26.6499 5.62173 26.6499H4.44495C3.47006 26.6499 2.4276 27.5437 2.4276 28.5402V29.7432C2.4276 30.2177 2.05127 30.6024 1.58704 30.6024H0.998643C0.534413 30.6024 0.158081 30.2177 0.158081 29.7432L0.158081 28.5402Z"
14
+ fill="black"
15
+ />
16
+ <path
17
+ fillRule="evenodd"
18
+ clipRule="evenodd"
19
+ d="M15.6244 35.2423C15.6244 37.6625 13.7051 39.6244 11.3376 39.6244H10.1608C9.69654 39.6244 9.3202 39.2397 9.3202 38.7652V37.9919C9.3202 37.5173 9.69654 37.1326 10.1608 37.1326H11.3376C12.3124 37.1326 13.3549 36.2388 13.3549 35.2423V34.0394C13.3549 33.5648 13.7312 33.1801 14.1955 33.1801H14.7839C15.2481 33.1801 15.6244 33.5648 15.6244 34.0394V35.2423Z"
20
+ fill="black"
21
+ />
22
+ <path
23
+ fillRule="evenodd"
24
+ clipRule="evenodd"
25
+ d="M11.3376 24.1581C13.7051 24.1581 15.6244 26.12 15.6244 28.5402V29.7432C15.6244 30.2177 15.2481 30.6024 14.7839 30.6024H14.0274C13.5631 30.6024 13.1868 30.2177 13.1868 29.7432V28.5402C13.1868 27.5437 12.3124 26.478 11.3376 26.478H10.1608C9.69654 26.478 9.3202 26.0934 9.3202 25.6188V25.0173C9.3202 24.5428 9.69654 24.1581 10.1608 24.1581L11.3376 24.1581Z"
26
+ fill="black"
27
+ />
28
+ <path
29
+ fillRule="evenodd"
30
+ clipRule="evenodd"
31
+ d="M4.44495 39.6244C2.07738 39.6244 0.158081 37.6625 0.158081 35.2423L0.158082 34.0394C0.158082 33.5648 0.534414 33.1801 0.998643 33.1801H1.75515C2.21938 33.1801 2.59571 33.5648 2.59571 34.0394L2.59571 35.2423C2.59571 36.2388 3.47006 37.3045 4.44495 37.3045L5.62173 37.3045C6.08596 37.3045 6.46229 37.6892 6.46229 38.1637V38.7652C6.46229 39.2397 6.08596 39.6244 5.62173 39.6244L4.44495 39.6244Z"
32
+ fill="black"
33
+ />
34
+ <path
35
+ d="M36.6965 29.5082C36.289 26.5653 34.0253 24.8578 31.0888 24.8578C27.6285 24.8578 25.0219 27.3802 25.0219 31.662C25.0219 35.9373 27.5961 38.4663 31.0888 38.4663C34.2387 38.4663 36.3279 36.4289 36.6965 33.8999L34.2775 33.887C33.9735 35.4393 32.7058 36.3125 31.1212 36.3125C28.9738 36.3125 27.4345 34.7019 27.4345 31.662C27.4345 28.6738 28.9609 27.0116 31.1276 27.0116C32.7382 27.0116 33.9994 27.9236 34.2775 29.5082H36.6965Z"
36
+ fill="black"
37
+ />
38
+ <path
39
+ d="M41.1319 25.0389H38.7905V38.2852H41.1319V25.0389Z"
40
+ fill="black"
41
+ />
42
+ <path
43
+ d="M43.5396 38.2852H45.881V28.3504H43.5396V38.2852ZM44.7168 26.9404C45.4606 26.9404 46.0686 26.3712 46.0686 25.6727C46.0686 24.9677 45.4606 24.3985 44.7168 24.3985C43.9665 24.3985 43.3585 24.9677 43.3585 25.6727C43.3585 26.3712 43.9665 26.9404 44.7168 26.9404Z"
44
+ fill="black"
45
+ />
46
+ <path
47
+ d="M48.2887 42.0107H50.6301V36.7199H50.7271C51.0958 37.4443 51.8654 38.4598 53.573 38.4598C55.9144 38.4598 57.6672 36.6035 57.6672 33.3307C57.6672 30.0192 55.8626 28.2211 53.5665 28.2211C51.8137 28.2211 51.0828 29.2754 50.7271 29.9933H50.5913V28.3504H48.2887V42.0107ZM50.5848 33.3178C50.5848 31.3904 51.4127 30.1421 52.9197 30.1421C54.4785 30.1421 55.2805 31.468 55.2805 33.3178C55.2805 35.1806 54.4656 36.5388 52.9197 36.5388C51.4256 36.5388 50.5848 35.2453 50.5848 33.3178Z"
48
+ fill="black"
49
+ />
50
+ <path
51
+ d="M64.2645 38.2852C68.3005 38.2852 70.6936 35.7886 70.6936 31.6491C70.6936 27.5225 68.3005 25.0389 64.355 25.0389H59.7757V38.2852H64.2645ZM62.1753 36.209V27.1151H64.2192C66.9099 27.1151 68.3134 28.6156 68.3134 31.6491C68.3134 34.6955 66.9099 36.209 64.1481 36.209H62.1753Z"
52
+ fill="black"
53
+ />
54
+ <path
55
+ d="M72.8701 38.2852H75.2115V32.4446C75.2115 31.1834 76.1622 30.2908 77.4494 30.2908C77.8439 30.2908 78.3355 30.362 78.536 30.4266V28.2728C78.3225 28.234 77.9539 28.2081 77.6951 28.2081C76.5568 28.2081 75.606 28.8549 75.2438 30.0062H75.1403V28.3504H72.8701V38.2852Z"
56
+ fill="black"
57
+ />
58
+ <path
59
+ d="M84.0159 38.4792C86.9265 38.4792 88.7763 36.4289 88.7763 33.3566C88.7763 30.2779 86.9265 28.2211 84.0159 28.2211C81.1054 28.2211 79.2555 30.2779 79.2555 33.3566C79.2555 36.4289 81.1054 38.4792 84.0159 38.4792ZM84.0289 36.6035C82.4183 36.6035 81.6293 35.1676 81.6293 33.3501C81.6293 31.5327 82.4183 30.0774 84.0289 30.0774C85.6135 30.0774 86.4026 31.5327 86.4026 33.3501C86.4026 35.1676 85.6135 36.6035 84.0289 36.6035Z"
60
+ fill="black"
61
+ />
62
+ <path
63
+ d="M90.7636 42.0107H93.105V36.7199H93.202C93.5707 37.4443 94.3404 38.4598 96.0479 38.4598C98.3893 38.4598 100.142 36.6035 100.142 33.3307C100.142 30.0192 98.3375 28.2211 96.0414 28.2211C94.2886 28.2211 93.5577 29.2754 93.202 29.9933H93.0662V28.3504H90.7636V42.0107ZM93.0597 33.3178C93.0597 31.3904 93.8876 30.1421 95.3946 30.1421C96.9534 30.1421 97.7554 31.468 97.7554 33.3178C97.7554 35.1806 96.9405 36.5388 95.3946 36.5388C93.9005 36.5388 93.0597 35.2453 93.0597 33.3178Z"
64
+ fill="black"
65
+ />
66
+ <path
67
+ d="M1.03871 3.54545V13H2.39595V6.15376H2.48366L5.27202 12.9862H6.39844L9.18679 6.15838H9.2745V13H10.6317V3.54545H8.90057L5.89062 10.8949H5.77983L2.76989 3.54545H1.03871ZM14.6585 13.157C15.8311 13.157 16.4912 12.5614 16.7544 12.0305H16.8097V13H18.1578V8.29119C18.1578 6.22763 16.5328 5.81676 15.4063 5.81676C14.123 5.81676 12.9411 6.33381 12.4795 7.62642L13.7767 7.92188C13.9798 7.41868 14.4969 6.93395 15.4248 6.93395C16.3158 6.93395 16.7728 7.40021 16.7728 8.20348V8.2358C16.7728 8.73899 16.2558 8.72976 14.9816 8.87749C13.6382 9.03445 12.2625 9.3853 12.2625 10.9964C12.2625 12.3906 13.3105 13.157 14.6585 13.157ZM14.9585 12.049C14.1784 12.049 13.6151 11.6982 13.6151 11.0149C13.6151 10.2763 14.2707 10.0131 15.0693 9.90696C15.5171 9.84695 16.5789 9.72692 16.7774 9.52841V10.4425C16.7774 11.2827 16.108 12.049 14.9585 12.049ZM22.6507 13.1385C23.9434 13.1385 24.4512 12.3491 24.7005 11.8967H24.8159V13H26.1639V3.54545H24.7836V7.05859H24.7005C24.4512 6.62003 23.9803 5.81676 22.66 5.81676C20.9473 5.81676 19.687 7.16939 19.687 9.46839C19.687 11.7628 20.9288 13.1385 22.6507 13.1385ZM22.9554 11.9613C21.7228 11.9613 21.0811 10.8764 21.0811 9.45455C21.0811 8.04652 21.709 6.98935 22.9554 6.98935C24.1603 6.98935 24.8066 7.97266 24.8066 9.45455C24.8066 10.9457 24.1465 11.9613 22.9554 11.9613ZM31.1901 13.1431C32.7366 13.1431 33.8307 12.3814 34.1446 11.2273L32.8382 10.9918C32.5889 11.6612 31.9887 12.0028 31.2039 12.0028C30.0221 12.0028 29.2281 11.2365 29.1911 9.87003H34.2323V9.38068C34.2323 6.81854 32.6997 5.81676 31.0931 5.81676C29.1173 5.81676 27.8154 7.32173 27.8154 9.50071C27.8154 11.7028 29.0988 13.1431 31.1901 13.1431ZM29.1958 8.83594C29.2512 7.82955 29.9806 6.95703 31.1024 6.95703C32.1734 6.95703 32.8751 7.75107 32.8797 8.83594H29.1958ZM40.7416 13H42.145L43.5853 7.88033H43.6915L45.1318 13H46.5399L48.6219 5.90909H47.1954L45.8151 11.0934H45.7458L44.3609 5.90909H42.9344L41.5402 11.1165H41.471L40.0814 5.90909H38.6549L40.7416 13ZM49.9318 13H51.3121V5.90909H49.9318V13ZM50.6289 4.81499C51.1044 4.81499 51.5014 4.44567 51.5014 3.99325C51.5014 3.54084 51.1044 3.1669 50.6289 3.1669C50.1488 3.1669 49.7564 3.54084 49.7564 3.99325C49.7564 4.44567 50.1488 4.81499 50.6289 4.81499ZM56.4791 5.90909H55.0249V4.21023H53.6446V5.90909H52.6059V7.01705H53.6446V11.2042C53.64 12.4922 54.6233 13.1154 55.7128 13.0923C56.1514 13.0877 56.4468 13.0046 56.6084 12.9446L56.3591 11.8043C56.2668 11.8228 56.096 11.8643 55.8744 11.8643C55.4266 11.8643 55.0249 11.7166 55.0249 10.918V7.01705H56.4791V5.90909ZM59.5387 8.78977C59.5387 7.65874 60.2543 7.01243 61.2376 7.01243C62.1886 7.01243 62.7564 7.61719 62.7564 8.65589V13H64.1367V8.4897C64.1367 6.72159 63.1673 5.81676 61.7085 5.81676C60.6051 5.81676 59.9403 6.29688 59.608 7.06321H59.5202V3.54545H58.1584V13H59.5387V8.78977ZM75.1389 13.1477L79.8847 8.40199C81.0203 7.26633 81.0111 5.39204 79.8847 4.28409C78.7306 3.14844 76.9255 3.15767 75.7668 4.28409L75.1389 4.89347L74.5111 4.28409C73.3524 3.15767 71.5473 3.14844 70.3932 4.28409C69.2668 5.39204 69.2575 7.26633 70.3932 8.40199L75.1389 13.1477ZM86.2623 13H87.6104V11.8967H87.7258C87.9751 12.3491 88.4829 13.1385 89.7755 13.1385C91.4928 13.1385 92.7393 11.7628 92.7393 9.46839C92.7393 7.16939 91.4743 5.81676 89.7616 5.81676C88.4459 5.81676 87.9704 6.62003 87.7258 7.05859H87.6427V3.54545H86.2623V13ZM87.615 9.45455C87.615 7.97266 88.2613 6.98935 89.4662 6.98935C90.7172 6.98935 91.3451 8.04652 91.3451 9.45455C91.3451 10.8764 90.6988 11.9613 89.4662 11.9613C88.2797 11.9613 87.615 10.9457 87.615 9.45455ZM94.9078 15.6591C96.0481 15.6591 96.7683 15.0636 97.1792 13.9464L100.111 5.92294L98.6195 5.90909L96.8237 11.4119H96.7498L94.954 5.90909H93.4767L96.0712 13.0923L95.9004 13.5632C95.5495 14.505 95.0556 14.5835 94.2985 14.3757L93.9661 15.5067C94.1323 15.5806 94.4924 15.6591 94.9078 15.6591Z"
68
+ fill="black"
69
+ />
70
+ </svg>
71
+ )
72
+ }
src/components/Modal.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ReactNode } from 'react'
2
+
3
+ interface ModalProps {
4
+ children?: ReactNode
5
+ }
6
+
7
+ export default function Modal(props: ModalProps) {
8
+ const { children } = props
9
+ return (
10
+ <div
11
+ className={[
12
+ 'absolute w-full h-full flex justify-center items-center',
13
+ 'bg-white bg-opacity-40 backdrop-filter backdrop-blur-md',
14
+ ].join(' ')}
15
+ >
16
+ <div className="bg-primary p-16 max-w-4xl">{children}</div>
17
+ </div>
18
+ )
19
+ }
src/components/Progress.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface ProgressProps {
2
+ percent: number
3
+ }
4
+
5
+ export default function Progress({ percent }: ProgressProps) {
6
+ return (
7
+ <div className="w-full flex items-center">
8
+ <div className="relative flex-1 bg-black/20 h-2 mr-4">
9
+ <div
10
+ className="absolute left-0 top-0 h-full bg-black duration-100"
11
+ style={{ width: `${percent}%` }}
12
+ />
13
+ </div>
14
+ <span className="w-20 text-right">{percent.toFixed(2)}%</span>
15
+ </div>
16
+ )
17
+ }
src/components/Slider.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ type SliderProps = {
2
+ label?: any
3
+ value?: number
4
+ min?: number
5
+ max?: number
6
+ onChange: (value: number) => void
7
+ onStart?: () => void
8
+ }
9
+
10
+ export default function Slider(props: SliderProps) {
11
+ const { value, label, min, max, onChange, onStart } = props
12
+
13
+ const step = ((max || 100) - (min || 0)) / 100
14
+
15
+ return (
16
+ <div className="inline-flex items-center space-x-4 text-black">
17
+ <span>{label}</span>
18
+ <input
19
+ className={['appearance-none rounded-lg h-4', 'bg-primary'].join(' ')}
20
+ type="range"
21
+ step={step}
22
+ min={min}
23
+ max={max}
24
+ value={value}
25
+ onPointerDown={onStart}
26
+ onChange={ev => {
27
+ ev.preventDefault()
28
+ ev.stopPropagation()
29
+ onChange(parseInt(ev.currentTarget.value, 10))
30
+ }}
31
+ />
32
+ </div>
33
+ )
34
+ }
src/index.css ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
src/index.tsx ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import ReactDOM from 'react-dom'
2
+ import './index.css'
3
+ import App from './App'
4
+ import { loadingOnnxruntime } from './adapters/util'
5
+
6
+ loadingOnnxruntime()
7
+
8
+ ReactDOM.render(<App />, document.getElementById('root'))
src/react-app-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="react-scripts" />
src/setupTests.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom/extend-expect'
src/utils.ts ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useCallback, useEffect, useState } from 'react'
2
+
3
+ export function dataURItoBlob(dataURI: string) {
4
+ const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
5
+ const binary = atob(dataURI.split(',')[1])
6
+ const array = []
7
+ for (let i = 0; i < binary.length; i += 1) {
8
+ array.push(binary.charCodeAt(i))
9
+ }
10
+ return new Blob([new Uint8Array(array)], { type: mime })
11
+ }
12
+
13
+ // const dataURItoBlob = (dataURI: string) => {
14
+ // const bytes =
15
+ // dataURI.split(',')[0].indexOf('base64') >= 0
16
+ // ? atob(dataURI.split(',')[1])
17
+ // : unescape(dataURI.split(',')[1])
18
+ // const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
19
+ // const max = bytes.length
20
+ // const ia = new Uint8Array(max)
21
+ // for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i)
22
+ // return new Blob([ia], { type: mime })
23
+ // }
24
+
25
+ export function downloadImage(uri: string, name: string) {
26
+ const link = document.createElement('a')
27
+ link.href = uri
28
+ link.download = name
29
+
30
+ // this is necessary as link.click() does not work on the latest firefox
31
+ link.dispatchEvent(
32
+ new MouseEvent('click', {
33
+ bubbles: true,
34
+ cancelable: true,
35
+ view: window,
36
+ })
37
+ )
38
+
39
+ setTimeout(() => {
40
+ // For Firefox it is necessary to delay revoking the ObjectURL
41
+ // window.URL.revokeObjectURL(base64)
42
+ link.remove()
43
+ }, 100)
44
+ }
45
+
46
+ export function loadImage(image: HTMLImageElement, src: string) {
47
+ return new Promise((resolve, reject) => {
48
+ const initSRC = image.src
49
+ const img = image
50
+ img.onload = resolve
51
+ img.onerror = err => {
52
+ img.src = initSRC
53
+ reject(err)
54
+ }
55
+ img.src = src
56
+ })
57
+ }
58
+
59
+ export function useImage(
60
+ file: Blob | MediaSource
61
+ ): [HTMLImageElement, boolean, (width: number, height: number) => void] {
62
+ const [image, setImage] = useState(new Image())
63
+ const [isLoaded, setIsLoaded] = useState(false)
64
+
65
+ // 调整图像分辨率的函数
66
+ const adjustResolution = useCallback(
67
+ (width, height) => {
68
+ const canvas = document.createElement('canvas')
69
+ const context = canvas.getContext('2d')!
70
+ canvas.width = width
71
+ canvas.height = height
72
+ context.drawImage(image, 0, 0, width, height)
73
+ const resizedImage = new Image()
74
+ resizedImage.src = canvas.toDataURL()
75
+ setImage(resizedImage)
76
+ },
77
+ [image]
78
+ )
79
+
80
+ useEffect(() => {
81
+ const newImage = new Image()
82
+ newImage.onload = () => {
83
+ setIsLoaded(true)
84
+ }
85
+ newImage.src = URL.createObjectURL(file)
86
+ setImage(newImage)
87
+
88
+ return () => {
89
+ newImage.onload = null
90
+ }
91
+ }, [file])
92
+
93
+ return [image, isLoaded, adjustResolution]
94
+ }
95
+
96
+ // https://stackoverflow.com/questions/23945494/use-html5-to-resize-an-image-before-upload
97
+ interface ResizeImageFileResult {
98
+ file: File
99
+ resized: boolean
100
+ originalWidth?: number
101
+ originalHeight?: number
102
+ }
103
+ export function resizeImageFile(
104
+ file: File,
105
+ maxSize: number
106
+ ): Promise<ResizeImageFileResult> {
107
+ const reader = new FileReader()
108
+ const image = new Image()
109
+ const canvas = document.createElement('canvas')
110
+
111
+ const resize = (): ResizeImageFileResult => {
112
+ let { width, height } = image
113
+
114
+ if (width > height) {
115
+ if (width > maxSize) {
116
+ height *= maxSize / width
117
+ width = maxSize
118
+ }
119
+ } else if (height > maxSize) {
120
+ width *= maxSize / height
121
+ height = maxSize
122
+ }
123
+
124
+ if (width === image.width && height === image.height) {
125
+ return { file, resized: false }
126
+ }
127
+
128
+ canvas.width = width
129
+ canvas.height = height
130
+ const ctx = canvas.getContext('2d')
131
+ if (!ctx) {
132
+ throw new Error('could not get context')
133
+ }
134
+ canvas.getContext('2d')?.drawImage(image, 0, 0, width, height)
135
+ const dataUrl = canvas.toDataURL('image/jpeg')
136
+ const blob = dataURItoBlob(dataUrl)
137
+ const f = new File([blob], file.name, {
138
+ type: file.type,
139
+ })
140
+ return {
141
+ file: f,
142
+ resized: true,
143
+ originalWidth: image.width,
144
+ originalHeight: image.height,
145
+ }
146
+ }
147
+
148
+ return new Promise((resolve, reject) => {
149
+ if (!file.type.match(/image.*/)) {
150
+ reject(new Error('Not an image'))
151
+ return
152
+ }
153
+ reader.onload = (readerEvent: any) => {
154
+ image.onload = () => resolve(resize())
155
+ image.src = readerEvent.target.result
156
+ }
157
+ reader.readAsDataURL(file)
158
+ })
159
+ }
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
tailwind.config.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const tailwindScrollbar = require('tailwind-scrollbar')
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ module.exports = {
5
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
6
+ theme: {
7
+ extend: {
8
+ animation: {
9
+ 'pulse-fast': 'pulse 0.7s cubic-bezier(0.4, 0, 0.6, 1) infinite',
10
+ },
11
+ colors: {
12
+ primary: '#BDFF01',
13
+ },
14
+ },
15
+ },
16
+ variants: {},
17
+ plugins: [tailwindScrollbar],
18
+ }
tsconfig.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "noEmit": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "jsx": "react-jsx",
18
+ "types": ["vite/client", "vite-plugin-svgr/client"]
19
+ },
20
+ "include": ["src"]
21
+ }
vite.config.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vitest/config'
2
+ import react from '@vitejs/plugin-react-swc'
3
+ import eslintPlugin from 'vite-plugin-eslint'
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig({
7
+ base: '/',
8
+ plugins: [react(), eslintPlugin()],
9
+ test: {
10
+ globals: true,
11
+ environment: 'jsdom',
12
+ setupFiles: './src/setupTests.ts',
13
+ css: true,
14
+ reporters: ['verbose'],
15
+ coverage: {
16
+ reporter: ['text', 'json', 'html'],
17
+ include: ['src/**/*'],
18
+ exclude: [],
19
+ },
20
+ },
21
+ server: {
22
+ headers: {
23
+ 'Cross-Origin-Opener-Policy': 'same-origin',
24
+ 'Cross-Origin-Embedder-Policy': 'require-corp',
25
+ },
26
+ },
27
+ })