Преглед на файлове

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)
tags/v0.2.0
balibabu преди 1 година
родител
ревизия
ad6f0a1ce5
No account linked to committer's email address

+ 341
- 4
web/package-lock.json Целия файл

"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",
"@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",
"@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",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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",
"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": "*"
} }
"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",
"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",
"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",
"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",
"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==",
"dev": true,
"peer": true
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
}, },
"node_modules/tiny-warning": { "node_modules/tiny-warning": {
"version": "1.0.3", "version": "1.0.3",
"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",

+ 4
- 1
web/package.json Целия файл

"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",
"@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",

+ 15
- 0
web/src/app.tsx Целия файл

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,

+ 27
- 0
web/src/components/copy-to-clipboard.tsx Целия файл

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;

+ 88
- 0
web/src/components/line-chart/index.tsx Целия файл

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;

+ 85
- 1
web/src/hooks/chatHooks.ts Целия файл

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';




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

+ 18
- 0
web/src/interfaces/database/chat.ts Целия файл

// 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][];
}

+ 20
- 0
web/src/locales/en.ts Целия файл

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',
'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).', 'This sets the maximum length of the model’s 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',

+ 23
- 3
web/src/locales/zh-traditional.ts Целия файл

edit: '編輯', edit: '編輯',
upload: '上傳', upload: '上傳',
english: '英語', english: '英語',
chinese: '中文簡體',
traditionalChinese: '中文繁體',
chinese: '簡體中文',
traditionalChinese: '繁體中文',
language: '語言', language: '語言',
languageMessage: '請輸入語言', languageMessage: '請輸入語言',
languagePlaceholder: '請選擇語言', languagePlaceholder: '請選擇語言',
copy: '複製',
copied: '複製成功',
}, },
login: { login: {
login: '登入', login: '登入',
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,变量可能会帮助您使用不同的策略与客户聊天。
'這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。', '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
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: '概述',

+ 22
- 2
web/src/locales/zh.ts Целия файл

edit: '编辑', edit: '编辑',
upload: '上传', upload: '上传',
english: '英文', english: '英文',
chinese: '中文简体',
traditionalChinese: '中文繁体',
chinese: '简体中文',
traditionalChinese: '繁体中文',
language: '语言', language: '语言',
languageMessage: '请输入语言', languageMessage: '请输入语言',
languagePlaceholder: '请选择语言', languagePlaceholder: '请选择语言',
copy: '复制',
copied: '复制成功',
}, },
login: { login: {
login: '登录', login: '登录',
'这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。', '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
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: '概要',

+ 4
- 0
web/src/pages/add-knowledge/components/knowledge-file/index.tsx Целия файл

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 = () => {
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'),

+ 70
- 0
web/src/pages/chat/chat-api-key-modal/index.tsx Целия файл

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;

+ 10
- 1
web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx Целия файл

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';


> >
<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"

+ 1
- 9
web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx Целия файл

> >
<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}>

+ 21
- 0
web/src/pages/chat/chat-overview-modal/index.less Целия файл

.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;
}

+ 97
- 0
web/src/pages/chat/chat-overview-modal/index.tsx Целия файл

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;

+ 112
- 1
web/src/pages/chat/hooks.ts Целия файл

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,
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

+ 39
- 3
web/src/pages/chat/index.tsx Целия файл

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 = () => {
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);
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 }) => {
}, },
]; ];
const buildAppItems = (dialogId: string) => {
const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id;
const appItems: MenuProps['items'] = [ const appItems: MenuProps['items'] = [
{ {
key: '1', key: '1',
</Space> </Space>
), ),
}, },
{ type: 'divider' },
// {
// key: '3',
// onClick: handleShowOverviewModal(dialog),
// label: (
// <Space>
// <ProfileOutlined />
// {t('overview')}
// </Space>
// ),
// },
]; ];
return appItems; return appItems;
</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>
initialName={initialConversationName} initialName={initialConversationName}
loading={conversationRenameLoading} loading={conversationRenameLoading}
></RenameModal> ></RenameModal>
<ChatOverviewModal
visible={overviewVisible}
hideModal={hideOverviewModal}
dialog={currentRecord}
></ChatOverviewModal>
</Flex> </Flex>
); );
}; };

+ 96
- 1
web/src/pages/chat/model.ts Целия файл

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';
currentDialog: IDialog; currentDialog: IDialog;
conversationList: IConversation[]; conversationList: IConversation[];
currentConversation: IClientConversation; currentConversation: IClientConversation;
tokenList: IToken[];
stats: IStats;
} }
const model: DvaModel<ChatModelState> = { 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) {
currentConversation: { ...payload, message: messageList }, currentConversation: { ...payload, message: messageList },
}; };
}, },
setTokenList(state, { payload }) {
return {
...state,
tokenList: payload,
};
},
setStats(state, { payload }) {
return {
...state,
stats: payload,
};
},
}, },
effects: { effects: {
} }
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;
},
}, },
}; };

+ 35
- 0
web/src/services/chatService.ts Целия файл

completeConversation, completeConversation,
listConversation, listConversation,
removeConversation, removeConversation,
createToken,
listToken,
removeToken,
getStats,
createExternalConversation,
getExternalConversation,
completeExternalConversation,
} = api; } = api;


const methods = { 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);

+ 14
- 6
web/src/utils/api.ts Целия файл

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`,
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`,
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`,
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`,
}; };

+ 17
- 0
web/src/utils/commonUtil.ts Целия файл

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;
};

+ 5
- 5
web/src/utils/date.ts Целия файл

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');
} }

+ 7
- 3
web/src/utils/registerServer.ts Целия файл

) => { ) => {
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,
}); });
} }

+ 6
- 0
web/src/utils/request.ts Целия файл

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
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,

Loading…
Отказ
Запис