Dinamush commited on
Commit
fa3f072
·
1 Parent(s): 780498a

Implement mock API state management and offline mode handling in the frontend. Added functions for loading and persisting mock state, and updated API requests to support offline functionality. Enhanced App component to reflect offline status and display appropriate messages.

Browse files
frontend/package-lock.json ADDED
@@ -0,0 +1,981 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "image-classifier-frontend",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "image-classifier-frontend",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "vite": "^5.4.10"
16
+ }
17
+ },
18
+ "node_modules/@esbuild/aix-ppc64": {
19
+ "version": "0.21.5",
20
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
21
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
22
+ "cpu": [
23
+ "ppc64"
24
+ ],
25
+ "dev": true,
26
+ "optional": true,
27
+ "os": [
28
+ "aix"
29
+ ],
30
+ "engines": {
31
+ "node": ">=12"
32
+ }
33
+ },
34
+ "node_modules/@esbuild/android-arm": {
35
+ "version": "0.21.5",
36
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
37
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
38
+ "cpu": [
39
+ "arm"
40
+ ],
41
+ "dev": true,
42
+ "optional": true,
43
+ "os": [
44
+ "android"
45
+ ],
46
+ "engines": {
47
+ "node": ">=12"
48
+ }
49
+ },
50
+ "node_modules/@esbuild/android-arm64": {
51
+ "version": "0.21.5",
52
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
53
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
54
+ "cpu": [
55
+ "arm64"
56
+ ],
57
+ "dev": true,
58
+ "optional": true,
59
+ "os": [
60
+ "android"
61
+ ],
62
+ "engines": {
63
+ "node": ">=12"
64
+ }
65
+ },
66
+ "node_modules/@esbuild/android-x64": {
67
+ "version": "0.21.5",
68
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
69
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
70
+ "cpu": [
71
+ "x64"
72
+ ],
73
+ "dev": true,
74
+ "optional": true,
75
+ "os": [
76
+ "android"
77
+ ],
78
+ "engines": {
79
+ "node": ">=12"
80
+ }
81
+ },
82
+ "node_modules/@esbuild/darwin-arm64": {
83
+ "version": "0.21.5",
84
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
85
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
86
+ "cpu": [
87
+ "arm64"
88
+ ],
89
+ "dev": true,
90
+ "optional": true,
91
+ "os": [
92
+ "darwin"
93
+ ],
94
+ "engines": {
95
+ "node": ">=12"
96
+ }
97
+ },
98
+ "node_modules/@esbuild/darwin-x64": {
99
+ "version": "0.21.5",
100
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
101
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
102
+ "cpu": [
103
+ "x64"
104
+ ],
105
+ "dev": true,
106
+ "optional": true,
107
+ "os": [
108
+ "darwin"
109
+ ],
110
+ "engines": {
111
+ "node": ">=12"
112
+ }
113
+ },
114
+ "node_modules/@esbuild/freebsd-arm64": {
115
+ "version": "0.21.5",
116
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
117
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
118
+ "cpu": [
119
+ "arm64"
120
+ ],
121
+ "dev": true,
122
+ "optional": true,
123
+ "os": [
124
+ "freebsd"
125
+ ],
126
+ "engines": {
127
+ "node": ">=12"
128
+ }
129
+ },
130
+ "node_modules/@esbuild/freebsd-x64": {
131
+ "version": "0.21.5",
132
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
133
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
134
+ "cpu": [
135
+ "x64"
136
+ ],
137
+ "dev": true,
138
+ "optional": true,
139
+ "os": [
140
+ "freebsd"
141
+ ],
142
+ "engines": {
143
+ "node": ">=12"
144
+ }
145
+ },
146
+ "node_modules/@esbuild/linux-arm": {
147
+ "version": "0.21.5",
148
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
149
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
150
+ "cpu": [
151
+ "arm"
152
+ ],
153
+ "dev": true,
154
+ "optional": true,
155
+ "os": [
156
+ "linux"
157
+ ],
158
+ "engines": {
159
+ "node": ">=12"
160
+ }
161
+ },
162
+ "node_modules/@esbuild/linux-arm64": {
163
+ "version": "0.21.5",
164
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
165
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
166
+ "cpu": [
167
+ "arm64"
168
+ ],
169
+ "dev": true,
170
+ "optional": true,
171
+ "os": [
172
+ "linux"
173
+ ],
174
+ "engines": {
175
+ "node": ">=12"
176
+ }
177
+ },
178
+ "node_modules/@esbuild/linux-ia32": {
179
+ "version": "0.21.5",
180
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
181
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
182
+ "cpu": [
183
+ "ia32"
184
+ ],
185
+ "dev": true,
186
+ "optional": true,
187
+ "os": [
188
+ "linux"
189
+ ],
190
+ "engines": {
191
+ "node": ">=12"
192
+ }
193
+ },
194
+ "node_modules/@esbuild/linux-loong64": {
195
+ "version": "0.21.5",
196
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
197
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
198
+ "cpu": [
199
+ "loong64"
200
+ ],
201
+ "dev": true,
202
+ "optional": true,
203
+ "os": [
204
+ "linux"
205
+ ],
206
+ "engines": {
207
+ "node": ">=12"
208
+ }
209
+ },
210
+ "node_modules/@esbuild/linux-mips64el": {
211
+ "version": "0.21.5",
212
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
213
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
214
+ "cpu": [
215
+ "mips64el"
216
+ ],
217
+ "dev": true,
218
+ "optional": true,
219
+ "os": [
220
+ "linux"
221
+ ],
222
+ "engines": {
223
+ "node": ">=12"
224
+ }
225
+ },
226
+ "node_modules/@esbuild/linux-ppc64": {
227
+ "version": "0.21.5",
228
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
229
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
230
+ "cpu": [
231
+ "ppc64"
232
+ ],
233
+ "dev": true,
234
+ "optional": true,
235
+ "os": [
236
+ "linux"
237
+ ],
238
+ "engines": {
239
+ "node": ">=12"
240
+ }
241
+ },
242
+ "node_modules/@esbuild/linux-riscv64": {
243
+ "version": "0.21.5",
244
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
245
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
246
+ "cpu": [
247
+ "riscv64"
248
+ ],
249
+ "dev": true,
250
+ "optional": true,
251
+ "os": [
252
+ "linux"
253
+ ],
254
+ "engines": {
255
+ "node": ">=12"
256
+ }
257
+ },
258
+ "node_modules/@esbuild/linux-s390x": {
259
+ "version": "0.21.5",
260
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
261
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
262
+ "cpu": [
263
+ "s390x"
264
+ ],
265
+ "dev": true,
266
+ "optional": true,
267
+ "os": [
268
+ "linux"
269
+ ],
270
+ "engines": {
271
+ "node": ">=12"
272
+ }
273
+ },
274
+ "node_modules/@esbuild/linux-x64": {
275
+ "version": "0.21.5",
276
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
277
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
278
+ "cpu": [
279
+ "x64"
280
+ ],
281
+ "dev": true,
282
+ "optional": true,
283
+ "os": [
284
+ "linux"
285
+ ],
286
+ "engines": {
287
+ "node": ">=12"
288
+ }
289
+ },
290
+ "node_modules/@esbuild/netbsd-x64": {
291
+ "version": "0.21.5",
292
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
293
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
294
+ "cpu": [
295
+ "x64"
296
+ ],
297
+ "dev": true,
298
+ "optional": true,
299
+ "os": [
300
+ "netbsd"
301
+ ],
302
+ "engines": {
303
+ "node": ">=12"
304
+ }
305
+ },
306
+ "node_modules/@esbuild/openbsd-x64": {
307
+ "version": "0.21.5",
308
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
309
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
310
+ "cpu": [
311
+ "x64"
312
+ ],
313
+ "dev": true,
314
+ "optional": true,
315
+ "os": [
316
+ "openbsd"
317
+ ],
318
+ "engines": {
319
+ "node": ">=12"
320
+ }
321
+ },
322
+ "node_modules/@esbuild/sunos-x64": {
323
+ "version": "0.21.5",
324
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
325
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
326
+ "cpu": [
327
+ "x64"
328
+ ],
329
+ "dev": true,
330
+ "optional": true,
331
+ "os": [
332
+ "sunos"
333
+ ],
334
+ "engines": {
335
+ "node": ">=12"
336
+ }
337
+ },
338
+ "node_modules/@esbuild/win32-arm64": {
339
+ "version": "0.21.5",
340
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
341
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
342
+ "cpu": [
343
+ "arm64"
344
+ ],
345
+ "dev": true,
346
+ "optional": true,
347
+ "os": [
348
+ "win32"
349
+ ],
350
+ "engines": {
351
+ "node": ">=12"
352
+ }
353
+ },
354
+ "node_modules/@esbuild/win32-ia32": {
355
+ "version": "0.21.5",
356
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
357
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
358
+ "cpu": [
359
+ "ia32"
360
+ ],
361
+ "dev": true,
362
+ "optional": true,
363
+ "os": [
364
+ "win32"
365
+ ],
366
+ "engines": {
367
+ "node": ">=12"
368
+ }
369
+ },
370
+ "node_modules/@esbuild/win32-x64": {
371
+ "version": "0.21.5",
372
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
373
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
374
+ "cpu": [
375
+ "x64"
376
+ ],
377
+ "dev": true,
378
+ "optional": true,
379
+ "os": [
380
+ "win32"
381
+ ],
382
+ "engines": {
383
+ "node": ">=12"
384
+ }
385
+ },
386
+ "node_modules/@rollup/rollup-android-arm-eabi": {
387
+ "version": "4.59.0",
388
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
389
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
390
+ "cpu": [
391
+ "arm"
392
+ ],
393
+ "dev": true,
394
+ "optional": true,
395
+ "os": [
396
+ "android"
397
+ ]
398
+ },
399
+ "node_modules/@rollup/rollup-android-arm64": {
400
+ "version": "4.59.0",
401
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
402
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
403
+ "cpu": [
404
+ "arm64"
405
+ ],
406
+ "dev": true,
407
+ "optional": true,
408
+ "os": [
409
+ "android"
410
+ ]
411
+ },
412
+ "node_modules/@rollup/rollup-darwin-arm64": {
413
+ "version": "4.59.0",
414
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
415
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
416
+ "cpu": [
417
+ "arm64"
418
+ ],
419
+ "dev": true,
420
+ "optional": true,
421
+ "os": [
422
+ "darwin"
423
+ ]
424
+ },
425
+ "node_modules/@rollup/rollup-darwin-x64": {
426
+ "version": "4.59.0",
427
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
428
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
429
+ "cpu": [
430
+ "x64"
431
+ ],
432
+ "dev": true,
433
+ "optional": true,
434
+ "os": [
435
+ "darwin"
436
+ ]
437
+ },
438
+ "node_modules/@rollup/rollup-freebsd-arm64": {
439
+ "version": "4.59.0",
440
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
441
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
442
+ "cpu": [
443
+ "arm64"
444
+ ],
445
+ "dev": true,
446
+ "optional": true,
447
+ "os": [
448
+ "freebsd"
449
+ ]
450
+ },
451
+ "node_modules/@rollup/rollup-freebsd-x64": {
452
+ "version": "4.59.0",
453
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
454
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
455
+ "cpu": [
456
+ "x64"
457
+ ],
458
+ "dev": true,
459
+ "optional": true,
460
+ "os": [
461
+ "freebsd"
462
+ ]
463
+ },
464
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
465
+ "version": "4.59.0",
466
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
467
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
468
+ "cpu": [
469
+ "arm"
470
+ ],
471
+ "dev": true,
472
+ "optional": true,
473
+ "os": [
474
+ "linux"
475
+ ]
476
+ },
477
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
478
+ "version": "4.59.0",
479
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
480
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
481
+ "cpu": [
482
+ "arm"
483
+ ],
484
+ "dev": true,
485
+ "optional": true,
486
+ "os": [
487
+ "linux"
488
+ ]
489
+ },
490
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
491
+ "version": "4.59.0",
492
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
493
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
494
+ "cpu": [
495
+ "arm64"
496
+ ],
497
+ "dev": true,
498
+ "optional": true,
499
+ "os": [
500
+ "linux"
501
+ ]
502
+ },
503
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
504
+ "version": "4.59.0",
505
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
506
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
507
+ "cpu": [
508
+ "arm64"
509
+ ],
510
+ "dev": true,
511
+ "optional": true,
512
+ "os": [
513
+ "linux"
514
+ ]
515
+ },
516
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
517
+ "version": "4.59.0",
518
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
519
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
520
+ "cpu": [
521
+ "loong64"
522
+ ],
523
+ "dev": true,
524
+ "optional": true,
525
+ "os": [
526
+ "linux"
527
+ ]
528
+ },
529
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
530
+ "version": "4.59.0",
531
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
532
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
533
+ "cpu": [
534
+ "loong64"
535
+ ],
536
+ "dev": true,
537
+ "optional": true,
538
+ "os": [
539
+ "linux"
540
+ ]
541
+ },
542
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
543
+ "version": "4.59.0",
544
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
545
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
546
+ "cpu": [
547
+ "ppc64"
548
+ ],
549
+ "dev": true,
550
+ "optional": true,
551
+ "os": [
552
+ "linux"
553
+ ]
554
+ },
555
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
556
+ "version": "4.59.0",
557
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
558
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
559
+ "cpu": [
560
+ "ppc64"
561
+ ],
562
+ "dev": true,
563
+ "optional": true,
564
+ "os": [
565
+ "linux"
566
+ ]
567
+ },
568
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
569
+ "version": "4.59.0",
570
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
571
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
572
+ "cpu": [
573
+ "riscv64"
574
+ ],
575
+ "dev": true,
576
+ "optional": true,
577
+ "os": [
578
+ "linux"
579
+ ]
580
+ },
581
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
582
+ "version": "4.59.0",
583
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
584
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
585
+ "cpu": [
586
+ "riscv64"
587
+ ],
588
+ "dev": true,
589
+ "optional": true,
590
+ "os": [
591
+ "linux"
592
+ ]
593
+ },
594
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
595
+ "version": "4.59.0",
596
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
597
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
598
+ "cpu": [
599
+ "s390x"
600
+ ],
601
+ "dev": true,
602
+ "optional": true,
603
+ "os": [
604
+ "linux"
605
+ ]
606
+ },
607
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
608
+ "version": "4.59.0",
609
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
610
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
611
+ "cpu": [
612
+ "x64"
613
+ ],
614
+ "dev": true,
615
+ "optional": true,
616
+ "os": [
617
+ "linux"
618
+ ]
619
+ },
620
+ "node_modules/@rollup/rollup-linux-x64-musl": {
621
+ "version": "4.59.0",
622
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
623
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
624
+ "cpu": [
625
+ "x64"
626
+ ],
627
+ "dev": true,
628
+ "optional": true,
629
+ "os": [
630
+ "linux"
631
+ ]
632
+ },
633
+ "node_modules/@rollup/rollup-openbsd-x64": {
634
+ "version": "4.59.0",
635
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
636
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
637
+ "cpu": [
638
+ "x64"
639
+ ],
640
+ "dev": true,
641
+ "optional": true,
642
+ "os": [
643
+ "openbsd"
644
+ ]
645
+ },
646
+ "node_modules/@rollup/rollup-openharmony-arm64": {
647
+ "version": "4.59.0",
648
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
649
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
650
+ "cpu": [
651
+ "arm64"
652
+ ],
653
+ "dev": true,
654
+ "optional": true,
655
+ "os": [
656
+ "openharmony"
657
+ ]
658
+ },
659
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
660
+ "version": "4.59.0",
661
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
662
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
663
+ "cpu": [
664
+ "arm64"
665
+ ],
666
+ "dev": true,
667
+ "optional": true,
668
+ "os": [
669
+ "win32"
670
+ ]
671
+ },
672
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
673
+ "version": "4.59.0",
674
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
675
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
676
+ "cpu": [
677
+ "ia32"
678
+ ],
679
+ "dev": true,
680
+ "optional": true,
681
+ "os": [
682
+ "win32"
683
+ ]
684
+ },
685
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
686
+ "version": "4.59.0",
687
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
688
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
689
+ "cpu": [
690
+ "x64"
691
+ ],
692
+ "dev": true,
693
+ "optional": true,
694
+ "os": [
695
+ "win32"
696
+ ]
697
+ },
698
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
699
+ "version": "4.59.0",
700
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
701
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
702
+ "cpu": [
703
+ "x64"
704
+ ],
705
+ "dev": true,
706
+ "optional": true,
707
+ "os": [
708
+ "win32"
709
+ ]
710
+ },
711
+ "node_modules/@types/estree": {
712
+ "version": "1.0.8",
713
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
714
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
715
+ "dev": true
716
+ },
717
+ "node_modules/esbuild": {
718
+ "version": "0.21.5",
719
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
720
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
721
+ "dev": true,
722
+ "hasInstallScript": true,
723
+ "bin": {
724
+ "esbuild": "bin/esbuild"
725
+ },
726
+ "engines": {
727
+ "node": ">=12"
728
+ },
729
+ "optionalDependencies": {
730
+ "@esbuild/aix-ppc64": "0.21.5",
731
+ "@esbuild/android-arm": "0.21.5",
732
+ "@esbuild/android-arm64": "0.21.5",
733
+ "@esbuild/android-x64": "0.21.5",
734
+ "@esbuild/darwin-arm64": "0.21.5",
735
+ "@esbuild/darwin-x64": "0.21.5",
736
+ "@esbuild/freebsd-arm64": "0.21.5",
737
+ "@esbuild/freebsd-x64": "0.21.5",
738
+ "@esbuild/linux-arm": "0.21.5",
739
+ "@esbuild/linux-arm64": "0.21.5",
740
+ "@esbuild/linux-ia32": "0.21.5",
741
+ "@esbuild/linux-loong64": "0.21.5",
742
+ "@esbuild/linux-mips64el": "0.21.5",
743
+ "@esbuild/linux-ppc64": "0.21.5",
744
+ "@esbuild/linux-riscv64": "0.21.5",
745
+ "@esbuild/linux-s390x": "0.21.5",
746
+ "@esbuild/linux-x64": "0.21.5",
747
+ "@esbuild/netbsd-x64": "0.21.5",
748
+ "@esbuild/openbsd-x64": "0.21.5",
749
+ "@esbuild/sunos-x64": "0.21.5",
750
+ "@esbuild/win32-arm64": "0.21.5",
751
+ "@esbuild/win32-ia32": "0.21.5",
752
+ "@esbuild/win32-x64": "0.21.5"
753
+ }
754
+ },
755
+ "node_modules/fsevents": {
756
+ "version": "2.3.3",
757
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
758
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
759
+ "dev": true,
760
+ "hasInstallScript": true,
761
+ "optional": true,
762
+ "os": [
763
+ "darwin"
764
+ ],
765
+ "engines": {
766
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
767
+ }
768
+ },
769
+ "node_modules/js-tokens": {
770
+ "version": "4.0.0",
771
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
772
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
773
+ },
774
+ "node_modules/loose-envify": {
775
+ "version": "1.4.0",
776
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
777
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
778
+ "dependencies": {
779
+ "js-tokens": "^3.0.0 || ^4.0.0"
780
+ },
781
+ "bin": {
782
+ "loose-envify": "cli.js"
783
+ }
784
+ },
785
+ "node_modules/nanoid": {
786
+ "version": "3.3.11",
787
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
788
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
789
+ "dev": true,
790
+ "funding": [
791
+ {
792
+ "type": "github",
793
+ "url": "https://github.com/sponsors/ai"
794
+ }
795
+ ],
796
+ "bin": {
797
+ "nanoid": "bin/nanoid.cjs"
798
+ },
799
+ "engines": {
800
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
801
+ }
802
+ },
803
+ "node_modules/picocolors": {
804
+ "version": "1.1.1",
805
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
806
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
807
+ "dev": true
808
+ },
809
+ "node_modules/postcss": {
810
+ "version": "8.5.6",
811
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
812
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
813
+ "dev": true,
814
+ "funding": [
815
+ {
816
+ "type": "opencollective",
817
+ "url": "https://opencollective.com/postcss/"
818
+ },
819
+ {
820
+ "type": "tidelift",
821
+ "url": "https://tidelift.com/funding/github/npm/postcss"
822
+ },
823
+ {
824
+ "type": "github",
825
+ "url": "https://github.com/sponsors/ai"
826
+ }
827
+ ],
828
+ "dependencies": {
829
+ "nanoid": "^3.3.11",
830
+ "picocolors": "^1.1.1",
831
+ "source-map-js": "^1.2.1"
832
+ },
833
+ "engines": {
834
+ "node": "^10 || ^12 || >=14"
835
+ }
836
+ },
837
+ "node_modules/react": {
838
+ "version": "18.3.1",
839
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
840
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
841
+ "dependencies": {
842
+ "loose-envify": "^1.1.0"
843
+ },
844
+ "engines": {
845
+ "node": ">=0.10.0"
846
+ }
847
+ },
848
+ "node_modules/react-dom": {
849
+ "version": "18.3.1",
850
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
851
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
852
+ "dependencies": {
853
+ "loose-envify": "^1.1.0",
854
+ "scheduler": "^0.23.2"
855
+ },
856
+ "peerDependencies": {
857
+ "react": "^18.3.1"
858
+ }
859
+ },
860
+ "node_modules/rollup": {
861
+ "version": "4.59.0",
862
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
863
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
864
+ "dev": true,
865
+ "dependencies": {
866
+ "@types/estree": "1.0.8"
867
+ },
868
+ "bin": {
869
+ "rollup": "dist/bin/rollup"
870
+ },
871
+ "engines": {
872
+ "node": ">=18.0.0",
873
+ "npm": ">=8.0.0"
874
+ },
875
+ "optionalDependencies": {
876
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
877
+ "@rollup/rollup-android-arm64": "4.59.0",
878
+ "@rollup/rollup-darwin-arm64": "4.59.0",
879
+ "@rollup/rollup-darwin-x64": "4.59.0",
880
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
881
+ "@rollup/rollup-freebsd-x64": "4.59.0",
882
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
883
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
884
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
885
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
886
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
887
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
888
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
889
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
890
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
891
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
892
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
893
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
894
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
895
+ "@rollup/rollup-openbsd-x64": "4.59.0",
896
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
897
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
898
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
899
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
900
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
901
+ "fsevents": "~2.3.2"
902
+ }
903
+ },
904
+ "node_modules/scheduler": {
905
+ "version": "0.23.2",
906
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
907
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
908
+ "dependencies": {
909
+ "loose-envify": "^1.1.0"
910
+ }
911
+ },
912
+ "node_modules/source-map-js": {
913
+ "version": "1.2.1",
914
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
915
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
916
+ "dev": true,
917
+ "engines": {
918
+ "node": ">=0.10.0"
919
+ }
920
+ },
921
+ "node_modules/vite": {
922
+ "version": "5.4.21",
923
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
924
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
925
+ "dev": true,
926
+ "dependencies": {
927
+ "esbuild": "^0.21.3",
928
+ "postcss": "^8.4.43",
929
+ "rollup": "^4.20.0"
930
+ },
931
+ "bin": {
932
+ "vite": "bin/vite.js"
933
+ },
934
+ "engines": {
935
+ "node": "^18.0.0 || >=20.0.0"
936
+ },
937
+ "funding": {
938
+ "url": "https://github.com/vitejs/vite?sponsor=1"
939
+ },
940
+ "optionalDependencies": {
941
+ "fsevents": "~2.3.3"
942
+ },
943
+ "peerDependencies": {
944
+ "@types/node": "^18.0.0 || >=20.0.0",
945
+ "less": "*",
946
+ "lightningcss": "^1.21.0",
947
+ "sass": "*",
948
+ "sass-embedded": "*",
949
+ "stylus": "*",
950
+ "sugarss": "*",
951
+ "terser": "^5.4.0"
952
+ },
953
+ "peerDependenciesMeta": {
954
+ "@types/node": {
955
+ "optional": true
956
+ },
957
+ "less": {
958
+ "optional": true
959
+ },
960
+ "lightningcss": {
961
+ "optional": true
962
+ },
963
+ "sass": {
964
+ "optional": true
965
+ },
966
+ "sass-embedded": {
967
+ "optional": true
968
+ },
969
+ "stylus": {
970
+ "optional": true
971
+ },
972
+ "sugarss": {
973
+ "optional": true
974
+ },
975
+ "terser": {
976
+ "optional": true
977
+ }
978
+ }
979
+ }
980
+ }
981
+ }
frontend/src/App.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from "react";
2
  import { api } from "./api";
