diff --git a/web/package-lock.json b/web/package-lock.json
index 318cc80d70e63ca6d213414605799ab9a893797e..82f228491045f737db476c6cb51480a59d97de5f 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -13,19 +13,21 @@
         "antd": "^5.12.7",
         "axios": "^1.6.3",
         "classnames": "^2.5.1",
+        "dayjs": "^1.11.10",
         "i18next": "^23.7.16",
         "js-base64": "^3.7.5",
         "jsencrypt": "^3.3.2",
         "lodash": "^4.17.21",
-        "moment": "^2.30.1",
         "rc-tween-one": "^3.0.6",
         "react-chat-elements": "^12.0.13",
+        "react-copy-to-clipboard": "^5.1.0",
         "react-i18next": "^14.0.0",
         "react-infinite-scroll-component": "^6.1.0",
         "react-markdown": "^9.0.1",
         "react-pdf-highlighter": "^6.1.0",
         "react-string-replace": "^1.1.1",
         "react-syntax-highlighter": "^15.5.0",
+        "recharts": "^2.12.4",
         "remark-gfm": "^4.0.0",
         "umi": "^4.0.90",
         "umi-request": "^1.4.0",
@@ -36,6 +38,7 @@
         "@react-dev-inspector/umi4-plugin": "^2.0.1",
         "@types/lodash": "^4.14.202",
         "@types/react": "^18.0.33",
+        "@types/react-copy-to-clipboard": "^5.0.7",
         "@types/react-dom": "^18.0.11",
         "@types/react-syntax-highlighter": "^15.5.11",
         "@types/uuid": "^9.0.8",
@@ -2676,6 +2679,60 @@
         "@babel/types": "^7.20.7"
       }
     },
+    "node_modules/@types/d3-array": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz",
+      "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
+    },
+    "node_modules/@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
+    },
+    "node_modules/@types/d3-ease": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+      "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
+    },
+    "node_modules/@types/d3-interpolate": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+      "dependencies": {
+        "@types/d3-color": "*"
+      }
+    },
+    "node_modules/@types/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
+    },
+    "node_modules/@types/d3-scale": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.8.tgz",
+      "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
+      "dependencies": {
+        "@types/d3-time": "*"
+      }
+    },
+    "node_modules/@types/d3-shape": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.6.tgz",
+      "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
+      "dependencies": {
+        "@types/d3-path": "*"
+      }
+    },
+    "node_modules/@types/d3-time": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.3.tgz",
+      "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
+    },
+    "node_modules/@types/d3-timer": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+      "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
+    },
     "node_modules/@types/debug": {
       "version": "4.1.12",
       "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.12.tgz",
@@ -2884,6 +2941,15 @@
         "csstype": "^3.0.2"
       }
     },
+    "node_modules/@types/react-copy-to-clipboard": {
+      "version": "5.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.7.tgz",
+      "integrity": "sha512-Gft19D+as4M+9Whq1oglhmK49vqPhcLzk8WfvfLvaYMIPYanyfLy0+CwFucMJfdKoSFyySPmkkWn8/E6voQXjQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/react-dom": {
       "version": "18.2.18",
       "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.18.tgz",
@@ -5832,6 +5898,14 @@
         "node": ">=12"
       }
     },
+    "node_modules/clsx": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.0.tgz",
+      "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/coa": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/coa/-/coa-2.0.2.tgz",
@@ -6640,11 +6714,132 @@
       "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-1.2.4.tgz",
       "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
     },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-ease": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz",
+      "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-format": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-format/-/d3-format-3.1.0.tgz",
+      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/d3-polygon": {
       "version": "1.0.6",
       "resolved": "https://registry.npmmirror.com/d3-polygon/-/d3-polygon-1.0.6.tgz",
       "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ=="
     },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale/node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "dependencies": {
+        "d3-time": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time/node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/data-uri-to-buffer": {
       "version": "4.0.1",
       "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@@ -6705,6 +6900,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/decimal.js-light": {
+      "version": "2.5.1",
+      "resolved": "https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+      "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
+    },
     "node_modules/decode-named-character-reference": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
@@ -7032,6 +7232,15 @@
         "utila": "~0.4"
       }
     },
+    "node_modules/dom-helpers": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmmirror.com/dom-helpers/-/dom-helpers-5.2.1.tgz",
+      "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+      "dependencies": {
+        "@babel/runtime": "^7.8.7",
+        "csstype": "^3.0.2"
+      }
+    },
     "node_modules/dom-serializer": {
       "version": "1.4.1",
       "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz",
@@ -8151,6 +8360,11 @@
         "es5-ext": "~0.10.14"
       }
     },
+    "node_modules/eventemitter3": {
+      "version": "4.0.7",
+      "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
+      "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+    },
     "node_modules/events": {
       "version": "3.3.0",
       "resolved": "https://registry.npmmirror.com/events/-/events-3.3.0.tgz",
@@ -8356,6 +8570,14 @@
       "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
+    "node_modules/fast-equals": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-5.0.1.tgz",
+      "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
     "node_modules/fast-glob": {
       "version": "3.2.12",
       "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz",
@@ -9693,6 +9915,14 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/intersection-observer": {
       "version": "0.12.2",
       "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz",
@@ -11925,6 +12155,7 @@
       "version": "2.30.1",
       "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
       "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+      "devOptional": true,
       "engines": {
         "node": "*"
       }
@@ -14356,6 +14587,18 @@
         "react-dom": "18.2.0"
       }
     },
+    "node_modules/react-copy-to-clipboard": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
+      "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==",
+      "dependencies": {
+        "copy-to-clipboard": "^3.3.1",
+        "prop-types": "^15.8.1"
+      },
+      "peerDependencies": {
+        "react": "^15.3.0 || 16 || 17 || 18"
+      }
+    },
     "node_modules/react-dev-inspector": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/react-dev-inspector/-/react-dev-inspector-2.0.1.tgz",
