From a08f59f663cf22157f6998d5c51ce8023fc4f730 Mon Sep 17 00:00:00 2001 From: ArnoChen Date: Mon, 10 Feb 2025 22:02:06 +0800 Subject: [PATCH] add properties view --- lightrag/api/graph_viewer_webui/bun.lock | 68 ++--- .../api/graph_viewer_webui/components.json | 21 ++ lightrag/api/graph_viewer_webui/package.json | 10 +- .../graph_viewer_webui/src/GraphViewer.tsx | 69 ++--- .../src/components/FocusOnNode.tsx | 6 +- .../src/components/GraphControl.tsx | 40 ++- .../src/components/GraphSearch.tsx | 2 +- .../src/components/PropertiesView.tsx | 231 ++++++++++++++++ .../src/components/Settings.tsx | 2 +- .../src/components/ThemeProvider.tsx | 2 +- .../src/components/ui/AsyncSelect.tsx | 4 +- .../src/components/ui/Text.tsx | 49 ++++ .../src/hooks/useLightragGraph.tsx | 143 ++++------ .../src/hooks/useRandomGraph.tsx | 3 + lightrag/api/graph_viewer_webui/src/index.css | 249 ++++++++---------- .../api/graph_viewer_webui/src/lib/utils.ts | 15 ++ .../graph_viewer_webui/src/stores/graph.ts | 139 ++++++++++ .../src/{lib => stores}/settings.ts | 17 +- 18 files changed, 723 insertions(+), 347 deletions(-) create mode 100644 lightrag/api/graph_viewer_webui/components.json create mode 100644 lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx create mode 100644 lightrag/api/graph_viewer_webui/src/components/ui/Text.tsx create mode 100644 lightrag/api/graph_viewer_webui/src/stores/graph.ts rename lightrag/api/graph_viewer_webui/src/{lib => stores}/settings.ts (66%) diff --git a/lightrag/api/graph_viewer_webui/bun.lock b/lightrag/api/graph_viewer_webui/bun.lock index 6d4ac25a..fcd1a18d 100644 --- a/lightrag/api/graph_viewer_webui/bun.lock +++ b/lightrag/api/graph_viewer_webui/bun.lock @@ -37,23 +37,23 @@ "devDependencies": { "@eslint/js": "^9.20.0", "@stylistic/eslint-plugin-js": "^3.1.0", - "@tailwindcss/vite": "^4.0.4", + "@tailwindcss/vite": "^4.0.5", "@types/bun": "^1.2.2", "@types/node": "^22.13.1", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@types/seedrandom": "^3.0.8", - "@vitejs/plugin-react-swc": "^3.7.2", + "@vitejs/plugin-react-swc": "^3.8.0", "eslint": "^9.20.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.18", + "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.14.0", "graphology-types": "^0.24.8", - "prettier": "^3.4.2", + "prettier": "^3.5.0", "prettier-plugin-tailwindcss": "^0.6.11", - "tailwindcss": "^4.0.4", + "tailwindcss": "^4.0.5", "tailwindcss-animate": "^1.0.7", "typescript": "~5.7.3", "typescript-eslint": "^8.23.0", @@ -320,59 +320,59 @@ "@stylistic/eslint-plugin-js": ["@stylistic/eslint-plugin-js@3.1.0", "", { "dependencies": { "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0" }, "peerDependencies": { "eslint": ">=8.40.0" } }, "sha512-lQktsOiCr8S6StG29C5fzXYxLOD6ID1rp4j6TRS+E/qY1xd59Fm7dy5qm9UauJIEoSTlYx6yGsCHYh5UkgXPyg=="], - "@swc/core": ["@swc/core@1.10.14", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.17" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.10.14", "@swc/core-darwin-x64": "1.10.14", "@swc/core-linux-arm-gnueabihf": "1.10.14", "@swc/core-linux-arm64-gnu": "1.10.14", "@swc/core-linux-arm64-musl": "1.10.14", "@swc/core-linux-x64-gnu": "1.10.14", "@swc/core-linux-x64-musl": "1.10.14", "@swc/core-win32-arm64-msvc": "1.10.14", "@swc/core-win32-ia32-msvc": "1.10.14", "@swc/core-win32-x64-msvc": "1.10.14" }, "peerDependencies": { "@swc/helpers": "*" }, "optionalPeers": ["@swc/helpers"] }, "sha512-WSrnE6JRnH20ZYjOOgSS4aOaPv9gxlkI2KRkN24kagbZnPZMnN8bZZyzw1rrLvwgpuRGv17Uz+hflosbR+SP6w=="], + "@swc/core": ["@swc/core@1.10.15", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.17" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.10.15", "@swc/core-darwin-x64": "1.10.15", "@swc/core-linux-arm-gnueabihf": "1.10.15", "@swc/core-linux-arm64-gnu": "1.10.15", "@swc/core-linux-arm64-musl": "1.10.15", "@swc/core-linux-x64-gnu": "1.10.15", "@swc/core-linux-x64-musl": "1.10.15", "@swc/core-win32-arm64-msvc": "1.10.15", "@swc/core-win32-ia32-msvc": "1.10.15", "@swc/core-win32-x64-msvc": "1.10.15" }, "peerDependencies": { "@swc/helpers": "*" }, "optionalPeers": ["@swc/helpers"] }, "sha512-/iFeQuNaGdK7mfJbQcObhAhsMqLT7qgMYl7jX2GEIO+VDTejESpzAyKwaMeYXExN8D6e5BRHBCe7M5YlsuzjDA=="], - "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.10.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Dh4VyrhDDb05tdRmqJ/MucOPMTnrB4pRJol18HVyLlqu1HOT5EzonUniNTCdQbUXjgdv5UVJSTE1lYTzrp+myA=="], + "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.10.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zFdZ6/yHqMCPk7OhLFqHy/MQ1EqJhcZMpNHd1gXYT7VRU3FaqvvKETrUlG3VYl65McPC7AhMRfXPyJ0JO/jARQ=="], - "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.10.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-KpzotL/I0O12RE3tF8NmQErINv0cQe/0mnN/Q50ESFzB5kU6bLgp2HMnnwDTm/XEZZRJCNe0oc9WJ5rKbAJFRQ=="], + "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.10.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-8g4yiQwbr8fxOOjKXdot0dEkE5zgE8uNZudLy/ZyAhiwiZ8pbJ8/wVrDOu6dqbX7FBXAoDnvZ7fwN1jk4C8jdA=="], - "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.10.14", "", { "os": "linux", "cpu": "arm" }, "sha512-20yRXZjMJVz1wp1TcscKiGTVXistG+saIaxOmxSNQia1Qun3hSWLL+u6+5kXbfYGr7R2N6kqSwtZbIfJI25r9Q=="], + "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.10.15", "", { "os": "linux", "cpu": "arm" }, "sha512-rl+eVOltl2+7WXOnvmWBpMgh6aO13G5x0U0g8hjwlmD6ku3Y9iRcThpOhm7IytMEarUp5pQxItNoPq+VUGjVHg=="], - "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.10.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-Gy7cGrNkiMfPxQyLGxdgXPwyWzNzbHuWycJFcoKBihxZKZIW8hkPBttkGivuLC+0qOgsV2/U+S7tlvAju7FtmQ=="], + "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.10.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-qxWEQeyAJMWJqjaN4hi58WMpPdt3Tn0biSK9CYRegQtvZWCbewr6v2agtSu5AZ2rudeH6OfCWAMDQQeSgn6PJQ=="], - "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.10.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-+oYVqJvFw62InZ8PIy1rBACJPC2WTe4vbVb9kM1jJj2D7dKLm9acnnYIVIDsM5Wo7Uab8RvPHXVbs19IBurzuw=="], + "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.10.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-QcELd9/+HjZx0WCxRrKcyKGWTiQ0485kFb5w8waxcSNd0d9Lgk4EFfWWVyvIb5gIHpDQmhrgzI/yRaWQX4YSZQ=="], - "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.10.14", "", { "os": "linux", "cpu": "x64" }, "sha512-OmEbVEKQFLQVHwo4EJl9osmlulURy46k232Opfpn/1ji0t2KcNCci3POsnfMuoZjLkGJv8vGNJdPQxX+CP+wSA=="], + "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.10.15", "", { "os": "linux", "cpu": "x64" }, "sha512-S1+ZEEn3+a/MiMeQqQypbwTGoBG8/sPoCvpNbk+uValyygT+jSn3U0xVr45FbukpmMB+NhBMqfedMLqKA0QnJA=="], - "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.10.14", "", { "os": "linux", "cpu": "x64" }, "sha512-OZW+Icm8DMPqHbhdxplkuG8qrNnPk5i7xJOZWYi1y5bTjgGFI4nEzrsmmeHKMdQTaWwsFrm3uK1rlyQ48MmXmg=="], + "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.10.15", "", { "os": "linux", "cpu": "x64" }, "sha512-qW+H9g/2zTJ4jP7NDw4VAALY0ZlNEKzYsEoSj/HKi7k3tYEHjMzsxjfsY9I8WZCft23bBdV3RTCPoxCshaj1CQ=="], - "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.10.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-sTvc+xrDQXy3HXZFtTEClY35Efvuc3D+busYm0+rb1+Thau4HLRY9WP+sOKeGwH9/16rzfzYEqD7Ds8A9ykrHw=="], + "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.10.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-AhRB11aA6LxjIqut+mg7qsu/7soQDmbK6MKR9nP3hgBszpqtXbRba58lr24xIbBCMr+dpo6kgEapWt+t5Po6Zg=="], - "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.10.14", "", { "os": "win32", "cpu": "ia32" }, "sha512-j2iQ4y9GWTKtES5eMU0sDsFdYni7IxME7ejFej25Tv3Fq4B+U9tgtYWlJwh1858nIWDXelHiKcSh/UICAyVMdQ=="], + "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.10.15", "", { "os": "win32", "cpu": "ia32" }, "sha512-UGdh430TQwbDn6KjgvRTg1fO022sbQ4yCCHUev0+5B8uoBwi9a89qAz3emy2m56C8TXxUoihW9Y9OMfaRwPXUw=="], - "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.10.14", "", { "os": "win32", "cpu": "x64" }, "sha512-TYtWkUSMkjs0jGPeWdtWbex4B+DlQZmN/ySVLiPI+EltYCLEXsFMkVFq6aWn48dqFHggFK0UYfvDrJUR2c3Qxg=="], + "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.10.15", "", { "os": "win32", "cpu": "x64" }, "sha512-XJzBCqO1m929qbJsOG7FZXQWX26TnEoMctS3QjuCoyBmkHxxQmZsy78KjMes1aomTcKHCyFYgrRGWgVmk7tT4Q=="], "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/types": ["@swc/types@0.1.17", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ=="], - "@tailwindcss/node": ["@tailwindcss/node@4.0.4", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.4" } }, "sha512-VLFq80IyoV1hsHPcCm1mmlyPyUT6NlovQLoO2y7PGm84mW94ZrNJ7ax5H6K4M7Aj/fdMfem5IX7Ka+LXWZpDGg=="], + "@tailwindcss/node": ["@tailwindcss/node@4.0.5", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.5" } }, "sha512-ffTz4DX1cgr4XPuqjhm32YV6Lyx58R1CxAAnSFTamg6wXwfk3oWdb6exgAbGesPzvUgicTO0gwUdQGSsg4nNog=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.4", "@tailwindcss/oxide-darwin-arm64": "4.0.4", "@tailwindcss/oxide-darwin-x64": "4.0.4", "@tailwindcss/oxide-freebsd-x64": "4.0.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.4", "@tailwindcss/oxide-linux-arm64-musl": "4.0.4", "@tailwindcss/oxide-linux-x64-gnu": "4.0.4", "@tailwindcss/oxide-linux-x64-musl": "4.0.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.4", "@tailwindcss/oxide-win32-x64-msvc": "4.0.4" } }, "sha512-vPpu30KFLiGyPOoElkYt8WRvzGKVrrOz49KpfiGGtnQGmyUpL8VCbJzzEEcpKT5BpaaQidhFok+OXscf6hHjOQ=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.5", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.5", "@tailwindcss/oxide-darwin-arm64": "4.0.5", "@tailwindcss/oxide-darwin-x64": "4.0.5", "@tailwindcss/oxide-freebsd-x64": "4.0.5", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.5", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.5", "@tailwindcss/oxide-linux-arm64-musl": "4.0.5", "@tailwindcss/oxide-linux-x64-gnu": "4.0.5", "@tailwindcss/oxide-linux-x64-musl": "4.0.5", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.5", "@tailwindcss/oxide-win32-x64-msvc": "4.0.5" } }, "sha512-iWGyOCu0TuzvCBisWbGv2K9+7QCfE0ztgtrZOvb9iF7V7ChVkD15Obe3HevZrhjngAc34jDA+OMSuSvkrpTy4A=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.4", "", { "os": "android", "cpu": "arm64" }, "sha512-hiGUA8d15ynH/LdurQNObnuTjri7i4ApAzhesusNxoz4br7vhZ6QO5CFgniYAYNZvf8Q8wCTBg0nj61RalBeVQ=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.5", "", { "os": "android", "cpu": "arm64" }, "sha512-kK/ik8aIAKWDIEYDZGUCJcnU1qU5sPoMBlVzPvtsUqiV6cSHcnVRUdkcLwKqTeUowzZtjjRiamELLd9Gb0x5BQ=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vTca+ysNl8BYmYJTni9pLC+L3S4bvrj0ai1eUV3yYXYa5Cpugr5Fni6ylV0gcTZOyETm2RCCJ/0azU6MgqE6HA=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vkbXFv0FfAEbrSa5NBjFEE+xi06ha7mxuxjY8LRn7d7/tBGrAZOEJnnsEbB6M1+x2pGRTjjei0XyTIXdVCglJA=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-rxPWb5AQJ/aAM/5UDCjaQaMYIcrZHe/Dr9xZu9+P9nJf3WAweNsGi+e+SW9EYGRiF3hkBtP2dvxVNAkTiEbNQQ=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-PedA64rHBXEa4e6abBWE4Yj4gHulfPb5T+rBNnX+WGkjjge5Txa2oS99TLmJ5BPDkXXqz/Ba7oweWIDDG7i5NQ=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UOnRHzlS5V5cxaMgBo6rk1E92tTDUtO/falc9vOpNiRdWhNcofYNN9zvZP63Wuo5FC6/XCyAnJo6OXUm18TwrQ=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-silz3nuZdEYDfic3v/ooVUQChj9hbxDSee43GCQNwr/iD9L4K/JsZtoNqr0w69pUkvWcKINOGOG0r7WqUqkAeg=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-0Ry9Qfnf22rmJwHxsCFmHQIl5RZw+yOUUGHaqNT42REL8r308cU/bi4UqdrjqVRfAlu51gOGxTRf2NRueczuIA=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-ElneG75XS64B9I2G83A/Hc7EtNVOD5xahs7avq0aeW7mEX6CtMc8m8RCXMn3jGhz8enFE52l6QU0wO7iVkEtXQ=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-5a7WD30nVdI7Rl1ohZ0Ojj9t5yRnZkJBizvh3uIW52h9UeNpon8TfoknF6rU/TwD32dQ0Cjo5CcCHtQ2wW9PCA=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-8yoXpWTeIFaByUaKy2qRAppznLVaDHP9xYCAbS3FG7+uUwHi8CHE4TcomM7eyamo0U7dbUIDgKMGoAX5s2iVrA=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-m6s5jKSqos07l6NtHFd49Ljcaw4jIWHE7jq6eNPNz9SCzQqRzs4esP1t7jH8UljQ7JffKOl7yZPwK5Nf+irliw=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-BDlVSiiJ08GRz9KKnXgaPFs2fkukPF3pym6uK3oWEKW45jKlVGgybLqulcV5nLEqREOuyq4Rn4vnZss4/bbQ/g=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-K5dBjGHzby9eyUBwy9YHFhKY+5i8fzIBZM1NBWp6L2xpM7OzW9WJDgNcgESkZami9g+EozkQLt3ZmMZHAieXkw=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.5", "", { "os": "linux", "cpu": "x64" }, "sha512-DYgieNDRkTy69bWPgdsc47nAXa74P63P/RetUwYM9vYj5USyOfHCEcqIthkCuYw3dXKBhjgwe697TmL2g2jpAw=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-J8sskt+fA5ooq+kxy0Tf4E2TRWZD9Y8j3K+pnBwp9zdilLmSd8OHrB3e0/rO78KveZ6BE9ae75cKOWrT6wONmw=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.5", "", { "os": "linux", "cpu": "x64" }, "sha512-z2RzUvOQl0ZqrZqmCFP53tJbBXQ3UmLD/E6J7+q0e+4VaFnXCcIYTfQbHgI8f3fash+q6gK80Ko/ywEQ+bvv6Q=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-flFaaMc77NQbz0Fq73wBs9EH2lX1Oc2Z/3JuxoewpnGHpAGJ/j05tvBNMyTaGrKcHvf/+dk+mCDxb6+PmzGgnQ=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-ho1dJ4o5Q8nAOxdMkbfBu5aSqI+/bzQ0jEeHcXaEdEJzf2fSWs3HY7bIKtE6vQS8c4SmSBvls7IhGPuJxNg+2Q=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-WzMA0aL/24/JyNrv2Yhr/Og24QGRPWJMjRyCJ4HRoGMs6/8svOQKrnnZ/9LUFwn56irAndFBjWWnlaqykH+g5Q=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.5", "", { "os": "win32", "cpu": "x64" }, "sha512-yjw6JhtyDXr+G0aZrj3L3NlEV7CobSqOdPyfo6G3d91WEZ5b8PyGm86IAreX08Jp9DChGXEd53gWysVpWCTs+w=="], - "@tailwindcss/vite": ["@tailwindcss/vite@4.0.4", "", { "dependencies": { "@tailwindcss/node": "^4.0.4", "@tailwindcss/oxide": "^4.0.4", "lightningcss": "^1.29.1", "tailwindcss": "4.0.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-zrWGbluPeXeoetUQoDFmt1dQIeiOBThfznla7zPIqST69rMmiDD4SZwJrHVoL5CvXz06AYQXz/M/jELSakL7Rg=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.0.5", "", { "dependencies": { "@tailwindcss/node": "^4.0.5", "@tailwindcss/oxide": "^4.0.5", "lightningcss": "^1.29.1", "tailwindcss": "4.0.5" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-/i4hjLTUYVjUG0MTUviQP3HR/hzwyzv8Sq4sz2pnsNuf+FIjjhJB0vcnIMH1KIX0k8ozD6CBv2Dl76tlm/JFFA=="], "@types/bun": ["@types/bun@1.2.2", "", { "dependencies": { "bun-types": "1.2.2" } }, "sha512-tr74gdku+AEDN5ergNiBnplr7hpDp3V1h7fqI2GcR/rsUaM39jpSeKH0TFibRvU0KwniRx5POgaYnaXbk0hU+w=="], @@ -410,7 +410,7 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.23.0", "", { "dependencies": { "@typescript-eslint/types": "8.23.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ=="], - "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.7.2", "", { "dependencies": { "@swc/core": "^1.7.26" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" } }, "sha512-y0byko2b2tSVVf5Gpng1eEhX1OvPC7x8yns1Fx8jDzlJp4LS6CMkCPfLw47cjyoMrshQDoQw4qcgjsU9VvlCew=="], + "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@3.8.0", "", { "dependencies": { "@swc/core": "^1.10.15" }, "peerDependencies": { "vite": "^4 || ^5 || ^6" } }, "sha512-T4sHPvS+DIqDP51ifPqa9XIRAz/kIvIi8oXcnOZZgHmMotgmmdxe/DD5tMFlt5nuIRzT0/QuiwmKlH0503Aapw=="], "@yomguithereal/helpers": ["@yomguithereal/helpers@1.1.1", "", {}, "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="], @@ -540,7 +540,7 @@ "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.1.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw=="], - "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.18", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw=="], + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.19", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ=="], "eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="], @@ -834,7 +834,7 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="], + "prettier": ["prettier@3.5.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA=="], "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=="], @@ -932,7 +932,7 @@ "tailwind-merge": ["tailwind-merge@3.0.1", "", {}, "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g=="], - "tailwindcss": ["tailwindcss@4.0.4", "", {}, "sha512-/ezDLEkOLf1lXkr9F2iI5BHJbexJpty5zkV2B8bGHCqAdbc9vk85Jgdkq+ZOvNkNPa3yAaqJ8DjRt584Bc84kw=="], + "tailwindcss": ["tailwindcss@4.0.5", "", {}, "sha512-DZZIKX3tA23LGTjHdnwlJOTxfICD1cPeykLLsYF1RQBI9QsCR3i0szohJfJDVjr6aNRAIio5WVO7FGB77fRHwg=="], "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], diff --git a/lightrag/api/graph_viewer_webui/components.json b/lightrag/api/graph_viewer_webui/components.json new file mode 100644 index 00000000..8bfc737f --- /dev/null +++ b/lightrag/api/graph_viewer_webui/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/lightrag/api/graph_viewer_webui/package.json b/lightrag/api/graph_viewer_webui/package.json index 2bd68236..cd488cec 100644 --- a/lightrag/api/graph_viewer_webui/package.json +++ b/lightrag/api/graph_viewer_webui/package.json @@ -43,23 +43,23 @@ "devDependencies": { "@eslint/js": "^9.20.0", "@stylistic/eslint-plugin-js": "^3.1.0", - "@tailwindcss/vite": "^4.0.4", + "@tailwindcss/vite": "^4.0.5", "@types/bun": "^1.2.2", "@types/node": "^22.13.1", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@types/seedrandom": "^3.0.8", - "@vitejs/plugin-react-swc": "^3.7.2", + "@vitejs/plugin-react-swc": "^3.8.0", "eslint": "^9.20.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.18", + "eslint-plugin-react-refresh": "^0.4.19", "globals": "^15.14.0", "graphology-types": "^0.24.8", - "prettier": "^3.4.2", + "prettier": "^3.5.0", "prettier-plugin-tailwindcss": "^0.6.11", - "tailwindcss": "^4.0.4", + "tailwindcss": "^4.0.5", "tailwindcss-animate": "^1.0.7", "typescript": "~5.7.3", "typescript-eslint": "^8.23.0", diff --git a/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx b/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx index 2191113f..41606fe4 100644 --- a/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx +++ b/lightrag/api/graph_viewer_webui/src/GraphViewer.tsx @@ -1,8 +1,8 @@ -import { useEffect, useState, useCallback } from 'react' +import { useEffect, useState, useCallback, useMemo } from 'react' // import { MiniMap } from '@react-sigma/minimap' import { SigmaContainer, useRegisterEvents, useSigma } from '@react-sigma/core' import { Settings as SigmaSettings } from 'sigma/settings' -import { GraphSearchOption } from '@react-sigma/graph-search' +import { GraphSearchOption, OptionItem } from '@react-sigma/graph-search' import { EdgeArrowProgram, NodePointProgram, NodeCircleProgram } from 'sigma/rendering' import { NodeBorderProgram } from '@sigma/node-border' import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve' @@ -15,8 +15,10 @@ import ZoomControl from '@/components/ZoomControl' import FullScreenControl from '@/components/FullScreenControl' import Settings from '@/components/Settings' import GraphSearch from '@/components/GraphSearch' +import PropertiesView from '@/components/PropertiesView' -import { useSettingsStore } from '@/lib/settings' +import { useSettingsStore } from '@/stores/settings' +import { useGraphStore } from '@/stores/graph' import '@react-sigma/core/lib/style.css' import '@react-sigma/graph-search/lib/style.css' @@ -97,10 +99,11 @@ const GraphEvents = () => { } export const GraphViewer = () => { - const [selectedNode, setSelectedNode] = useState(null) - const [focusedNode, setFocusedNode] = useState(null) const [sigmaSettings, setSigmaSettings] = useState(defaultSigmaSettings) - const [autoMoveToFocused, setAutoMoveToFocused] = useState(false) + + const selectedNode = useGraphStore.use.selectedNode() + const focusedNode = useGraphStore.use.focusedNode() + const moveToSelectedNode = useGraphStore.use.moveToSelectedNode() const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents() const enableNodeDrag = useSettingsStore.use.enableNodeDrag() @@ -114,45 +117,39 @@ export const GraphViewer = () => { }) }, [enableEdgeEvents, renderEdgeLabels]) - const onFocus = useCallback( - (value: GraphSearchOption | null) => { - if (value === null) setFocusedNode(null) - else if (value.type === 'nodes') setFocusedNode(value.id) - }, - [setFocusedNode] - ) + const onSearchFocus = useCallback((value: GraphSearchOption | null) => { + if (value === null) useGraphStore.getState().setFocusedNode(null) + else if (value.type === 'nodes') useGraphStore.getState().setFocusedNode(value.id) + }, []) - const onSelect = useCallback( - (value: GraphSearchOption | null) => { - if (value === null) setSelectedNode(null) - else if (value.type === 'nodes') { - setAutoMoveToFocused(true) - setSelectedNode(value.id) - setTimeout(() => setAutoMoveToFocused(false), 100) - } - }, - [setSelectedNode, setAutoMoveToFocused] + const onSearchSelect = useCallback((value: GraphSearchOption | null) => { + if (value === null) { + useGraphStore.getState().setSelectedNode(null) + } else if (value.type === 'nodes') { + useGraphStore.getState().setSelectedNode(value.id, true) + } + }, []) + + const autoFocusedNode = useMemo(() => focusedNode ?? selectedNode, [focusedNode, selectedNode]) + const searchInitSelectedNode = useMemo( + (): OptionItem | null => (selectedNode ? { type: 'nodes', id: selectedNode } : null), + [selectedNode] ) return ( - + {enableNodeDrag && } - + -
+
@@ -164,6 +161,10 @@ export const GraphViewer = () => {
+
+ +
+ {/*
*/} diff --git a/lightrag/api/graph_viewer_webui/src/components/FocusOnNode.tsx b/lightrag/api/graph_viewer_webui/src/components/FocusOnNode.tsx index 4bed98c1..cfefb7bb 100644 --- a/lightrag/api/graph_viewer_webui/src/components/FocusOnNode.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/FocusOnNode.tsx @@ -1,5 +1,6 @@ import { useCamera, useSigma } from '@react-sigma/core' import { useEffect } from 'react' +import { useGraphStore } from '@/stores/graph' /** * Component that highlights a node and centers the camera on it. @@ -14,7 +15,10 @@ const FocusOnNode = ({ node, move }: { node: string | null; move?: boolean }) => useEffect(() => { if (!node) return sigma.getGraph().setNodeAttribute(node, 'highlighted', true) - if (move) gotoNode(node) + if (move) { + gotoNode(node) + useGraphStore.getState().setMoveToSelectedNode(false) + } return () => { sigma.getGraph().setNodeAttribute(node, 'highlighted', false) diff --git a/lightrag/api/graph_viewer_webui/src/components/GraphControl.tsx b/lightrag/api/graph_viewer_webui/src/components/GraphControl.tsx index 5e8ead73..ecdf121a 100644 --- a/lightrag/api/graph_viewer_webui/src/components/GraphControl.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/GraphControl.tsx @@ -1,13 +1,15 @@ import { useLoadGraph, useRegisterEvents, useSetSettings, useSigma } from '@react-sigma/core' // import { useLayoutCircular } from '@react-sigma/layout-circular' import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2' -import { useEffect, useState } from 'react' +import { useEffect } from 'react' // import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph' import useLightragGraph, { EdgeType, NodeType } from '@/hooks/useLightragGraph' import useTheme from '@/hooks/useTheme' import * as Constants from '@/lib/constants' -import { useSettingsStore } from '@/lib/settings' + +import { useSettingsStore } from '@/stores/settings' +import { useGraphStore } from '@/stores/graph' const isButtonPressed = (ev: MouseEvent | TouchEvent) => { if (ev.type.startsWith('mouse')) { @@ -18,19 +20,7 @@ const isButtonPressed = (ev: MouseEvent | TouchEvent) => { return false } -const GraphControl = ({ - disableHoverEffect, - selectedNode, - setSelectedNode, - focusedNode, - setFocusedNode -}: { - disableHoverEffect?: boolean - selectedNode: string | null - setSelectedNode: (node: string | null) => void - focusedNode: string | null - setFocusedNode: (node: string | null) => void -}) => { +const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean }) => { const { lightrageGraph } = useLightragGraph() const sigma = useSigma() const registerEvents = useRegisterEvents() @@ -39,11 +29,13 @@ const GraphControl = ({ const { assign: assignLayout } = useLayoutForceAtlas2({ iterations: 20 }) - const [focusedEdge, setfocusedEdge] = useState(null) - const [selectedEdge, setSelectedEdge] = useState(null) const { theme } = useTheme() const hideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges() + const selectedNode = useGraphStore.use.selectedNode() + const focusedNode = useGraphStore.use.focusedNode() + const selectedEdge = useGraphStore.use.selectedEdge() + const focusedEdge = useGraphStore.use.focusedEdge() /** * When component mount @@ -58,6 +50,9 @@ const GraphControl = ({ Object.assign(graph, { __force_applied: true }) } + const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } = + useGraphStore.getState() + // Register the events registerEvents({ enterNode: (event) => { @@ -80,20 +75,17 @@ const GraphControl = ({ }, enterEdge: (event) => { if (!isButtonPressed(event.event.original)) { - setfocusedEdge(event.edge) + setFocusedEdge(event.edge) } }, leaveEdge: (event) => { if (!isButtonPressed(event.event.original)) { - setfocusedEdge(null) + setFocusedEdge(null) } }, - clickStage: () => { - setSelectedEdge(null) - setSelectedNode(null) - } + clickStage: () => clearSelection() }) - }, [assignLayout, loadGraph, registerEvents, lightrageGraph, setFocusedNode, setSelectedNode]) + }, [assignLayout, loadGraph, registerEvents, lightrageGraph]) /** * When component mount or hovered node change diff --git a/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx b/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx index ad831284..f115fd5e 100644 --- a/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/GraphSearch.tsx @@ -70,7 +70,7 @@ export const GraphSearchInput = ({ return ( item.id} diff --git a/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx b/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx new file mode 100644 index 00000000..99d4af2e --- /dev/null +++ b/lightrag/api/graph_viewer_webui/src/components/PropertiesView.tsx @@ -0,0 +1,231 @@ +import { useEffect, useState } from 'react' +import { useGraphStore, RawNodeType, RawEdgeType } from '@/stores/graph' +import Text from '@/components/ui/Text' +import useLightragGraph from '@/hooks/useLightragGraph' + +/** + * Component that view properties of elements in graph. + */ +const PropertiesView = () => { + const { getNode, getEdge } = useLightragGraph() + const selectedNode = useGraphStore.use.selectedNode() + const focusedNode = useGraphStore.use.focusedNode() + const selectedEdge = useGraphStore.use.selectedEdge() + const focusedEdge = useGraphStore.use.focusedEdge() + + const [currentElement, setCurrentElement] = useState(null) + const [currentType, setCurrentType] = useState<'node' | 'edge' | null>(null) + + useEffect(() => { + let type: 'node' | 'edge' | null = null + let element: RawNodeType | RawEdgeType | null = null + if (focusedNode) { + type = 'node' + element = getNode(focusedNode) + } else if (selectedNode) { + type = 'node' + element = getNode(selectedNode) + } else if (focusedEdge) { + type = 'edge' + element = getEdge(focusedEdge, true) + } else if (selectedEdge) { + type = 'edge' + element = getEdge(selectedEdge, true) + } + + if (element) { + if (type == 'node') { + setCurrentElement(refineNodeProperties(element as any)) + } else { + setCurrentElement(refineEdgeProperties(element as any)) + } + setCurrentType(type) + } else { + setCurrentElement(null) + setCurrentType(null) + } + }, [ + focusedNode, + selectedNode, + focusedEdge, + selectedEdge, + setCurrentElement, + setCurrentType, + getNode, + getEdge + ]) + + if (!currentElement) { + return <> + } + return ( +
+ {currentType == 'node' ? ( + + ) : ( + + )} +
+ ) +} + +type NodeType = RawNodeType & { + relationships: { + type: string + id: string + label: string + }[] +} + +type EdgeType = RawEdgeType & { + sourceNode?: RawNodeType + targetNode?: RawNodeType +} + +const refineNodeProperties = (node: RawNodeType): NodeType => { + const state = useGraphStore.getState() + const relationships = [] + + if (state.sigmaGraph && state.rawGraph) { + for (const edgeId of state.sigmaGraph.edges(node.id)) { + const edge = state.rawGraph.getEdge(edgeId, true) + if (edge) { + const isTarget = node.id === edge.source + const neighbourId = isTarget ? edge.target : edge.source + const neighbour = state.rawGraph.getNode(neighbourId) + if (neighbour) { + relationships.push({ + type: isTarget ? 'Target' : 'Source', + id: neighbourId, + label: neighbour.labels.join(', ') + }) + } + } + } + } + return { + ...node, + relationships + } +} + +const refineEdgeProperties = (edge: RawEdgeType): EdgeType => { + const state = useGraphStore.getState() + const sourceNode = state.rawGraph?.getNode(edge.source) + const targetNode = state.rawGraph?.getNode(edge.target) + return { + ...edge, + sourceNode, + targetNode + } +} + +const PropertyRow = ({ + name, + value, + onClick, + tooltip +}: { + name: string + value: any + onClick?: () => void + tooltip?: string +}) => { + return ( +
+ : + +
+ ) +} + +const NodePropertiesView = ({ node }: { node: NodeType }) => { + return ( +
+ +
+ + { + useGraphStore.getState().setSelectedNode(node.id, true) + }} + /> + +
+ +
+ {Object.keys(node.properties) + .sort() + .map((name) => { + return + })} +
+ {node.relationships.length > 0 && ( + <> + +
+ {node.relationships.map(({ type, id, label }) => { + return ( + { + useGraphStore.getState().setSelectedNode(id, true) + }} + /> + ) + })} +
+ + )} +
+ ) +} + +const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => { + return ( +
+ +
+ + + { + useGraphStore.getState().setSelectedNode(edge.source, true) + }} + /> + { + useGraphStore.getState().setSelectedNode(edge.target, true) + }} + /> +
+ +
+ {Object.keys(edge.properties) + .sort() + .map((name) => { + return + })} +
+
+ ) +} + +export default PropertiesView diff --git a/lightrag/api/graph_viewer_webui/src/components/Settings.tsx b/lightrag/api/graph_viewer_webui/src/components/Settings.tsx index 8f9dcee5..2127e44f 100644 --- a/lightrag/api/graph_viewer_webui/src/components/Settings.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/Settings.tsx @@ -3,7 +3,7 @@ import { Checkbox } from '@/components/ui/Checkbox' import Button from '@/components/ui/Button' import { useState, useCallback } from 'react' import { controlButtonVariant } from '@/lib/constants' -import { useSettingsStore } from '@/lib/settings' +import { useSettingsStore } from '@/stores/settings' import { SettingsIcon } from 'lucide-react' diff --git a/lightrag/api/graph_viewer_webui/src/components/ThemeProvider.tsx b/lightrag/api/graph_viewer_webui/src/components/ThemeProvider.tsx index 3b8532e6..873b92a4 100644 --- a/lightrag/api/graph_viewer_webui/src/components/ThemeProvider.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/ThemeProvider.tsx @@ -1,5 +1,5 @@ import { createContext, useEffect, useState } from 'react' -import { Theme, useSettingsStore } from '@/lib/settings' +import { Theme, useSettingsStore } from '@/stores/settings' type ThemeProviderProps = { children: React.ReactNode diff --git a/lightrag/api/graph_viewer_webui/src/components/ui/AsyncSelect.tsx b/lightrag/api/graph_viewer_webui/src/components/ui/AsyncSelect.tsx index c56d0196..41747e26 100644 --- a/lightrag/api/graph_viewer_webui/src/components/ui/AsyncSelect.tsx +++ b/lightrag/api/graph_viewer_webui/src/components/ui/AsyncSelect.tsx @@ -144,7 +144,7 @@ export function AsyncSelect({ setOptions(originalOptions) } } - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [fetcher, debouncedSearchTerm, mounted, preload, filterFn]) const handleSelect = useCallback( @@ -191,7 +191,7 @@ export function AsyncSelect({ )} -