3
 
4
  const DEFAULT_SETTINGS = {
@@ -10,6 +10,7 @@ const DEFAULT_SETTINGS = {
10
 
11
  function App() {
12
  const [settings, setSettings] = useState(DEFAULT_SETTINGS);
 
13
  const [runId, setRunId] = useState(null);
14
  const [runMeta, setRunMeta] = useState(null);
15
  const [items, setItems] = useState([]);
@@ -34,6 +35,7 @@ function App() {
34
  .then((data) => {
35
  setSettings(data);
36
  setMigrateMode(data.default_migrate_mode || "copy");
 
37
  })
38
  .catch((err) => setError(err.message));
39
  }, []);
@@ -54,6 +56,7 @@ function App() {
54
  try {
55
  const saved = await api.saveSettings(settings);
56
  setSettings(saved);
 
57
  } catch (err) {
58
  setError(err.message);
59
  } finally {
@@ -73,6 +76,7 @@ function App() {
73
  ...settings,
74
  selected_folders: selectedFolders.length > 0 ? selectedFolders : null,
75
  });
 
76
  setRunId(result.run_id);
77
  await refreshItems(result.run_id);
78
  } catch (err) {
@@ -104,6 +108,7 @@ function App() {
104
  setError("");
105
  try {
106
  await api.migrateRun(runId, { mode: migrateMode, create_missing_folders: true });
 
107
  await refreshItems(runId);
108
  } catch (err) {
109
  setError(err.message);
@@ -119,6 +124,11 @@ function App() {
119
  Configure source + category roots, run tagging, review predictions, then approve and
120
  migrate using move/copy.
121
  </p>
 
 
 
 
 
122
 
123
  {error && <div className="error">{error}</div>}
124
 
 
1
+ import React, { useEffect, useMemo, useState } from "react";
2
  import { api } from "./api";
3
 
4
  const DEFAULT_SETTINGS = {
 
10
 
11
  function App() {
12
  const [settings, setSettings] = useState(DEFAULT_SETTINGS);
13
+ const [offlineMode, setOfflineMode] = useState(false);
14
  const [runId, setRunId] = useState(null);
15
  const [runMeta, setRunMeta] = useState(null);
16
  const [items, setItems] = useState([]);
 
35
  .then((data) => {
36
  setSettings(data);
37
  setMigrateMode(data.default_migrate_mode || "copy");
38
+ setOfflineMode(api.isOfflineMode());
39
  })
40
  .catch((err) => setError(err.message));
41
  }, []);
 
56
  try {
57
  const saved = await api.saveSettings(settings);
58
  setSettings(saved);
59
+ setOfflineMode(api.isOfflineMode());
60
  } catch (err) {
61
  setError(err.message);
62
  } finally {
 
76
  ...settings,
77
  selected_folders: selectedFolders.length > 0 ? selectedFolders : null,
78
  });
79
+ setOfflineMode(api.isOfflineMode());
80
  setRunId(result.run_id);
81
  await refreshItems(result.run_id);
82
  } catch (err) {
 
108
  setError("");
109
  try {
110
  await api.migrateRun(runId, { mode: migrateMode, create_missing_folders: true });
111
+ setOfflineMode(api.isOfflineMode());
112
  await refreshItems(runId);
113
  } catch (err) {
114
  setError(err.message);
 
124
  Configure source + category roots, run tagging, review predictions, then approve and
125
  migrate using move/copy.
126
  </p>
127
+ {offlineMode && (
128
+ <div className="error">
129
+ Backend is offline. Running in browser-only demo mode with local mock data.
130
+ </div>
131
+ )}
132
 
133
  {error && <div className="error">{error}</div>}
134
 
frontend/src/api.js CHANGED
@@ -1,4 +1,36 @@
1
  const API_BASE = "http://127.0.0.1:8000/api";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  async function jsonRequest(path, options = {}) {
4
  const response = await fetch(`${API_BASE}${path}`, {
@@ -12,39 +44,221 @@ async function jsonRequest(path, options = {}) {
12
  return response.json();
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  export const api = {
16
- getSettings: () => jsonRequest("/settings"),
 
17
  saveSettings: (payload) =>
18
- jsonRequest("/settings", {
19
  method: "PUT",
20
  body: JSON.stringify(payload),
21
  }),
22
  startRun: (payload) =>
23
- jsonRequest("/runs/start", {
24
  method: "POST",
25
  body: JSON.stringify(payload),
26
  }),
27
- getRun: (runId) => jsonRequest(`/runs/${runId}`),
28
  getRunItems: (runId, filters = {}) => {
29
  const params = new URLSearchParams();
30
  if (filters.status) params.set("status", filters.status);
31
- if (filters.needs_review !== undefined)
32
  params.set("needs_review", String(filters.needs_review));
 
33
  const query = params.toString();
34
- return jsonRequest(`/runs/${runId}/items${query ? `?${query}` : ""}`);
35
  },
36
  updateItem: (itemId, payload) =>
37
- jsonRequest(`/items/${itemId}`, {
38
  method: "PATCH",
39
  body: JSON.stringify(payload),
40
  }),
41
  batchUpdate: (runId, payload) =>
42
- jsonRequest(`/runs/${runId}/batch`, {
43
  method: "POST",
44
  body: JSON.stringify(payload),
45
  }),
46
  migrateRun: (runId, payload) =>
47
- jsonRequest(`/runs/${runId}/migrate`, {
48
  method: "POST",
49
  body: JSON.stringify(payload),
50
  }),
 
1
  const API_BASE = "http://127.0.0.1:8000/api";
2
+ const STORAGE_KEY = "imageClassifierMockStateV1";
3
+
4
+ const defaultSettings = {
5
+ root_repo: "",
6
+ categories_root: "",
7
+ confidence_threshold: 0.6,
8
+ default_migrate_mode: "copy",
9
+ };
10
+
11
+ const mockState = loadMockState();
12
+ let backendAvailable = null;
13
+
14
+ function loadMockState() {
15
+ try {
16
+ const raw = localStorage.getItem(STORAGE_KEY);
17
+ if (!raw) {
18
+ return { settings: { ...defaultSettings }, nextRunId: 1, runs: {} };
19
+ }
20
+ const parsed = JSON.parse(raw);
21
+ return {
22
+ settings: { ...defaultSettings, ...(parsed.settings || {}) },
23
+ nextRunId: parsed.nextRunId || 1,
24
+ runs: parsed.runs || {},
25
+ };
26
+ } catch (_) {
27
+ return { settings: { ...defaultSettings }, nextRunId: 1, runs: {} };
28
+ }
29
+ }
30
+
31
+ function persistMockState() {
32
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(mockState));
33
+ }
34
 
35
  async function jsonRequest(path, options = {}) {
36
  const response = await fetch(`${API_BASE}${path}`, {
 
44
  return response.json();
45
  }
46
 
47
+ async function request(path, options = {}) {
48
+ if (backendAvailable === false) {
49
+ return mockRequest(path, options);
50
+ }
51
+ try {
52
+ const data = await jsonRequest(path, options);
53
+ backendAvailable = true;
54
+ return data;
55
+ } catch (err) {
56
+ if (backendAvailable === null) {
57
+ backendAvailable = false;
58
+ return mockRequest(path, options);
59
+ }
60
+ throw err;
61
+ }
62
+ }
63
+
64
+ function parseBody(options) {
65
+ if (!options.body) return {};
66
+ return JSON.parse(options.body);
67
+ }
68
+
69
+ function mockCounts(items) {
70
+ const byStatus = {};
71
+ for (const item of items) {
72
+ byStatus[item.status] = (byStatus[item.status] || 0) + 1;
73
+ }
74
+ return Object.entries(byStatus).map(([status, count]) => ({ status, count }));
75
+ }
76
+
77
+ function makeMockItems(runId, selectedFolders, threshold) {
78
+ const pool = selectedFolders.length ? selectedFolders : ["1girl", "solo", "blush"];
79
+ return pool.slice(0, 8).map((tag, idx) => {
80
+ const score = Math.max(0.3, 0.9 - idx * 0.06);
81
+ const needsReview = score < threshold;
82
+ return {
83
+ id: runId * 1000 + idx + 1,
84
+ run_id: runId,
85
+ file_path: `/mock/images/sample_${idx + 1}.jpg`,
86
+ relative_path: `sample_${idx + 1}.jpg`,
87
+ primary_tag: tag,
88
+ primary_score: score,
89
+ secondary_suggestions: pool
90
+ .filter((x) => x !== tag)
91
+ .slice(0, 3)
92
+ .map((x, i) => ({ tag: x, score: Math.max(0.2, score - 0.1 - i * 0.05) })),
93
+ suggested_destination: `${mockState.settings.categories_root || "/mock/categories"}/${tag}`,
94
+ final_tag: tag,
95
+ final_destination: `${mockState.settings.categories_root || "/mock/categories"}/${tag}`,
96
+ status: "proposed",
97
+ needs_review: needsReview,
98
+ review_reason: needsReview ? `Below threshold (${score.toFixed(3)} < ${threshold.toFixed(3)})` : null,
99
+ migrated_to: null,
100
+ };
101
+ });
102
+ }
103
+
104
+ function mockRequest(path, options = {}) {
105
+ const method = (options.method || "GET").toUpperCase();
106
+ const body = parseBody(options);
107
+
108
+ if (path === "/settings" && method === "GET") {
109
+ return Promise.resolve(mockState.settings);
110
+ }
111
+ if (path === "/settings" && method === "PUT") {
112
+ mockState.settings = { ...defaultSettings, ...body };
113
+ persistMockState();
114
+ return Promise.resolve(mockState.settings);
115
+ }
116
+ if (path === "/runs/start" && method === "POST") {
117
+ mockState.settings = { ...mockState.settings, ...body };
118
+ const runId = mockState.nextRunId++;
119
+ const selectedFolders = body.selected_folders || [];
120
+ const threshold = Number(mockState.settings.confidence_threshold || 0.6);
121
+ const items = makeMockItems(runId, selectedFolders, threshold);
122
+ const mappings = selectedFolders.map((name) => ({
123
+ folder_name: name,
124
+ normalized_name: name.toLowerCase(),
125
+ matched_tag: name.toLowerCase(),
126
+ matched: true,
127
+ }));
128
+ mockState.runs[runId] = {
129
+ run: {
130
+ id: runId,
131
+ root_repo: mockState.settings.root_repo,
132
+ categories_root: mockState.settings.categories_root,
133
+ confidence_threshold: threshold,
134
+ },
135
+ items,
136
+ };
137
+ persistMockState();
138
+ return Promise.resolve({
139
+ run_id: runId,
140
+ stats: {
141
+ total_files: items.length + 2,
142
+ eligible_images: items.length,
143
+ ignored_unsupported: 1,
144
+ ignored_gif: 1,
145
+ failed_to_read: 0,
146
+ },
147
+ mappings,
148
+ unmatched_folders: [],
149
+ created_items: items.length,
150
+ });
151
+ }
152
+ if (path.startsWith("/runs/") && path.endsWith("/items") && method === "GET") {
153
+ const runId = Number(path.split("/")[2]);
154
+ const run = mockState.runs[runId];
155
+ if (!run) return Promise.resolve([]);
156
+ return Promise.resolve(run.items);
157
+ }
158
+ if (path.startsWith("/runs/") && !path.includes("/items") && method === "GET") {
159
+ const runId = Number(path.split("/")[2]);
160
+ const run = mockState.runs[runId];
161
+ if (!run) return Promise.resolve({ run: null, counts: [] });
162
+ return Promise.resolve({ run: run.run, counts: mockCounts(run.items) });
163
+ }
164
+ if (path.startsWith("/items/") && method === "PATCH") {
165
+ const itemId = Number(path.split("/")[2]);
166
+ const run = Object.values(mockState.runs).find((r) => r.items.some((i) => i.id === itemId));
167
+ if (!run) return Promise.resolve(null);
168
+ const item = run.items.find((i) => i.id === itemId);
169
+ if (body.status) item.status = body.status;
170
+ if (body.final_tag) {
171
+ item.final_tag = body.final_tag;
172
+ item.final_destination = `${mockState.settings.categories_root || "/mock/categories"}/${body.final_tag}`;
173
+ }
174
+ item.needs_review = false;
175
+ item.review_reason = null;
176
+ persistMockState();
177
+ return Promise.resolve(item);
178
+ }
179
+ if (path.match(/^\/runs\/\d+\/batch$/) && method === "POST") {
180
+ const runId = Number(path.split("/")[2]);
181
+ const run = mockState.runs[runId];
182
+ if (!run) return Promise.resolve({ updated: 0 });
183
+ let updated = 0;
184
+ for (const item of run.items) {
185
+ if (!body.item_ids.includes(item.id)) continue;
186
+ if (body.status) item.status = body.status;
187
+ if (body.final_tag) {
188
+ item.final_tag = body.final_tag;
189
+ item.final_destination = `${mockState.settings.categories_root || "/mock/categories"}/${body.final_tag}`;
190
+ }
191
+ item.needs_review = false;
192
+ item.review_reason = null;
193
+ updated += 1;
194
+ }
195
+ persistMockState();
196
+ return Promise.resolve({ updated });
197
+ }
198
+ if (path.match(/^\/runs\/\d+\/migrate$/) && method === "POST") {
199
+ const runId = Number(path.split("/")[2]);
200
+ const run = mockState.runs[runId];
201
+ if (!run) return Promise.resolve({ mode: body.mode, total_candidates: 0, migrated_count: 0, failed_count: 0, results: [] });
202
+ const approved = run.items.filter((x) => x.status === "approved");
203
+ const results = approved.map((item) => {
204
+ item.status = "migrated";
205
+ item.migrated_to = `${item.final_destination}/${item.relative_path}`;
206
+ return {
207
+ item_id: item.id,
208
+ source: item.file_path,
209
+ destination: item.migrated_to,
210
+ success: true,
211
+ error: null,
212
+ };
213
+ });
214
+ persistMockState();
215
+ return Promise.resolve({
216
+ mode: body.mode,
217
+ total_candidates: approved.length,
218
+ migrated_count: approved.length,
219
+ failed_count: 0,
220
+ results,
221
+ });
222
+ }
223
+
224
+ return Promise.reject(new Error(`Unsupported mock route: ${method} ${path}`));
225
+ }
226
+
227
  export const api = {
228
+ isOfflineMode: () => backendAvailable === false,
229
+ getSettings: () => request("/settings"),
230
  saveSettings: (payload) =>
231
+ request("/settings", {
232
  method: "PUT",
233
  body: JSON.stringify(payload),
234
  }),
235
  startRun: (payload) =>
236
+ request("/runs/start", {
237
  method: "POST",
238
  body: JSON.stringify(payload),
239
  }),
240
+ getRun: (runId) => request(`/runs/${runId}`),
241
  getRunItems: (runId, filters = {}) => {
242
  const params = new URLSearchParams();
243
  if (filters.status) params.set("status", filters.status);
244
+ if (filters.needs_review !== undefined) {
245
  params.set("needs_review", String(filters.needs_review));
246
+ }
247
  const query = params.toString();
248
+ return request(`/runs/${runId}/items${query ? `?${query}` : ""}`);
249
  },
250
  updateItem: (itemId, payload) =>
251
+ request(`/items/${itemId}`, {
252
  method: "PATCH",
253
  body: JSON.stringify(payload),
254
  }),
255
  batchUpdate: (runId, payload) =>
256
+ request(`/runs/${runId}/batch`, {
257
  method: "POST",
258
  body: JSON.stringify(payload),
259
  }),
260
  migrateRun: (runId, payload) =>
261
+ request(`/runs/${runId}/migrate`, {
262
  method: "POST",
263
  body: JSON.stringify(payload),
264
  }),