ArnoChen commited on
Commit
0e5f52f
·
1 Parent(s): 07feee7

add document manager and site heaer

Browse files
lightrag_webui/bun.lock CHANGED
@@ -4,13 +4,17 @@
4
  "": {
5
  "name": "lightrag-webui",
6
  "dependencies": {
7
- "@faker-js/faker": "^9.4.0",
8
  "@radix-ui/react-checkbox": "^1.1.4",
9
  "@radix-ui/react-dialog": "^1.1.6",
10
  "@radix-ui/react-popover": "^1.1.6",
 
 
11
  "@radix-ui/react-separator": "^1.1.2",
12
  "@radix-ui/react-slot": "^1.1.2",
 
13
  "@radix-ui/react-tooltip": "^1.1.8",
 
14
  "@react-sigma/core": "^5.0.2",
15
  "@react-sigma/graph-search": "^5.0.3",
16
  "@react-sigma/layout-circlepack": "^5.0.2",
@@ -22,6 +26,7 @@
22
  "@react-sigma/minimap": "^5.0.2",
23
  "@sigma/edge-curve": "^3.1.0",
24
  "@sigma/node-border": "^3.0.0",
 
25
  "class-variance-authority": "^0.7.1",
26
  "clsx": "^2.1.1",
27
  "cmdk": "^1.0.4",
@@ -31,8 +36,10 @@
31
  "minisearch": "^7.1.1",
32
  "react": "^19.0.0",
33
  "react-dom": "^19.0.0",
 
34
  "seedrandom": "^3.0.5",
35
  "sigma": "^3.0.1",
 
36
  "tailwind-merge": "^3.0.1",
37
  "zustand": "^5.0.3",
38
  },
@@ -41,19 +48,19 @@
41
  "@stylistic/eslint-plugin-js": "^3.1.0",
42
  "@tailwindcss/vite": "^4.0.6",
43
  "@types/bun": "^1.2.2",
44
- "@types/node": "^22.13.1",
45
  "@types/react": "^19.0.8",
46
  "@types/react-dom": "^19.0.3",
47
  "@types/seedrandom": "^3.0.8",
48
  "@vitejs/plugin-react-swc": "^3.8.0",
49
- "eslint": "^9.20.0",
50
  "eslint-config-prettier": "^10.0.1",
51
  "eslint-plugin-react": "^7.37.4",
52
  "eslint-plugin-react-hooks": "^5.1.0",
53
  "eslint-plugin-react-refresh": "^0.4.19",
54
- "globals": "^15.14.0",
55
  "graphology-types": "^0.24.8",
56
- "prettier": "^3.5.0",
57
  "prettier-plugin-tailwindcss": "^0.6.11",
58
  "tailwindcss": "^4.0.6",
59
  "tailwindcss-animate": "^1.0.7",
@@ -172,7 +179,7 @@
172
 
173
  "@eslint/plugin-kit": ["@eslint/[email protected]", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
174
 
175
- "@faker-js/faker": ["@faker-js/faker@9.4.0", "", {}, "sha512-85+k0AxaZSTowL0gXp8zYWDIrWclTbRPg/pm/V0dSFZ6W6D4lhcG3uuZl4zLsEKfEvs69xDbLN2cHQudwp95JA=="],
176
 
177
  "@floating-ui/core": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
178
 
@@ -206,18 +213,24 @@
206
 
207
  "@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
208
 
 
 
209
  "@radix-ui/primitive": ["@radix-ui/[email protected]", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
210
 
211
  "@radix-ui/react-arrow": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
212
 
213
  "@radix-ui/react-checkbox": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
214
 
 
 
215
  "@radix-ui/react-compose-refs": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
216
 
217
  "@radix-ui/react-context": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
218
 
219
  "@radix-ui/react-dialog": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="],
220
 
 
 
221
  "@radix-ui/react-dismissable-layer": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
222
 
223
  "@radix-ui/react-focus-guards": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
@@ -236,10 +249,18 @@
236
 
237
  "@radix-ui/react-primitive": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
238
 
 
 
 
 
 
 
239
  "@radix-ui/react-separator": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
240
 
241
  "@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
242
 
 
 
243
  "@radix-ui/react-tooltip": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="],
244
 
245
  "@radix-ui/react-use-callback-ref": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
@@ -384,7 +405,7 @@
384
 
385
  "@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
386
 
387
- "@types/node": ["@types/[email protected].1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
388
 
389
  "@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
390
 
@@ -446,8 +467,14 @@
446
 
447
  "async-function": ["[email protected]", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
448
 
 
 
 
 
449
  "available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
450
 
 
 
451
  "babel-plugin-macros": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
452
 
453
  "balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -478,6 +505,8 @@
478
 
479
  "color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
480
 
 
 
481
  "concat-map": ["[email protected]", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
482
 
483
  "convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
@@ -502,6 +531,8 @@
502
 
503
  "define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
504
 
 
 
505
  "detect-libc": ["[email protected]", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
506
 
507
  "detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
@@ -536,7 +567,7 @@
536
 
537
  "escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
538
 
539
- "eslint": ["[email protected].0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA=="],
540
 
541
  "eslint-config-prettier": ["[email protected]", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "build/bin/cli.js" } }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="],
542
 
@@ -574,6 +605,8 @@
574
 
575
  "file-entry-cache": ["[email protected]", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
576
 
 
 
577
  "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
578
 
579
  "find-root": ["[email protected]", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
@@ -584,8 +617,12 @@
584
 
585
  "flatted": ["[email protected]", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
586
 
 
 
587
  "for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
588
 
 
 
589
  "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
590
 
591
  "function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -604,7 +641,7 @@
604
 
605
  "glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
606
 
607
- "globals": ["globals@15.14.0", "", {}, "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig=="],
608
 
609
  "globalthis": ["[email protected]", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
610
 
@@ -778,6 +815,10 @@
778
 
779
  "micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
780
 
 
 
 
 
781
  "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
782
 
783
  "minisearch": ["[email protected]", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
@@ -838,12 +879,14 @@
838
 
839
  "prelude-ls": ["[email protected]", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
840
 
841
- "prettier": ["[email protected].0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA=="],
842
 
843
  "prettier-plugin-tailwindcss": ["[email protected]", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
844
 
845
  "prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
846
 
 
 
847
  "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
848
 
849
  "queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
@@ -852,6 +895,8 @@
852
 
853
  "react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
854
 
 
 
855
  "react-is": ["[email protected]", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
856
 
857
  "react-remove-scroll": ["[email protected]", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
@@ -912,6 +957,8 @@
912
 
913
  "sigma": ["[email protected]", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
914
 
 
 
915
  "source-map": ["[email protected]", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
916
 
917
  "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -1006,12 +1053,16 @@
1006
 
1007
  "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
1008
 
 
 
1009
  "@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
1010
 
1011
  "@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
1012
 
1013
  "babel-plugin-macros/resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
1014
 
 
 
1015
  "fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
1016
 
1017
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 
4
  "": {
5
  "name": "lightrag-webui",
6
  "dependencies": {
7
+ "@faker-js/faker": "^9.5.0",
8
  "@radix-ui/react-checkbox": "^1.1.4",
9
  "@radix-ui/react-dialog": "^1.1.6",
10
  "@radix-ui/react-popover": "^1.1.6",
11
+ "@radix-ui/react-progress": "^1.1.2",
12
+ "@radix-ui/react-scroll-area": "^1.2.3",
13
  "@radix-ui/react-separator": "^1.1.2",
14
  "@radix-ui/react-slot": "^1.1.2",
15
+ "@radix-ui/react-tabs": "^1.1.3",
16
  "@radix-ui/react-tooltip": "^1.1.8",
17
+ "@radix-ui/react-use-controllable-state": "^1.1.0",
18
  "@react-sigma/core": "^5.0.2",
19
  "@react-sigma/graph-search": "^5.0.3",
20
  "@react-sigma/layout-circlepack": "^5.0.2",
 
26
  "@react-sigma/minimap": "^5.0.2",
27
  "@sigma/edge-curve": "^3.1.0",
28
  "@sigma/node-border": "^3.0.0",
29
+ "axios": "^1.7.9",
30
  "class-variance-authority": "^0.7.1",
31
  "clsx": "^2.1.1",
32
  "cmdk": "^1.0.4",
 
36
  "minisearch": "^7.1.1",
37
  "react": "^19.0.0",
38
  "react-dom": "^19.0.0",
39
+ "react-dropzone": "^14.3.5",
40
  "seedrandom": "^3.0.5",
41
  "sigma": "^3.0.1",
42
+ "sonner": "^1.7.4",
43
  "tailwind-merge": "^3.0.1",
44
  "zustand": "^5.0.3",
45
  },
 
48
  "@stylistic/eslint-plugin-js": "^3.1.0",
49
  "@tailwindcss/vite": "^4.0.6",
50
  "@types/bun": "^1.2.2",
51
+ "@types/node": "^22.13.4",
52
  "@types/react": "^19.0.8",
53
  "@types/react-dom": "^19.0.3",
54
  "@types/seedrandom": "^3.0.8",
55
  "@vitejs/plugin-react-swc": "^3.8.0",
56
+ "eslint": "^9.20.1",
57
  "eslint-config-prettier": "^10.0.1",
58
  "eslint-plugin-react": "^7.37.4",
59
  "eslint-plugin-react-hooks": "^5.1.0",
60
  "eslint-plugin-react-refresh": "^0.4.19",
61
+ "globals": "^15.15.0",
62
  "graphology-types": "^0.24.8",
63
+ "prettier": "^3.5.1",
64
  "prettier-plugin-tailwindcss": "^0.6.11",
65
  "tailwindcss": "^4.0.6",
66
  "tailwindcss-animate": "^1.0.7",
 
179
 
180
  "@eslint/plugin-kit": ["@eslint/[email protected]", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
181
 
182
+ "@faker-js/faker": ["@faker-js/faker@9.5.0", "", {}, "sha512-3qbjLv+fzuuCg3umxc9/7YjrEXNaKwHgmig949nfyaTx8eL4FAsvFbu+1JcFUj1YAXofhaDn6JdEUBTYuk0Ssw=="],
183
 
184
  "@floating-ui/core": ["@floating-ui/[email protected]", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
185
 
 
213
 
214
  "@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
215
 
216
+ "@radix-ui/number": ["@radix-ui/[email protected]", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="],
217
+
218
  "@radix-ui/primitive": ["@radix-ui/[email protected]", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
219
 
220
  "@radix-ui/react-arrow": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
221
 
222
  "@radix-ui/react-checkbox": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
223
 
224
+ "@radix-ui/react-collection": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="],
225
+
226
  "@radix-ui/react-compose-refs": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
227
 
228
  "@radix-ui/react-context": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
229
 
230
  "@radix-ui/react-dialog": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="],
231
 
232
+ "@radix-ui/react-direction": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="],
233
+
234
  "@radix-ui/react-dismissable-layer": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
235
 
236
  "@radix-ui/react-focus-guards": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
 
249
 
250
  "@radix-ui/react-primitive": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
251
 
252
+ "@radix-ui/react-progress": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA=="],
253
+
254
+ "@radix-ui/react-roving-focus": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw=="],
255
+
256
+ "@radix-ui/react-scroll-area": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ=="],
257
+
258
  "@radix-ui/react-separator": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
259
 
260
  "@radix-ui/react-slot": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
261
 
262
+ "@radix-ui/react-tabs": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng=="],
263
+
264
  "@radix-ui/react-tooltip": ["@radix-ui/[email protected]", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA=="],
265
 
266
  "@radix-ui/react-use-callback-ref": ["@radix-ui/[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
 
405
 
406
  "@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
407
 
408
+ "@types/node": ["@types/[email protected].4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="],
409
 
410
  "@types/parse-json": ["@types/[email protected]", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
411
 
 
467
 
468
  "async-function": ["[email protected]", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
469
 
470
+ "asynckit": ["[email protected]", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
471
+
472
+ "attr-accept": ["[email protected]", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
473
+
474
  "available-typed-arrays": ["[email protected]", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
475
 
476
+ "axios": ["[email protected]", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="],
477
+
478
  "babel-plugin-macros": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
479
 
480
  "balanced-match": ["[email protected]", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
 
505
 
506
  "color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
507
 
508
+ "combined-stream": ["[email protected]", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
509
+
510
  "concat-map": ["[email protected]", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
511
 
512
  "convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
 
531
 
532
  "define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
533
 
534
+ "delayed-stream": ["[email protected]", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
535
+
536
  "detect-libc": ["[email protected]", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
537
 
538
  "detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
 
567
 
568
  "escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
569
 
570
+ "eslint": ["[email protected].1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.20.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g=="],
571
 
572
  "eslint-config-prettier": ["[email protected]", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "build/bin/cli.js" } }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="],
573
 
 
605
 
606
  "file-entry-cache": ["[email protected]", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
607
 
608
+ "file-selector": ["[email protected]", "", { "dependencies": { "tslib": "^2.7.0" } }, "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig=="],
609
+
610
  "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
611
 
612
  "find-root": ["[email protected]", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
 
617
 
618
  "flatted": ["[email protected]", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
619
 
620
+ "follow-redirects": ["[email protected]", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
621
+
622
  "for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
623
 
624
+ "form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
625
+
626
  "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
627
 
628
  "function-bind": ["[email protected]", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
 
641
 
642
  "glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
643
 
644
+ "globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="],
645
 
646
  "globalthis": ["[email protected]", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
647
 
 
815
 
816
  "micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
817
 
818
+ "mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
819
+
820
+ "mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
821
+
822
  "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
823
 
824
  "minisearch": ["[email protected]", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
 
879
 
880
  "prelude-ls": ["[email protected]", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
881
 
882
+ "prettier": ["[email protected].1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw=="],
883
 
884
  "prettier-plugin-tailwindcss": ["[email protected]", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-import-sort": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-style-order": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-import-sort", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-style-order", "prettier-plugin-svelte"] }, "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA=="],
885
 
886
  "prop-types": ["[email protected]", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
887
 
888
+ "proxy-from-env": ["[email protected]", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
889
+
890
  "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
891
 
892
  "queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
 
895
 
896
  "react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
897
 
898
+ "react-dropzone": ["[email protected]", "", { "dependencies": { "attr-accept": "^2.2.4", "file-selector": "^2.1.0", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">= 16.8 || 18.0.0" } }, "sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ=="],
899
+
900
  "react-is": ["[email protected]", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
901
 
902
  "react-remove-scroll": ["[email protected]", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
 
957
 
958
  "sigma": ["[email protected]", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
959
 
960
+ "sonner": ["[email protected]", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
961
+
962
  "source-map": ["[email protected]", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
963
 
964
  "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
 
1053
 
1054
  "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
1055
 
1056
+ "@types/ws/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
1057
+
1058
  "@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
1059
 
1060
  "@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
1061
 
1062
  "babel-plugin-macros/resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
1063
 
1064
+ "bun-types/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
1065
+
1066
  "fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
1067
 
1068
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
lightrag_webui/eslint.config.js CHANGED
@@ -7,27 +7,31 @@ import tseslint from 'typescript-eslint'
7
  import prettier from 'eslint-config-prettier'
8
  import react from 'eslint-plugin-react'
9
 
10
- export default tseslint.config({ ignores: ['dist'] }, prettier, {
11
- extends: [js.configs.recommended, ...tseslint.configs.recommended],
12
- files: ['**/*.{ts,tsx,js,jsx}'],
13
- languageOptions: {
14
- ecmaVersion: 2020,
15
- globals: globals.browser
16
- },
17
- settings: { react: { version: '19.0' } },
18
- plugins: {
19
- 'react-hooks': reactHooks,
20
- 'react-refresh': reactRefresh,
21
- '@stylistic/js': stylisticJs,
22
- react
23
- },
24
- rules: {
25
- ...reactHooks.configs.recommended.rules,
26
- 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
27
- ...react.configs.recommended.rules,
28
- ...react.configs['jsx-runtime'].rules,
29
- '@stylistic/js/indent': ['error', 2],
30
- '@stylistic/js/quotes': ['error', 'single'],
31
- '@typescript-eslint/no-explicit-any': ['off']
 
 
 
 
32
  }
33
- })
 
7
  import prettier from 'eslint-config-prettier'
8
  import react from 'eslint-plugin-react'
9
 
10
+ export default tseslint.config(
11
+ { ignores: ['dist'] },
12
+ {
13
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
14
+ files: ['**/*.{ts,tsx,js,jsx}'],
15
+ languageOptions: {
16
+ ecmaVersion: 2020,
17
+ globals: globals.browser
18
+ },
19
+ settings: { react: { version: '19.0' } },
20
+ plugins: {
21
+ 'react-hooks': reactHooks,
22
+ 'react-refresh': reactRefresh,
23
+ '@stylistic/js': stylisticJs,
24
+ react
25
+ },
26
+ rules: {
27
+ ...reactHooks.configs.recommended.rules,
28
+ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
29
+ ...react.configs.recommended.rules,
30
+ ...react.configs['jsx-runtime'].rules,
31
+ '@stylistic/js/indent': ['error', 2],
32
+ '@stylistic/js/quotes': ['error', 'single'],
33
+ '@typescript-eslint/no-explicit-any': ['off']
34
+ },
35
+ prettier
36
  }
37
+ )
lightrag_webui/package.json CHANGED
@@ -10,13 +10,17 @@
10
  "preview": "bunx --bun vite preview"
11
  },
12
  "dependencies": {
13
- "@faker-js/faker": "^9.4.0",
14
  "@radix-ui/react-checkbox": "^1.1.4",
15
  "@radix-ui/react-dialog": "^1.1.6",
16
  "@radix-ui/react-popover": "^1.1.6",
 
 
17
  "@radix-ui/react-separator": "^1.1.2",
18
  "@radix-ui/react-slot": "^1.1.2",
 
19
  "@radix-ui/react-tooltip": "^1.1.8",
 
20
  "@react-sigma/core": "^5.0.2",
21
  "@react-sigma/graph-search": "^5.0.3",
22
  "@react-sigma/layout-circlepack": "^5.0.2",
@@ -28,6 +32,7 @@
28
  "@react-sigma/minimap": "^5.0.2",
29
  "@sigma/edge-curve": "^3.1.0",
30
  "@sigma/node-border": "^3.0.0",
 
31
  "class-variance-authority": "^0.7.1",
32
  "clsx": "^2.1.1",
33
  "cmdk": "^1.0.4",
@@ -37,8 +42,10 @@
37
  "minisearch": "^7.1.1",
38
  "react": "^19.0.0",
39
  "react-dom": "^19.0.0",
 
40
  "seedrandom": "^3.0.5",
41
  "sigma": "^3.0.1",
 
42
  "tailwind-merge": "^3.0.1",
43
  "zustand": "^5.0.3"
44
  },
@@ -47,19 +54,19 @@
47
  "@stylistic/eslint-plugin-js": "^3.1.0",
48
  "@tailwindcss/vite": "^4.0.6",
49
  "@types/bun": "^1.2.2",
50
- "@types/node": "^22.13.1",
51
  "@types/react": "^19.0.8",
52
  "@types/react-dom": "^19.0.3",
53
  "@types/seedrandom": "^3.0.8",
54
  "@vitejs/plugin-react-swc": "^3.8.0",
55
- "eslint": "^9.20.0",
56
  "eslint-config-prettier": "^10.0.1",
57
  "eslint-plugin-react": "^7.37.4",
58
  "eslint-plugin-react-hooks": "^5.1.0",
59
  "eslint-plugin-react-refresh": "^0.4.19",
60
- "globals": "^15.14.0",
61
  "graphology-types": "^0.24.8",
62
- "prettier": "^3.5.0",
63
  "prettier-plugin-tailwindcss": "^0.6.11",
64
  "tailwindcss": "^4.0.6",
65
  "tailwindcss-animate": "^1.0.7",
 
10
  "preview": "bunx --bun vite preview"
11
  },
12
  "dependencies": {
13
+ "@faker-js/faker": "^9.5.0",
14
  "@radix-ui/react-checkbox": "^1.1.4",
15
  "@radix-ui/react-dialog": "^1.1.6",
16
  "@radix-ui/react-popover": "^1.1.6",
17
+ "@radix-ui/react-progress": "^1.1.2",
18
+ "@radix-ui/react-scroll-area": "^1.2.3",
19
  "@radix-ui/react-separator": "^1.1.2",
20
  "@radix-ui/react-slot": "^1.1.2",
21
+ "@radix-ui/react-tabs": "^1.1.3",
22
  "@radix-ui/react-tooltip": "^1.1.8",
23
+ "@radix-ui/react-use-controllable-state": "^1.1.0",
24
  "@react-sigma/core": "^5.0.2",
25
  "@react-sigma/graph-search": "^5.0.3",
26
  "@react-sigma/layout-circlepack": "^5.0.2",
 
32
  "@react-sigma/minimap": "^5.0.2",
33
  "@sigma/edge-curve": "^3.1.0",
34
  "@sigma/node-border": "^3.0.0",
35
+ "axios": "^1.7.9",
36
  "class-variance-authority": "^0.7.1",
37
  "clsx": "^2.1.1",
38
  "cmdk": "^1.0.4",
 
42
  "minisearch": "^7.1.1",
43
  "react": "^19.0.0",
44
  "react-dom": "^19.0.0",
45
+ "react-dropzone": "^14.3.5",
46
  "seedrandom": "^3.0.5",
47
  "sigma": "^3.0.1",
48
+ "sonner": "^1.7.4",
49
  "tailwind-merge": "^3.0.1",
50
  "zustand": "^5.0.3"
51
  },
 
54
  "@stylistic/eslint-plugin-js": "^3.1.0",
55
  "@tailwindcss/vite": "^4.0.6",
56
  "@types/bun": "^1.2.2",
57
+ "@types/node": "^22.13.4",
58
  "@types/react": "^19.0.8",
59
  "@types/react-dom": "^19.0.3",
60
  "@types/seedrandom": "^3.0.8",
61
  "@vitejs/plugin-react-swc": "^3.8.0",
62
+ "eslint": "^9.20.1",
63
  "eslint-config-prettier": "^10.0.1",
64
  "eslint-plugin-react": "^7.37.4",
65
  "eslint-plugin-react-hooks": "^5.1.0",
66
  "eslint-plugin-react-refresh": "^0.4.19",
67
+ "globals": "^15.15.0",
68
  "graphology-types": "^0.24.8",
69
+ "prettier": "^3.5.1",
70
  "prettier-plugin-tailwindcss": "^0.6.11",
71
  "tailwindcss": "^4.0.6",
72
  "tailwindcss-animate": "^1.0.7",
lightrag_webui/src/App.tsx CHANGED
@@ -1,11 +1,17 @@
1
  import ThemeProvider from '@/components/ThemeProvider'
2
  import MessageAlert from '@/components/MessageAlert'
3
  import StatusIndicator from '@/components/StatusIndicator'
4
- import GraphViewer from '@/GraphViewer'
5
  import { healthCheckInterval } from '@/lib/constants'
6
  import { useBackendState } from '@/stores/state'
7
  import { useSettingsStore } from '@/stores/settings'
8
  import { useEffect } from 'react'
 
 
 
 
 
 
 
9
 
10
  function App() {
11
  const message = useBackendState.use.message()
@@ -26,11 +32,23 @@ function App() {
26
 
27
  return (
28
  <ThemeProvider>
29
- <div className="h-screen w-screen">
30
- <GraphViewer />
 
 
 
 
 
 
 
 
 
 
 
31
  </div>
32
  {enableHealthCheck && <StatusIndicator />}
33
  {message !== null && <MessageAlert />}
 
34
  </ThemeProvider>
35
  )
36
  }
 
1
  import ThemeProvider from '@/components/ThemeProvider'
2
  import MessageAlert from '@/components/MessageAlert'
3
  import StatusIndicator from '@/components/StatusIndicator'
 
4
  import { healthCheckInterval } from '@/lib/constants'
5
  import { useBackendState } from '@/stores/state'
6
  import { useSettingsStore } from '@/stores/settings'
7
  import { useEffect } from 'react'
8
+ import { Toaster } from 'sonner'
9
+ import SiteHeader from '@/features/SiteHeader'
10
+
11
+ import GraphViewer from '@/features/GraphViewer'
12
+ import DocumentManager from '@/features/DocumentManager'
13
+
14
+ import { Tabs, TabsContent } from '@/components/ui/Tabs'
15
 
16
  function App() {
17
  const message = useBackendState.use.message()
 
32
 
33
  return (
34
  <ThemeProvider>
35
+ <div className="flex h-screen w-screen">
36
+ <Tabs defaultValue="knowledge-graph" className="flex size-full flex-col">
37
+ <SiteHeader />
38
+ <TabsContent value="documents" className="flex-1">
39
+ <DocumentManager />
40
+ </TabsContent>
41
+ <TabsContent value="knowledge-graph" className="flex-1">
42
+ <GraphViewer />
43
+ </TabsContent>
44
+ <TabsContent value="settings" className="size-full">
45
+ <h1> Settings </h1>
46
+ </TabsContent>
47
+ </Tabs>
48
  </div>
49
  {enableHealthCheck && <StatusIndicator />}
50
  {message !== null && <MessageAlert />}
51
+ <Toaster />
52
  </ThemeProvider>
53
  )
54
  }
lightrag_webui/src/api/lightrag.ts CHANGED
@@ -1,3 +1,4 @@
 
1
  import { backendBaseUrl } from '@/lib/constants'
2
  import { errorMessage } from '@/lib/utils'
3
  import { useSettingsStore } from '@/stores/settings'
@@ -64,81 +65,64 @@ export type QueryResponse = {
64
  response: string
65
  }
66
 
 
 
 
 
 
 
67
  export const InvalidApiKeyError = 'Invalid API Key'
68
  export const RequireApiKeError = 'API Key required'
69
 
70
- // Helper functions
71
- const getResponseContent = async (response: Response) => {
72
- const contentType = response.headers.get('content-type')
73
- if (contentType) {
74
- if (contentType.includes('application/json')) {
75
- const data = await response.json()
76
- return JSON.stringify(data, undefined, 2)
77
- } else if (contentType.startsWith('text/')) {
78
- return await response.text()
79
- } else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
80
- return await response.text()
81
- } else if (contentType.includes('application/octet-stream')) {
82
- const buffer = await response.arrayBuffer()
83
- const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true })
84
- return decoder.decode(buffer)
85
- } else {
86
- try {
87
- return await response.text()
88
- } catch (error) {
89
- console.warn('Failed to decode as text, may be binary:', error)
90
- return `[Could not decode response body. Content-Type: ${contentType}]`
91
- }
92
- }
93
- } else {
94
- try {
95
- return await response.text()
96
- } catch (error) {
97
- console.warn('Failed to decode as text, may be binary:', error)
98
- return '[Could not decode response body. No Content-Type header.]'
99
- }
100
  }
101
- return ''
102
- }
103
 
104
- const fetchWithAuth = async (url: string, options: RequestInit = {}): Promise<Response> => {
 
105
  const apiKey = useSettingsStore.getState().apiKey
106
- const headers = {
107
- ...(options.headers || {}),
108
- ...(apiKey ? { 'X-API-Key': apiKey } : {})
109
  }
110
-
111
- const response = await fetch(backendBaseUrl + url, {
112
- ...options,
113
- headers
114
- })
115
-
116
- if (!response.ok) {
117
- throw new Error(
118
- `${response.status} ${response.statusText}\n${await getResponseContent(response)}\n${response.url}`
119
- )
 
 
 
 
 
120
  }
121
-
122
- return response
123
- }
124
 
125
  // API methods
126
  export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
127
- const response = await fetchWithAuth(`/graphs?label=${label}`)
128
- return await response.json()
129
  }
130
 
131
  export const getGraphLabels = async (): Promise<string[]> => {
132
- const response = await fetchWithAuth('/graph/label/list')
133
- return await response.json()
134
  }
135
 
136
  export const checkHealth = async (): Promise<
137
  LightragStatus | { status: 'error'; message: string }
138
  > => {
139
  try {
140
- const response = await fetchWithAuth('/health')
141
- return await response.json()
142
  } catch (e) {
143
  return {
144
  status: 'error',
@@ -148,63 +132,33 @@ export const checkHealth = async (): Promise<
148
  }
149
 
150
  export const getDocuments = async (): Promise<string[]> => {
151
- const response = await fetchWithAuth('/documents')
152
- return await response.json()
153
- }
154
-
155
- export const getDocumentsScanProgress = async (): Promise<LightragDocumentsScanProgress> => {
156
- const response = await fetchWithAuth('/documents/scan-progress')
157
- return await response.json()
158
  }
159
 
160
- export const uploadDocument = async (
161
- file: File
162
- ): Promise<{
163
- status: string
164
- message: string
165
- total_documents: number
166
- }> => {
167
- const formData = new FormData()
168
- formData.append('file', file)
169
-
170
- const response = await fetchWithAuth('/documents/upload', {
171
- method: 'POST',
172
- body: formData
173
- })
174
- return await response.json()
175
  }
176
 
177
- export const startDocumentScan = async (): Promise<{ status: string }> => {
178
- const response = await fetchWithAuth('/documents/scan', {
179
- method: 'POST'
180
- })
181
- return await response.json()
182
  }
183
 
184
  export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
185
- const response = await fetchWithAuth('/query', {
186
- method: 'POST',
187
- headers: {
188
- 'Content-Type': 'application/json'
189
- },
190
- body: JSON.stringify(request)
191
- })
192
- return await response.json()
193
  }
194
 
195
  export const queryTextStream = async (request: QueryRequest, onChunk: (chunk: string) => void) => {
196
- const response = await fetchWithAuth('/query/stream', {
197
- method: 'POST',
198
- headers: {
199
- 'Content-Type': 'application/json'
200
- },
201
- body: JSON.stringify(request)
202
  })
203
 
204
- const reader = response.body?.getReader()
205
- if (!reader) throw new Error('No response body')
206
-
207
  const decoder = new TextDecoder()
 
208
  while (true) {
209
  const { done, value } = await reader.read()
210
  if (done) break
@@ -226,53 +180,50 @@ export const queryTextStream = async (request: QueryRequest, onChunk: (chunk: st
226
  }
227
  }
228
 
229
- // Text insertion API
230
  export const insertText = async (
231
  text: string,
232
  description?: string
233
- ): Promise<{
234
- status: string
235
- message: string
236
- document_count: number
237
- }> => {
238
- const response = await fetchWithAuth('/documents/text', {
239
- method: 'POST',
240
- headers: {
241
- 'Content-Type': 'application/json'
242
- },
243
- body: JSON.stringify({ text, description })
244
- })
245
- return await response.json()
246
  }
247
 
248
- // Batch file upload API
249
- export const uploadBatchDocuments = async (
250
- files: File[]
251
- ): Promise<{
252
- status: string
253
- message: string
254
- document_count: number
255
- }> => {
256
  const formData = new FormData()
257
- files.forEach((file) => {
258
- formData.append('files', file)
259
- })
260
 
261
- const response = await fetchWithAuth('/documents/batch', {
262
- method: 'POST',
263
- body: formData
 
 
 
 
 
 
 
 
264
  })
265
- return await response.json()
266
  }
267
 
268
- // Clear all documents API
269
- export const clearDocuments = async (): Promise<{
270
- status: string
271
- message: string
272
- document_count: number
273
- }> => {
274
- const response = await fetchWithAuth('/documents', {
275
- method: 'DELETE'
276
- })
277
- return await response.json()
 
 
 
 
 
 
278
  }
 
1
+ import axios, { AxiosError } from 'axios'
2
  import { backendBaseUrl } from '@/lib/constants'
3
  import { errorMessage } from '@/lib/utils'
4
  import { useSettingsStore } from '@/stores/settings'
 
65
  response: string
66
  }
67
 
68
+ export type DocumentActionResponse = {
69
+ status: 'success' | 'partial_success' | 'failure'
70
+ message: string
71
+ document_count: number
72
+ }
73
+
74
  export const InvalidApiKeyError = 'Invalid API Key'
75
  export const RequireApiKeError = 'API Key required'
76
 
77
+ // Axios instance
78
+ const axiosInstance = axios.create({
79
+ baseURL: backendBaseUrl,
80
+ headers: {
81
+ 'Content-Type': 'application/json'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
+ })
 
84
 
85
+ // Interceptor:add api key
86
+ axiosInstance.interceptors.request.use((config) => {
87
  const apiKey = useSettingsStore.getState().apiKey
88
+ if (apiKey) {
89
+ config.headers['X-API-Key'] = apiKey
 
90
  }
91
+ return config
92
+ })
93
+
94
+ // Interceptor:hanle error
95
+ axiosInstance.interceptors.response.use(
96
+ (response) => response,
97
+ (error: AxiosError) => {
98
+ if (error.response) {
99
+ throw new Error(
100
+ `${error.response.status} ${error.response.statusText}\n${JSON.stringify(
101
+ error.response.data
102
+ )}\n${error.config?.url}`
103
+ )
104
+ }
105
+ throw error
106
  }
107
+ )
 
 
108
 
109
  // API methods
110
  export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
111
+ const response = await axiosInstance.get(`/graphs?label=${label}`)
112
+ return response.data
113
  }
114
 
115
  export const getGraphLabels = async (): Promise<string[]> => {
116
+ const response = await axiosInstance.get('/graph/label/list')
117
+ return response.data
118
  }
119
 
120
  export const checkHealth = async (): Promise<
121
  LightragStatus | { status: 'error'; message: string }
122
  > => {
123
  try {
124
+ const response = await axiosInstance.get('/health')
125
+ return response.data
126
  } catch (e) {
127
  return {
128
  status: 'error',
 
132
  }
133
 
134
  export const getDocuments = async (): Promise<string[]> => {
135
+ const response = await axiosInstance.get('/documents')
136
+ return response.data
 
 
 
 
 
137
  }
138
 
139
+ export const scanNewDocuments = async (): Promise<{ status: string }> => {
140
+ const response = await axiosInstance.post('/documents/scan')
141
+ return response.data
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
 
144
+ export const getDocumentsScanProgress = async (): Promise<LightragDocumentsScanProgress> => {
145
+ const response = await axiosInstance.get('/documents/scan-progress')
146
+ return response.data
 
 
147
  }
148
 
149
  export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
150
+ const response = await axiosInstance.post('/query', request)
151
+ return response.data
 
 
 
 
 
 
152
  }
153
 
154
  export const queryTextStream = async (request: QueryRequest, onChunk: (chunk: string) => void) => {
155
+ const response = await axiosInstance.post('/query/stream', request, {
156
+ responseType: 'stream'
 
 
 
 
157
  })
158
 
159
+ const reader = response.data.getReader()
 
 
160
  const decoder = new TextDecoder()
161
+
162
  while (true) {
163
  const { done, value } = await reader.read()
164
  if (done) break
 
180
  }
181
  }
182
 
 
183
  export const insertText = async (
184
  text: string,
185
  description?: string
186
+ ): Promise<DocumentActionResponse> => {
187
+ const response = await axiosInstance.post('/documents/text', { text, description })
188
+ return response.data
 
 
 
 
 
 
 
 
 
 
189
  }
190
 
191
+ export const uploadDocument = async (
192
+ file: File,
193
+ onUploadProgress?: (percentCompleted: number) => void
194
+ ): Promise<DocumentActionResponse> => {
 
 
 
 
195
  const formData = new FormData()
196
+ formData.append('file', file)
 
 
197
 
198
+ const response = await axiosInstance.post('/documents/upload', formData, {
199
+ headers: {
200
+ 'Content-Type': 'multipart/form-data'
201
+ },
202
+ onUploadProgress:
203
+ onUploadProgress !== undefined
204
+ ? (progressEvent) => {
205
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total!)
206
+ onUploadProgress(percentCompleted)
207
+ }
208
+ : undefined
209
  })
210
+ return response.data
211
  }
212
 
213
+ export const batchUploadDocuments = async (
214
+ files: File[],
215
+ onUploadProgress?: (fileName: string, percentCompleted: number) => void
216
+ ): Promise<DocumentActionResponse[]> => {
217
+ return await Promise.all(
218
+ files.map(async (file) => {
219
+ return await uploadDocument(file, (percentCompleted) => {
220
+ onUploadProgress?.(file.name, percentCompleted)
221
+ })
222
+ })
223
+ )
224
+ }
225
+
226
+ export const clearDocuments = async (): Promise<DocumentActionResponse> => {
227
+ const response = await axiosInstance.delete('/documents')
228
+ return response.data
229
  }
lightrag_webui/src/components/MessageAlert.tsx CHANGED
@@ -22,10 +22,11 @@ const MessageAlert = () => {
22
 
23
  return (
24
  <Alert
25
- variant={health ? 'default' : 'destructive'}
26
  className={cn(
27
- 'bg-background/90 absolute top-2 left-1/2 flex w-auto -translate-x-1/2 transform items-center gap-4 shadow-md backdrop-blur-lg transition-all duration-500 ease-in-out',
28
- isMounted ? 'translate-y-0 opacity-100' : '-translate-y-20 opacity-0'
 
29
  )}
30
  >
31
  {!health && (
@@ -42,7 +43,7 @@ const MessageAlert = () => {
42
  <Button
43
  size="sm"
44
  variant={controlButtonVariant}
45
- className="text-primary max-h-8 border !p-2 text-xs"
46
  onClick={() => useBackendState.getState().clear()}
47
  >
48
  Close
 
22
 
23
  return (
24
  <Alert
25
+ // variant={health ? 'default' : 'destructive'}
26
  className={cn(
27
+ 'bg-background/90 absolute top-12 left-1/2 flex w-auto max-w-lg -translate-x-1/2 transform items-center gap-4 shadow-md backdrop-blur-lg transition-all duration-500 ease-in-out',
28
+ isMounted ? 'translate-y-0 opacity-100' : '-translate-y-20 opacity-0',
29
+ !health && 'bg-red-700 text-white'
30
  )}
31
  >
32
  {!health && (
 
43
  <Button
44
  size="sm"
45
  variant={controlButtonVariant}
46
+ className="border-primary max-h-8 border !p-2 text-xs"
47
  onClick={() => useBackendState.getState().clear()}
48
  >
49
  Close
lightrag_webui/src/components/PropertiesView.tsx CHANGED
@@ -200,7 +200,7 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => {
200
  <label className="text-md pl-1 font-bold tracking-wide text-teal-600">Relationship</label>
201
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
202
  <PropertyRow name={'Id'} value={edge.id} />
203
- <PropertyRow name={'Type'} value={edge.type} />
204
  <PropertyRow
205
  name={'Source'}
206
  value={edge.sourceNode ? edge.sourceNode.labels.join(', ') : edge.source}
 
200
  <label className="text-md pl-1 font-bold tracking-wide text-teal-600">Relationship</label>
201
  <div className="bg-primary/5 max-h-96 overflow-auto rounded p-1">
202
  <PropertyRow name={'Id'} value={edge.id} />
203
+ {edge.type && <PropertyRow name={'Type'} value={edge.type} />}
204
  <PropertyRow
205
  name={'Source'}
206
  value={edge.sourceNode ? edge.sourceNode.labels.join(', ') : edge.source}
lightrag_webui/src/components/document/ClearDocumentsDialog.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useCallback } from 'react'
2
+ import Button from '@/components/ui/Button'
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogTrigger
10
+ } from '@/components/ui/Dialog'
11
+ import { toast } from 'sonner'
12
+ import { errorMessage } from '@/lib/utils'
13
+ import { clearDocuments } from '@/api/lightrag'
14
+
15
+ import { EraserIcon } from 'lucide-react'
16
+
17
+ export default function ClearDocumentsDialog() {
18
+ const [open, setOpen] = useState(false) // 添加状态控制
19
+
20
+ const handleClear = useCallback(async () => {
21
+ try {
22
+ const result = await clearDocuments()
23
+ if (result.status === 'success') {
24
+ toast.success('Documents cleared successfully')
25
+ setOpen(false)
26
+ } else {
27
+ toast.error(`Clear Documents Failed:\n${result.message}`)
28
+ }
29
+ } catch (err) {
30
+ toast.error('Clear Documents Failed:\n' + errorMessage(err))
31
+ }
32
+ }, [setOpen])
33
+
34
+ return (
35
+ <Dialog open={open} onOpenChange={setOpen}>
36
+ <DialogTrigger asChild>
37
+ <Button variant="outline" tooltip="Clear documents" side="bottom" size="icon">
38
+ <EraserIcon />
39
+ </Button>
40
+ </DialogTrigger>
41
+ <DialogContent className="sm:max-w-xl" onCloseAutoFocus={(e) => e.preventDefault()}>
42
+ <DialogHeader>
43
+ <DialogTitle>Clear documents</DialogTitle>
44
+ <DialogDescription>Do you really want to clear all documents?</DialogDescription>
45
+ </DialogHeader>
46
+ <Button variant="destructive" onClick={handleClear}>
47
+ YES
48
+ </Button>
49
+ </DialogContent>
50
+ </Dialog>
51
+ )
52
+ }
lightrag_webui/src/components/document/UploadDocumentsDialog.tsx ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useCallback } from 'react'
2
+ import Button from '@/components/ui/Button'
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogTrigger
10
+ } from '@/components/ui/Dialog'
11
+ import FileUploader from '@/components/ui/FileUploader'
12
+ import { toast } from 'sonner'
13
+ import { errorMessage } from '@/lib/utils'
14
+ import { uploadDocument } from '@/api/lightrag'
15
+
16
+ import { UploadIcon } from 'lucide-react'
17
+
18
+ export default function UploadDocumentsDialog() {
19
+ const [open, setOpen] = useState(false)
20
+ const [isUploading, setIsUploading] = useState(false)
21
+ const [progresses, setProgresses] = useState<Record<string, number>>({})
22
+
23
+ const handleDocumentsUpload = useCallback(
24
+ async (filesToUpload: File[]) => {
25
+ setIsUploading(true)
26
+
27
+ try {
28
+ await Promise.all(
29
+ filesToUpload.map(async (file) => {
30
+ try {
31
+ const result = await uploadDocument(file, (percentCompleted: number) => {
32
+ console.debug(`Uploading ${file.name}: ${percentCompleted}%`)
33
+ setProgresses((pre) => ({
34
+ ...pre,
35
+ [file.name]: percentCompleted
36
+ }))
37
+ })
38
+ if (result.status === 'success') {
39
+ toast.success(`Upload Success:\n${file.name} uploaded successfully`)
40
+ } else {
41
+ toast.error(`Upload Failed:\n${file.name}\n${result.message}`)
42
+ }
43
+ } catch (err) {
44
+ toast.error(`Upload Failed:\n${file.name}\n${errorMessage(err)}`)
45
+ }
46
+ })
47
+ )
48
+ } catch (err) {
49
+ toast.error('Upload Failed\n' + errorMessage(err))
50
+ } finally {
51
+ setIsUploading(false)
52
+ setOpen(false)
53
+ }
54
+ },
55
+ [setIsUploading, setProgresses, setOpen]
56
+ )
57
+
58
+ return (
59
+ <Dialog
60
+ open={open}
61
+ onOpenChange={(open) => {
62
+ if (isUploading && !open) {
63
+ return
64
+ }
65
+ setOpen(open)
66
+ }}
67
+ >
68
+ <DialogTrigger asChild>
69
+ <Button variant="outline" tooltip="Upload documents" side="bottom" size="icon">
70
+ <UploadIcon />
71
+ </Button>
72
+ </DialogTrigger>
73
+ <DialogContent className="sm:max-w-xl" onCloseAutoFocus={(e) => e.preventDefault()}>
74
+ <DialogHeader>
75
+ <DialogTitle>Upload documents</DialogTitle>
76
+ <DialogDescription>
77
+ Drag and drop your documents here or click to browse.
78
+ </DialogDescription>
79
+ </DialogHeader>
80
+ <FileUploader
81
+ maxFileCount={Infinity}
82
+ maxSize={200 * 1024 * 1024}
83
+ description="supported types: TXT, MD, DOC, PDF, PPTX"
84
+ onUpload={handleDocumentsUpload}
85
+ progresses={progresses}
86
+ disabled={isUploading}
87
+ />
88
+ </DialogContent>
89
+ </Dialog>
90
+ )
91
+ }
lightrag_webui/src/components/ui/AsyncSearch.tsx CHANGED
@@ -193,7 +193,7 @@ export function AsyncSearch<T>({
193
  </div>
194
  )}
195
  </div>
196
- <CommandList className="max-h-auto" hidden={!open || debouncedSearchTerm.length === 0}>
197
  {error && <div className="text-destructive p-4 text-center">{error}</div>}
198
  {loading && options.length === 0 && (loadingSkeleton || <DefaultLoadingSkeleton />)}
199
  {!loading &&
 
193
  </div>
194
  )}
195
  </div>
196
+ <CommandList hidden={!open || debouncedSearchTerm.length === 0}>
197
  {error && <div className="text-destructive p-4 text-center">{error}</div>}
198
  {loading && options.length === 0 && (loadingSkeleton || <DefaultLoadingSkeleton />)}
199
  {!loading &&
lightrag_webui/src/components/ui/Badge.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const badgeVariants = cva(
7
+ 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
12
+ secondary:
13
+ 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
14
+ destructive:
15
+ 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
16
+ outline: 'text-foreground'
17
+ }
18
+ },
19
+ defaultVariants: {
20
+ variant: 'default'
21
+ }
22
+ }
23
+ )
24
+
25
+ export interface BadgeProps
26
+ extends React.HTMLAttributes<HTMLDivElement>,
27
+ VariantProps<typeof badgeVariants> {}
28
+
29
+ function Badge({ className, variant, ...props }: BadgeProps) {
30
+ return <div className={cn(badgeVariants({ variant }), className)} {...props} />
31
+ }
32
+
33
+ export default Badge
lightrag_webui/src/components/ui/Card.tsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn('bg-card text-card-foreground rounded-xl border shadow', className)}
10
+ {...props}
11
+ />
12
+ )
13
+ )
14
+ Card.displayName = 'Card'
15
+
16
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
+ ({ className, ...props }, ref) => (
18
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
19
+ )
20
+ )
21
+ CardHeader.displayName = 'CardHeader'
22
+
23
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
24
+ ({ className, ...props }, ref) => (
25
+ <div
26
+ ref={ref}
27
+ className={cn('leading-none font-semibold tracking-tight', className)}
28
+ {...props}
29
+ />
30
+ )
31
+ )
32
+ CardTitle.displayName = 'CardTitle'
33
+
34
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
35
+ ({ className, ...props }, ref) => (
36
+ <div ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
37
+ )
38
+ )
39
+ CardDescription.displayName = 'CardDescription'
40
+
41
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
42
+ ({ className, ...props }, ref) => (
43
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
44
+ )
45
+ )
46
+ CardContent.displayName = 'CardContent'
47
+
48
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
49
+ ({ className, ...props }, ref) => (
50
+ <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
51
+ )
52
+ )
53
+ CardFooter.displayName = 'CardFooter'
54
+
55
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
lightrag_webui/src/components/ui/DataTable.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
2
+
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow
10
+ } from '@/components/ui/Table'
11
+
12
+ interface DataTableProps<TData, TValue> {
13
+ columns: ColumnDef<TData, TValue>[]
14
+ data: TData[]
15
+ }
16
+
17
+ export default function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
18
+ const table = useReactTable({
19
+ data,
20
+ columns,
21
+ getCoreRowModel: getCoreRowModel()
22
+ })
23
+
24
+ return (
25
+ <div className="rounded-md border">
26
+ <Table>
27
+ <TableHeader>
28
+ {table.getHeaderGroups().map((headerGroup) => (
29
+ <TableRow key={headerGroup.id}>
30
+ {headerGroup.headers.map((header) => {
31
+ return (
32
+ <TableHead key={header.id}>
33
+ {header.isPlaceholder
34
+ ? null
35
+ : flexRender(header.column.columnDef.header, header.getContext())}
36
+ </TableHead>
37
+ )
38
+ })}
39
+ </TableRow>
40
+ ))}
41
+ </TableHeader>
42
+ <TableBody>
43
+ {table.getRowModel().rows?.length ? (
44
+ table.getRowModel().rows.map((row) => (
45
+ <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
46
+ {row.getVisibleCells().map((cell) => (
47
+ <TableCell key={cell.id}>
48
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
49
+ </TableCell>
50
+ ))}
51
+ </TableRow>
52
+ ))
53
+ ) : (
54
+ <TableRow>
55
+ <TableCell colSpan={columns.length} className="h-24 text-center">
56
+ No results.
57
+ </TableCell>
58
+ </TableRow>
59
+ )}
60
+ </TableBody>
61
+ </Table>
62
+ </div>
63
+ )
64
+ }
lightrag_webui/src/components/ui/Dialog.tsx CHANGED
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
36
  <DialogPrimitive.Content
37
  ref={ref}
38
  className={cn(
39
- 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
40
  className
41
  )}
42
  {...props}
@@ -65,7 +65,7 @@ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivEleme
65
  DialogFooter.displayName = 'DialogFooter'
66
 
67
  const DialogTitle = React.forwardRef<
68
- React.ElementRef<typeof DialogPrimitive.Title>,
69
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
70
  >(({ className, ...props }, ref) => (
71
  <DialogPrimitive.Title
@@ -77,7 +77,7 @@ const DialogTitle = React.forwardRef<
77
  DialogTitle.displayName = DialogPrimitive.Title.displayName
78
 
79
  const DialogDescription = React.forwardRef<
80
- React.ElementRef<typeof DialogPrimitive.Description>,
81
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
82
  >(({ className, ...props }, ref) => (
83
  <DialogPrimitive.Description
 
36
  <DialogPrimitive.Content
37
  ref={ref}
38
  className={cn(
39
+ 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
40
  className
41
  )}
42
  {...props}
 
65
  DialogFooter.displayName = 'DialogFooter'
66
 
67
  const DialogTitle = React.forwardRef<
68
+ React.ComponentRef<typeof DialogPrimitive.Title>,
69
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
70
  >(({ className, ...props }, ref) => (
71
  <DialogPrimitive.Title
 
77
  DialogTitle.displayName = DialogPrimitive.Title.displayName
78
 
79
  const DialogDescription = React.forwardRef<
80
+ React.ComponentRef<typeof DialogPrimitive.Description>,
81
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
82
  >(({ className, ...props }, ref) => (
83
  <DialogPrimitive.Description
lightrag_webui/src/components/ui/EmptyCard.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { cn } from '@/lib/utils'
2
+ import { Card, CardDescription, CardTitle } from '@/components/ui/Card'
3
+ import { FilesIcon } from 'lucide-react'
4
+
5
+ interface EmptyCardProps extends React.ComponentPropsWithoutRef<typeof Card> {
6
+ title: string
7
+ description?: string
8
+ action?: React.ReactNode
9
+ icon?: React.ComponentType<{ className?: string }>
10
+ }
11
+
12
+ export default function EmptyCard({
13
+ title,
14
+ description,
15
+ icon: Icon = FilesIcon,
16
+ action,
17
+ className,
18
+ ...props
19
+ }: EmptyCardProps) {
20
+ return (
21
+ <Card
22
+ className={cn(
23
+ 'flex w-full flex-col items-center justify-center space-y-6 bg-transparent p-16',
24
+ className
25
+ )}
26
+ {...props}
27
+ >
28
+ <div className="mr-4 shrink-0 rounded-full border border-dashed p-4">
29
+ <Icon className="text-muted-foreground size-8" aria-hidden="true" />
30
+ </div>
31
+ <div className="flex flex-col items-center gap-1.5 text-center">
32
+ <CardTitle>{title}</CardTitle>
33
+ {description ? <CardDescription>{description}</CardDescription> : null}
34
+ </div>
35
+ {action ? action : null}
36
+ </Card>
37
+ )
38
+ }
lightrag_webui/src/components/ui/FileUploader.tsx ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @see https://github.com/sadmann7/file-uploader
3
+ */
4
+
5
+ import * as React from 'react'
6
+ import { FileText, Upload, X } from 'lucide-react'
7
+ import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone'
8
+ import { toast } from 'sonner'
9
+
10
+ import { cn } from '@/lib/utils'
11
+ import { useControllableState } from '@radix-ui/react-use-controllable-state'
12
+ import Button from '@/components/ui/Button'
13
+ import Progress from '@/components/ui/Progress'
14
+ import { ScrollArea } from '@/components/ui/ScrollArea'
15
+ import { supportedFileTypes } from '@/lib/constants'
16
+
17
+ interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
18
+ /**
19
+ * Value of the uploader.
20
+ * @type File[]
21
+ * @default undefined
22
+ * @example value={files}
23
+ */
24
+ value?: File[]
25
+
26
+ /**
27
+ * Function to be called when the value changes.
28
+ * @type (files: File[]) => void
29
+ * @default undefined
30
+ * @example onValueChange={(files) => setFiles(files)}
31
+ */
32
+ onValueChange?: (files: File[]) => void
33
+
34
+ /**
35
+ * Function to be called when files are uploaded.
36
+ * @type (files: File[]) => Promise<void>
37
+ * @default undefined
38
+ * @example onUpload={(files) => uploadFiles(files)}
39
+ */
40
+ onUpload?: (files: File[]) => Promise<void>
41
+
42
+ /**
43
+ * Progress of the uploaded files.
44
+ * @type Record<string, number> | undefined
45
+ * @default undefined
46
+ * @example progresses={{ "file1.png": 50 }}
47
+ */
48
+ progresses?: Record<string, number>
49
+
50
+ /**
51
+ * Accepted file types for the uploader.
52
+ * @type { [key: string]: string[]}
53
+ * @default
54
+ * ```ts
55
+ * { "text/*": [] }
56
+ * ```
57
+ * @example accept={["text/plain", "application/pdf"]}
58
+ */
59
+ accept?: DropzoneProps['accept']
60
+
61
+ /**
62
+ * Maximum file size for the uploader.
63
+ * @type number | undefined
64
+ * @default 1024 * 1024 * 200 // 200MB
65
+ * @example maxSize={1024 * 1024 * 2} // 2MB
66
+ */
67
+ maxSize?: DropzoneProps['maxSize']
68
+
69
+ /**
70
+ * Maximum number of files for the uploader.
71
+ * @type number | undefined
72
+ * @default 1
73
+ * @example maxFileCount={4}
74
+ */
75
+ maxFileCount?: DropzoneProps['maxFiles']
76
+
77
+ /**
78
+ * Whether the uploader should accept multiple files.
79
+ * @type boolean
80
+ * @default false
81
+ * @example multiple
82
+ */
83
+ multiple?: boolean
84
+
85
+ /**
86
+ * Whether the uploader is disabled.
87
+ * @type boolean
88
+ * @default false
89
+ * @example disabled
90
+ */
91
+ disabled?: boolean
92
+
93
+ description?: string
94
+ }
95
+
96
+ function formatBytes(
97
+ bytes: number,
98
+ opts: {
99
+ decimals?: number
100
+ sizeType?: 'accurate' | 'normal'
101
+ } = {}
102
+ ) {
103
+ const { decimals = 0, sizeType = 'normal' } = opts
104
+
105
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
106
+ const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB']
107
+ if (bytes === 0) return '0 Byte'
108
+ const i = Math.floor(Math.log(bytes) / Math.log(1024))
109
+ return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
110
+ sizeType === 'accurate' ? (accurateSizes[i] ?? 'Bytes') : (sizes[i] ?? 'Bytes')
111
+ }`
112
+ }
113
+
114
+ function FileUploader(props: FileUploaderProps) {
115
+ const {
116
+ value: valueProp,
117
+ onValueChange,
118
+ onUpload,
119
+ progresses,
120
+ accept = supportedFileTypes,
121
+ maxSize = 1024 * 1024 * 200,
122
+ maxFileCount = 1,
123
+ multiple = false,
124
+ disabled = false,
125
+ description,
126
+ className,
127
+ ...dropzoneProps
128
+ } = props
129
+
130
+ const [files, setFiles] = useControllableState({
131
+ prop: valueProp,
132
+ onChange: onValueChange
133
+ })
134
+
135
+ const onDrop = React.useCallback(
136
+ (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
137
+ if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
138
+ toast.error('Cannot upload more than 1 file at a time')
139
+ return
140
+ }
141
+
142
+ if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
143
+ toast.error(`Cannot upload more than ${maxFileCount} files`)
144
+ return
145
+ }
146
+
147
+ const newFiles = acceptedFiles.map((file) =>
148
+ Object.assign(file, {
149
+ preview: URL.createObjectURL(file)
150
+ })
151
+ )
152
+
153
+ const updatedFiles = files ? [...files, ...newFiles] : newFiles
154
+
155
+ setFiles(updatedFiles)
156
+
157
+ if (rejectedFiles.length > 0) {
158
+ rejectedFiles.forEach(({ file }) => {
159
+ toast.error(`File ${file.name} was rejected`)
160
+ })
161
+ }
162
+
163
+ if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFileCount) {
164
+ const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : 'file'
165
+
166
+ toast.promise(onUpload(updatedFiles), {
167
+ loading: `Uploading ${target}...`,
168
+ success: () => {
169
+ setFiles([])
170
+ return `${target} uploaded`
171
+ },
172
+ error: `Failed to upload ${target}`
173
+ })
174
+ }
175
+ },
176
+
177
+ [files, maxFileCount, multiple, onUpload, setFiles]
178
+ )
179
+
180
+ function onRemove(index: number) {
181
+ if (!files) return
182
+ const newFiles = files.filter((_, i) => i !== index)
183
+ setFiles(newFiles)
184
+ onValueChange?.(newFiles)
185
+ }
186
+
187
+ // Revoke preview url when component unmounts
188
+ React.useEffect(() => {
189
+ return () => {
190
+ if (!files) return
191
+ files.forEach((file) => {
192
+ if (isFileWithPreview(file)) {
193
+ URL.revokeObjectURL(file.preview)
194
+ }
195
+ })
196
+ }
197
+ // eslint-disable-next-line react-hooks/exhaustive-deps
198
+ }, [])
199
+
200
+ const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount
201
+
202
+ return (
203
+ <div className="relative flex flex-col gap-6 overflow-hidden">
204
+ <Dropzone
205
+ onDrop={onDrop}
206
+ accept={accept}
207
+ maxSize={maxSize}
208
+ maxFiles={maxFileCount}
209
+ multiple={maxFileCount > 1 || multiple}
210
+ disabled={isDisabled}
211
+ >
212
+ {({ getRootProps, getInputProps, isDragActive }) => (
213
+ <div
214
+ {...getRootProps()}
215
+ className={cn(
216
+ 'group border-muted-foreground/25 hover:bg-muted/25 relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition',
217
+ 'ring-offset-background focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
218
+ isDragActive && 'border-muted-foreground/50',
219
+ isDisabled && 'pointer-events-none opacity-60',
220
+ className
221
+ )}
222
+ {...dropzoneProps}
223
+ >
224
+ <input {...getInputProps()} />
225
+ {isDragActive ? (
226
+ <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
227
+ <div className="rounded-full border border-dashed p-3">
228
+ <Upload className="text-muted-foreground size-7" aria-hidden="true" />
229
+ </div>
230
+ <p className="text-muted-foreground font-medium">Drop the files here</p>
231
+ </div>
232
+ ) : (
233
+ <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
234
+ <div className="rounded-full border border-dashed p-3">
235
+ <Upload className="text-muted-foreground size-7" aria-hidden="true" />
236
+ </div>
237
+ <div className="flex flex-col gap-px">
238
+ <p className="text-muted-foreground font-medium">
239
+ Drag and drop files here, or click to select files
240
+ </p>
241
+ {description ? (
242
+ <p className="text-muted-foreground/70 text-sm">{description}</p>
243
+ ) : (
244
+ <p className="text-muted-foreground/70 text-sm">
245
+ You can upload
246
+ {maxFileCount > 1
247
+ ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}
248
+ files (up to ${formatBytes(maxSize)} each)`
249
+ : ` a file with ${formatBytes(maxSize)}`}
250
+ Supported formats: TXT, MD, DOC, PDF, PPTX
251
+ </p>
252
+ )}
253
+ </div>
254
+ </div>
255
+ )}
256
+ </div>
257
+ )}
258
+ </Dropzone>
259
+ {files?.length ? (
260
+ <ScrollArea className="h-fit w-full px-3">
261
+ <div className="flex max-h-48 flex-col gap-4">
262
+ {files?.map((file, index) => (
263
+ <FileCard
264
+ key={index}
265
+ file={file}
266
+ onRemove={() => onRemove(index)}
267
+ progress={progresses?.[file.name]}
268
+ />
269
+ ))}
270
+ </div>
271
+ </ScrollArea>
272
+ ) : null}
273
+ </div>
274
+ )
275
+ }
276
+
277
+ interface FileCardProps {
278
+ file: File
279
+ onRemove: () => void
280
+ progress?: number
281
+ }
282
+
283
+ function FileCard({ file, progress, onRemove }: FileCardProps) {
284
+ return (
285
+ <div className="relative flex items-center gap-2.5">
286
+ <div className="flex flex-1 gap-2.5">
287
+ {isFileWithPreview(file) ? <FilePreview file={file} /> : null}
288
+ <div className="flex w-full flex-col gap-2">
289
+ <div className="flex flex-col gap-px">
290
+ <p className="text-foreground/80 line-clamp-1 text-sm font-medium">{file.name}</p>
291
+ <p className="text-muted-foreground text-xs">{formatBytes(file.size)}</p>
292
+ </div>
293
+ {progress ? <Progress value={progress} /> : null}
294
+ </div>
295
+ </div>
296
+ <div className="flex items-center gap-2">
297
+ <Button type="button" variant="outline" size="icon" className="size-7" onClick={onRemove}>
298
+ <X className="size-4" aria-hidden="true" />
299
+ <span className="sr-only">Remove file</span>
300
+ </Button>
301
+ </div>
302
+ </div>
303
+ )
304
+ }
305
+
306
+ function isFileWithPreview(file: File): file is File & { preview: string } {
307
+ return 'preview' in file && typeof file.preview === 'string'
308
+ }
309
+
310
+ interface FilePreviewProps {
311
+ file: File & { preview: string }
312
+ }
313
+
314
+ function FilePreview({ file }: FilePreviewProps) {
315
+ if (file.type.startsWith('image/')) {
316
+ return <div className="aspect-square shrink-0 rounded-md object-cover" />
317
+ }
318
+
319
+ return <FileText className="text-muted-foreground size-10" aria-hidden="true" />
320
+ }
321
+
322
+ export default FileUploader
lightrag_webui/src/components/ui/Progress.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import * as ProgressPrimitive from '@radix-ui/react-progress'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const Progress = React.forwardRef<
7
+ React.ComponentRef<typeof ProgressPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
9
+ >(({ className, value, ...props }, ref) => (
10
+ <ProgressPrimitive.Root
11
+ ref={ref}
12
+ className={cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className)}
13
+ {...props}
14
+ >
15
+ <ProgressPrimitive.Indicator
16
+ className="bg-primary h-full w-full flex-1 transition-all"
17
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
18
+ />
19
+ </ProgressPrimitive.Root>
20
+ ))
21
+ Progress.displayName = ProgressPrimitive.Root.displayName
22
+
23
+ export default Progress
lightrag_webui/src/components/ui/ScrollArea.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const ScrollArea = React.forwardRef<
7
+ React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9
+ >(({ className, children, ...props }, ref) => (
10
+ <ScrollAreaPrimitive.Root
11
+ ref={ref}
12
+ className={cn('relative overflow-hidden', className)}
13
+ {...props}
14
+ >
15
+ <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
16
+ {children}
17
+ </ScrollAreaPrimitive.Viewport>
18
+ <ScrollBar />
19
+ <ScrollAreaPrimitive.Corner />
20
+ </ScrollAreaPrimitive.Root>
21
+ ))
22
+ ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23
+
24
+ const ScrollBar = React.forwardRef<
25
+ React.ComponentRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26
+ React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27
+ >(({ className, orientation = 'vertical', ...props }, ref) => (
28
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
29
+ ref={ref}
30
+ orientation={orientation}
31
+ className={cn(
32
+ 'flex touch-none transition-colors select-none',
33
+ orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
34
+ orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
35
+ className
36
+ )}
37
+ {...props}
38
+ >
39
+ <ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
40
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
41
+ ))
42
+ ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
43
+
44
+ export { ScrollArea, ScrollBar }
lightrag_webui/src/components/ui/Table.tsx ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div className="relative w-full overflow-auto">
8
+ <table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
9
+ </div>
10
+ )
11
+ )
12
+ Table.displayName = 'Table'
13
+
14
+ const TableHeader = React.forwardRef<
15
+ HTMLTableSectionElement,
16
+ React.HTMLAttributes<HTMLTableSectionElement>
17
+ >(({ className, ...props }, ref) => (
18
+ <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
19
+ ))
20
+ TableHeader.displayName = 'TableHeader'
21
+
22
+ const TableBody = React.forwardRef<
23
+ HTMLTableSectionElement,
24
+ React.HTMLAttributes<HTMLTableSectionElement>
25
+ >(({ className, ...props }, ref) => (
26
+ <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
27
+ ))
28
+ TableBody.displayName = 'TableBody'
29
+
30
+ const TableFooter = React.forwardRef<
31
+ HTMLTableSectionElement,
32
+ React.HTMLAttributes<HTMLTableSectionElement>
33
+ >(({ className, ...props }, ref) => (
34
+ <tfoot
35
+ ref={ref}
36
+ className={cn('bg-muted/50 border-t font-medium [&>tr]:last:border-b-0', className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ TableFooter.displayName = 'TableFooter'
41
+
42
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
43
+ ({ className, ...props }, ref) => (
44
+ <tr
45
+ ref={ref}
46
+ className={cn(
47
+ 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ )
53
+ )
54
+ TableRow.displayName = 'TableRow'
55
+
56
+ const TableHead = React.forwardRef<
57
+ HTMLTableCellElement,
58
+ React.ThHTMLAttributes<HTMLTableCellElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <th
61
+ ref={ref}
62
+ className={cn(
63
+ 'text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ ))
69
+ TableHead.displayName = 'TableHead'
70
+
71
+ const TableCell = React.forwardRef<
72
+ HTMLTableCellElement,
73
+ React.TdHTMLAttributes<HTMLTableCellElement>
74
+ >(({ className, ...props }, ref) => (
75
+ <td
76
+ ref={ref}
77
+ className={cn(
78
+ 'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
79
+ className
80
+ )}
81
+ {...props}
82
+ />
83
+ ))
84
+ TableCell.displayName = 'TableCell'
85
+
86
+ const TableCaption = React.forwardRef<
87
+ HTMLTableCaptionElement,
88
+ React.HTMLAttributes<HTMLTableCaptionElement>
89
+ >(({ className, ...props }, ref) => (
90
+ <caption ref={ref} className={cn('text-muted-foreground mt-4 text-sm', className)} {...props} />
91
+ ))
92
+ TableCaption.displayName = 'TableCaption'
93
+
94
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
lightrag_webui/src/components/ui/Tabs.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ const Tabs = TabsPrimitive.Root
7
+
8
+ const TabsList = React.forwardRef<
9
+ React.ComponentRef<typeof TabsPrimitive.List>,
10
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
11
+ >(({ className, ...props }, ref) => (
12
+ <TabsPrimitive.List
13
+ ref={ref}
14
+ className={cn(
15
+ 'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1',
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ TabsList.displayName = TabsPrimitive.List.displayName
22
+
23
+ const TabsTrigger = React.forwardRef<
24
+ React.ComponentRef<typeof TabsPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
26
+ >(({ className, ...props }, ref) => (
27
+ <TabsPrimitive.Trigger
28
+ ref={ref}
29
+ className={cn(
30
+ 'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center rounded-sm px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-all focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ ))
36
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37
+
38
+ const TabsContent = React.forwardRef<
39
+ React.ComponentRef<typeof TabsPrimitive.Content>,
40
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
41
+ >(({ className, ...props }, ref) => (
42
+ <TabsPrimitive.Content
43
+ ref={ref}
44
+ className={cn(
45
+ 'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ ))
51
+ TabsContent.displayName = TabsPrimitive.Content.displayName
52
+
53
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
lightrag_webui/src/components/ui/Tooltip.tsx CHANGED
@@ -16,7 +16,7 @@ const TooltipContent = React.forwardRef<
16
  ref={ref}
17
  sideOffset={sideOffset}
18
  className={cn(
19
- 'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
20
  className
21
  )}
22
  {...props}
 
16
  ref={ref}
17
  sideOffset={sideOffset}
18
  className={cn(
19
+ 'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 mx-1 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
20
  className
21
  )}
22
  {...props}
lightrag_webui/src/features/DocumentManager.tsx ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import Button from '@/components/ui/Button'
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow
10
+ } from '@/components/ui/Table'
11
+ import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
12
+ import Progress from '@/components/ui/Progress'
13
+ import EmptyCard from '@/components/ui/EmptyCard'
14
+ import UploadDocumentsDialog from '@/components/document/UploadDocumentsDialog'
15
+ import ClearDocumentsDialog from '@/components/document/ClearDocumentsDialog'
16
+
17
+ import {
18
+ getDocuments,
19
+ getDocumentsScanProgress,
20
+ scanNewDocuments,
21
+ LightragDocumentsScanProgress
22
+ } from '@/api/lightrag'
23
+ import { errorMessage } from '@/lib/utils'
24
+ import { toast } from 'sonner'
25
+ import { useBackendState } from '@/stores/state'
26
+
27
+ import { RefreshCwIcon, TrashIcon } from 'lucide-react'
28
+
29
+ // type DocumentStatus = 'indexed' | 'pending' | 'indexing' | 'error'
30
+
31
+ export default function DocumentManager() {
32
+ const health = useBackendState.use.health()
33
+ const [files, setFiles] = useState<string[]>([])
34
+ const [indexedFiles, setIndexedFiles] = useState<string[]>([])
35
+ const [scanProgress, setScanProgress] = useState<LightragDocumentsScanProgress | null>(null)
36
+
37
+ const fetchDocuments = useCallback(async () => {
38
+ try {
39
+ const docs = await getDocuments()
40
+ setFiles(docs)
41
+ } catch (err) {
42
+ toast.error('Failed to load documents\n' + errorMessage(err))
43
+ }
44
+ }, [setFiles])
45
+
46
+ useEffect(() => {
47
+ fetchDocuments()
48
+ }, [])
49
+
50
+ const scanDocuments = useCallback(async () => {
51
+ try {
52
+ const { status } = await scanNewDocuments()
53
+ toast.message(status)
54
+ } catch (err) {
55
+ toast.error('Failed to load documents\n' + errorMessage(err))
56
+ }
57
+ }, [setFiles])
58
+
59
+ useEffect(() => {
60
+ const interval = setInterval(async () => {
61
+ try {
62
+ if (!health) return
63
+ const progress = await getDocumentsScanProgress()
64
+ setScanProgress((pre) => {
65
+ if (pre?.is_scanning === progress.is_scanning && progress.is_scanning === false) {
66
+ return pre
67
+ }
68
+ return progress
69
+ })
70
+ console.log(progress)
71
+ } catch (err) {
72
+ toast.error('Failed to get scan progress\n' + errorMessage(err))
73
+ }
74
+ }, 2000)
75
+ return () => clearInterval(interval)
76
+ }, [health])
77
+
78
+ const handleDelete = async (fileName: string) => {
79
+ console.log(`deleting ${fileName}`)
80
+ }
81
+
82
+ return (
83
+ <Card className="!size-full !rounded-none !border-none">
84
+ <CardHeader>
85
+ <CardTitle className="text-lg">Document Management</CardTitle>
86
+ </CardHeader>
87
+ <CardContent className="space-y-4">
88
+ <div className="flex gap-2">
89
+ <Button
90
+ variant="outline"
91
+ size="icon"
92
+ tooltip="Scan Documents"
93
+ onClick={scanDocuments}
94
+ side="bottom"
95
+ >
96
+ <RefreshCwIcon />
97
+ </Button>
98
+ <div className="flex-1" />
99
+ <ClearDocumentsDialog />
100
+ <UploadDocumentsDialog />
101
+ </div>
102
+
103
+ {scanProgress?.is_scanning && (
104
+ <div className="space-y-2">
105
+ <div className="flex justify-between text-sm">
106
+ <span>Indexing {scanProgress.current_file}</span>
107
+ <span>{scanProgress.progress}%</span>
108
+ </div>
109
+ <Progress value={scanProgress.progress} />
110
+ </div>
111
+ )}
112
+
113
+ <Card>
114
+ <CardHeader>
115
+ <CardTitle>Uploaded documents</CardTitle>
116
+ <CardDescription>view the uploaded documents here</CardDescription>
117
+ </CardHeader>
118
+
119
+ <CardContent>
120
+ {files.length == 0 && (
121
+ <EmptyCard
122
+ title="No documents uploades"
123
+ description="upload documents to see them here"
124
+ />
125
+ )}
126
+ {files.length > 0 && (
127
+ <Table>
128
+ <TableHeader>
129
+ <TableRow>
130
+ <TableHead>Filename</TableHead>
131
+ <TableHead>Status</TableHead>
132
+ <TableHead>Actions</TableHead>
133
+ </TableRow>
134
+ </TableHeader>
135
+ <TableBody>
136
+ {files.map((file) => (
137
+ <TableRow key={file}>
138
+ <TableCell>{file}</TableCell>
139
+ <TableCell>
140
+ {indexedFiles.includes(file) ? (
141
+ <span className="text-green-600">Indexed</span>
142
+ ) : (
143
+ <span className="text-yellow-600">Pending</span>
144
+ )}
145
+ </TableCell>
146
+ <TableCell>
147
+ <Button
148
+ variant="ghost"
149
+ size="sm"
150
+ onClick={() => handleDelete(file)}
151
+ // disabled={isUploading}
152
+ >
153
+ <TrashIcon />
154
+ </Button>
155
+ </TableCell>
156
+ </TableRow>
157
+ ))}
158
+ </TableBody>
159
+ </Table>
160
+ )}
161
+ </CardContent>
162
+ </Card>
163
+ </CardContent>
164
+ </Card>
165
+ )
166
+ }
lightrag_webui/src/{GraphViewer.tsx → features/GraphViewer.tsx} RENAMED
@@ -10,7 +10,7 @@ import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve'
10
  import FocusOnNode from '@/components/FocusOnNode'
11
  import LayoutsControl from '@/components/LayoutsControl'
12
  import GraphControl from '@/components/GraphControl'
13
- import ThemeToggle from '@/components/ThemeToggle'
14
  import ZoomControl from '@/components/ZoomControl'
15
  import FullScreenControl from '@/components/FullScreenControl'
16
  import Settings from '@/components/Settings'
@@ -166,7 +166,7 @@ const GraphViewer = () => {
166
  <ZoomControl />
167
  <LayoutsControl />
168
  <FullScreenControl />
169
- <ThemeToggle />
170
  </div>
171
 
172
  {showPropertyPanel && (
 
10
  import FocusOnNode from '@/components/FocusOnNode'
11
  import LayoutsControl from '@/components/LayoutsControl'
12
  import GraphControl from '@/components/GraphControl'
13
+ // import ThemeToggle from '@/components/ThemeToggle'
14
  import ZoomControl from '@/components/ZoomControl'
15
  import FullScreenControl from '@/components/FullScreenControl'
16
  import Settings from '@/components/Settings'
 
166
  <ZoomControl />
167
  <LayoutsControl />
168
  <FullScreenControl />
169
+ {/* <ThemeToggle /> */}
170
  </div>
171
 
172
  {showPropertyPanel && (
lightrag_webui/src/features/SiteHeader.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Button from '@/components/ui/Button'
2
+ import { SiteInfo } from '@/lib/constants'
3
+ import ThemeToggle from '@/components/ThemeToggle'
4
+ import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
5
+
6
+ import { ZapIcon, GithubIcon } from 'lucide-react'
7
+
8
+ export default function SiteHeader() {
9
+ return (
10
+ <header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
11
+ <a href="/" className="mr-6 flex items-center gap-2">
12
+ <ZapIcon className="size-4 text-teal-400" aria-hidden="true" />
13
+ <span className="font-bold md:inline-block">{SiteInfo.name}</span>
14
+ </a>
15
+
16
+ <div className="flex h-10 flex-1 justify-center">
17
+ <div className="flex h-8 self-center">
18
+ <TabsList className="h-full gap-2">
19
+ <TabsTrigger
20
+ value="documents"
21
+ className="hover:bg-background/60 cursor-pointer px-2 py-1 transition-all"
22
+ >
23
+ Documents
24
+ </TabsTrigger>
25
+ <TabsTrigger
26
+ value="knowledge-graph"
27
+ className="hover:bg-background/60 cursor-pointer px-2 py-1 transition-all"
28
+ >
29
+ Knowledge Graph
30
+ </TabsTrigger>
31
+ {/* <TabsTrigger
32
+ value="settings"
33
+ className="hover:bg-background/60 cursor-pointer px-2 py-1 transition-all"
34
+ >
35
+ Settings
36
+ </TabsTrigger> */}
37
+ </TabsList>
38
+ </div>
39
+ </div>
40
+
41
+ <nav className="flex items-center">
42
+ <Button variant="ghost" size="icon">
43
+ <a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
44
+ <GithubIcon className="size-4" aria-hidden="true" />
45
+ </a>
46
+ </Button>
47
+ <ThemeToggle />
48
+ </nav>
49
+ </header>
50
+ )
51
+ }
lightrag_webui/src/hooks/useLightragGraph.tsx CHANGED
@@ -24,7 +24,7 @@ const validateGraph = (graph: RawGraph) => {
24
  }
25
 
26
  for (const edge of graph.edges) {
27
- if (!edge.id || !edge.source || !edge.target || !edge.type || !edge.properties) {
28
  return false
29
  }
30
  }
@@ -88,6 +88,14 @@ const fetchGraph = async (label: string) => {
88
  if (source !== undefined && source !== undefined) {
89
  const sourceNode = rawData.nodes[source]
90
  const targetNode = rawData.nodes[target]
 
 
 
 
 
 
 
 
91
  sourceNode.degree += 1
92
  targetNode.degree += 1
93
  }
@@ -146,7 +154,7 @@ const createSigmaGraph = (rawGraph: RawGraph | null) => {
146
 
147
  for (const rawEdge of rawGraph?.edges ?? []) {
148
  rawEdge.dynamicId = graph.addDirectedEdge(rawEdge.source, rawEdge.target, {
149
- label: rawEdge.type
150
  })
151
  }
152
 
 
24
  }
25
 
26
  for (const edge of graph.edges) {
27
+ if (!edge.id || !edge.source || !edge.target) {
28
  return false
29
  }
30
  }
 
88
  if (source !== undefined && source !== undefined) {
89
  const sourceNode = rawData.nodes[source]
90
  const targetNode = rawData.nodes[target]
91
+ if (!sourceNode) {
92
+ console.error(`Source node ${edge.source} is undefined`)
93
+ continue
94
+ }
95
+ if (!targetNode) {
96
+ console.error(`Target node ${edge.target} is undefined`)
97
+ continue
98
+ }
99
  sourceNode.degree += 1
100
  targetNode.degree += 1
101
  }
 
154
 
155
  for (const rawEdge of rawGraph?.edges ?? []) {
156
  rawEdge.dynamicId = graph.addDirectedEdge(rawEdge.source, rawEdge.target, {
157
+ label: rawEdge.type || undefined
158
  })
159
  }
160
 
lightrag_webui/src/index.css CHANGED
@@ -1,6 +1,7 @@
1
  @import 'tailwindcss';
2
 
3
  @plugin 'tailwindcss-animate';
 
4
 
5
  @custom-variant dark (&:is(.dark *));
6
 
@@ -142,3 +143,27 @@
142
  @apply bg-background text-foreground;
143
  }
144
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  @import 'tailwindcss';
2
 
3
  @plugin 'tailwindcss-animate';
4
+ @plugin 'tailwind-scrollbar';
5
 
6
  @custom-variant dark (&:is(.dark *));
7
 
 
143
  @apply bg-background text-foreground;
144
  }
145
  }
146
+
147
+ ::-webkit-scrollbar {
148
+ width: 10px;
149
+ height: 10px;
150
+ }
151
+
152
+ ::-webkit-scrollbar-thumb {
153
+ background-color: hsl(0 0% 80%);
154
+ border-radius: 5px;
155
+ }
156
+
157
+ ::-webkit-scrollbar-track {
158
+ background-color: hsl(0 0% 95%);
159
+ }
160
+
161
+ .dark {
162
+ ::-webkit-scrollbar-thumb {
163
+ background-color: hsl(0 0% 90%);
164
+ }
165
+
166
+ ::-webkit-scrollbar-track {
167
+ background-color: hsl(0 0% 0%);
168
+ }
169
+ }
lightrag_webui/src/lib/constants.ts CHANGED
@@ -23,3 +23,18 @@ export const maxNodeSize = 20
23
  export const healthCheckInterval = 15 // seconds
24
 
25
  export const defaultQueryLabel = '*'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  export const healthCheckInterval = 15 // seconds
24
 
25
  export const defaultQueryLabel = '*'
26
+
27
+ // reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types
28
+ export const supportedFileTypes = {
29
+ 'text/plain': ['.txt', '.md'],
30
+ 'application/pdf': ['.pdf'],
31
+ 'application/msword': ['.doc'],
32
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
33
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx']
34
+ }
35
+
36
+ export const SiteInfo = {
37
+ name: 'LightRAG',
38
+ home: '/',
39
+ github: 'https://github.com/HKUDS/LightRAG'
40
+ }
lightrag_webui/src/stores/graph.ts CHANGED
@@ -19,7 +19,7 @@ export type RawEdgeType = {
19
  id: string
20
  source: string
21
  target: string
22
- type: string
23
  properties: Record<string, any>
24
 
25
  dynamicId: string
 
19
  id: string
20
  source: string
21
  target: string
22
+ type?: string
23
  properties: Record<string, any>
24
 
25
  dynamicId: string