feat(knowledge): add knowledge graph visualization component
- Add @xyflow/react dependency for graph visualization - Create KnowledgeGraphView component with custom nodes and edges - Extend knowledge detail hook to fetch and display graph data - Add tabs in knowledge detail page to switch between documents and graph views
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"@mui/material": "^7.3.4",
|
||||
"@mui/x-data-grid": "^8.14.0",
|
||||
"@mui/x-date-pickers": "^8.14.0",
|
||||
"@xyflow/react": "^12.8.6",
|
||||
"ahooks": "^3.9.5",
|
||||
"axios": "^1.12.2",
|
||||
"dayjs": "^1.11.18",
|
||||
|
||||
236
pnpm-lock.yaml
generated
236
pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
||||
'@mui/x-date-pickers':
|
||||
specifier: ^8.14.0
|
||||
version: 8.14.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(dayjs@1.11.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@xyflow/react':
|
||||
specifier: ^12.8.6
|
||||
version: 12.8.6(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
ahooks:
|
||||
specifier: ^3.9.5
|
||||
version: 3.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -92,7 +95,7 @@ importers:
|
||||
version: 19.2.1(@types/react@19.2.2)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4(vite@7.1.9(@types/node@24.7.1))
|
||||
version: 5.0.4(vite@7.1.9(@types/node@24.7.1)(terser@5.44.0))
|
||||
eslint:
|
||||
specifier: ^9.36.0
|
||||
version: 9.37.0
|
||||
@@ -113,7 +116,7 @@ importers:
|
||||
version: 8.46.0(eslint@9.37.0)(typescript@5.9.3)
|
||||
vite:
|
||||
specifier: ^7.1.7
|
||||
version: 7.1.9(@types/node@24.7.1)
|
||||
version: 7.1.9(@types/node@24.7.1)(terser@5.44.0)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -478,6 +481,9 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/source-map@0.3.11':
|
||||
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
@@ -792,6 +798,24 @@ packages:
|
||||
'@types/babel__traverse@7.28.0':
|
||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||
|
||||
'@types/d3-color@3.1.3':
|
||||
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||
|
||||
'@types/d3-drag@3.0.7':
|
||||
resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
|
||||
|
||||
'@types/d3-interpolate@3.0.4':
|
||||
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||
|
||||
'@types/d3-selection@3.0.11':
|
||||
resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
|
||||
|
||||
'@types/d3-transition@3.0.9':
|
||||
resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
|
||||
|
||||
'@types/d3-zoom@3.0.8':
|
||||
resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
@@ -891,6 +915,15 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
|
||||
'@xyflow/react@12.8.6':
|
||||
resolution: {integrity: sha512-SksAm2m4ySupjChphMmzvm55djtgMDPr+eovPDdTnyGvShf73cvydfoBfWDFllooIQ4IaiUL5yfxHRwU0c37EA==}
|
||||
peerDependencies:
|
||||
react: '>=17'
|
||||
react-dom: '>=17'
|
||||
|
||||
'@xyflow/system@0.0.70':
|
||||
resolution: {integrity: sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@@ -950,6 +983,9 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -965,6 +1001,9 @@ packages:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
classcat@5.0.5:
|
||||
resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -980,6 +1019,9 @@ packages:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
@@ -1004,6 +1046,44 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
d3-color@3.1.0:
|
||||
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-dispatch@3.0.1:
|
||||
resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-drag@3.0.0:
|
||||
resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-ease@3.0.1:
|
||||
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-interpolate@3.0.1:
|
||||
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-selection@3.0.0:
|
||||
resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-timer@3.0.1:
|
||||
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
d3-transition@3.0.1:
|
||||
resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
d3-selection: 2 - 3
|
||||
|
||||
d3-zoom@3.0.0:
|
||||
resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
dayjs@1.11.18:
|
||||
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
|
||||
|
||||
@@ -1601,10 +1681,17 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
|
||||
source-map@0.5.7:
|
||||
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1620,6 +1707,11 @@ packages:
|
||||
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
terser@5.44.0:
|
||||
resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1738,6 +1830,21 @@ packages:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
zustand@4.5.7:
|
||||
resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8'
|
||||
immer: '>=9.0.6'
|
||||
react: '>=16.8'
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
immer:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
|
||||
zustand@5.0.8:
|
||||
resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
@@ -2102,6 +2209,12 @@ snapshots:
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/source-map@0.3.11':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
optional: true
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
@@ -2358,6 +2471,27 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.28.4
|
||||
|
||||
'@types/d3-color@3.1.3': {}
|
||||
|
||||
'@types/d3-drag@3.0.7':
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-interpolate@3.0.4':
|
||||
dependencies:
|
||||
'@types/d3-color': 3.1.3
|
||||
|
||||
'@types/d3-selection@3.0.11': {}
|
||||
|
||||
'@types/d3-transition@3.0.9':
|
||||
dependencies:
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/d3-zoom@3.0.8':
|
||||
dependencies:
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-selection': 3.0.11
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/js-cookie@3.0.6': {}
|
||||
@@ -2479,7 +2613,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.46.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@vitejs/plugin-react@5.0.4(vite@7.1.9(@types/node@24.7.1))':
|
||||
'@vitejs/plugin-react@5.0.4(vite@7.1.9(@types/node@24.7.1)(terser@5.44.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.4
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4)
|
||||
@@ -2487,10 +2621,33 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.38
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 7.1.9(@types/node@24.7.1)
|
||||
vite: 7.1.9(@types/node@24.7.1)(terser@5.44.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@xyflow/react@12.8.6(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@xyflow/system': 0.0.70
|
||||
classcat: 5.0.5
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
zustand: 4.5.7(@types/react@19.2.2)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- immer
|
||||
|
||||
'@xyflow/system@0.0.70':
|
||||
dependencies:
|
||||
'@types/d3-drag': 3.0.7
|
||||
'@types/d3-interpolate': 3.0.4
|
||||
'@types/d3-selection': 3.0.11
|
||||
'@types/d3-transition': 3.0.9
|
||||
'@types/d3-zoom': 3.0.8
|
||||
d3-drag: 3.0.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-selection: 3.0.0
|
||||
d3-zoom: 3.0.0
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -2566,6 +2723,9 @@ snapshots:
|
||||
node-releases: 2.0.23
|
||||
update-browserslist-db: 1.1.3(browserslist@4.26.3)
|
||||
|
||||
buffer-from@1.1.2:
|
||||
optional: true
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
@@ -2580,6 +2740,8 @@ snapshots:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
classcat@5.0.5: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
color-convert@2.0.1:
|
||||
@@ -2592,6 +2754,9 @@ snapshots:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@2.20.3:
|
||||
optional: true
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
convert-source-map@1.9.0: {}
|
||||
@@ -2616,6 +2781,42 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
d3-color@3.1.0: {}
|
||||
|
||||
d3-dispatch@3.0.1: {}
|
||||
|
||||
d3-drag@3.0.0:
|
||||
dependencies:
|
||||
d3-dispatch: 3.0.1
|
||||
d3-selection: 3.0.0
|
||||
|
||||
d3-ease@3.0.1: {}
|
||||
|
||||
d3-interpolate@3.0.1:
|
||||
dependencies:
|
||||
d3-color: 3.1.0
|
||||
|
||||
d3-selection@3.0.0: {}
|
||||
|
||||
d3-timer@3.0.1: {}
|
||||
|
||||
d3-transition@3.0.1(d3-selection@3.0.0):
|
||||
dependencies:
|
||||
d3-color: 3.1.0
|
||||
d3-dispatch: 3.0.1
|
||||
d3-ease: 3.0.1
|
||||
d3-interpolate: 3.0.1
|
||||
d3-selection: 3.0.0
|
||||
d3-timer: 3.0.1
|
||||
|
||||
d3-zoom@3.0.0:
|
||||
dependencies:
|
||||
d3-dispatch: 3.0.1
|
||||
d3-drag: 3.0.0
|
||||
d3-interpolate: 3.0.1
|
||||
d3-selection: 3.0.0
|
||||
d3-transition: 3.0.1(d3-selection@3.0.0)
|
||||
|
||||
dayjs@1.11.18: {}
|
||||
|
||||
debug@4.4.3:
|
||||
@@ -3189,8 +3390,17 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
source-map-support@0.5.21:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
source-map: 0.6.1
|
||||
optional: true
|
||||
|
||||
source-map@0.5.7: {}
|
||||
|
||||
source-map@0.6.1:
|
||||
optional: true
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
stylis@4.2.0: {}
|
||||
@@ -3201,6 +3411,14 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
terser@5.44.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.11
|
||||
acorn: 8.15.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
optional: true
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -3251,7 +3469,7 @@ snapshots:
|
||||
|
||||
uuid@13.0.0: {}
|
||||
|
||||
vite@7.1.9(@types/node@24.7.1):
|
||||
vite@7.1.9(@types/node@24.7.1)(terser@5.44.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.10
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -3262,6 +3480,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/node': 24.7.1
|
||||
fsevents: 2.3.3
|
||||
terser: 5.44.0
|
||||
|
||||
void-elements@3.1.0: {}
|
||||
|
||||
@@ -3277,6 +3496,13 @@ snapshots:
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
zustand@4.5.7(@types/react@19.2.2)(react@18.3.1):
|
||||
dependencies:
|
||||
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.2
|
||||
react: 18.3.1
|
||||
|
||||
zustand@5.0.8(@types/react@19.2.2)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import knowledgeService from '@/services/knowledge_service';
|
||||
import type { IKnowledge, IKnowledgeResult, IParserConfig } from '@/interfaces/database/knowledge';
|
||||
import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult } from '@/interfaces/database/knowledge';
|
||||
import type { IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge';
|
||||
|
||||
/**
|
||||
@@ -200,6 +200,12 @@ export const useKnowledgeDetail = (kbId: string) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [knowledgeGraph, setKnowledgeGraph] = useState<IKnowledgeGraph | null>(null);
|
||||
|
||||
const showKnowledgeGraph = useMemo(() => {
|
||||
return knowledgeGraph !== null && Object.keys(knowledgeGraph?.graph || {}).length > 0;
|
||||
}, [knowledgeGraph]);
|
||||
|
||||
const fetchKnowledgeDetail = useCallback(async () => {
|
||||
if (!kbId) return;
|
||||
|
||||
@@ -223,15 +229,41 @@ export const useKnowledgeDetail = (kbId: string) => {
|
||||
}
|
||||
}, [kbId]);
|
||||
|
||||
const fetchKnowledgeGraph = useCallback(async () => {
|
||||
if (!kbId) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await knowledgeService.getKnowledgeGraph(kbId);
|
||||
|
||||
if (response.data.code === 0) {
|
||||
setKnowledgeGraph(response.data.data);
|
||||
} else {
|
||||
throw new Error(response.data.message || '获取知识库图失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
const errorMessage = err.response?.data?.message || err.message || '获取知识库图失败';
|
||||
setError(errorMessage);
|
||||
console.error('Failed to fetch knowledge graph:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [kbId]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchKnowledgeDetail();
|
||||
}, [fetchKnowledgeDetail]);
|
||||
fetchKnowledgeGraph();
|
||||
}, [fetchKnowledgeDetail, fetchKnowledgeGraph]);
|
||||
|
||||
return {
|
||||
knowledge,
|
||||
knowledgeGraph,
|
||||
loading,
|
||||
error,
|
||||
refresh: fetchKnowledgeDetail,
|
||||
showKnowledgeGraph,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
351
src/pages/knowledge/components/KnowledgeGraphView.tsx
Normal file
351
src/pages/knowledge/components/KnowledgeGraphView.tsx
Normal file
@@ -0,0 +1,351 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
type Node,
|
||||
type Edge,
|
||||
Controls,
|
||||
Background,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
ConnectionMode,
|
||||
Panel,
|
||||
Handle,
|
||||
Position,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { Box, Typography, Alert, Chip, Card, CardContent, Tooltip, Avatar } from '@mui/material';
|
||||
import type { IKnowledgeGraph } from '@/interfaces/database/knowledge';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
interface KnowledgeGraphProps {
|
||||
knowledgeGraph?: IKnowledgeGraph | null;
|
||||
}
|
||||
|
||||
// 根据实体类型获取节点颜色
|
||||
const getNodeColor = (entityType?: string): string => {
|
||||
const colorMap: Record<string, string> = {
|
||||
PERSON: '#FF6B6B', // 红色 - 人物
|
||||
ORGANIZATION: '#4ECDC4', // 青色 - 组织
|
||||
LOCATION: '#45B7D1', // 蓝色 - 地点
|
||||
EVENT: '#96CEB4', // 绿色 - 事件
|
||||
CONCEPT: '#FFEAA7', // 黄色 - 概念
|
||||
CATEGORY: '#DDA0DD', // 紫色 - 分类
|
||||
TECHNOLOGY: '#98D8C8', // 薄荷绿 - 技术
|
||||
PRODUCT: '#F7DC6F', // 金黄色 - 产品
|
||||
SERVICE: '#BB8FCE', // 淡紫色 - 服务
|
||||
};
|
||||
return colorMap[entityType || 'CONCEPT'] || '#74B9FF';
|
||||
};
|
||||
|
||||
// 自定义节点组件
|
||||
const CustomNode = ({ data }: { data: any }) => {
|
||||
const nodeColor = getNodeColor(data.entity_type);
|
||||
const nodeSize = Math.max(80, Math.min(140, (data.pagerank || 0.1) * 500));
|
||||
|
||||
// 获取节点首字母作为Avatar显示
|
||||
const getInitials = (name: string) => {
|
||||
if (!name) return '?';
|
||||
const words = name.trim().split(/\s+/);
|
||||
if (words.length === 1) {
|
||||
return words[0].charAt(0).toUpperCase();
|
||||
}
|
||||
return words.slice(0, 2).map(word => word.charAt(0).toUpperCase()).join('');
|
||||
};
|
||||
|
||||
const tooltipContent = (
|
||||
<Box sx={{ p: 1, maxWidth: 300 }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 1 }}>
|
||||
{data.label || data.name || 'Unknown'}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 0.5 }}>
|
||||
<strong>类型:</strong> {data.entity_type || 'Unknown'}
|
||||
</Typography>
|
||||
{data.description && (
|
||||
<Typography variant="body2">
|
||||
<strong>描述:</strong> {data.description}
|
||||
</Typography>
|
||||
)}
|
||||
{data.pagerank && (
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 1 }}>
|
||||
PageRank: {data.pagerank.toFixed(4)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
title={tooltipContent}
|
||||
placement="top"
|
||||
arrow
|
||||
enterDelay={500}
|
||||
leaveDelay={200}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
width: nodeSize,
|
||||
height: nodeSize,
|
||||
borderRadius: '50%',
|
||||
border: '3px solid #fff',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: nodeColor,
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.1)',
|
||||
boxShadow: '0 6px 20px rgba(0,0,0,0.25)',
|
||||
zIndex: 10,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* React Flow Handle 组件 */}
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
style={{ background: '#555', opacity: 0 }}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
style={{ background: '#555', opacity: 0 }}
|
||||
/>
|
||||
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 1,
|
||||
'&:last-child': { pb: 1 },
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
fontSize: Math.max(10, nodeSize * 0.08),
|
||||
textAlign: 'center',
|
||||
lineHeight: 1.1,
|
||||
wordBreak: 'break-word',
|
||||
textShadow: '1px 1px 2px rgba(0,0,0,0.7)',
|
||||
maxWidth: '90%',
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
>
|
||||
{data.label || data.name || 'Unknown'}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const nodeTypes = {
|
||||
custom: CustomNode,
|
||||
};
|
||||
|
||||
const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) => {
|
||||
// 转换数据格式为 React Flow 所需的格式
|
||||
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
|
||||
const graphData = knowledgeGraph?.graph || {};
|
||||
if (!graphData?.nodes || !graphData?.edges) {
|
||||
return { nodes: [], edges: [] };
|
||||
}
|
||||
|
||||
// 转换节点数据
|
||||
// @ts-ignore
|
||||
const nodes: Node[] = graphData.nodes.map((node, index) => {
|
||||
console.log(`节点 ${index}:`, node);
|
||||
console.log(`节点ID: ${node.id}`);
|
||||
|
||||
const encodeId = encodeURIComponent(String(node.id));
|
||||
|
||||
const n: Node = {
|
||||
id: encodeId,
|
||||
type: 'custom',
|
||||
position: {
|
||||
x: Math.random() * 800,
|
||||
y: Math.random() * 600,
|
||||
},
|
||||
data: {
|
||||
label: node.entity_name || node.id,
|
||||
entity_type: node.entity_type,
|
||||
pagerank: node.pagerank,
|
||||
description: node.description,
|
||||
...node,
|
||||
},
|
||||
};
|
||||
return n
|
||||
});
|
||||
|
||||
// 转换边数据
|
||||
// @ts-ignore
|
||||
const edges: Edge[] = graphData.edges.map((edge, index) => {
|
||||
console.log(`边 ${index}:`, edge);
|
||||
console.log(`src_id: ${edge.src_id}, tgt_id: ${edge.tgt_id}`);
|
||||
// 检查source和target是否存在
|
||||
if (!edge.src_id || !edge.tgt_id) {
|
||||
console.warn(`边 ${index} 缺少src_id或tgt_id:`, edge);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查对应的节点是否存在
|
||||
const sourceExists = nodes.some(node => node.id === encodeURIComponent(edge.src_id));
|
||||
const targetExists = nodes.some(node => node.id === encodeURIComponent(edge.tgt_id));
|
||||
|
||||
if (!sourceExists || !targetExists) {
|
||||
console.warn(`边 ${index} 的节点不存在: source=${sourceExists}, target=${targetExists}`, edge);
|
||||
return null;
|
||||
}
|
||||
|
||||
const weight = Number(edge.weight) || 1;
|
||||
const strokeWidth = Math.max(1, Math.min(3, weight));
|
||||
|
||||
const sourceId = encodeURIComponent(String(edge.src_id));
|
||||
const targetId = encodeURIComponent(String(edge.tgt_id));
|
||||
|
||||
const edgeObj: Edge = {
|
||||
id: `${sourceId}-${targetId}`,
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: 'simplebezier',
|
||||
// animated: weight > 5,
|
||||
style: {
|
||||
strokeWidth,
|
||||
stroke: '#99ADD1',
|
||||
},
|
||||
// label: edge.description ? edge.description.substring(0, 50) + '...' : '',
|
||||
labelStyle: {
|
||||
fontSize: '10px',
|
||||
fill: '#666',
|
||||
},
|
||||
data: {
|
||||
weight: edge.weight,
|
||||
description: edge.description,
|
||||
keywords: edge.keywords,
|
||||
},
|
||||
};
|
||||
return edgeObj;
|
||||
})
|
||||
// @ts-ignore
|
||||
.filter(edge => edge != null); // 过滤掉null值
|
||||
return { nodes, edges };
|
||||
}, [knowledgeGraph]);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
|
||||
console.log('转换后的节点:', nodes);
|
||||
console.log('转换后的边:', edges);
|
||||
// 节点点击事件
|
||||
const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
|
||||
console.log('节点点击:', node.data);
|
||||
}, []);
|
||||
|
||||
// 边点击事件
|
||||
const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
|
||||
console.log('边点击:', edge.data);
|
||||
}, []);
|
||||
|
||||
if (!knowledgeGraph || initialNodes.length === 0) {
|
||||
return (
|
||||
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }}>
|
||||
<Alert severity="info">暂无知识图谱数据</Alert>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%', height: '600px', border: '1px solid #e0e0e0', borderRadius: 1 }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onNodeClick={onNodeClick}
|
||||
onEdgeClick={onEdgeClick}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionMode={ConnectionMode.Loose}
|
||||
fitView
|
||||
fitViewOptions={{ padding: 0.2 }}
|
||||
minZoom={0.1}
|
||||
maxZoom={2}
|
||||
>
|
||||
<Background />
|
||||
<Controls />
|
||||
|
||||
{/* 图例面板 */}
|
||||
<Panel position="top-right">
|
||||
<Box sx={{
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
minWidth: 200,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
||||
图例
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{['PERSON', 'ORGANIZATION', 'CATEGORY', 'TECHNOLOGY'].map((type) => (
|
||||
<Box key={type} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: getNodeColor(type),
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<Typography variant="caption">{type}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Panel>
|
||||
|
||||
{/* 统计信息面板 */}
|
||||
<Panel position="top-left">
|
||||
<Box sx={{
|
||||
backgroundColor: 'rgba(255,255,255,0.9)',
|
||||
p: 2,
|
||||
borderRadius: 1,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||
}}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
||||
图谱统计
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
label={`节点: ${nodes.length}`}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
/>
|
||||
<Chip
|
||||
label={`边: ${edges.length}`}
|
||||
size="small"
|
||||
color="secondary"
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Panel>
|
||||
</ReactFlow>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default KnowledgeGraphView;
|
||||
@@ -18,9 +18,10 @@ import {
|
||||
CardContent,
|
||||
Divider,
|
||||
Chip,
|
||||
Tabs,
|
||||
Tab,
|
||||
} from '@mui/material';
|
||||
import { type GridRowSelectionModel } from '@mui/x-data-grid';
|
||||
import knowledgeService from '@/services/knowledge_service';
|
||||
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||
import type { IDocumentInfoFilter } from '@/interfaces/database/document';
|
||||
import FileUploadDialog from '@/components/FileUploadDialog';
|
||||
@@ -28,16 +29,16 @@ import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
||||
import DocumentListComponent from './components/DocumentListComponent';
|
||||
import FloatingActionButtons from './components/FloatingActionButtons';
|
||||
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||
import KnowledgeGraphView from './components/KnowledgeGraphView';
|
||||
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
||||
import { RUNNING_STATUS_KEYS } from '@/constants/knowledge';
|
||||
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
||||
|
||||
function KnowledgeBaseDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 状态管理
|
||||
const [knowledgeBase, setKnowledgeBase] = useState<IKnowledge | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>({
|
||||
@@ -46,11 +47,13 @@ function KnowledgeBaseDetail() {
|
||||
});
|
||||
const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [testingDialogOpen, setTestingDialogOpen] = useState(false);
|
||||
const [configDialogOpen, setConfigDialogOpen] = useState(false);
|
||||
|
||||
const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false);
|
||||
const [selectedFileDetails, setSelectedFileDetails] = useState<IKnowledgeFile | null>(null);
|
||||
|
||||
// 标签页状态
|
||||
const [currentTab, setCurrentTab] = useState(0);
|
||||
|
||||
// 轮询相关状态
|
||||
const pollingIntervalRef = useRef<any>(null);
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
@@ -80,25 +83,9 @@ function KnowledgeBaseDetail() {
|
||||
error: operationError,
|
||||
} = useDocumentOperations();
|
||||
|
||||
// 获取知识库详情
|
||||
const fetchKnowledgeDetail = async () => {
|
||||
if (!id) return;
|
||||
const { knowledge: knowledgeBase, refresh: fetchKnowledgeDetail, loading, showKnowledgeGraph, knowledgeGraph } = useKnowledgeDetail(id || '');
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await knowledgeService.getKnowledgeDetail({ kb_id: id });
|
||||
|
||||
if (response.data.code === 0) {
|
||||
setKnowledgeBase(response.data.data);
|
||||
} else {
|
||||
setError(response.data.message || '获取知识库详情失败');
|
||||
}
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.message || err.message || '获取知识库详情失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
console.log('showKnowledgeGraph:', showKnowledgeGraph, knowledgeGraph);
|
||||
|
||||
// 删除文件
|
||||
const handleDeleteFiles = async () => {
|
||||
@@ -291,42 +278,104 @@ function KnowledgeBaseDetail() {
|
||||
{/* 知识库信息卡片 */}
|
||||
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
||||
|
||||
{/* 文件列表组件 */}
|
||||
<DocumentListComponent
|
||||
files={files}
|
||||
loading={filesLoading}
|
||||
searchKeyword={searchKeyword}
|
||||
onSearchChange={setSearchKeyword}
|
||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||
onDelete={(fileIds) => {
|
||||
console.log('删除文件:', fileIds);
|
||||
setRowSelectionModel({
|
||||
type: 'include',
|
||||
ids: new Set(fileIds)
|
||||
});
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
onUpload={() => setUploadDialogOpen(true)}
|
||||
onRefresh={() => {
|
||||
refreshFiles();
|
||||
fetchKnowledgeDetail();
|
||||
}}
|
||||
rowSelectionModel={rowSelectionModel}
|
||||
onRowSelectionModelChange={(newModel) => {
|
||||
console.log('新的选择模型:', newModel);
|
||||
setRowSelectionModel(newModel);
|
||||
}}
|
||||
total={total}
|
||||
page={currentPage}
|
||||
pageSize={pageSize}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
{/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */}
|
||||
{showKnowledgeGraph ? (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={(event, newValue) => setCurrentTab(newValue)}
|
||||
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
||||
>
|
||||
<Tab label="Documents" />
|
||||
<Tab label="Graph" />
|
||||
</Tabs>
|
||||
|
||||
{/* Document List 标签页内容 */}
|
||||
{currentTab === 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<DocumentListComponent
|
||||
files={files}
|
||||
loading={filesLoading}
|
||||
searchKeyword={searchKeyword}
|
||||
onSearchChange={setSearchKeyword}
|
||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||
onDelete={(fileIds) => {
|
||||
console.log('删除文件:', fileIds);
|
||||
setRowSelectionModel({
|
||||
type: 'include',
|
||||
ids: new Set(fileIds)
|
||||
});
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
onUpload={() => setUploadDialogOpen(true)}
|
||||
onRefresh={() => {
|
||||
refreshFiles();
|
||||
fetchKnowledgeDetail();
|
||||
}}
|
||||
rowSelectionModel={rowSelectionModel}
|
||||
onRowSelectionModelChange={(newModel) => {
|
||||
console.log('新的选择模型:', newModel);
|
||||
setRowSelectionModel(newModel);
|
||||
}}
|
||||
total={total}
|
||||
page={currentPage}
|
||||
pageSize={pageSize}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Graph 标签页内容 */}
|
||||
{currentTab === 1 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<KnowledgeGraphView knowledgeGraph={knowledgeGraph} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
/* 原有的文件列表组件 - 当showKnowledgeGraph为false时显示 */
|
||||
<DocumentListComponent
|
||||
files={files}
|
||||
loading={filesLoading}
|
||||
searchKeyword={searchKeyword}
|
||||
onSearchChange={setSearchKeyword}
|
||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||
onDelete={(fileIds) => {
|
||||
console.log('删除文件:', fileIds);
|
||||
setRowSelectionModel({
|
||||
type: 'include',
|
||||
ids: new Set(fileIds)
|
||||
});
|
||||
setDeleteDialogOpen(true);
|
||||
}}
|
||||
onUpload={() => setUploadDialogOpen(true)}
|
||||
onRefresh={() => {
|
||||
refreshFiles();
|
||||
fetchKnowledgeDetail();
|
||||
}}
|
||||
rowSelectionModel={rowSelectionModel}
|
||||
onRowSelectionModelChange={(newModel) => {
|
||||
console.log('新的选择模型:', newModel);
|
||||
setRowSelectionModel(newModel);
|
||||
}}
|
||||
total={total}
|
||||
page={currentPage}
|
||||
pageSize={pageSize}
|
||||
onPageChange={setCurrentPage}
|
||||
onPageSizeChange={setPageSize}
|
||||
onRename={handleRename}
|
||||
onChangeStatus={handleChangeStatus}
|
||||
onCancelRun={handleCancelRun}
|
||||
onViewDetails={handleViewDetails}
|
||||
onViewProcessDetails={handleViewProcessDetails}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 浮动操作按钮 */}
|
||||
<FloatingActionButtons
|
||||
@@ -359,88 +408,6 @@ function KnowledgeBaseDetail() {
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* 检索测试对话框 */}
|
||||
<Dialog open={testingDialogOpen} onClose={() => setTestingDialogOpen(false)} maxWidth="md" fullWidth>
|
||||
<DialogTitle>检索测试</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={3} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="测试查询"
|
||||
placeholder="输入要测试的查询内容..."
|
||||
multiline
|
||||
rows={3}
|
||||
/>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<TextField
|
||||
label="返回结果数量"
|
||||
type="number"
|
||||
defaultValue={5}
|
||||
sx={{ width: 150 }}
|
||||
/>
|
||||
<TextField
|
||||
label="相似度阈值"
|
||||
type="number"
|
||||
defaultValue={0.7}
|
||||
inputProps={{ min: 0, max: 1, step: 0.1 }}
|
||||
sx={{ width: 150 }}
|
||||
/>
|
||||
</Stack>
|
||||
<Box sx={{ minHeight: 200, border: '1px solid #e0e0e0', borderRadius: 1, p: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
测试结果将在这里显示...
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setTestingDialogOpen(false)}>关闭</Button>
|
||||
<Button variant="contained">开始测试</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* 配置设置对话框 */}
|
||||
<Dialog open={configDialogOpen} onClose={() => setConfigDialogOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>配置设置</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={3} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="知识库名称"
|
||||
defaultValue={knowledgeBase?.name}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="描述"
|
||||
multiline
|
||||
rows={3}
|
||||
defaultValue={knowledgeBase?.description}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="语言"
|
||||
defaultValue={knowledgeBase?.language}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="嵌入模型"
|
||||
defaultValue={knowledgeBase?.embd_id}
|
||||
disabled
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="解析器"
|
||||
defaultValue={knowledgeBase?.parser_id}
|
||||
disabled
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setConfigDialogOpen(false)}>取消</Button>
|
||||
<Button variant="contained">保存</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* 文档详情对话框 */}
|
||||
<Dialog
|
||||
open={processDetailsDialogOpen}
|
||||
|
||||
Reference in New Issue
Block a user