@@ -14934,6 +15177,20 @@
         "react": ">=15"
       }
     },
+    "node_modules/react-smooth": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmmirror.com/react-smooth/-/react-smooth-4.0.1.tgz",
+      "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==",
+      "dependencies": {
+        "fast-equals": "^5.0.1",
+        "prop-types": "^15.8.1",
+        "react-transition-group": "^4.4.5"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/react-spinkit": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/react-spinkit/-/react-spinkit-3.0.0.tgz",
@@ -14968,6 +15225,21 @@
         "react": ">= 0.14.0"
       }
     },
+    "node_modules/react-transition-group": {
+      "version": "4.4.5",
+      "resolved": "https://registry.npmmirror.com/react-transition-group/-/react-transition-group-4.4.5.tgz",
+      "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+      "dependencies": {
+        "@babel/runtime": "^7.5.5",
+        "dom-helpers": "^5.0.1",
+        "loose-envify": "^1.4.0",
+        "prop-types": "^15.6.2"
+      },
+      "peerDependencies": {
+        "react": ">=16.6.0",
+        "react-dom": ">=16.6.0"
+      }
+    },
     "node_modules/reactcss": {
       "version": "1.2.3",
       "resolved": "https://registry.npmmirror.com/reactcss/-/reactcss-1.2.3.tgz",
@@ -15145,6 +15417,41 @@
         "node": ">= 12.13.0"
       }
     },
