ArnoChen
		
	commited on
		
		
					Commit 
							
							·
						
						0e5f52f
	
1
								Parent(s):
							
							07feee7
								
add document manager and site heaer
Browse files- lightrag_webui/bun.lock +61 -10
 - lightrag_webui/eslint.config.js +27 -23
 - lightrag_webui/package.json +12 -5
 - lightrag_webui/src/App.tsx +21 -3
 - lightrag_webui/src/api/lightrag.ts +89 -138
 - lightrag_webui/src/components/MessageAlert.tsx +5 -4
 - lightrag_webui/src/components/PropertiesView.tsx +1 -1
 - lightrag_webui/src/components/document/ClearDocumentsDialog.tsx +52 -0
 - lightrag_webui/src/components/document/UploadDocumentsDialog.tsx +91 -0
 - lightrag_webui/src/components/ui/AsyncSearch.tsx +1 -1
 - lightrag_webui/src/components/ui/Badge.tsx +33 -0
 - lightrag_webui/src/components/ui/Card.tsx +55 -0
 - lightrag_webui/src/components/ui/DataTable.tsx +64 -0
 - lightrag_webui/src/components/ui/Dialog.tsx +3 -3
 - lightrag_webui/src/components/ui/EmptyCard.tsx +38 -0
 - lightrag_webui/src/components/ui/FileUploader.tsx +322 -0
 - lightrag_webui/src/components/ui/Progress.tsx +23 -0
 - lightrag_webui/src/components/ui/ScrollArea.tsx +44 -0
 - lightrag_webui/src/components/ui/Table.tsx +94 -0
 - lightrag_webui/src/components/ui/Tabs.tsx +53 -0
 - lightrag_webui/src/components/ui/Tooltip.tsx +1 -1
 - lightrag_webui/src/features/DocumentManager.tsx +166 -0
 - lightrag_webui/src/{GraphViewer.tsx → features/GraphViewer.tsx} +2 -2
 - lightrag_webui/src/features/SiteHeader.tsx +51 -0
 - lightrag_webui/src/hooks/useLightragGraph.tsx +10 -2
 - lightrag_webui/src/index.css +25 -0
 - lightrag_webui/src/lib/constants.ts +15 -0
 - lightrag_webui/src/stores/graph.ts +1 -1
 
    	
        lightrag_webui/bun.lock
    CHANGED
    
    | 
         @@ -4,13 +4,17 @@ 
     | 
