feat: add overview (#391)

### What problem does this PR solve?

feat: render stats charts
feat: create api token
feat: delete api token
feat: add ChatApiKeyModal
feat: add RagLineChart


Issue link: #345

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2024-04-16 19:06:47 +08:00 committed by GitHub
parent b3843138f4
commit ad6f0a1ce5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1177 additions and 40 deletions

345
web/package-lock.json generated
View File

@ -13,19 +13,21 @@
"antd": "^5.12.7",
"axios": "^1.6.3",
"classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0",
"umi": "^4.0.90",
"umi-request": "^1.4.0",
@ -36,6 +38,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8",
@ -2676,6 +2679,60 @@
"@babel/types": "^7.20.7"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.8.tgz",
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.6",
"resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.6.tgz",
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.3.tgz",
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@ -2884,6 +2941,15 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-copy-to-clipboard": {
"version": "5.0.7",
"resolved": "https://registry.npmmirror.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz",
"integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-dom": {
"version": "18.2.18",
"resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.18.tgz",
@ -5832,6 +5898,14 @@
"node": ">=12"
}
},
"node_modules/clsx": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.0.tgz",
"integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
"engines": {
"node": ">=6"
}
},
"node_modules/coa": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz",
@ -6640,11 +6714,132 @@
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-1.2.4.tgz",
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
},
"node_modules/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-format": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
"dependencies": {
"d3-color": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-path": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-polygon": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz",
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
},
"node_modules/d3-scale": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
"dependencies": {
"d3-array": "2.10.0 - 3",
"d3-format": "1 - 3",
"d3-interpolate": "1.2.0 - 3",
"d3-time": "2.1.1 - 3",
"d3-time-format": "2 - 4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-scale/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
"dependencies": {
"d3-path": "^3.1.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
"dependencies": {
"d3-array": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time-format": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-time/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -6705,6 +6900,11 @@
"node": ">=0.10.0"
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/decode-named-character-reference": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@ -7032,6 +7232,15 @@
"utila": "~0.4"
}
},
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -8151,6 +8360,11 @@
"es5-ext": "~0.10.14"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
@ -8356,6 +8570,14 @@
"resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-equals": {
"version": "5.0.1",
"resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.0.1.tgz",
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz",
@ -9693,6 +9915,14 @@
"node": ">= 0.4"
}
},
"node_modules/internmap": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
"engines": {
"node": ">=12"
}
},
"node_modules/intersection-observer": {
"version": "0.12.2",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
@ -11925,6 +12155,7 @@
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"devOptional": true,
"engines": {
"node": "*"
}
@ -14356,6 +14587,18 @@
"react-dom": "18.2.0"
}
},
"node_modules/react-copy-to-clipboard": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
"integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": "^15.3.0 || 16 || 17 || 18"
}
},
"node_modules/react-dev-inspector": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz",
@ -14934,6 +15177,20 @@
"react": ">=15"
}
},
"node_modules/react-smooth": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.1.tgz",
"integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
"dependencies": {
"fast-equals": "^5.0.1",
"prop-types": "^15.8.1",
"react-transition-group": "^4.4.5"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-spinkit": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz",
@ -14968,6 +15225,21 @@
"react": ">= 0.14.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz",
@ -15145,6 +15417,41 @@
"node": ">= 12.13.0"
}
},
"node_modules/recharts": {
"version": "2.12.4",
"resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.12.4.tgz",
"integrity": "sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA==",
"dependencies": {
"clsx": "^2.0.0",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.21",
"react-is": "^16.10.2",
"react-smooth": "^4.0.0",
"recharts-scale": "^0.4.4",
"tiny-invariant": "^1.3.1",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/recharts/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/recursive-readdir": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
@ -17000,9 +17307,7 @@
"node_modules/tiny-invariant": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
"dev": true,
"peer": true
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
@ -18221,6 +18526,38 @@
"unist-util-stringify-position": "^4.0.0"
}
},
"node_modules/victory-vendor": {
"version": "36.9.2",
"resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
"integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/victory-vendor/node_modules/d3-array": {
"version": "3.2.4",
"resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
"dependencies": {
"internmap": "1 - 2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/vite": {
"version": "4.3.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz",

View File

@ -17,19 +17,21 @@
"antd": "^5.12.7",
"axios": "^1.6.3",
"classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0",
"umi": "^4.0.90",
"umi-request": "^1.4.0",
@ -40,6 +42,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202",
"@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8",

View File

@ -6,6 +6,21 @@ import zh_HK from 'antd/locale/zh_HK';
import React, { ReactNode, useEffect, useState } from 'react';
import storage from './utils/authorizationUtil';
import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import localeData from 'dayjs/plugin/localeData';
import weekday from 'dayjs/plugin/weekday';
import weekOfYear from 'dayjs/plugin/weekOfYear';
import weekYear from 'dayjs/plugin/weekYear';
dayjs.extend(customParseFormat);
dayjs.extend(advancedFormat);
dayjs.extend(weekday);
dayjs.extend(localeData);
dayjs.extend(weekOfYear);
dayjs.extend(weekYear);
const AntLanguageMap = {
en: enUS,
zh: zhCN,

View File

@ -0,0 +1,27 @@
import { useTranslate } from '@/hooks/commonHooks';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { useState } from 'react';
import { CopyToClipboard as Clipboard, Props } from 'react-copy-to-clipboard';
const CopyToClipboard = ({ text }: Props) => {
const [copied, setCopied] = useState(false);
const { t } = useTranslate('common');
const handleCopy = () => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
};
return (
<Tooltip title={copied ? t('copied') : t('copy')}>
<Clipboard text={text} onCopy={handleCopy}>
{copied ? <CheckOutlined /> : <CopyOutlined />}
</Clipboard>
</Tooltip>
);
};
export default CopyToClipboard;

View File

@ -0,0 +1,88 @@
import {
CartesianGrid,
Legend,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { CategoricalChartProps } from 'recharts/types/chart/generateCategoricalChart';
const data = [
{
name: 'Page A',
uv: 4000,
pv: 2400,
},
{
name: 'Page B',
uv: 3000,
pv: 1398,
},
{
name: 'Page C',
uv: 2000,
pv: 9800,
},
{
name: 'Page D',
uv: 2780,
pv: 3908,
},
{
name: 'Page E',
uv: 1890,
pv: 4800,
},
{
name: 'Page F',
uv: 2390,
pv: 3800,
},
{
name: 'Page G',
uv: 3490,
pv: 4300,
},
];
interface IProps extends CategoricalChartProps {
data?: Array<{ xAxis: string; yAxis: number }>;
}
const RagLineChart = ({ data }: IProps) => {
return (
<ResponsiveContainer width="100%" height="100%">
<LineChart
// width={500}
// height={300}
data={data}
margin={
{
// top: 5,
// right: 30,
// left: 20,
// bottom: 10,
}
}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="xAxis" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="yAxis"
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
{/* <Line type="monotone" dataKey="uv" stroke="#82ca9d" /> */}
</LineChart>
</ResponsiveContainer>
);
};
export default RagLineChart;

View File

@ -1,4 +1,9 @@
import { IConversation, IDialog } from '@/interfaces/database/chat';
import {
IConversation,
IDialog,
IStats,
IToken,
} from '@/interfaces/database/chat';
import { useCallback } from 'react';
import { useDispatch, useSelector } from 'umi';
@ -164,3 +169,82 @@ export const useCompleteConversation = () => {
return completeConversation;
};
// #region API provided for external calls
export const useCreateToken = (dialogId: string) => {
const dispatch = useDispatch();
const createToken = useCallback(() => {
return dispatch<any>({
type: 'chatModel/createToken',
payload: { dialogId },
});
}, [dispatch, dialogId]);
return createToken;
};
export const useListToken = () => {
const dispatch = useDispatch();
const listToken = useCallback(
(dialogId: string) => {
return dispatch<any>({
type: 'chatModel/listToken',
payload: { dialogId },
});
},
[dispatch],
);
return listToken;
};
export const useSelectTokenList = () => {
const tokenList: IToken[] = useSelector(
(state: any) => state.chatModel.tokenList,
);
return tokenList;
};
export const useRemoveToken = () => {
const dispatch = useDispatch();
const removeToken = useCallback(
(payload: { tenantId: string; dialogId: string; tokens: string[] }) => {
return dispatch<any>({
type: 'chatModel/removeToken',
payload: payload,
});
},
[dispatch],
);
return removeToken;
};
export const useFetchStats = () => {
const dispatch = useDispatch();
const fetchStats = useCallback(
(payload: any) => {
return dispatch<any>({
type: 'chatModel/getStats',
payload,
});
},
[dispatch],
);
return fetchStats;
};
export const useSelectStats = () => {
const stats: IStats = useSelector((state: any) => state.chatModel.stats);
return stats;
};
//#endregion

View File

@ -91,3 +91,21 @@ export interface Docagg {
// term_similarity: number;
// vector_similarity: number;
// }
export interface IToken {
create_date: string;
create_time: number;
tenant_id: string;
token: string;
update_date?: any;
update_time?: any;
}
export interface IStats {
pv: [string, number][];
uv: [string, number][];
speed: [string, number][];
tokens: [string, number][];
round: [string, number][];
thumb_up: [string, number][];
}

View File

@ -20,6 +20,8 @@ export default {
language: 'Language',
languageMessage: 'Please input your language!',
languagePlaceholder: 'select your language',
copy: 'Copy',
copied: 'Copied',
},
login: {
login: 'Sign in',
@ -335,6 +337,24 @@ export default {
'This sets the maximum length of the models output, measured in the number of tokens (words or pieces of words).',
quote: 'Show Quote',
quoteTip: 'Should the source of the original text be displayed?',
overview: 'Overview',
pv: 'Number of messages',
uv: 'Active user number',
speed: 'Token output speed',
tokens: 'Consume the token number',
round: 'Session Interaction Number',
thumbUp: 'customer satisfaction',
publicUrl: 'Public URL',
preview: 'Preview',
embedded: 'Embedded',
serviceApiEndpoint: 'Service API Endpoint',
apiKey: 'Api Key',
apiReference: 'Api Reference',
dateRange: 'Date Range:',
backendServiceApi: 'Backend service API',
createNewKey: 'Create new key',
created: 'Created',
action: 'Action',
},
setting: {
profile: 'Profile',

View File

@ -15,11 +15,13 @@ export default {
edit: '編輯',
upload: '上傳',
english: '英語',
chinese: '中文簡體',
traditionalChinese: '中文繁體',
chinese: '簡體中文',
traditionalChinese: '繁體中文',
language: '語言',
languageMessage: '請輸入語言',
languagePlaceholder: '請選擇語言',
copy: '複製',
copied: '複製成功',
},
login: {
login: '登入',
@ -269,7 +271,7 @@ export default {
systemMessage: '請輸入',
systemTip:
'當LLM回答問題時你需要LLM遵循的說明比如角色設計、答案長度和答案語言等。',
topN: 'top n',
topN: 'Top N',
topNTip: `並非所有相似度得分高於“相似度閾值”的塊都會被提供給法學碩士。LLM 只能看到這些“Top N”塊。`,
variable: '變量',
variableTip: `如果您使用对话 API变量可能会帮助您使用不同的策略与客户聊天。
@ -310,6 +312,24 @@ export default {
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
quote: '顯示引文',
quoteTip: '是否應該顯示原文出處?',
overview: '概覽',
pv: '消息數',
uv: '活躍用戶數',
speed: 'Token 輸出速度',
tokens: '消耗Token數',
round: '會話互動數',
thumbUp: '用戶滿意度',
publicUrl: '公共url',
preview: '預覽',
embedded: '嵌入',
serviceApiEndpoint: '服務API端點',
apiKey: 'API鍵',
apiReference: 'API參考',
dateRange: '日期範圍:',
backendServiceApi: '後端服務API',
createNewKey: '創建新密鑰',
created: '創建於',
action: '操作',
},
setting: {
profile: '概述',

View File

@ -15,11 +15,13 @@ export default {
edit: '编辑',
upload: '上传',
english: '英文',
chinese: '中文简体',
traditionalChinese: '中文繁体',
chinese: '简体中文',
traditionalChinese: '繁体中文',
language: '语言',
languageMessage: '请输入语言',
languagePlaceholder: '请选择语言',
copy: '复制',
copied: '复制成功',
},
login: {
login: '登录',
@ -326,6 +328,24 @@ export default {
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
quote: '显示引文',
quoteTip: '是否应该显示原文出处?',
overview: '概览',
pv: '消息数',
uv: '活跃用户数',
speed: 'Token 输出速度',
tokens: '消耗Token数',
round: '会话互动数',
thumbUp: '用户满意度',
publicUrl: '公共Url',
preview: '预览',
embedded: '嵌入',
serviceApiEndpoint: '服务API端点',
apiKey: 'API键',
apiReference: 'API参考',
dateRange: '日期范围:',
backendServiceApi: '后端服务API',
createNewKey: '创建新密钥',
created: '创建于',
action: '操作',
},
setting: {
profile: '概要',

View File

@ -26,6 +26,7 @@ import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import { formatDate } from '@/utils/date';
import styles from './index.less';
const KnowledgeFile = () => {
@ -94,6 +95,9 @@ const KnowledgeFile = () => {
title: t('uploadDate'),
dataIndex: 'create_date',
key: 'create_date',
render(value) {
return formatDate(value);
},
},
{
title: t('chunkMethod'),

View File

@ -0,0 +1,70 @@
import CopyToClipboard from '@/components/copy-to-clipboard';
import { useTranslate } from '@/hooks/commonHooks';
import { IModalProps } from '@/interfaces/common';
import { IToken } from '@/interfaces/database/chat';
import { formatDate } from '@/utils/date';
import { DeleteOutlined } from '@ant-design/icons';
import type { TableProps } from 'antd';
import { Button, Modal, Space, Table } from 'antd';
import { useOperateApiKey } from '../hooks';
const ChatApiKeyModal = ({
visible,
dialogId,
hideModal,
}: IModalProps<any> & { dialogId: string }) => {
const { createToken, removeToken, tokenList, listLoading, creatingLoading } =
useOperateApiKey(visible, dialogId);
const { t } = useTranslate('chat');
const columns: TableProps<IToken>['columns'] = [
{
title: 'Token',
dataIndex: 'token',
key: 'token',
render: (text) => <a>{text}</a>,
},
{
title: t('created'),
dataIndex: 'create_date',
key: 'create_date',
render: (text) => formatDate(text),
},
{
title: t('action'),
key: 'action',
render: (_, record) => (
<Space size="middle">
<CopyToClipboard text={record.token}></CopyToClipboard>
<DeleteOutlined
onClick={() => removeToken(record.token, record.tenant_id)}
/>
</Space>
),
},
];
return (
<>
<Modal
title={t('apiKey')}
open={visible}
onCancel={hideModal}
style={{ top: 300 }}
width={'50vw'}
>
<Table
columns={columns}
dataSource={tokenList}
rowKey={'token'}
loading={listLoading}
/>
<Button onClick={createToken} loading={creatingLoading}>
{t('createNewKey')}
</Button>
</Modal>
</>
);
};
export default ChatApiKeyModal;

View File

@ -1,6 +1,6 @@
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import { PlusOutlined } from '@ant-design/icons';
import { Form, Input, Select, Upload } from 'antd';
import { Form, Input, Select, Switch, Upload } from 'antd';
import classNames from 'classnames';
import { ISegmentedContentProps } from '../interface';
@ -83,6 +83,15 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
>
<Input.TextArea autoSize={{ minRows: 5 }} />
</Form.Item>
<Form.Item
label={t('quote')}
valuePropName="checked"
name={['prompt_config', 'quote']}
tooltip={t('quoteTip')}
initialValue={true}
>
<Switch />
</Form.Item>
<Form.Item
label={t('knowledgeBases')}
name="kb_ids"

View File

@ -172,15 +172,7 @@ const PromptEngine = (
>
<Slider max={30} />
</Form.Item>
<Form.Item
label={t('quote')}
valuePropName="checked"
name={['prompt_config', 'quote']}
tooltip={t('quoteTip')}
initialValue={true}
>
<Switch />
</Form.Item>
<section className={classNames(styles.variableContainer)}>
<Row align={'middle'} justify="end">
<Col span={7} className={styles.variableAlign}>

View File

@ -0,0 +1,21 @@
.chartWrapper {
height: 40vh;
overflow: auto;
}
.chartItem {
height: 300px;
padding: 10px 0 30px;
}
.chartLabel {
display: inline-block;
padding-left: 60px;
padding-bottom: 20px;
}
.linkText {
border-radius: 6px;
padding: 6px 10px;
background-color: #eff8ff;
border: 1px;
}

View File

@ -0,0 +1,97 @@
import LineChart from '@/components/line-chart';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { IModalProps } from '@/interfaces/common';
import { IDialog, IStats } from '@/interfaces/database/chat';
import { Button, Card, DatePicker, Flex, Modal, Space, Typography } from 'antd';
import { RangePickerProps } from 'antd/es/date-picker';
import dayjs from 'dayjs';
import camelCase from 'lodash/camelCase';
import ChatApiKeyModal from '../chat-api-key-modal';
import { useFetchStatsOnMount, useSelectChartStatsList } from '../hooks';
import styles from './index.less';
const { Paragraph } = Typography;
const { RangePicker } = DatePicker;
const ChatOverviewModal = ({
visible,
hideModal,
dialog,
}: IModalProps<any> & { dialog: IDialog }) => {
const { t } = useTranslate('chat');
const chartList = useSelectChartStatsList();
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const { pickerValue, setPickerValue } = useFetchStatsOnMount(visible);
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
return current && current > dayjs().endOf('day');
};
return (
<>
<Modal
title={t('overview')}
open={visible}
onCancel={hideModal}
width={'100vw'}
>
<Flex vertical gap={'middle'}>
<Card title={dialog.name}>
<Flex gap={8} vertical>
{t('publicUrl')}
<Paragraph copyable className={styles.linkText}>
This is a copyable text.
</Paragraph>
</Flex>
<Space size={'middle'}>
<Button>{t('preview')}</Button>
<Button>{t('embedded')}</Button>
</Space>
</Card>
<Card title={t('backendServiceApi')}>
<Flex gap={8} vertical>
{t('serviceApiEndpoint')}
<Paragraph copyable className={styles.linkText}>
This is a copyable text.
</Paragraph>
</Flex>
<Space size={'middle'}>
<Button onClick={showApiKeyModal}>{t('apiKey')}</Button>
<Button>{t('apiReference')}</Button>
</Space>
</Card>
<Space>
<b>{t('dateRange')}</b>
<RangePicker
disabledDate={disabledDate}
value={pickerValue}
onChange={setPickerValue}
allowClear={false}
/>
</Space>
<div className={styles.chartWrapper}>
{Object.keys(chartList).map((x) => (
<div key={x} className={styles.chartItem}>
<b className={styles.chartLabel}>{t(camelCase(x))}</b>
<LineChart data={chartList[x as keyof IStats]}></LineChart>
</div>
))}
</div>
</Flex>
<ChatApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
dialogId={dialog.id}
></ChatApiKeyModal>
</Modal>
</>
);
};
export default ChatOverviewModal;

View File

@ -2,22 +2,28 @@ import { MessageType } from '@/constants/chat';
import { fileIconMap } from '@/constants/common';
import {
useCompleteConversation,
useCreateToken,
useFetchConversation,
useFetchConversationList,
useFetchDialog,
useFetchDialogList,
useFetchStats,
useListToken,
useRemoveConversation,
useRemoveDialog,
useRemoveToken,
useSelectConversationList,
useSelectDialogList,
useSelectTokenList,
useSetDialog,
useUpdateConversation,
} from '@/hooks/chatHooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IConversation, IDialog } from '@/interfaces/database/chat';
import { IConversation, IDialog, IStats } from '@/interfaces/database/chat';
import { IChunk } from '@/interfaces/database/knowledge';
import { getFileExtension } from '@/utils';
import dayjs, { Dayjs } from 'dayjs';
import omit from 'lodash/omit';
import {
ChangeEventHandler,
@ -704,3 +710,108 @@ export const useGetSendButtonDisabled = () => {
return dialogId === '' && conversationId === '';
};
//#endregion
//#region API provided for external calls
type RangeValue = [Dayjs | null, Dayjs | null] | null;
export const useFetchStatsOnMount = (visible: boolean) => {
const fetchStats = useFetchStats();
const [pickerValue, setPickerValue] = useState<RangeValue>([
dayjs(),
dayjs().subtract(7, 'day'),
]);
useEffect(() => {
if (visible && Array.isArray(pickerValue) && pickerValue[0]) {
fetchStats({ fromDate: pickerValue[0], toDate: pickerValue[1] });
}
}, [fetchStats, pickerValue, visible]);
return {
pickerValue,
setPickerValue,
};
};
export const useOperateApiKey = (visible: boolean, dialogId: string) => {
const removeToken = useRemoveToken();
const createToken = useCreateToken(dialogId);
const listToken = useListToken();
const tokenList = useSelectTokenList();
const creatingLoading = useOneNamespaceEffectsLoading('chatModel', [
'createToken',
]);
const listLoading = useOneNamespaceEffectsLoading('chatModel', ['list']);
const showDeleteConfirm = useShowDeleteConfirm();
const onRemoveToken = (token: string, tenantId: string) => {
showDeleteConfirm({
onOk: () => removeToken({ dialogId, tokens: [token], tenantId }),
});
};
useEffect(() => {
if (visible && dialogId) {
listToken(dialogId);
}
}, [listToken, dialogId, visible]);
return {
removeToken: onRemoveToken,
createToken,
tokenList,
creatingLoading,
listLoading,
};
};
type ChartStatsType = {
[k in keyof IStats]: Array<{ xAxis: string; yAxis: number }>;
};
export const useSelectChartStatsList = (): ChartStatsType => {
// const stats: IStats = useSelectStats();
const stats = {
pv: [
['2024-06-01', 1],
['2024-07-24', 3],
['2024-09-01', 10],
],
uv: [
['2024-02-01', 0],
['2024-03-01', 99],
['2024-05-01', 3],
],
speed: [
['2024-09-01', 2],
['2024-09-01', 3],
],
tokens: [
['2024-09-01', 1],
['2024-09-01', 3],
],
round: [
['2024-09-01', 0],
['2024-09-01', 3],
],
thumb_up: [
['2024-09-01', 3],
['2024-09-01', 9],
],
};
return Object.keys(stats).reduce((pre, cur) => {
const item = stats[cur as keyof IStats];
if (item.length > 0) {
pre[cur as keyof IStats] = item.map((x) => ({
xAxis: x[0] as string,
yAxis: x[1] as number,
}));
}
return pre;
}, {} as ChartStatsType);
};
//#endregion

View File

@ -35,7 +35,10 @@ import {
useSelectFirstDialogOnMount,
} from './hooks';
import { useTranslate } from '@/hooks/commonHooks';
import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
import { useSetSelectedRecord } from '@/hooks/logicHooks';
import { IDialog } from '@/interfaces/database/chat';
import ChatOverviewModal from './chat-overview-modal';
import styles from './index.less';
const Chat = () => {
@ -73,6 +76,12 @@ const Chat = () => {
const dialogLoading = useSelectDialogListLoading();
const conversationLoading = useSelectConversationListLoading();
const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
useFetchDialogOnMount(dialogId, true);
@ -100,6 +109,15 @@ const Chat = () => {
onRemoveDialog([dialogId]);
};
const handleShowOverviewModal =
(dialog: IDialog): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
};
const handleRemoveConversation =
(conversationId: string): MenuItemProps['onClick'] =>
({ domEvent }) => {
@ -141,7 +159,9 @@ const Chat = () => {
},
];
const buildAppItems = (dialogId: string) => {
const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [
{
key: '1',
@ -164,6 +184,17 @@ const Chat = () => {
</Space>
),
},
{ type: 'divider' },
// {
// key: '3',
// onClick: handleShowOverviewModal(dialog),
// label: (
// <Space>
// <ProfileOutlined />
// {t('overview')}
// </Space>
// ),
// },
];
return appItems;
@ -230,7 +261,7 @@ const Chat = () => {
</Space>
{activated === x.id && (
<section>
<Dropdown menu={{ items: buildAppItems(x.id) }}>
<Dropdown menu={{ items: buildAppItems(x) }}>
<ChatAppCube
className={styles.cubeIcon}
></ChatAppCube>
@ -315,6 +346,11 @@ const Chat = () => {
initialName={initialConversationName}
loading={conversationRenameLoading}
></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex>
);
};

View File

@ -1,7 +1,14 @@
import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
import {
IConversation,
IDialog,
IStats,
IToken,
Message,
} from '@/interfaces/database/chat';
import i18n from '@/locales/config';
import chatService from '@/services/chatService';
import { message } from 'antd';
import omit from 'lodash/omit';
import { DvaModel } from 'umi';
import { v4 as uuid } from 'uuid';
import { IClientConversation, IMessage } from './interface';
@ -13,6 +20,8 @@ export interface ChatModelState {
currentDialog: IDialog;
conversationList: IConversation[];
currentConversation: IClientConversation;
tokenList: IToken[];
stats: IStats;
}
const model: DvaModel<ChatModelState> = {
@ -23,6 +32,8 @@ const model: DvaModel<ChatModelState> = {
currentDialog: <IDialog>{},
conversationList: [],
currentConversation: {} as IClientConversation,
tokenList: [],
stats: {} as IStats,
},
reducers: {
save(state, action) {
@ -60,6 +71,18 @@ const model: DvaModel<ChatModelState> = {
currentConversation: { ...payload, message: messageList },
};
},
setTokenList(state, { payload }) {
return {
...state,
tokenList: payload,
};
},
setStats(state, { payload }) {
return {
...state,
stats: payload,
};
},
},
effects: {
@ -160,6 +183,78 @@ const model: DvaModel<ChatModelState> = {
}
return data.retcode;
},
*createToken({ payload }, { call, put }) {
const { data } = yield call(chatService.createToken, payload);
if (data.retcode === 0) {
yield put({
type: 'listToken',
payload: payload,
});
message.success(i18n.t('message.created'));
}
return data.retcode;
},
*listToken({ payload }, { call, put }) {
const { data } = yield call(chatService.listToken, payload);
if (data.retcode === 0) {
yield put({
type: 'setTokenList',
payload: data.data,
});
}
return data.retcode;
},
*removeToken({ payload }, { call, put }) {
const { data } = yield call(
chatService.removeToken,
omit(payload, ['dialogId']),
);
if (data.retcode === 0) {
yield put({
type: 'listToken',
payload: { dialog_id: payload.dialogId },
});
}
return data.retcode;
},
*getStats({ payload }, { call, put }) {
const { data } = yield call(chatService.getStats, payload);
if (data.retcode === 0) {
yield put({
type: 'setStats',
payload: data.data,
});
}
return data.retcode;
},
*createExternalConversation({ payload }, { call, put }) {
const { data } = yield call(
chatService.createExternalConversation,
payload,
);
if (data.retcode === 0) {
yield put({
type: 'getExternalConversation',
payload: { conversation_id: payload.conversationId },
});
}
return data.retcode;
},
*getExternalConversation({ payload }, { call }) {
const { data } = yield call(
chatService.getExternalConversation,
null,
payload,
);
return data.retcode;
},
*completeExternalConversation({ payload }, { call }) {
const { data } = yield call(
chatService.completeExternalConversation,
payload,
);
return data.retcode;
},
},
};

View File

@ -12,6 +12,13 @@ const {
completeConversation,
listConversation,
removeConversation,
createToken,
listToken,
removeToken,
getStats,
createExternalConversation,
getExternalConversation,
completeExternalConversation,
} = api;
const methods = {
@ -51,6 +58,34 @@ const methods = {
url: removeConversation,
method: 'post',
},
createToken: {
url: createToken,
method: 'post',
},
listToken: {
url: listToken,
method: 'get',
},
removeToken: {
url: removeToken,
method: 'post',
},
getStats: {
url: getStats,
method: 'get',
},
createExternalConversation: {
url: createExternalConversation,
method: 'post',
},
getExternalConversation: {
url: getExternalConversation,
method: 'get',
},
completeExternalConversation: {
url: completeExternalConversation,
method: 'post',
},
} as const;
const chatService = registerServer<keyof typeof methods>(methods, request);

View File

@ -3,7 +3,7 @@ let api_host = `/v1`;
export { api_host };
export default {
// 用户
// user
login: `${api_host}/user/login`,
logout: `${api_host}/user/logout`,
register: `${api_host}/user/register`,
@ -12,21 +12,21 @@ export default {
tenant_info: `${api_host}/user/tenant_info`,
set_tenant_info: `${api_host}/user/set_tenant_info`,
// 模型管理
// llm model
factories_list: `${api_host}/llm/factories`,
llm_list: `${api_host}/llm/list`,
my_llm: `${api_host}/llm/my_llms`,
set_api_key: `${api_host}/llm/set_api_key`,
add_llm: `${api_host}/llm/add_llm`,
//知识库管理
// knowledge base
kb_list: `${api_host}/kb/list`,
create_kb: `${api_host}/kb/create`,
update_kb: `${api_host}/kb/update`,
rm_kb: `${api_host}/kb/rm`,
get_kb_detail: `${api_host}/kb/detail`,
// chunk管理
// chunk
chunk_list: `${api_host}/chunk/list`,
create_chunk: `${api_host}/chunk/create`,
set_chunk: `${api_host}/chunk/set`,
@ -35,7 +35,7 @@ export default {
rm_chunk: `${api_host}/chunk/rm`,
retrieval_test: `${api_host}/chunk/retrieval_test`,
// 文件管理
// document
upload: `${api_host}/document/upload`,
get_document_list: `${api_host}/document/list`,
document_change_status: `${api_host}/document/change_status`,
@ -48,14 +48,22 @@ export default {
get_document_file: `${api_host}/document/get`,
document_upload: `${api_host}/document/upload`,
// chat
setDialog: `${api_host}/dialog/set`,
getDialog: `${api_host}/dialog/get`,
removeDialog: `${api_host}/dialog/rm`,
listDialog: `${api_host}/dialog/list`,
setConversation: `${api_host}/conversation/set`,
getConversation: `${api_host}/conversation/get`,
listConversation: `${api_host}/conversation/list`,
removeConversation: `${api_host}/conversation/rm`,
completeConversation: `${api_host}/conversation/completion`,
// chat for external
createToken: `${api_host}/api/new_token`,
listToken: `${api_host}/api/token_list`,
removeToken: `${api_host}/api/rm`,
getStats: `${api_host}/api/stats`,
createExternalConversation: `${api_host}/api/new_conversation`,
getExternalConversation: `${api_host}/api/conversation`,
completeExternalConversation: `${api_host}/api/completion`,
};

View File

@ -0,0 +1,17 @@
import isObject from 'lodash/isObject';
import snakeCase from 'lodash/snakeCase';
export const isFormData = (data: unknown): data is FormData => {
return data instanceof FormData;
};
export const convertTheKeysOfTheObjectToSnake = (data: unknown) => {
if (isObject(data) && !isFormData(data)) {
return Object.keys(data).reduce<Record<string, any>>((pre, cur) => {
const value = (data as Record<string, any>)[cur];
pre[isFormData(value) ? cur : snakeCase(cur)] = value;
return pre;
}, {});
}
return data;
};

View File

@ -1,20 +1,20 @@
import moment from 'moment';
import dayjs from 'dayjs';
export function today() {
return formatDate(moment());
return formatDate(dayjs());
}
export function lastDay() {
return formatDate(moment().subtract(1, 'days'));
return formatDate(dayjs().subtract(1, 'days'));
}
export function lastWeek() {
return formatDate(moment().subtract(1, 'weeks'));
return formatDate(dayjs().subtract(1, 'weeks'));
}
export function formatDate(date: any) {
if (!date) {
return '';
}
return moment(date).format('DD/MM/YYYY');
return dayjs(date).format('DD/MM/YYYY');
}

View File

@ -8,16 +8,20 @@ const registerServer = <T extends string>(
) => {
const server: Service<T> = {} as Service<T>;
for (let key in opt) {
server[key] = (params) => {
server[key] = (params: any, urlAppendix?: string) => {
let url = opt[key].url;
if (urlAppendix) {
url = url + '/' + urlAppendix;
}
if (opt[key].method === 'post' || opt[key].method === 'POST') {
return request(opt[key].url, {
return request(url, {
method: opt[key].method,
data: params,
});
}
if (opt[key].method === 'get' || opt[key].method === 'GET') {
return request.get(opt[key].url, {
return request.get(url, {
params,
});
}

View File

@ -4,6 +4,7 @@ import authorizationUtil from '@/utils/authorizationUtil';
import { message, notification } from 'antd';
import { history } from 'umi';
import { RequestMethod, extend } from 'umi-request';
import { convertTheKeysOfTheObjectToSnake } from './commonUtil';
const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
@ -87,10 +88,15 @@ const request: RequestMethod = extend({
request.interceptors.request.use((url: string, options: any) => {
const authorization = authorizationUtil.getAuthorization();
const data = convertTheKeysOfTheObjectToSnake(options.data);
const params = convertTheKeysOfTheObjectToSnake(options.params);
return {
url,
options: {
...options,
// data,
// params,
headers: {
...(options.skipToken ? undefined : { [Authorization]: authorization }),
...options.headers,