+    "node_modules/recharts": {
+      "version": "2.12.4",
+      "resolved": "https://registry.npmmirror.com/recharts/-/recharts-2.12.4.tgz",
+      "integrity": "sha512-dM4skmk4fDKEDjL9MNunxv6zcTxePGVEzRnLDXALRpfJ85JoQ0P0APJ/CoJlmnQI0gPjBlOkjzrwrfQrRST3KA==",
+      "dependencies": {
+        "clsx": "^2.0.0",
+        "eventemitter3": "^4.0.1",
+        "lodash": "^4.17.21",
+        "react-is": "^16.10.2",
+        "react-smooth": "^4.0.0",
+        "recharts-scale": "^0.4.4",
+        "tiny-invariant": "^1.3.1",
+        "victory-vendor": "^36.6.8"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "peerDependencies": {
+        "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+        "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/recharts-scale": {
+      "version": "0.4.5",
+      "resolved": "https://registry.npmmirror.com/recharts-scale/-/recharts-scale-0.4.5.tgz",
+      "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+      "dependencies": {
+        "decimal.js-light": "^2.4.1"
+      }
+    },
+    "node_modules/recharts/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/recursive-readdir": {
       "version": "2.2.3",
       "resolved": "https://registry.npmmirror.com/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
@@ -17000,9 +17307,7 @@
     "node_modules/tiny-invariant": {
       "version": "1.3.1",
       "resolved": "https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
-      "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
-      "dev": true,
-      "peer": true
+      "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
     },
     "node_modules/tiny-warning": {
       "version": "1.0.3",
@@ -18221,6 +18526,38 @@
         "unist-util-stringify-position": "^4.0.0"
       }
     },
+    "node_modules/victory-vendor": {
+      "version": "36.9.2",
+      "resolved": "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-36.9.2.tgz",
+      "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+      "dependencies": {
+        "@types/d3-array": "^3.0.3",
+        "@types/d3-ease": "^3.0.0",
+        "@types/d3-interpolate": "^3.0.1",
+        "@types/d3-scale": "^4.0.2",
+        "@types/d3-shape": "^3.1.0",
+        "@types/d3-time": "^3.0.0",
+        "@types/d3-timer": "^3.0.0",
+        "d3-array": "^3.1.6",
+        "d3-ease": "^3.0.1",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "d3-shape": "^3.1.0",
+        "d3-time": "^3.0.0",
+        "d3-timer": "^3.0.1"
+      }
+    },
+    "node_modules/victory-vendor/node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/vite": {
       "version": "4.3.1",
       "resolved": "https://registry.npmmirror.com/vite/-/vite-4.3.1.tgz",
diff --git a/web/package.json b/web/package.json
index 48a60b8d218a2682be0f6be8a8d7dc456c0b2e0b..861d96b17d0c2bc2f798b1b0886d7b79a13c0a77 100644
--- a/web/package.json
+++ b/web/package.json
@@ -17,19 +17,21 @@
     "antd": "^5.12.7",
     "axios": "^1.6.3",
     "classnames": "^2.5.1",
+    "dayjs": "^1.11.10",
     "i18next": "^23.7.16",
     "js-base64": "^3.7.5",
     "jsencrypt": "^3.3.2",
     "lodash": "^4.17.21",
-    "moment": "^2.30.1",
     "rc-tween-one": "^3.0.6",
     "react-chat-elements": "^12.0.13",
+    "react-copy-to-clipboard": "^5.1.0",
     "react-i18next": "^14.0.0",
     "react-infinite-scroll-component": "^6.1.0",
     "react-markdown": "^9.0.1",
     "react-pdf-highlighter": "^6.1.0",
     "react-string-replace": "^1.1.1",
     "react-syntax-highlighter": "^15.5.0",
+    "recharts": "^2.12.4",
     "remark-gfm": "^4.0.0",
     "umi": "^4.0.90",
     "umi-request": "^1.4.0",
@@ -40,6 +42,7 @@
     "@react-dev-inspector/umi4-plugin": "^2.0.1",
     "@types/lodash": "^4.14.202",
     "@types/react": "^18.0.33",
+    "@types/react-copy-to-clipboard": "^5.0.7",
     "@types/react-dom": "^18.0.11",
     "@types/react-syntax-highlighter": "^15.5.11",
     "@types/uuid": "^9.0.8",
diff --git a/web/src/app.tsx b/web/src/app.tsx
index e80c9b922e1176517521980557c1a7d54e967ca2..ff532b1d359056b5478b9895609a29c786166829 100644
--- a/web/src/app.tsx
+++ b/web/src/app.tsx
@@ -6,6 +6,21 @@ import zh_HK from 'antd/locale/zh_HK';
 import React, { ReactNode, useEffect, useState } from 'react';
 import storage from './utils/authorizationUtil';
 
+import dayjs from 'dayjs';
+import advancedFormat from 'dayjs/plugin/advancedFormat';
+import customParseFormat from 'dayjs/plugin/customParseFormat';
+import localeData from 'dayjs/plugin/localeData';
+import weekday from 'dayjs/plugin/weekday';
+import weekOfYear from 'dayjs/plugin/weekOfYear';
+import weekYear from 'dayjs/plugin/weekYear';
+
+dayjs.extend(customParseFormat);
+dayjs.extend(advancedFormat);
+dayjs.extend(weekday);
+dayjs.extend(localeData);
+dayjs.extend(weekOfYear);
+dayjs.extend(weekYear);
+
 const AntLanguageMap = {
   en: enUS,
   zh: zhCN,
diff --git a/web/src/components/copy-to-clipboard.tsx b/web/src/components/copy-to-clipboard.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ed2af3a2a68f22823030cdd7009c4008d0f32043
--- /dev/null
+++ b/web/src/components/copy-to-clipboard.tsx
@@ -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;
diff --git a/web/src/components/line-chart/index.tsx b/web/src/components/line-chart/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c032206f01f9eec1bf43ad07dd3fc49b57c1973
--- /dev/null
+++ b/web/src/components/line-chart/index.tsx
@@ -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;
diff --git a/web/src/hooks/chatHooks.ts b/web/src/hooks/chatHooks.ts
index 7bf9a2b9f382665d03280a7c9a8009c6eb983cfc..90d419d214935b9ead7e08caedd9eb28d90e4f69 100644
--- a/web/src/hooks/chatHooks.ts
+++ b/web/src/hooks/chatHooks.ts
@@ -1,4 +1,9 @@
-import { IConversation, IDialog } from '@/interfaces/database/chat';
+import {
+  IConversation,
+  IDialog,
+  IStats,
+  IToken,
+} from '@/interfaces/database/chat';
 import { useCallback } from 'react';
 import { useDispatch, useSelector } from 'umi';
 
@@ -164,3 +169,82 @@ export const useCompleteConversation = () => {
 
   return completeConversation;
 };
+
+// #region API provided for external calls
+
+export const useCreateToken = (dialogId: string) => {
+  const dispatch = useDispatch();
+
+  const createToken = useCallback(() => {
+    return dispatch<any>({
+      type: 'chatModel/createToken',
+      payload: { dialogId },
+    });
+  }, [dispatch, dialogId]);
+
+  return createToken;
+};
+
+export const useListToken = () => {
+  const dispatch = useDispatch();
+
+  const listToken = useCallback(
+    (dialogId: string) => {
+      return dispatch<any>({
+        type: 'chatModel/listToken',
+        payload: { dialogId },
+      });
+    },
+    [dispatch],
+  );
+
+  return listToken;
+};
+
+export const useSelectTokenList = () => {
+  const tokenList: IToken[] = useSelector(
+    (state: any) => state.chatModel.tokenList,
+  );
+
+  return tokenList;
+};
+
+export const useRemoveToken = () => {
+  const dispatch = useDispatch();
+
+  const removeToken = useCallback(
+    (payload: { tenantId: string; dialogId: string; tokens: string[] }) => {
+      return dispatch<any>({
+        type: 'chatModel/removeToken',
+        payload: payload,
+      });
+    },
+    [dispatch],
+  );
+
+  return removeToken;
+};
+
+export const useFetchStats = () => {
+  const dispatch = useDispatch();
+
+  const fetchStats = useCallback(
+    (payload: any) => {
+      return dispatch<any>({
+        type: 'chatModel/getStats',
+        payload,
+      });
+    },
+    [dispatch],
+  );
+
+  return fetchStats;
+};
+
+export const useSelectStats = () => {
+  const stats: IStats = useSelector((state: any) => state.chatModel.stats);
+
+  return stats;
+};
+
+//#endregion
diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts
index 76b590912ab0203a7aa226bda6db6a77d12b8690..a2c53032e8e63b04ee00d74f5004250fd9241410 100644
--- a/web/src/interfaces/database/chat.ts
+++ b/web/src/interfaces/database/chat.ts
@@ -91,3 +91,21 @@ export interface Docagg {
 //   term_similarity: number;
 //   vector_similarity: number;
 // }
+
+export interface IToken {
+  create_date: string;
+  create_time: number;
+  tenant_id: string;
+  token: string;
+  update_date?: any;
+  update_time?: any;
+}
+
+export interface IStats {
+  pv: [string, number][];
+  uv: [string, number][];
+  speed: [string, number][];
+  tokens: [string, number][];
+  round: [string, number][];
+  thumb_up: [string, number][];
+}
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index 430250bde2e236e97fc3e5a3f09fd63a60604ad7..07c61f423f7b4896964daca14fdc4b0db1b640b6 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -20,6 +20,8 @@ export default {
       language: 'Language',
       languageMessage: 'Please input your language!',
       languagePlaceholder: 'select your language',
+      copy: 'Copy',
+      copied: 'Copied',
     },
     login: {
       login: 'Sign in',
@@ -335,6 +337,24 @@ export default {
         'This sets the maximum length of the model’s output, measured in the number of tokens (words or pieces of words).',
       quote: 'Show Quote',
       quoteTip: 'Should the source of the original text be displayed?',
+      overview: 'Overview',
+      pv: 'Number of messages',
+      uv: 'Active user number',
+      speed: 'Token output speed',
+      tokens: 'Consume the token number',
+      round: 'Session Interaction Number',
+      thumbUp: 'customer satisfaction',
+      publicUrl: 'Public URL',
+      preview: 'Preview',
+      embedded: 'Embedded',
+      serviceApiEndpoint: 'Service API Endpoint',
+      apiKey: 'Api Key',
+      apiReference: 'Api Reference',
+      dateRange: 'Date Range:',
+      backendServiceApi: 'Backend service API',
+      createNewKey: 'Create new key',
+      created: 'Created',
+      action: 'Action',
     },
     setting: {
       profile: 'Profile',
diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts
index b6af1989669a8aa5196f4c92071bdc8a49a191ac..6ea310d8155503801e27a0b78ab4937e743e9940 100644
--- a/web/src/locales/zh-traditional.ts
+++ b/web/src/locales/zh-traditional.ts
@@ -15,11 +15,13 @@ export default {
       edit: '編輯',
       upload: '上傳',
       english: '英語',
-      chinese: '中文簡體',
-      traditionalChinese: '中文繁體',
+      chinese: '簡體中文',
+      traditionalChinese: '繁體中文',
       language: '語言',
       languageMessage: '請輸入語言',
       languagePlaceholder: '請選擇語言',
+      copy: '複製',
+      copied: '複製成功',
     },
     login: {
       login: '登入',
@@ -269,7 +271,7 @@ export default {
       systemMessage: '請輸入',
       systemTip:
         '當LLM回答問題時,你需要LLM遵循的說明,比如角色設計、答案長度和答案語言等。',
-      topN: 'top n',
+      topN: 'Top N',
       topNTip: `並非所有相似度得分高於“相似度閾值”的塊都會被提供給法學碩士。LLM 只能看到這些“Top N”塊。`,
       variable: '變量',
       variableTip: `如果您使用对话 API,变量可能会帮助您使用不同的策略与客户聊天。
@@ -310,6 +312,24 @@ export default {
         '這設置了模型輸出的最大長度,以標記(單詞或單詞片段)的數量來衡量。',
       quote: '顯示引文',
       quoteTip: '是否應該顯示原文出處?',
+      overview: '概覽',
+      pv: '消息數',
+      uv: '活躍用戶數',
+      speed: 'Token 輸出速度',
+      tokens: '消耗Token數',
+      round: '會話互動數',
+      thumbUp: '用戶滿意度',
+      publicUrl: '公共url',
+      preview: '預覽',
+      embedded: '嵌入',
+      serviceApiEndpoint: '服務API端點',
+      apiKey: 'API鍵',
+      apiReference: 'API參考',
+      dateRange: '日期範圍:',
+      backendServiceApi: '後端服務API',
+      createNewKey: '創建新密鑰',
+      created: '創建於',
+      action: '操作',
     },
     setting: {
       profile: '概述',
diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts
index e2bbd04828ec1f339ec74f243eb4f5f837e3a7ef..bf97cbf41aebd9571af5f23ab8fbcf1d1223c875 100644
--- a/web/src/locales/zh.ts
+++ b/web/src/locales/zh.ts
@@ -15,11 +15,13 @@ export default {
       edit: '编辑',
       upload: '上传',
       english: '英文',
-      chinese: '中文简体',
-      traditionalChinese: '中文繁体',
+      chinese: '简体中文',
+      traditionalChinese: '繁体中文',
       language: '语言',
       languageMessage: '请输入语言',
       languagePlaceholder: '请选择语言',
+      copy: '复制',
+      copied: '复制成功',
     },
     login: {
       login: '登录',
@@ -326,6 +328,24 @@ export default {
         '这设置了模型输出的最大长度,以标记(单词或单词片段)的数量来衡量。',
       quote: '显示引文',
       quoteTip: '是否应该显示原文出处?',
+      overview: '概览',
+      pv: '消息数',
+      uv: '活跃用户数',
+      speed: 'Token 输出速度',
+      tokens: '消耗Token数',
+      round: '会话互动数',
+      thumbUp: '用户满意度',
+      publicUrl: '公共Url',
+      preview: '预览',
+      embedded: '嵌入',
+      serviceApiEndpoint: '服务API端点',
+      apiKey: 'APIé”®',
+      apiReference: 'API参考',
+      dateRange: '日期范围:',
+      backendServiceApi: '后端服务API',
+      createNewKey: '创建新密钥',
+      created: '创建于',
+      action: '操作',
     },
     setting: {
       profile: '概要',
diff --git a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx
index 765d67bc866c22d74b1136d5cb5e3867e3eebe5c..a6bd7584d5b546e56b0b8984eae731eb80f71ee7 100644
--- a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx
@@ -26,6 +26,7 @@ import ParsingActionCell from './parsing-action-cell';
 import ParsingStatusCell from './parsing-status-cell';
 import RenameModal from './rename-modal';
 
+import { formatDate } from '@/utils/date';
 import styles from './index.less';
 
 const KnowledgeFile = () => {
@@ -94,6 +95,9 @@ const KnowledgeFile = () => {
       title: t('uploadDate'),
       dataIndex: 'create_date',
       key: 'create_date',
+      render(value) {
+        return formatDate(value);
+      },
     },
     {
       title: t('chunkMethod'),
diff --git a/web/src/pages/chat/chat-api-key-modal/index.tsx b/web/src/pages/chat/chat-api-key-modal/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b4545a40aaa52c4fbd72bc47e2fda90c015dd105
--- /dev/null
+++ b/web/src/pages/chat/chat-api-key-modal/index.tsx
@@ -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;
diff --git a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
index 0f4ba9201028cc0f6facffe0a7c4e38035e3cae6..fdaa08c29993fcd257b985d9be1409cf7ca34553 100644
--- a/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/assistant-setting.tsx
@@ -1,6 +1,6 @@
 import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
 import { PlusOutlined } from '@ant-design/icons';
-import { Form, Input, Select, Upload } from 'antd';
+import { Form, Input, Select, Switch, Upload } from 'antd';
 import classNames from 'classnames';
 import { ISegmentedContentProps } from '../interface';
 
@@ -83,6 +83,15 @@ const AssistantSetting = ({ show }: ISegmentedContentProps) => {
       >
         <Input.TextArea autoSize={{ minRows: 5 }} />
       </Form.Item>
+      <Form.Item
+        label={t('quote')}
+        valuePropName="checked"
+        name={['prompt_config', 'quote']}
+        tooltip={t('quoteTip')}
+        initialValue={true}
+      >
+        <Switch />
+      </Form.Item>
       <Form.Item
         label={t('knowledgeBases')}
         name="kb_ids"
diff --git a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
index 43d8c530e8963995eab5406faaa5b033b045af7b..15af1b62c1e8194bc5040bf51c67e6a3df8c8990 100644
--- a/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
+++ b/web/src/pages/chat/chat-configuration-modal/prompt-engine.tsx
@@ -172,15 +172,7 @@ const PromptEngine = (
       >
         <Slider max={30} />
       </Form.Item>
-      <Form.Item
-        label={t('quote')}
-        valuePropName="checked"
-        name={['prompt_config', 'quote']}
-        tooltip={t('quoteTip')}
-        initialValue={true}
-      >
-        <Switch />
-      </Form.Item>
+
       <section className={classNames(styles.variableContainer)}>
         <Row align={'middle'} justify="end">
           <Col span={7} className={styles.variableAlign}>
diff --git a/web/src/pages/chat/chat-overview-modal/index.less b/web/src/pages/chat/chat-overview-modal/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..1a376715df0f600885da17126d7fffbd0496376b
--- /dev/null
+++ b/web/src/pages/chat/chat-overview-modal/index.less
@@ -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;
+}
diff --git a/web/src/pages/chat/chat-overview-modal/index.tsx b/web/src/pages/chat/chat-overview-modal/index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9d3336dc5424163868f51b7c8a6b853b8d775b77
--- /dev/null
+++ b/web/src/pages/chat/chat-overview-modal/index.tsx
@@ -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;
diff --git a/web/src/pages/chat/hooks.ts b/web/src/pages/chat/hooks.ts
index b4cf9ca207daead3d4cc11b10f32cb9ec8751e1f..10100b48ce0f7997b9fb6a835d9aec554efdaff6 100644
--- a/web/src/pages/chat/hooks.ts
+++ b/web/src/pages/chat/hooks.ts
@@ -2,22 +2,28 @@ import { MessageType } from '@/constants/chat';
 import { fileIconMap } from '@/constants/common';
 import {
   useCompleteConversation,
+  useCreateToken,
   useFetchConversation,
   useFetchConversationList,
   useFetchDialog,
   useFetchDialogList,
+  useFetchStats,
+  useListToken,
   useRemoveConversation,
   useRemoveDialog,
+  useRemoveToken,
   useSelectConversationList,
   useSelectDialogList,
+  useSelectTokenList,
   useSetDialog,
   useUpdateConversation,
 } from '@/hooks/chatHooks';
 import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks';
 import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
-import { IConversation, IDialog } from '@/interfaces/database/chat';
+import { IConversation, IDialog, IStats } from '@/interfaces/database/chat';
 import { IChunk } from '@/interfaces/database/knowledge';
 import { getFileExtension } from '@/utils';
+import dayjs, { Dayjs } from 'dayjs';
 import omit from 'lodash/omit';
 import {
   ChangeEventHandler,
@@ -704,3 +710,108 @@ export const useGetSendButtonDisabled = () => {
   return dialogId === '' && conversationId === '';
 };
 //#endregion
+
+//#region API provided for external calls
+
+type RangeValue = [Dayjs | null, Dayjs | null] | null;
+
+export const useFetchStatsOnMount = (visible: boolean) => {
+  const fetchStats = useFetchStats();
+  const [pickerValue, setPickerValue] = useState<RangeValue>([
+    dayjs(),
+    dayjs().subtract(7, 'day'),
+  ]);
+
+  useEffect(() => {
+    if (visible && Array.isArray(pickerValue) && pickerValue[0]) {
+      fetchStats({ fromDate: pickerValue[0], toDate: pickerValue[1] });
+    }
+  }, [fetchStats, pickerValue, visible]);
+
+  return {
+    pickerValue,
+    setPickerValue,
+  };
+};
+
+export const useOperateApiKey = (visible: boolean, dialogId: string) => {
+  const removeToken = useRemoveToken();
+  const createToken = useCreateToken(dialogId);
+  const listToken = useListToken();
+  const tokenList = useSelectTokenList();
+  const creatingLoading = useOneNamespaceEffectsLoading('chatModel', [
+    'createToken',
+  ]);
+  const listLoading = useOneNamespaceEffectsLoading('chatModel', ['list']);
+
+  const showDeleteConfirm = useShowDeleteConfirm();
+
+  const onRemoveToken = (token: string, tenantId: string) => {
+    showDeleteConfirm({
+      onOk: () => removeToken({ dialogId, tokens: [token], tenantId }),
+    });
+  };
+
+  useEffect(() => {
+    if (visible && dialogId) {
+      listToken(dialogId);
+    }
+  }, [listToken, dialogId, visible]);
+
+  return {
+    removeToken: onRemoveToken,
+    createToken,
+    tokenList,
+    creatingLoading,
+    listLoading,
+  };
+};
+
+type ChartStatsType = {
+  [k in keyof IStats]: Array<{ xAxis: string; yAxis: number }>;
+};
+
+export const useSelectChartStatsList = (): ChartStatsType => {
+  // const stats: IStats = useSelectStats();
+  const stats = {
+    pv: [
+      ['2024-06-01', 1],
+      ['2024-07-24', 3],
+      ['2024-09-01', 10],
+    ],
+    uv: [
+      ['2024-02-01', 0],
+      ['2024-03-01', 99],
+      ['2024-05-01', 3],
+    ],
+    speed: [
+      ['2024-09-01', 2],
+      ['2024-09-01', 3],
+    ],
+    tokens: [
+      ['2024-09-01', 1],
+      ['2024-09-01', 3],
+    ],
+    round: [
+      ['2024-09-01', 0],
+      ['2024-09-01', 3],
+    ],
+    thumb_up: [
+      ['2024-09-01', 3],
+      ['2024-09-01', 9],
+    ],
+  };
+
+  return Object.keys(stats).reduce((pre, cur) => {
+    const item = stats[cur as keyof IStats];
+    if (item.length > 0) {
+      pre[cur as keyof IStats] = item.map((x) => ({
+        xAxis: x[0] as string,
+        yAxis: x[1] as number,
+      }));
+    }
+    return pre;
+  }, {} as ChartStatsType);
+};
+
+//#endregion
diff --git a/web/src/pages/chat/index.tsx b/web/src/pages/chat/index.tsx
index 38f826c9a45dabdf82e1017c622b523c5fc70880..b07897bc18eed52f7a7468ab9282542d6633d377 100644
--- a/web/src/pages/chat/index.tsx
+++ b/web/src/pages/chat/index.tsx
@@ -35,7 +35,10 @@ import {
   useSelectFirstDialogOnMount,
 } from './hooks';
 
-import { useTranslate } from '@/hooks/commonHooks';
+import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
+import { useSetSelectedRecord } from '@/hooks/logicHooks';
+import { IDialog } from '@/interfaces/database/chat';
+import ChatOverviewModal from './chat-overview-modal';
 import styles from './index.less';
 
 const Chat = () => {
@@ -73,6 +76,12 @@ const Chat = () => {
   const dialogLoading = useSelectDialogListLoading();
   const conversationLoading = useSelectConversationListLoading();
   const { t } = useTranslate('chat');
+  const {
+    visible: overviewVisible,
+    hideModal: hideOverviewModal,
+    showModal: showOverviewModal,
+  } = useSetModalState();
+  const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
 
   useFetchDialogOnMount(dialogId, true);
 
@@ -100,6 +109,15 @@ const Chat = () => {
       onRemoveDialog([dialogId]);
     };
 
+  const handleShowOverviewModal =
+    (dialog: IDialog): any =>
+    (info: any) => {
+      info?.domEvent?.preventDefault();
+      info?.domEvent?.stopPropagation();
+      setRecord(dialog);
+      showOverviewModal();
+    };
+
   const handleRemoveConversation =
     (conversationId: string): MenuItemProps['onClick'] =>
     ({ domEvent }) => {
@@ -141,7 +159,9 @@ const Chat = () => {
     },
   ];
 
-  const buildAppItems = (dialogId: string) => {
+  const buildAppItems = (dialog: IDialog) => {
+    const dialogId = dialog.id;
+
     const appItems: MenuProps['items'] = [
       {
         key: '1',
@@ -164,6 +184,17 @@ const Chat = () => {
           </Space>
         ),
       },
+      { type: 'divider' },
+      // {
+      //   key: '3',
+      //   onClick: handleShowOverviewModal(dialog),
+      //   label: (
+      //     <Space>
+      //       <ProfileOutlined />
+      //       {t('overview')}
+      //     </Space>
+      //   ),
+      // },
     ];
 
     return appItems;
@@ -230,7 +261,7 @@ const Chat = () => {
                     </Space>
                     {activated === x.id && (
                       <section>
-                        <Dropdown menu={{ items: buildAppItems(x.id) }}>
+                        <Dropdown menu={{ items: buildAppItems(x) }}>
                           <ChatAppCube
                             className={styles.cubeIcon}
                           ></ChatAppCube>
@@ -315,6 +346,11 @@ const Chat = () => {
         initialName={initialConversationName}
         loading={conversationRenameLoading}
       ></RenameModal>
+      <ChatOverviewModal
+        visible={overviewVisible}
+        hideModal={hideOverviewModal}
+        dialog={currentRecord}
+      ></ChatOverviewModal>
     </Flex>
   );
 };
diff --git a/web/src/pages/chat/model.ts b/web/src/pages/chat/model.ts
index 51eb71110e50c72593677be92156fa2aa17f65b0..c2498a012de0c90ef170ee4e22cf865e14ebb772 100644
--- a/web/src/pages/chat/model.ts
+++ b/web/src/pages/chat/model.ts
@@ -1,7 +1,14 @@
-import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
+import {
+  IConversation,
+  IDialog,
+  IStats,
+  IToken,
+  Message,
+} from '@/interfaces/database/chat';
 import i18n from '@/locales/config';
 import chatService from '@/services/chatService';
 import { message } from 'antd';
+import omit from 'lodash/omit';
 import { DvaModel } from 'umi';
 import { v4 as uuid } from 'uuid';
 import { IClientConversation, IMessage } from './interface';
@@ -13,6 +20,8 @@ export interface ChatModelState {
   currentDialog: IDialog;
   conversationList: IConversation[];
   currentConversation: IClientConversation;
+  tokenList: IToken[];
+  stats: IStats;
 }
 
 const model: DvaModel<ChatModelState> = {
@@ -23,6 +32,8 @@ const model: DvaModel<ChatModelState> = {
     currentDialog: <IDialog>{},
     conversationList: [],
     currentConversation: {} as IClientConversation,
+    tokenList: [],
+    stats: {} as IStats,
   },
   reducers: {
     save(state, action) {
@@ -60,6 +71,18 @@ const model: DvaModel<ChatModelState> = {
         currentConversation: { ...payload, message: messageList },
       };
     },
+    setTokenList(state, { payload }) {
+      return {
+        ...state,
+        tokenList: payload,
+      };
+    },
+    setStats(state, { payload }) {
+      return {
+        ...state,
+        stats: payload,
+      };
+    },
   },
 
   effects: {
@@ -160,6 +183,78 @@ const model: DvaModel<ChatModelState> = {
       }
       return data.retcode;
     },
+    *createToken({ payload }, { call, put }) {
+      const { data } = yield call(chatService.createToken, payload);
+      if (data.retcode === 0) {
+        yield put({
+          type: 'listToken',
+          payload: payload,
+        });
+        message.success(i18n.t('message.created'));
+      }
+      return data.retcode;
+    },
+    *listToken({ payload }, { call, put }) {
+      const { data } = yield call(chatService.listToken, payload);
+      if (data.retcode === 0) {
+        yield put({
+          type: 'setTokenList',
+          payload: data.data,
+        });
+      }
+      return data.retcode;
+    },
+    *removeToken({ payload }, { call, put }) {
+      const { data } = yield call(
+        chatService.removeToken,
+        omit(payload, ['dialogId']),
+      );
+      if (data.retcode === 0) {
+        yield put({
+          type: 'listToken',
+          payload: { dialog_id: payload.dialogId },
+        });
+      }
+      return data.retcode;
+    },
+    *getStats({ payload }, { call, put }) {
+      const { data } = yield call(chatService.getStats, payload);
+      if (data.retcode === 0) {
+        yield put({
+          type: 'setStats',
+          payload: data.data,
+        });
+      }
+      return data.retcode;
+    },
+    *createExternalConversation({ payload }, { call, put }) {
+      const { data } = yield call(
+        chatService.createExternalConversation,
+        payload,
+      );
+      if (data.retcode === 0) {
+        yield put({
+          type: 'getExternalConversation',
+          payload: { conversation_id: payload.conversationId },
+        });
+      }
+      return data.retcode;
+    },
+    *getExternalConversation({ payload }, { call }) {
+      const { data } = yield call(
+        chatService.getExternalConversation,
+        null,
+        payload,
+      );
+      return data.retcode;
+    },
+    *completeExternalConversation({ payload }, { call }) {
+      const { data } = yield call(
+        chatService.completeExternalConversation,
+        payload,
+      );
+      return data.retcode;
+    },
   },
 };
 
diff --git a/web/src/services/chatService.ts b/web/src/services/chatService.ts
index e2d8d37829f0f013a7d0bcac597d5ed335f879e8..0b4567560e801586750343e70438361db2b5e28d 100644
--- a/web/src/services/chatService.ts
+++ b/web/src/services/chatService.ts
@@ -12,6 +12,13 @@ const {
   completeConversation,
   listConversation,
   removeConversation,
+  createToken,
+  listToken,
+  removeToken,
+  getStats,
+  createExternalConversation,
+  getExternalConversation,
+  completeExternalConversation,
 } = api;
 
 const methods = {
@@ -51,6 +58,34 @@ const methods = {
     url: removeConversation,
     method: 'post',
   },
+  createToken: {
+    url: createToken,
+    method: 'post',
+  },
+  listToken: {
+    url: listToken,
+    method: 'get',
+  },
+  removeToken: {
+    url: removeToken,
+    method: 'post',
+  },
+  getStats: {
+    url: getStats,
+    method: 'get',
+  },
+  createExternalConversation: {
+    url: createExternalConversation,
+    method: 'post',
+  },
+  getExternalConversation: {
+    url: getExternalConversation,
+    method: 'get',
+  },
+  completeExternalConversation: {
+    url: completeExternalConversation,
+    method: 'post',
+  },
 } as const;
 
 const chatService = registerServer<keyof typeof methods>(methods, request);
diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts
index 54b1b1263abcf27bbf2adc8a06790306a15197f6..f404eceeae2c8fd51df4fc656eb78d50de2e7408 100644
--- a/web/src/utils/api.ts
+++ b/web/src/utils/api.ts
@@ -3,7 +3,7 @@ let api_host = `/v1`;
 export { api_host };
 
 export default {
-  // 用户
+  // user
   login: `${api_host}/user/login`,
   logout: `${api_host}/user/logout`,
   register: `${api_host}/user/register`,
@@ -12,21 +12,21 @@ export default {
   tenant_info: `${api_host}/user/tenant_info`,
   set_tenant_info: `${api_host}/user/set_tenant_info`,
 
-  // 模型管理
+  // llm model
   factories_list: `${api_host}/llm/factories`,
   llm_list: `${api_host}/llm/list`,
   my_llm: `${api_host}/llm/my_llms`,
   set_api_key: `${api_host}/llm/set_api_key`,
   add_llm: `${api_host}/llm/add_llm`,
 
-  //知识库管理
+  // knowledge base
   kb_list: `${api_host}/kb/list`,
   create_kb: `${api_host}/kb/create`,
   update_kb: `${api_host}/kb/update`,
   rm_kb: `${api_host}/kb/rm`,
   get_kb_detail: `${api_host}/kb/detail`,
 
-  // chunk管理
+  // chunk
   chunk_list: `${api_host}/chunk/list`,
   create_chunk: `${api_host}/chunk/create`,
   set_chunk: `${api_host}/chunk/set`,
@@ -35,7 +35,7 @@ export default {
   rm_chunk: `${api_host}/chunk/rm`,
   retrieval_test: `${api_host}/chunk/retrieval_test`,
 
-  // 文件管理
+  // document
   upload: `${api_host}/document/upload`,
   get_document_list: `${api_host}/document/list`,
   document_change_status: `${api_host}/document/change_status`,
@@ -48,14 +48,22 @@ export default {
   get_document_file: `${api_host}/document/get`,
   document_upload: `${api_host}/document/upload`,
 
+  // chat
   setDialog: `${api_host}/dialog/set`,
   getDialog: `${api_host}/dialog/get`,
   removeDialog: `${api_host}/dialog/rm`,
   listDialog: `${api_host}/dialog/list`,
-
   setConversation: `${api_host}/conversation/set`,
   getConversation: `${api_host}/conversation/get`,
   listConversation: `${api_host}/conversation/list`,
   removeConversation: `${api_host}/conversation/rm`,
   completeConversation: `${api_host}/conversation/completion`,
+  // chat for external
+  createToken: `${api_host}/api/new_token`,
+  listToken: `${api_host}/api/token_list`,
+  removeToken: `${api_host}/api/rm`,
+  getStats: `${api_host}/api/stats`,
+  createExternalConversation: `${api_host}/api/new_conversation`,
+  getExternalConversation: `${api_host}/api/conversation`,
+  completeExternalConversation: `${api_host}/api/completion`,
 };
diff --git a/web/src/utils/commonUtil.ts b/web/src/utils/commonUtil.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9263e99d019bc81e8e248cbc6bb52a8440f53916
--- /dev/null
+++ b/web/src/utils/commonUtil.ts
@@ -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;
+};
diff --git a/web/src/utils/date.ts b/web/src/utils/date.ts
index dc9272d3017bd20d15da1e55fd6f2ca6414d8b2d..90aee0aa46752b75f777827324315be42cbf248b 100644
--- a/web/src/utils/date.ts
+++ b/web/src/utils/date.ts
@@ -1,20 +1,20 @@
-import moment from 'moment';
+import dayjs from 'dayjs';
 
 export function today() {
-  return formatDate(moment());
+  return formatDate(dayjs());
 }
 
 export function lastDay() {
-  return formatDate(moment().subtract(1, 'days'));
+  return formatDate(dayjs().subtract(1, 'days'));
 }
 
 export function lastWeek() {
-  return formatDate(moment().subtract(1, 'weeks'));
+  return formatDate(dayjs().subtract(1, 'weeks'));
 }
 
 export function formatDate(date: any) {
   if (!date) {
     return '';
   }
-  return moment(date).format('DD/MM/YYYY');
+  return dayjs(date).format('DD/MM/YYYY');
 }
diff --git a/web/src/utils/registerServer.ts b/web/src/utils/registerServer.ts
index 9404ce0d4264abaf2ab5b6a85ba96de9f7f739b3..f36747b2f8cd50a993739e04b44d714a0e1d93e5 100644
--- a/web/src/utils/registerServer.ts
+++ b/web/src/utils/registerServer.ts
@@ -8,16 +8,20 @@ const registerServer = <T extends string>(
 ) => {
   const server: Service<T> = {} as Service<T>;
   for (let key in opt) {
-    server[key] = (params) => {
+    server[key] = (params: any, urlAppendix?: string) => {
+      let url = opt[key].url;
+      if (urlAppendix) {
+        url = url + '/' + urlAppendix;
+      }
       if (opt[key].method === 'post' || opt[key].method === 'POST') {
-        return request(opt[key].url, {
+        return request(url, {
           method: opt[key].method,
           data: params,
         });
       }
 
       if (opt[key].method === 'get' || opt[key].method === 'GET') {
-        return request.get(opt[key].url, {
+        return request.get(url, {
           params,
         });
       }
diff --git a/web/src/utils/request.ts b/web/src/utils/request.ts
index c5fd867d122aae703612b73dcea946c6ee738963..91e7d4b3843ebfaf8a50194c1424581c510fe621 100644
--- a/web/src/utils/request.ts
+++ b/web/src/utils/request.ts
@@ -4,6 +4,7 @@ import authorizationUtil from '@/utils/authorizationUtil';
 import { message, notification } from 'antd';
 import { history } from 'umi';
 import { RequestMethod, extend } from 'umi-request';
+import { convertTheKeysOfTheObjectToSnake } from './commonUtil';
 
 const ABORT_REQUEST_ERR_MESSAGE = 'The user aborted a request.'; // 手动中断请求。errorHandler 抛出的error message
 
@@ -87,10 +88,15 @@ const request: RequestMethod = extend({
 
 request.interceptors.request.use((url: string, options: any) => {
   const authorization = authorizationUtil.getAuthorization();
+  const data = convertTheKeysOfTheObjectToSnake(options.data);
+  const params = convertTheKeysOfTheObjectToSnake(options.params);
+
   return {
     url,
     options: {
       ...options,
+      // data,
+      // params,
       headers: {
         ...(options.skipToken ? undefined : { [Authorization]: authorization }),
         ...options.headers,