|
| 4 | 
         
             
                "": {
         
     | 
| 5 | 
         
             
                  "name": "lightrag-webui",
         
     | 
| 6 | 
         
             
                  "dependencies": {
         
     | 
| 7 | 
         
            -
                    "@faker-js/faker": "^9. 
     | 
| 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. 
     | 
| 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. 
     | 
| 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. 
     | 
| 55 | 
         
             
                    "graphology-types": "^0.24.8",
         
     | 
| 56 | 
         
            -
                    "prettier": "^3.5. 
     | 
| 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/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
         
     | 
| 174 | 
         | 
| 175 | 
         
            -
                "@faker-js/faker": ["@faker-js/faker@9. 
     | 
| 176 | 
         | 
| 177 | 
         
             
                "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
         
     | 
| 178 | 
         | 
| 
         @@ -206,18 +213,24 @@ 
     | 
|
| 206 | 
         | 
| 207 | 
         
             
                "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
         
     | 
| 208 | 
         | 
| 
         | 
|
| 
         | 
|
| 209 | 
         
             
                "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
         
     | 
| 210 | 
         | 
| 211 | 
         
             
                "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "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/react-checkbox@1.1.4", "", { "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/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-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
         
     | 
| 216 | 
         | 
| 217 | 
         
             
                "@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "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/react-dialog@1.1.6", "", { "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/react-dismissable-layer@1.1.5", "", { "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/react-focus-guards@1.1.1", "", { "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/react-primitive@2.0.2", "", { "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/react-separator@1.1.2", "", { "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/react-slot@1.1.2", "", { "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/react-tooltip@1.1.8", "", { "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/react-use-callback-ref@1.1.0", "", { "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/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
         
     | 
| 386 | 
         | 
| 387 | 
         
            -
                "@types/node": ["@types/node@22.13. 
     | 
| 388 | 
         | 
| 389 | 
         
             
                "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
         
     | 
| 390 | 
         | 
| 
         @@ -446,8 +467,14 @@ 
     | 
|
| 446 | 
         | 
| 447 | 
         
             
                "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
         
     | 
| 448 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 449 | 
         
             
                "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
         
     | 
| 450 | 
         | 
| 
         | 
|
| 
         | 
|
| 451 | 
         
             
                "babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
         
     | 
| 452 | 
         | 
| 453 | 
         
             
                "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
         
     | 
| 
         @@ -478,6 +505,8 @@ 
     | 
|
| 478 | 
         | 
| 479 | 
         
             
                "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
         
     | 
| 480 | 
         | 
| 
         | 
|
| 
         | 
|
| 481 | 
         
             
                "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
         
     | 
| 482 | 
         | 
| 483 | 
         
             
                "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
         
     | 
| 
         @@ -502,6 +531,8 @@ 
     | 
|
| 502 | 
         | 
| 503 | 
         
             
                "define-properties": ["define-properties@1.2.1", "", { "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": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
         
     | 
| 506 | 
         | 
| 507 | 
         
             
                "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
         
     | 
| 
         @@ -536,7 +567,7 @@ 
     | 
|
| 536 | 
         | 
| 537 | 
         
             
                "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
         
     | 
| 538 | 
         | 
| 539 | 
         
            -
                "eslint": ["eslint@9.20. 
     | 
| 540 | 
         | 
| 541 | 
         
             
                "eslint-config-prettier": ["eslint-config-prettier@10.0.1", "", { "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": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
         
     | 
| 576 | 
         | 
| 
         | 
|
| 
         | 
|
| 577 | 
         
             
                "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
         
     | 
| 578 | 
         | 
| 579 | 
         
             
                "find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
         
     | 
| 
         @@ -584,8 +617,12 @@ 
     | 
|
| 584 | 
         | 
| 585 | 
         
             
                "flatted": ["flatted@3.3.2", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
         
     | 
| 586 | 
         | 
| 
         | 
|
| 
         | 
|
| 587 | 
         
             
                "for-each": ["for-each@0.3.4", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
         
     | 
| 588 | 
         | 
| 
         | 
|
| 
         | 
|
| 589 | 
         
             
                "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
         
     | 
| 590 | 
         | 
| 591 | 
         
             
                "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
         
     | 
| 
         @@ -604,7 +641,7 @@ 
     | 
|
| 604 | 
         | 
| 605 | 
         
             
                "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
         
     | 
| 606 | 
         | 
| 607 | 
         
            -
                "globals": ["globals@15. 
     | 
| 608 | 
         | 
| 609 | 
         
             
                "globalthis": ["globalthis@1.0.4", "", { "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": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
         
     | 
| 780 | 
         | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 781 | 
         
             
                "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
         
     | 
| 782 | 
         | 
| 783 | 
         
             
                "minisearch": ["minisearch@7.1.1", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
         
     | 
| 
         @@ -838,12 +879,14 @@ 
     | 
|
| 838 | 
         | 
| 839 | 
         
             
                "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
         
     | 
| 840 | 
         | 
| 841 | 
         
            -
                "prettier": ["prettier@3.5. 
     | 
| 842 | 
         | 
| 843 | 
         
             
                "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "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": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
         
     | 
| 846 | 
         | 
| 
         | 
|
| 
         | 
|
| 847 | 
         
             
                "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
         
     | 
| 848 | 
         | 
| 849 | 
         
             
                "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
         
     | 
| 
         @@ -852,6 +895,8 @@ 
     | 
|
| 852 | 
         | 
| 853 | 
         
             
                "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
         
     | 
| 854 | 
         | 
| 
         | 
|
| 
         | 
|
| 855 | 
         
             
                "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
         
     | 
| 856 | 
         | 
| 857 | 
         
             
                "react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "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": ["sigma@3.0.1", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
         
     | 
| 914 | 
         | 
| 
         | 
|
| 
         | 
|
| 915 | 
         
             
                "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
         
     | 
| 916 | 
         | 
| 917 | 
         
             
                "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
         
     | 
| 
         @@ -1006,12 +1053,16 @@ 
     | 
|
| 1006 | 
         | 
| 1007 | 
         
             
                "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
         
     | 
| 1008 | 
         | 
| 
         | 
|
| 
         | 
|
| 1009 | 
         
             
                "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
         
     | 
| 1010 | 
         | 
| 1011 | 
         
             
                "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
         
     | 
| 1012 | 
         | 
| 1013 | 
         
             
                "babel-plugin-macros/resolve": ["resolve@1.22.10", "", { "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": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
         
     | 
| 1016 | 
         | 
| 1017 | 
         
             
                "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "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/plugin-kit@0.2.5", "", { "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/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
         
     | 
| 185 | 
         | 
| 
         | 
|
| 213 | 
         | 
| 214 | 
         
             
                "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
         
     | 
| 215 | 
         | 
| 216 | 
         
            +
                "@radix-ui/number": ["@radix-ui/number@1.1.0", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="],
         
     | 
| 217 | 
         
            +
             
     | 
| 218 | 
         
             
                "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
         
     | 
| 219 | 
         | 
| 220 | 
         
             
                "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "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/react-checkbox@1.1.4", "", { "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/react-collection@1.1.2", "", { "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/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-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
         
     | 
| 227 | 
         | 
| 228 | 
         
             
                "@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "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/react-dialog@1.1.6", "", { "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/react-direction@1.1.0", "", { "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/react-dismissable-layer@1.1.5", "", { "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/react-focus-guards@1.1.1", "", { "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/react-primitive@2.0.2", "", { "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/react-progress@1.1.2", "", { "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/react-roving-focus@1.1.2", "", { "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/react-scroll-area@1.2.3", "", { "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/react-separator@1.1.2", "", { "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/react-slot@1.1.2", "", { "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/react-tabs@1.1.3", "", { "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/react-tooltip@1.1.8", "", { "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/react-use-callback-ref@1.1.0", "", { "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/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
         
     | 
| 407 | 
         | 
| 408 | 
         
            +
                "@types/node": ["@types/node@22.13.4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="],
         
     | 
| 409 | 
         | 
| 410 | 
         
             
                "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
         
     | 
| 411 | 
         | 
| 
         | 
|
| 467 | 
         | 
| 468 | 
         
             
                "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
         
     | 
| 469 | 
         | 
| 470 | 
         
            +
                "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
         
     | 
| 471 | 
         
            +
             
     | 
| 472 | 
         
            +
                "attr-accept": ["attr-accept@2.2.5", "", {}, "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="],
         
     | 
| 473 | 
         
            +
             
     | 
| 474 | 
         
             
                "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
         
     | 
| 475 | 
         | 
| 476 | 
         
            +
                "axios": ["axios@1.7.9", "", { "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": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
         
     | 
| 479 | 
         | 
| 480 | 
         
             
                "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
         
     | 
| 
         | 
|
| 505 | 
         | 
| 506 | 
         
             
                "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
         
     | 
| 507 | 
         | 
| 508 | 
         
            +
                "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
         
     | 
| 509 | 
         
            +
             
     | 
| 510 | 
         
             
                "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
         
     | 
| 511 | 
         | 
| 512 | 
         
             
                "convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
         
     | 
| 
         | 
|
| 531 | 
         | 
| 532 | 
         
             
                "define-properties": ["define-properties@1.2.1", "", { "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": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
         
     | 
| 535 | 
         
            +
             
     | 
| 536 | 
         
             
                "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
         
     | 
| 537 | 
         | 
| 538 | 
         
             
                "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
         
     | 
| 
         | 
|
| 567 | 
         | 
| 568 | 
         
             
                "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
         
     | 
| 569 | 
         | 
| 570 | 
         
            +
                "eslint": ["eslint@9.20.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": ["eslint-config-prettier@10.0.1", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "build/bin/cli.js" } }, "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw=="],
         
     | 
| 573 | 
         | 
| 
         | 
|
| 605 | 
         | 
| 606 | 
         
             
                "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
         
     | 
| 607 | 
         | 
| 608 | 
         
            +
                "file-selector": ["file-selector@2.1.2", "", { "dependencies": { "tslib": "^2.7.0" } }, "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig=="],
         
     | 
| 609 | 
         
            +
             
     | 
| 610 | 
         
             
                "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
         
     | 
| 611 | 
         | 
| 612 | 
         
             
                "find-root": ["find-root@1.1.0", "", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="],
         
     | 
| 
         | 
|
| 617 | 
         | 
| 618 | 
         
             
                "flatted": ["flatted@3.3.2", "", {}, "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA=="],
         
     | 
| 619 | 
         | 
| 620 | 
         
            +
                "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
         
     | 
| 621 | 
         
            +
             
     | 
| 622 | 
         
             
                "for-each": ["for-each@0.3.4", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
         
     | 
| 623 | 
         | 
| 624 | 
         
            +
                "form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
         
     | 
| 625 | 
         
            +
             
     | 
| 626 | 
         
             
                "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
         
     | 
| 627 | 
         | 
| 628 | 
         
             
                "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
         
     | 
| 
         | 
|
| 641 | 
         | 
| 642 | 
         
             
                "glob-parent": ["glob-parent@6.0.2", "", { "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": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
         
     | 
| 647 | 
         | 
| 
         | 
|
| 815 | 
         | 
| 816 | 
         
             
                "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
         
     | 
| 817 | 
         | 
| 818 | 
         
            +
                "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
         
     | 
| 819 | 
         
            +
             
     | 
| 820 | 
         
            +
                "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
         
     | 
| 821 | 
         
            +
             
     | 
| 822 | 
         
             
                "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
         
     | 
| 823 | 
         | 
| 824 | 
         
             
                "minisearch": ["minisearch@7.1.1", "", {}, "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw=="],
         
     | 
| 
         | 
|
| 879 | 
         | 
| 880 | 
         
             
                "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
         
     | 
| 881 | 
         | 
| 882 | 
         
            +
                "prettier": ["prettier@3.5.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw=="],
         
     | 
| 883 | 
         | 
| 884 | 
         
             
                "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.6.11", "", { "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": ["prop-types@15.8.1", "", { "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": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
         
     | 
| 889 | 
         
            +
             
     | 
| 890 | 
         
             
                "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
         
     | 
| 891 | 
         | 
| 892 | 
         
             
                "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
         
     | 
| 
         | 
|
| 895 | 
         | 
| 896 | 
         
             
                "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
         
     | 
| 897 | 
         | 
| 898 | 
         
            +
                "react-dropzone": ["react-dropzone@14.3.5", "", { "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": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
         
     | 
| 901 | 
         | 
| 902 | 
         
             
                "react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "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": ["sigma@3.0.1", "", { "dependencies": { "events": "^3.3.0", "graphology-utils": "^2.5.2" } }, "sha512-z67BX1FhIpD+wLs2WJ7QS2aR49TcSr3YaVZ2zU8cAc5jMiUYlSbeDp4EI6euBDUpm3/lzO4pfytP/gW4BhXWuA=="],
         
     | 
| 959 | 
         | 
| 960 | 
         
            +
                "sonner": ["sonner@1.7.4", "", { "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": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
         
     | 
| 963 | 
         | 
| 964 | 
         
             
                "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
         
     | 
| 
         | 
|
| 1053 | 
         | 
| 1054 | 
         
             
                "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
         
     | 
| 1055 | 
         | 
| 1056 | 
         
            +
                "@types/ws/@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
         
     | 
| 1057 | 
         
            +
             
     | 
| 1058 | 
         
             
                "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
         
     | 
| 1059 | 
         | 
| 1060 | 
         
             
                "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
         
     | 
| 1061 | 
         | 
| 1062 | 
         
             
                "babel-plugin-macros/resolve": ["resolve@1.22.10", "", { "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/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="],
         
     | 
| 1065 | 
         
            +
             
     | 
| 1066 | 
         
             
                "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
         
     | 
| 1067 | 
         | 
| 1068 | 
         
             
                "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "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( 
     | 
| 11 | 
         
            -
               
     | 
| 12 | 
         
            -
               
     | 
| 13 | 
         
            -
             
     | 
| 14 | 
         
            -
                 
     | 
| 15 | 
         
            -
                 
     | 
| 16 | 
         
            -
             
     | 
| 17 | 
         
            -
             
     | 
| 18 | 
         
            -
             
     | 
| 19 | 
         
            -
                 
     | 
| 20 | 
         
            -
                 
     | 
| 21 | 
         
            -
             
     | 
| 22 | 
         
            -
             
     | 
| 23 | 
         
            -
             
     | 
| 24 | 
         
            -
             
     | 
| 25 | 
         
            -
                 
     | 
| 26 | 
         
            -
                 
     | 
| 27 | 
         
            -
             
     | 
| 28 | 
         
            -
             
     | 
| 29 | 
         
            -
             
     | 
| 30 | 
         
            -
             
     | 
| 31 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 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. 
     | 
| 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. 
     | 
| 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. 
     | 
| 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. 
     | 
| 61 | 
         
             
                "graphology-types": "^0.24.8",
         
     | 
| 62 | 
         
            -
                "prettier": "^3.5. 
     | 
| 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 | 
         
            -
                    < 
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 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 | 
         
            -
            //  
     | 
| 71 | 
         
            -
            const  
     | 
| 72 | 
         
            -
               
     | 
| 73 | 
         
            -
               
     | 
| 74 | 
         
            -
                 
     | 
| 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 | 
         
            -
             
     | 
| 102 | 
         
            -
            }
         
     | 
| 103 | 
         | 
| 104 | 
         
            -
             
     | 
| 
         | 
|
| 105 | 
         
             
              const apiKey = useSettingsStore.getState().apiKey
         
     | 
| 106 | 
         
            -
               
     | 
| 107 | 
         
            -
                 
     | 
| 108 | 
         
            -
                ...(apiKey ? { 'X-API-Key': apiKey } : {})
         
     | 
| 109 | 
         
             
              }
         
     | 
| 110 | 
         
            -
             
     | 
| 111 | 
         
            -
             
     | 
| 112 | 
         
            -
             
     | 
| 113 | 
         
            -
             
     | 
| 114 | 
         
            -
             
     | 
| 115 | 
         
            -
             
     | 
| 116 | 
         
            -
               
     | 
| 117 | 
         
            -
                 
     | 
| 118 | 
         
            -
                   
     | 
| 119 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 120 | 
         
             
              }
         
     | 
| 121 | 
         
            -
             
     | 
| 122 | 
         
            -
              return response
         
     | 
| 123 | 
         
            -
            }
         
     | 
| 124 | 
         | 
| 125 | 
         
             
            // API methods
         
     | 
| 126 | 
         
             
            export const queryGraphs = async (label: string): Promise<LightragGraphType> => {
         
     | 
| 127 | 
         
            -
              const response = await  
     | 
| 128 | 
         
            -
              return  
     | 
| 129 | 
         
             
            }
         
     | 
| 130 | 
         | 
| 131 | 
         
             
            export const getGraphLabels = async (): Promise<string[]> => {
         
     | 
| 132 | 
         
            -
              const response = await  
     | 
| 133 | 
         
            -
              return  
     | 
| 134 | 
         
             
            }
         
     | 
| 135 | 
         | 
| 136 | 
         
             
            export const checkHealth = async (): Promise<
         
     | 
| 137 | 
         
             
              LightragStatus | { status: 'error'; message: string }
         
     | 
| 138 | 
         
             
            > => {
         
     | 
| 139 | 
         
             
              try {
         
     | 
| 140 | 
         
            -
                const response = await  
     | 
| 141 | 
         
            -
                return  
     | 
| 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  
     | 
| 152 | 
         
            -
              return  
     | 
| 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  
     | 
| 161 | 
         
            -
               
     | 
| 162 | 
         
            -
             
     | 
| 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  
     | 
| 178 | 
         
            -
              const response = await  
     | 
| 179 | 
         
            -
             
     | 
| 180 | 
         
            -
              })
         
     | 
| 181 | 
         
            -
              return await response.json()
         
     | 
| 182 | 
         
             
            }
         
     | 
| 183 | 
         | 
| 184 | 
         
             
            export const queryText = async (request: QueryRequest): Promise<QueryResponse> => {
         
     | 
| 185 | 
         
            -
              const response = await  
     | 
| 186 | 
         
            -
             
     | 
| 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  
     | 
| 197 | 
         
            -
                 
     | 
| 198 | 
         
            -
                headers: {
         
     | 
| 199 | 
         
            -
                  'Content-Type': 'application/json'
         
     | 
| 200 | 
         
            -
                },
         
     | 
| 201 | 
         
            -
                body: JSON.stringify(request)
         
     | 
| 202 | 
         
             
              })
         
     | 
| 203 | 
         | 
| 204 | 
         
            -
              const reader = response. 
     | 
| 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 | 
         
            -
               
     | 
| 235 | 
         
            -
               
     | 
| 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 | 
         
            -
             
     | 
| 249 | 
         
            -
             
     | 
| 250 | 
         
            -
               
     | 
| 251 | 
         
            -
            ): Promise<{
         
     | 
| 252 | 
         
            -
              status: string
         
     | 
| 253 | 
         
            -
              message: string
         
     | 
| 254 | 
         
            -
              document_count: number
         
     | 
| 255 | 
         
            -
            }> => {
         
     | 
| 256 | 
         
             
              const formData = new FormData()
         
     | 
| 257 | 
         
            -
               
     | 
| 258 | 
         
            -
                formData.append('files', file)
         
     | 
| 259 | 
         
            -
              })
         
     | 
| 260 | 
         | 
| 261 | 
         
            -
              const response = await  
     | 
| 262 | 
         
            -
                 
     | 
| 263 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 264 | 
         
             
              })
         
     | 
| 265 | 
         
            -
              return  
     | 
| 266 | 
         
             
            }
         
     | 
| 267 | 
         | 
| 268 | 
         
            -
             
     | 
| 269 | 
         
            -
             
     | 
| 270 | 
         
            -
               
     | 
| 271 | 
         
            -
             
     | 
| 272 | 
         
            -
               
     | 
| 273 | 
         
            -
             
     | 
| 274 | 
         
            -
             
     | 
| 275 | 
         
            -
             
     | 
| 276 | 
         
            -
             
     | 
| 277 | 
         
            -
             
     | 
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 
         | 
|
| 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- 
     | 
| 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=" 
     | 
| 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  
     | 
| 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- 
     | 
| 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. 
     | 
| 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. 
     | 
| 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 
     | 
| 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 
     | 
| 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
         
     |