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", "antd": "^5.12.7",
"axios": "^1.6.3", "axios": "^1.6.3",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16", "i18next": "^23.7.16",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6", "rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13", "react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0", "react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0", "react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"umi": "^4.0.90", "umi": "^4.0.90",
"umi-request": "^1.4.0", "umi-request": "^1.4.0",
@ -36,6 +38,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1", "@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202", "@types/lodash": "^4.14.202",
"@types/react": "^18.0.33", "@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11", "@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
@ -2676,6 +2679,60 @@
"@babel/types": "^7.20.7" "@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": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@ -2884,6 +2941,15 @@
"csstype": "^3.0.2" "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": { "node_modules/@types/react-dom": {
"version": "18.2.18", "version": "18.2.18",
"resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.18.tgz", "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.18.tgz",
@ -5832,6 +5898,14 @@
"node": ">=12" "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": { "node_modules/coa": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz", "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", "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-1.2.4.tgz",
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" "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": { "node_modules/d3-polygon": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz", "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz",
"integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" "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": { "node_modules/data-uri-to-buffer": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "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": ">=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": { "node_modules/decode-named-character-reference": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@ -7032,6 +7232,15 @@
"utila": "~0.4" "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": { "node_modules/dom-serializer": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz", "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -8151,6 +8360,11 @@
"es5-ext": "~0.10.14" "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": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz", "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", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" "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": { "node_modules/fast-glob": {
"version": "3.2.12", "version": "3.2.12",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz",
@ -9693,6 +9915,14 @@
"node": ">= 0.4" "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": { "node_modules/intersection-observer": {
"version": "0.12.2", "version": "0.12.2",
"resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz", "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
@ -11925,6 +12155,7 @@
"version": "2.30.1", "version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"devOptional": true,
"engines": { "engines": {
"node": "*" "node": "*"
} }
@ -14356,6 +14587,18 @@
"react-dom": "18.2.0" "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": { "node_modules/react-dev-inspector": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz",
@ -14934,6 +15177,20 @@
"react": ">=15" "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": { "node_modules/react-spinkit": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz", "resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz",
@ -14968,6 +15225,21 @@
"react": ">= 0.14.0" "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": { "node_modules/reactcss": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz",
@ -15145,6 +15417,41 @@
"node": ">= 12.13.0" "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": { "node_modules/recursive-readdir": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
@ -17000,9 +17307,7 @@
"node_modules/tiny-invariant": { "node_modules/tiny-invariant": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz", "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
"dev": true,
"peer": true
}, },
"node_modules/tiny-warning": { "node_modules/tiny-warning": {
"version": "1.0.3", "version": "1.0.3",
@ -18221,6 +18526,38 @@
"unist-util-stringify-position": "^4.0.0" "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": { "node_modules/vite": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz",

View File

@ -17,19 +17,21 @@
"antd": "^5.12.7", "antd": "^5.12.7",
"axios": "^1.6.3", "axios": "^1.6.3",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"dayjs": "^1.11.10",
"i18next": "^23.7.16", "i18next": "^23.7.16",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"moment": "^2.30.1",
"rc-tween-one": "^3.0.6", "rc-tween-one": "^3.0.6",
"react-chat-elements": "^12.0.13", "react-chat-elements": "^12.0.13",
"react-copy-to-clipboard": "^5.1.0",
"react-i18next": "^14.0.0", "react-i18next": "^14.0.0",
"react-infinite-scroll-component": "^6.1.0", "react-infinite-scroll-component": "^6.1.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-pdf-highlighter": "^6.1.0", "react-pdf-highlighter": "^6.1.0",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"recharts": "^2.12.4",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
"umi": "^4.0.90", "umi": "^4.0.90",
"umi-request": "^1.4.0", "umi-request": "^1.4.0",
@ -40,6 +42,7 @@
"@react-dev-inspector/umi4-plugin": "^2.0.1", "@react-dev-inspector/umi4-plugin": "^2.0.1",
"@types/lodash": "^4.14.202", "@types/lodash": "^4.14.202",
"@types/react": "^18.0.33", "@types/react": "^18.0.33",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.11", "@types/react-syntax-highlighter": "^15.5.11",
"@types/uuid": "^9.0.8", "@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 React, { ReactNode, useEffect, useState } from 'react';
import storage from './utils/authorizationUtil'; 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 = { const AntLanguageMap = {
en: enUS, en: enUS,
zh: zhCN, 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 { useCallback } from 'react';
import { useDispatch, useSelector } from 'umi'; import { useDispatch, useSelector } from 'umi';
@ -164,3 +169,82 @@ export const useCompleteConversation = () => {
return completeConversation; 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; // term_similarity: number;
// vector_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', language: 'Language',
languageMessage: 'Please input your language!', languageMessage: 'Please input your language!',
languagePlaceholder: 'select your language', languagePlaceholder: 'select your language',
copy: 'Copy',
copied: 'Copied',
}, },
login: { login: {
login: 'Sign in', 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).', 'This sets the maximum length of the models output, measured in the number of tokens (words or pieces of words).',
quote: 'Show Quote', quote: 'Show Quote',
quoteTip: 'Should the source of the original text be displayed?', 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: { setting: {
profile: 'Profile', profile: 'Profile',

View File

@ -15,11 +15,13 @@ export default {
edit: '編輯', edit: '編輯',
upload: '上傳', upload: '上傳',
english: '英語', english: '英語',
chinese: '中文簡體', chinese: '簡體中文',
traditionalChinese: '中文繁體', traditionalChinese: '繁體中文',
language: '語言', language: '語言',
languageMessage: '請輸入語言', languageMessage: '請輸入語言',
languagePlaceholder: '請選擇語言', languagePlaceholder: '請選擇語言',
copy: '複製',
copied: '複製成功',
}, },
login: { login: {
login: '登入', login: '登入',
@ -269,7 +271,7 @@ export default {
systemMessage: '請輸入', systemMessage: '請輸入',
systemTip: systemTip:
'當LLM回答問題時你需要LLM遵循的說明比如角色設計、答案長度和答案語言等。', '當LLM回答問題時你需要LLM遵循的說明比如角色設計、答案長度和答案語言等。',
topN: 'top n', topN: 'Top N',
topNTip: `並非所有相似度得分高於“相似度閾值”的塊都會被提供給法學碩士。LLM 只能看到這些“Top N”塊。`, topNTip: `並非所有相似度得分高於“相似度閾值”的塊都會被提供給法學碩士。LLM 只能看到這些“Top N”塊。`,
variable: '變量', variable: '變量',
variableTip: `如果您使用对话 API变量可能会帮助您使用不同的策略与客户聊天。 variableTip: `如果您使用对话 API变量可能会帮助您使用不同的策略与客户聊天。
@ -310,6 +312,24 @@ export default {
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。', '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
quote: '顯示引文', quote: '顯示引文',
quoteTip: '是否應該顯示原文出處?', 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: { setting: {
profile: '概述', profile: '概述',

View File

@ -15,11 +15,13 @@ export default {
edit: '编辑', edit: '编辑',
upload: '上传', upload: '上传',
english: '英文', english: '英文',
chinese: '中文简体', chinese: '简体中文',
traditionalChinese: '中文繁体', traditionalChinese: '繁体中文',
language: '语言', language: '语言',
languageMessage: '请输入语言', languageMessage: '请输入语言',
languagePlaceholder: '请选择语言', languagePlaceholder: '请选择语言',
copy: '复制',
copied: '复制成功',
}, },
login: { login: {
login: '登录', login: '登录',
@ -326,6 +328,24 @@ export default {
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
quote: '显示引文', quote: '显示引文',
quoteTip: '是否应该显示原文出处?', 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: { setting: {
profile: '概要', profile: '概要',

View File

@ -26,6 +26,7 @@ import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell'; import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal'; import RenameModal from './rename-modal';
import { formatDate } from '@/utils/date';
import styles from './index.less'; import styles from './index.less';
const KnowledgeFile = () => { const KnowledgeFile = () => {
@ -94,6 +95,9 @@ const KnowledgeFile = () => {
title: t('uploadDate'), title: t('uploadDate'),
dataIndex: 'create_date', dataIndex: 'create_date',
key: 'create_date', key: 'create_date',
render(value) {
return formatDate(value);
},
}, },
{ {
title: t('chunkMethod'), 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 { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
import { PlusOutlined } from '@ant-design/icons'; 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 classNames from 'classnames';
import { ISegmentedContentProps } from '../interface'; import { ISegmentedContentProps } from '../interface';
@ -83,6 +83,15 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
> >
<Input.TextArea autoSize={{ minRows: 5 }} /> <Input.TextArea autoSize={{ minRows: 5 }} />
</Form.Item> </Form.Item>
<Form.Item
label={t('quote')}
valuePropName="checked"
name={['prompt_config', 'quote']}
tooltip={t('quoteTip')}
initialValue={true}
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
label={t('knowledgeBases')} label={t('knowledgeBases')}
name="kb_ids" name="kb_ids"

View File

@ -172,15 +172,7 @@ const PromptEngine = (
> >
<Slider max={30} /> <Slider max={30} />
</Form.Item> </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)}> <section className={classNames(styles.variableContainer)}>
<Row align={'middle'} justify="end"> <Row align={'middle'} justify="end">
<Col span={7} className={styles.variableAlign}> <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 { fileIconMap } from '@/constants/common';
import { import {
useCompleteConversation, useCompleteConversation,
useCreateToken,
useFetchConversation, useFetchConversation,
useFetchConversationList, useFetchConversationList,
useFetchDialog, useFetchDialog,
useFetchDialogList, useFetchDialogList,
useFetchStats,
useListToken,
useRemoveConversation, useRemoveConversation,
useRemoveDialog, useRemoveDialog,
useRemoveToken,
useSelectConversationList, useSelectConversationList,
useSelectDialogList, useSelectDialogList,
useSelectTokenList,
useSetDialog, useSetDialog,
useUpdateConversation, useUpdateConversation,
} from '@/hooks/chatHooks'; } from '@/hooks/chatHooks';
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks'; import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; 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 { IChunk } from '@/interfaces/database/knowledge';
import { getFileExtension } from '@/utils'; import { getFileExtension } from '@/utils';
import dayjs, { Dayjs } from 'dayjs';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { import {
ChangeEventHandler, ChangeEventHandler,
@ -704,3 +710,108 @@ export const useGetSendButtonDisabled = () => {
return dialogId === '' && conversationId === ''; return dialogId === '' && conversationId === '';
}; };
//#endregion //#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, useSelectFirstDialogOnMount,
} from './hooks'; } 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'; import styles from './index.less';
const Chat = () => { const Chat = () => {
@ -73,6 +76,12 @@ const Chat = () => {
const dialogLoading = useSelectDialogListLoading(); const dialogLoading = useSelectDialogListLoading();
const conversationLoading = useSelectConversationListLoading(); const conversationLoading = useSelectConversationListLoading();
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const {
visible: overviewVisible,
hideModal: hideOverviewModal,
showModal: showOverviewModal,
} = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
useFetchDialogOnMount(dialogId, true); useFetchDialogOnMount(dialogId, true);
@ -100,6 +109,15 @@ const Chat = () => {
onRemoveDialog([dialogId]); onRemoveDialog([dialogId]);
}; };
const handleShowOverviewModal =
(dialog: IDialog): any =>
(info: any) => {
info?.domEvent?.preventDefault();
info?.domEvent?.stopPropagation();
setRecord(dialog);
showOverviewModal();
};
const handleRemoveConversation = const handleRemoveConversation =
(conversationId: string): MenuItemProps['onClick'] => (conversationId: string): MenuItemProps['onClick'] =>
({ domEvent }) => { ({ domEvent }) => {
@ -141,7 +159,9 @@ const Chat = () => {
}, },
]; ];
const buildAppItems = (dialogId: string) => { const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [ const appItems: MenuProps['items'] = [
{ {
key: '1', key: '1',
@ -164,6 +184,17 @@ const Chat = () => {
</Space> </Space>
), ),
}, },
{ type: 'divider' },
// {
// key: '3',
// onClick: handleShowOverviewModal(dialog),
// label: (
// <Space>
// <ProfileOutlined />
// {t('overview')}
// </Space>
// ),
// },
]; ];
return appItems; return appItems;
@ -230,7 +261,7 @@ const Chat = () => {
</Space> </Space>
{activated === x.id && ( {activated === x.id && (
<section> <section>
<Dropdown menu={{ items: buildAppItems(x.id) }}> <Dropdown menu={{ items: buildAppItems(x) }}>
<ChatAppCube <ChatAppCube
className={styles.cubeIcon} className={styles.cubeIcon}
></ChatAppCube> ></ChatAppCube>
@ -315,6 +346,11 @@ const Chat = () => {
initialName={initialConversationName} initialName={initialConversationName}
loading={conversationRenameLoading} loading={conversationRenameLoading}
></RenameModal> ></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex> </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 i18n from '@/locales/config';
import chatService from '@/services/chatService'; import chatService from '@/services/chatService';
import { message } from 'antd'; import { message } from 'antd';
import omit from 'lodash/omit';
import { DvaModel } from 'umi'; import { DvaModel } from 'umi';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { IClientConversation, IMessage } from './interface'; import { IClientConversation, IMessage } from './interface';
@ -13,6 +20,8 @@ export interface ChatModelState {
currentDialog: IDialog; currentDialog: IDialog;
conversationList: IConversation[]; conversationList: IConversation[];
currentConversation: IClientConversation; currentConversation: IClientConversation;
tokenList: IToken[];
stats: IStats;
} }
const model: DvaModel<ChatModelState> = { const model: DvaModel<ChatModelState> = {
@ -23,6 +32,8 @@ const model: DvaModel<ChatModelState> = {
currentDialog: <IDialog>{}, currentDialog: <IDialog>{},
conversationList: [], conversationList: [],
currentConversation: {} as IClientConversation, currentConversation: {} as IClientConversation,
tokenList: [],
stats: {} as IStats,
}, },
reducers: { reducers: {
save(state, action) { save(state, action) {
@ -60,6 +71,18 @@ const model: DvaModel<ChatModelState> = {
currentConversation: { ...payload, message: messageList }, currentConversation: { ...payload, message: messageList },
}; };
}, },
setTokenList(state, { payload }) {
return {
...state,
tokenList: payload,
};
},
setStats(state, { payload }) {
return {
...state,
stats: payload,
};
},
}, },
effects: { effects: {
@ -160,6 +183,78 @@ const model: DvaModel<ChatModelState> = {
} }
return data.retcode; 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, completeConversation,
listConversation, listConversation,
removeConversation, removeConversation,
createToken,
listToken,
removeToken,
getStats,
createExternalConversation,
getExternalConversation,
completeExternalConversation,
} = api; } = api;
const methods = { const methods = {
@ -51,6 +58,34 @@ const methods = {
url: removeConversation, url: removeConversation,
method: 'post', 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; } as const;
const chatService = registerServer<keyof typeof methods>(methods, request); const chatService = registerServer<keyof typeof methods>(methods, request);

View File

@ -3,7 +3,7 @@ let api_host = `/v1`;
export { api_host }; export { api_host };
export default { export default {
// 用户 // user
login: `${api_host}/user/login`, login: `${api_host}/user/login`,
logout: `${api_host}/user/logout`, logout: `${api_host}/user/logout`,
register: `${api_host}/user/register`, register: `${api_host}/user/register`,
@ -12,21 +12,21 @@ export default {
tenant_info: `${api_host}/user/tenant_info`, tenant_info: `${api_host}/user/tenant_info`,
set_tenant_info: `${api_host}/user/set_tenant_info`, set_tenant_info: `${api_host}/user/set_tenant_info`,
// 模型管理 // llm model
factories_list: `${api_host}/llm/factories`, factories_list: `${api_host}/llm/factories`,
llm_list: `${api_host}/llm/list`, llm_list: `${api_host}/llm/list`,
my_llm: `${api_host}/llm/my_llms`, my_llm: `${api_host}/llm/my_llms`,
set_api_key: `${api_host}/llm/set_api_key`, set_api_key: `${api_host}/llm/set_api_key`,
add_llm: `${api_host}/llm/add_llm`, add_llm: `${api_host}/llm/add_llm`,
//知识库管理 // knowledge base
kb_list: `${api_host}/kb/list`, kb_list: `${api_host}/kb/list`,
create_kb: `${api_host}/kb/create`, create_kb: `${api_host}/kb/create`,
update_kb: `${api_host}/kb/update`, update_kb: `${api_host}/kb/update`,
rm_kb: `${api_host}/kb/rm`, rm_kb: `${api_host}/kb/rm`,
get_kb_detail: `${api_host}/kb/detail`, get_kb_detail: `${api_host}/kb/detail`,
// chunk管理 // chunk
chunk_list: `${api_host}/chunk/list`, chunk_list: `${api_host}/chunk/list`,
create_chunk: `${api_host}/chunk/create`, create_chunk: `${api_host}/chunk/create`,
set_chunk: `${api_host}/chunk/set`, set_chunk: `${api_host}/chunk/set`,
@ -35,7 +35,7 @@ export default {
rm_chunk: `${api_host}/chunk/rm`, rm_chunk: `${api_host}/chunk/rm`,
retrieval_test: `${api_host}/chunk/retrieval_test`, retrieval_test: `${api_host}/chunk/retrieval_test`,
// 文件管理 // document
upload: `${api_host}/document/upload`, upload: `${api_host}/document/upload`,
get_document_list: `${api_host}/document/list`, get_document_list: `${api_host}/document/list`,
document_change_status: `${api_host}/document/change_status`, document_change_status: `${api_host}/document/change_status`,
@ -48,14 +48,22 @@ export default {
get_document_file: `${api_host}/document/get`, get_document_file: `${api_host}/document/get`,
document_upload: `${api_host}/document/upload`, document_upload: `${api_host}/document/upload`,
// chat
setDialog: `${api_host}/dialog/set`, setDialog: `${api_host}/dialog/set`,
getDialog: `${api_host}/dialog/get`, getDialog: `${api_host}/dialog/get`,
removeDialog: `${api_host}/dialog/rm`, removeDialog: `${api_host}/dialog/rm`,
listDialog: `${api_host}/dialog/list`, listDialog: `${api_host}/dialog/list`,
setConversation: `${api_host}/conversation/set`, setConversation: `${api_host}/conversation/set`,
getConversation: `${api_host}/conversation/get`, getConversation: `${api_host}/conversation/get`,
listConversation: `${api_host}/conversation/list`, listConversation: `${api_host}/conversation/list`,
removeConversation: `${api_host}/conversation/rm`, removeConversation: `${api_host}/conversation/rm`,
completeConversation: `${api_host}/conversation/completion`, 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() { export function today() {
return formatDate(moment()); return formatDate(dayjs());
} }
export function lastDay() { export function lastDay() {
return formatDate(moment().subtract(1, 'days')); return formatDate(dayjs().subtract(1, 'days'));
} }
export function lastWeek() { export function lastWeek() {
return formatDate(moment().subtract(1, 'weeks')); return formatDate(dayjs().subtract(1, 'weeks'));
} }
export function formatDate(date: any) { export function formatDate(date: any) {
if (!date) { if (!date) {
return ''; 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>; const server: Service<T> = {} as Service<T>;
for (let key in opt) { 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') { if (opt[key].method === 'post' || opt[key].method === 'POST') {
return request(opt[key].url, { return request(url, {
method: opt[key].method, method: opt[key].method,
data: params, data: params,
}); });
} }
if (opt[key].method === 'get' || opt[key].method === 'GET') { if (opt[key].method === 'get' || opt[key].method === 'GET') {
return request.get(opt[key].url, { return request.get(url, {
params, params,
}); });
} }

View File

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