diff --git a/client/package.json b/client/package.json index e47d230ce..9a2314ed9 100644 --- a/client/package.json +++ b/client/package.json @@ -49,6 +49,7 @@ "react-hook-form": "^7.43.1", "react-markdown": "^8.0.7", "react-syntax-highlighter": "^15.5.0", + "reactflow": "^11.7.4", "rehype-katex": "^6.0.2", "remark-breaks": "^3.0.3", "remark-gfm": "^3.0.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 2a84498a2..47c626787 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -125,6 +125,9 @@ dependencies: react-syntax-highlighter: specifier: ^15.5.0 version: registry.npmmirror.com/react-syntax-highlighter@15.5.0(react@18.2.0) + reactflow: + specifier: ^11.7.4 + version: registry.npmmirror.com/reactflow@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) rehype-katex: specifier: ^6.0.2 version: registry.npmmirror.com/rehype-katex@6.0.2 @@ -4924,6 +4927,126 @@ packages: version: 2.11.8 dev: false + registry.npmmirror.com/@reactflow/background@11.2.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-SYQbCRCU0GuxT/40Tm7ZK+l5wByGnNJSLtZhbL9C/Hl7JhsJXV3UGXr0vrlhVZUBEtkWA7XhZM/5S9XEA5XSFA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/background/-/background-11.2.4.tgz} + id: registry.npmmirror.com/@reactflow/background/11.2.4 + name: '@reactflow/background' + version: 11.2.4 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + classcat: registry.npmmirror.com/classcat@5.0.4 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + + registry.npmmirror.com/@reactflow/controls@11.1.15(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-//33XfBYu8vQ6brfmlZwKrDoh+8hh93xO2d88XiqfIbrPEEb32SYjsb9mS9VuHKNlSIW+eB27fBA1Gt00mEj5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/controls/-/controls-11.1.15.tgz} + id: registry.npmmirror.com/@reactflow/controls/11.1.15 + name: '@reactflow/controls' + version: 11.1.15 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + classcat: registry.npmmirror.com/classcat@5.0.4 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + + registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-nt0T8ERp8TE7YCDQViaoEY9lb0StDPrWHVx3zBjhStFYET3wc88t8QRasZdf99xRTmyNtI3U3M40M5EBLNUpMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/core/-/core-11.7.4.tgz} + id: registry.npmmirror.com/@reactflow/core/11.7.4 + name: '@reactflow/core' + version: 11.7.4 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@types/d3': registry.npmmirror.com/@types/d3@7.4.0 + '@types/d3-drag': registry.npmmirror.com/@types/d3-drag@3.0.2 + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + '@types/d3-zoom': registry.npmmirror.com/@types/d3-zoom@3.0.3 + classcat: registry.npmmirror.com/classcat@5.0.4 + d3-drag: registry.npmmirror.com/d3-drag@3.0.0 + d3-selection: registry.npmmirror.com/d3-selection@3.0.0 + d3-zoom: registry.npmmirror.com/d3-zoom@3.0.0 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + + registry.npmmirror.com/@reactflow/minimap@11.5.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-1tDBj2zX2gxu2oHU6qvH5RGNrOWRfRxu8369KhDotuuBN5yJrGXJzWIKikwhzjsNsQJYOB+B0cS44yWAfwSwzw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.5.4.tgz} + id: registry.npmmirror.com/@reactflow/minimap/11.5.4 + name: '@reactflow/minimap' + version: 11.5.4 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + '@types/d3-zoom': registry.npmmirror.com/@types/d3-zoom@3.0.3 + classcat: registry.npmmirror.com/classcat@5.0.4 + d3-selection: registry.npmmirror.com/d3-selection@3.0.0 + d3-zoom: registry.npmmirror.com/d3-zoom@3.0.0 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + + registry.npmmirror.com/@reactflow/node-resizer@2.1.1(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5Q+IBmZfpp/bYsw3+KRVJB1nUbj6W3XAp5ycx4uNWH+K98vbssymyQsW0vvKkIhxEPg6tkiMzO4UWRWvwBwt1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.1.1.tgz} + id: registry.npmmirror.com/@reactflow/node-resizer/2.1.1 + name: '@reactflow/node-resizer' + version: 2.1.1 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + classcat: registry.npmmirror.com/classcat@5.0.4 + d3-drag: registry.npmmirror.com/d3-drag@3.0.0 + d3-selection: registry.npmmirror.com/d3-selection@3.0.0 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + + registry.npmmirror.com/@reactflow/node-toolbar@1.2.3(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-uFQy9xpog92s0G1wsPLniwV9nyH4i/MmL7QoMsWdnKaOi7XMhd8SJcCzUdHC3imR21HltsuQITff/XQ51ApMbg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.2.3.tgz} + id: registry.npmmirror.com/@reactflow/node-toolbar/1.2.3 + name: '@reactflow/node-toolbar' + version: 1.2.3 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + classcat: registry.npmmirror.com/classcat@5.0.4 + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + zustand: registry.npmmirror.com/zustand@4.3.5(immer@9.0.19)(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + registry.npmmirror.com/@rushstack/eslint-patch@1.3.1: resolution: {integrity: sha512-RkmuBcqiNioeeBKbgzMlOdreUkJfYaSjwgx9XDgGGpjvWgyaxWvDmZVSN9CS6LjEASadhgPv2BcFp+SeouWXXA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@rushstack/eslint-patch/-/eslint-patch-1.3.1.tgz} name: '@rushstack/eslint-patch' @@ -5198,6 +5321,247 @@ packages: version: 0.5.1 dev: true + registry.npmmirror.com/@types/d3-array@3.0.5: + resolution: {integrity: sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.0.5.tgz} + name: '@types/d3-array' + version: 3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-axis@3.0.2: + resolution: {integrity: sha512-uGC7DBh0TZrU/LY43Fd8Qr+2ja1FKmH07q2FoZFHo1eYl8aj87GhfVoY1saJVJiq24rp1+wpI6BvQJMKgQm8oA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.2.tgz} + name: '@types/d3-axis' + version: 3.0.2 + dependencies: + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-brush@3.0.2: + resolution: {integrity: sha512-2TEm8KzUG3N7z0TrSKPmbxByBx54M+S9lHoP2J55QuLU0VSQ9mE96EJSAOVNEqd1bbynMjeTS9VHmz8/bSw8rA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.2.tgz} + name: '@types/d3-brush' + version: 3.0.2 + dependencies: + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-chord@3.0.2: + resolution: {integrity: sha512-abT/iLHD3sGZwqMTX1TYCMEulr+wBd0SzyOQnjYNLp7sngdOHYtNkMRI5v3w5thoN+BWtlHVDx2Osvq6fxhZWw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.2.tgz} + name: '@types/d3-chord' + version: 3.0.2 + dev: false + + registry.npmmirror.com/@types/d3-color@3.1.0: + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.0.tgz} + name: '@types/d3-color' + version: 3.1.0 + dev: false + + registry.npmmirror.com/@types/d3-contour@3.0.2: + resolution: {integrity: sha512-k6/bGDoAGJZnZWaKzeB+9glgXCYGvh6YlluxzBREiVo8f/X2vpTEdgPy9DN7Z2i42PZOZ4JDhVdlTSTSkLDPlQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.2.tgz} + name: '@types/d3-contour' + version: 3.0.2 + dependencies: + '@types/d3-array': registry.npmmirror.com/@types/d3-array@3.0.5 + '@types/geojson': registry.npmmirror.com/@types/geojson@7946.0.10 + dev: false + + registry.npmmirror.com/@types/d3-delaunay@6.0.1: + resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz} + name: '@types/d3-delaunay' + version: 6.0.1 + dev: false + + registry.npmmirror.com/@types/d3-dispatch@3.0.2: + resolution: {integrity: sha512-rxN6sHUXEZYCKV05MEh4z4WpPSqIw+aP7n9ZN6WYAAvZoEAghEK1WeVZMZcHRBwyaKflU43PCUAJNjFxCzPDjg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.2.tgz} + name: '@types/d3-dispatch' + version: 3.0.2 + dev: false + + registry.npmmirror.com/@types/d3-drag@3.0.2: + resolution: {integrity: sha512-qmODKEDvyKWVHcWWCOVcuVcOwikLVsyc4q4EBJMREsoQnR2Qoc2cZQUyFUPgO9q4S3qdSqJKBsuefv+h0Qy+tw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.2.tgz} + name: '@types/d3-drag' + version: 3.0.2 + dependencies: + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-dsv@3.0.1: + resolution: {integrity: sha512-76pBHCMTvPLt44wFOieouXcGXWOF0AJCceUvaFkxSZEu4VDUdv93JfpMa6VGNFs01FHfuP4a5Ou68eRG1KBfTw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.1.tgz} + name: '@types/d3-dsv' + version: 3.0.1 + dev: false + + registry.npmmirror.com/@types/d3-ease@3.0.0: + resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.0.tgz} + name: '@types/d3-ease' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-fetch@3.0.2: + resolution: {integrity: sha512-gllwYWozWfbep16N9fByNBDTkJW/SyhH6SGRlXloR7WdtAaBui4plTP+gbUgiEot7vGw/ZZop1yDZlgXXSuzjA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.2.tgz} + name: '@types/d3-fetch' + version: 3.0.2 + dependencies: + '@types/d3-dsv': registry.npmmirror.com/@types/d3-dsv@3.0.1 + dev: false + + registry.npmmirror.com/@types/d3-force@3.0.4: + resolution: {integrity: sha512-q7xbVLrWcXvSBBEoadowIUJ7sRpS1yvgMWnzHJggFy5cUZBq2HZL5k/pBSm0GdYWS1vs5/EDwMjSKF55PDY4Aw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.4.tgz} + name: '@types/d3-force' + version: 3.0.4 + dev: false + + registry.npmmirror.com/@types/d3-format@3.0.1: + resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.1.tgz} + name: '@types/d3-format' + version: 3.0.1 + dev: false + + registry.npmmirror.com/@types/d3-geo@3.0.3: + resolution: {integrity: sha512-bK9uZJS3vuDCNeeXQ4z3u0E7OeJZXjUgzFdSOtNtMCJCLvDtWDwfpRVWlyt3y8EvRzI0ccOu9xlMVirawolSCw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.0.3.tgz} + name: '@types/d3-geo' + version: 3.0.3 + dependencies: + '@types/geojson': registry.npmmirror.com/@types/geojson@7946.0.10 + dev: false + + registry.npmmirror.com/@types/d3-hierarchy@3.1.2: + resolution: {integrity: sha512-9hjRTVoZjRFR6xo8igAJyNXQyPX6Aq++Nhb5ebrUF414dv4jr2MitM2fWiOY475wa3Za7TOS2Gh9fmqEhLTt0A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz} + name: '@types/d3-hierarchy' + version: 3.1.2 + dev: false + + registry.npmmirror.com/@types/d3-interpolate@3.0.1: + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz} + name: '@types/d3-interpolate' + version: 3.0.1 + dependencies: + '@types/d3-color': registry.npmmirror.com/@types/d3-color@3.1.0 + dev: false + + registry.npmmirror.com/@types/d3-path@3.0.0: + resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.0.0.tgz} + name: '@types/d3-path' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-polygon@3.0.0: + resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.0.tgz} + name: '@types/d3-polygon' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-quadtree@3.0.2: + resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz} + name: '@types/d3-quadtree' + version: 3.0.2 + dev: false + + registry.npmmirror.com/@types/d3-random@3.0.1: + resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.1.tgz} + name: '@types/d3-random' + version: 3.0.1 + dev: false + + registry.npmmirror.com/@types/d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz} + name: '@types/d3-scale-chromatic' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-scale@4.0.3: + resolution: {integrity: sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.3.tgz} + name: '@types/d3-scale' + version: 4.0.3 + dependencies: + '@types/d3-time': registry.npmmirror.com/@types/d3-time@3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-selection@3.0.5: + resolution: {integrity: sha512-xCB0z3Hi8eFIqyja3vW8iV01+OHGYR2di/+e+AiOcXIOrY82lcvWW8Ke1DYE/EUVMsBl4Db9RppSBS3X1U6J0w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.5.tgz} + name: '@types/d3-selection' + version: 3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-shape@3.1.1: + resolution: {integrity: sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.1.tgz} + name: '@types/d3-shape' + version: 3.1.1 + dependencies: + '@types/d3-path': registry.npmmirror.com/@types/d3-path@3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-time-format@4.0.0: + resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.0.tgz} + name: '@types/d3-time-format' + version: 4.0.0 + dev: false + + registry.npmmirror.com/@types/d3-time@3.0.0: + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.0.tgz} + name: '@types/d3-time' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-timer@3.0.0: + resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.0.tgz} + name: '@types/d3-timer' + version: 3.0.0 + dev: false + + registry.npmmirror.com/@types/d3-transition@3.0.3: + resolution: {integrity: sha512-/S90Od8Id1wgQNvIA8iFv9jRhCiZcGhPd2qX0bKF/PS+y0W5CrXKgIiELd2CvG1mlQrWK/qlYh3VxicqG1ZvgA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.3.tgz} + name: '@types/d3-transition' + version: 3.0.3 + dependencies: + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + dev: false + + registry.npmmirror.com/@types/d3-zoom@3.0.3: + resolution: {integrity: sha512-OWk1yYIIWcZ07+igN6BeoG6rqhnJ/pYe+R1qWFM2DtW49zsoSjgb9G5xB0ZXA8hh2jAzey1XuRmMSoXdKw8MDA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.3.tgz} + name: '@types/d3-zoom' + version: 3.0.3 + dependencies: + '@types/d3-interpolate': registry.npmmirror.com/@types/d3-interpolate@3.0.1 + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + dev: false + + registry.npmmirror.com/@types/d3@7.4.0: + resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/d3/-/d3-7.4.0.tgz} + name: '@types/d3' + version: 7.4.0 + dependencies: + '@types/d3-array': registry.npmmirror.com/@types/d3-array@3.0.5 + '@types/d3-axis': registry.npmmirror.com/@types/d3-axis@3.0.2 + '@types/d3-brush': registry.npmmirror.com/@types/d3-brush@3.0.2 + '@types/d3-chord': registry.npmmirror.com/@types/d3-chord@3.0.2 + '@types/d3-color': registry.npmmirror.com/@types/d3-color@3.1.0 + '@types/d3-contour': registry.npmmirror.com/@types/d3-contour@3.0.2 + '@types/d3-delaunay': registry.npmmirror.com/@types/d3-delaunay@6.0.1 + '@types/d3-dispatch': registry.npmmirror.com/@types/d3-dispatch@3.0.2 + '@types/d3-drag': registry.npmmirror.com/@types/d3-drag@3.0.2 + '@types/d3-dsv': registry.npmmirror.com/@types/d3-dsv@3.0.1 + '@types/d3-ease': registry.npmmirror.com/@types/d3-ease@3.0.0 + '@types/d3-fetch': registry.npmmirror.com/@types/d3-fetch@3.0.2 + '@types/d3-force': registry.npmmirror.com/@types/d3-force@3.0.4 + '@types/d3-format': registry.npmmirror.com/@types/d3-format@3.0.1 + '@types/d3-geo': registry.npmmirror.com/@types/d3-geo@3.0.3 + '@types/d3-hierarchy': registry.npmmirror.com/@types/d3-hierarchy@3.1.2 + '@types/d3-interpolate': registry.npmmirror.com/@types/d3-interpolate@3.0.1 + '@types/d3-path': registry.npmmirror.com/@types/d3-path@3.0.0 + '@types/d3-polygon': registry.npmmirror.com/@types/d3-polygon@3.0.0 + '@types/d3-quadtree': registry.npmmirror.com/@types/d3-quadtree@3.0.2 + '@types/d3-random': registry.npmmirror.com/@types/d3-random@3.0.1 + '@types/d3-scale': registry.npmmirror.com/@types/d3-scale@4.0.3 + '@types/d3-scale-chromatic': registry.npmmirror.com/@types/d3-scale-chromatic@3.0.0 + '@types/d3-selection': registry.npmmirror.com/@types/d3-selection@3.0.5 + '@types/d3-shape': registry.npmmirror.com/@types/d3-shape@3.1.1 + '@types/d3-time': registry.npmmirror.com/@types/d3-time@3.0.0 + '@types/d3-time-format': registry.npmmirror.com/@types/d3-time-format@4.0.0 + '@types/d3-timer': registry.npmmirror.com/@types/d3-timer@3.0.0 + '@types/d3-transition': registry.npmmirror.com/@types/d3-transition@3.0.3 + '@types/d3-zoom': registry.npmmirror.com/@types/d3-zoom@3.0.3 + dev: false + registry.npmmirror.com/@types/debug@4.1.8: resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/debug/-/debug-4.1.8.tgz} name: '@types/debug' @@ -5214,6 +5578,12 @@ packages: '@types/node': registry.npmmirror.com/@types/node@18.14.0 dev: true + registry.npmmirror.com/@types/geojson@7946.0.10: + resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.10.tgz} + name: '@types/geojson' + version: 7946.0.10 + dev: false + registry.npmmirror.com/@types/hast@2.3.4: resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz} name: '@types/hast' @@ -6045,6 +6415,12 @@ packages: fsevents: registry.npmmirror.com/fsevents@2.3.2 dev: false + registry.npmmirror.com/classcat@5.0.4: + resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/classcat/-/classcat-5.0.4.tgz} + name: classcat + version: 5.0.4 + dev: false + registry.npmmirror.com/client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz} name: client-only @@ -10700,6 +11076,27 @@ packages: loose-envify: registry.npmmirror.com/loose-envify@1.4.0 dev: false + registry.npmmirror.com/reactflow@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-QI6+oc1Ft6oFeLSdHlp+SmgymbI5Tm49wj5JyE84O4A54yN/ImfYaBhLit9Cmfzxn9Tz6tDqmGMGbk4bdtB8/w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/reactflow/-/reactflow-11.7.4.tgz} + id: registry.npmmirror.com/reactflow/11.7.4 + name: reactflow + version: 11.7.4 + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/background': registry.npmmirror.com/@reactflow/background@11.2.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/controls': registry.npmmirror.com/@reactflow/controls@11.1.15(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/core': registry.npmmirror.com/@reactflow/core@11.7.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/minimap': registry.npmmirror.com/@reactflow/minimap@11.5.4(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-resizer': registry.npmmirror.com/@reactflow/node-resizer@2.1.1(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-toolbar': registry.npmmirror.com/@reactflow/node-toolbar@1.2.3(immer@9.0.19)(react-dom@18.2.0)(react@18.2.0) + react: registry.npmmirror.com/react@18.2.0 + react-dom: registry.npmmirror.com/react-dom@18.2.0(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + registry.npmmirror.com/readable-stream@1.1.14: resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/readable-stream/-/readable-stream-1.1.14.tgz} name: readable-stream diff --git a/client/src/api/model.ts b/client/src/api/app.ts similarity index 78% rename from client/src/api/model.ts rename to client/src/api/app.ts index 4015c104a..7f491ffb6 100644 --- a/client/src/api/model.ts +++ b/client/src/api/app.ts @@ -1,5 +1,5 @@ import { GET, POST, DELETE, PUT } from './request'; -import type { ModelSchema } from '@/types/mongoSchema'; +import type { AppSchema } from '@/types/mongoSchema'; import type { ModelUpdateParams } from '@/types/model'; import { RequestPaging } from '../types/index'; import type { ModelListResponse } from './response/model'; @@ -22,13 +22,13 @@ export const delModelById = (id: string) => DELETE(`/model/del?modelId=${id}`); /** * 根据 ID 获取模型 */ -export const getModelById = (id: string) => GET(`/model/detail?modelId=${id}`); +export const getModelById = (id: string) => GET(`/model/detail?modelId=${id}`); /** * 根据 ID 更新模型 */ -export const putModelById = (id: string, data: ModelUpdateParams) => - PUT(`/model/update?modelId=${id}`, data); +export const putAppById = (id: string, data: ModelUpdateParams) => + PUT(`/model/update?appId=${id}`, data); /* 共享市场 */ /** diff --git a/client/src/api/fetch.ts b/client/src/api/fetch.ts index 1fdecea8a..39c0766c5 100644 --- a/client/src/api/fetch.ts +++ b/client/src/api/fetch.ts @@ -1,4 +1,4 @@ -import { Props, ChatResponseType } from '@/pages/api/openapi/v1/chat/completions'; +import { Props } from '@/pages/api/openapi/v1/chat/completions'; import { sseResponseEventEnum } from '@/constants/chat'; import { getErrText } from '@/utils/tools'; import { parseStreamChunk } from '@/utils/adapt'; @@ -9,10 +9,10 @@ interface StreamFetchProps { abortSignal: AbortController; } export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) => - new Promise( + new Promise<{ responseText: string; errMsg: string; newChatId: string | null }>( async (resolve, reject) => { try { - const response = await window.fetch('/api/openapi/v1/chat/test', { + const response = await window.fetch('/api/openapi/v1/chat/completions2', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -37,9 +37,8 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) // response data let responseText = ''; - let newChatId = ''; - let quoteLen = 0; let errMsg = ''; + const newChatId = response.headers.get('newChatId'); const read = async () => { try { @@ -48,9 +47,8 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) if (response.status === 200) { return resolve({ responseText, - newChatId, - quoteLen, - errMsg + errMsg, + newChatId }); } else { return reject('响应过程出现异常~'); @@ -72,11 +70,6 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) const answer: string = data?.choices?.[0].delta.content || ''; onMessage(answer); responseText += answer; - } else if (item.event === sseResponseEventEnum.chatResponse) { - const chatResponse = data as ChatResponseType; - newChatId = - chatResponse.newChatId !== undefined ? chatResponse.newChatId : newChatId; - quoteLen = chatResponse.quoteLen !== undefined ? chatResponse.quoteLen : quoteLen; } else if (item.event === sseResponseEventEnum.error) { errMsg = getErrText(data, '流响应错误'); } @@ -86,9 +79,8 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps) if (err?.message === 'The user aborted a request.') { return resolve({ responseText, - newChatId, - quoteLen, - errMsg + errMsg, + newChatId }); } reject(getErrText(err, '请求异常')); diff --git a/client/src/api/response/chat.d.ts b/client/src/api/response/chat.d.ts index c8c5480be..7b458c07a 100644 --- a/client/src/api/response/chat.d.ts +++ b/client/src/api/response/chat.d.ts @@ -1,4 +1,4 @@ -import type { ChatPopulate, ModelSchema } from '@/types/mongoSchema'; +import type { ChatPopulate, AppSchema } from '@/types/mongoSchema'; import type { ChatItemType } from '@/types/chat'; export interface InitChatResponse { @@ -12,7 +12,7 @@ export interface InitChatResponse { intro: string; canUse: boolean; }; - chatModel: ModelSchema['chat']['chatModel']; // 对话模型名 + chatModel: AppSchema['chat']['chatModel']; // 对话模型名 history: ChatItemType[]; } @@ -24,5 +24,5 @@ export interface InitShareChatResponse { avatar: string; intro: string; }; - chatModel: ModelSchema['chat']['chatModel']; // 对话模型名 + chatModel: AppSchema['chat']['chatModel']; // 对话模型名 } diff --git a/client/src/api/response/model.d.ts b/client/src/api/response/model.d.ts index e79617f5c..65ebd8b6f 100644 --- a/client/src/api/response/model.d.ts +++ b/client/src/api/response/model.d.ts @@ -1,6 +1,6 @@ import { ModelListItemType } from '@/types/model'; export type ModelListResponse = { - myModels: ModelListItemType[]; - myCollectionModels: ModelListItemType[]; + myApps: ModelListItemType[]; + myCollectionApps: ModelListItemType[]; }; diff --git a/client/src/components/Icon/icons/save.svg b/client/src/components/Icon/icons/save.svg new file mode 100644 index 000000000..8cdbca686 --- /dev/null +++ b/client/src/components/Icon/icons/save.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx index 59a498f55..4ef827685 100644 --- a/client/src/components/Icon/index.tsx +++ b/client/src/components/Icon/index.tsx @@ -34,7 +34,8 @@ const map = { history: require('./icons/history.svg').default, kbTest: require('./icons/kbTest.svg').default, date: require('./icons/date.svg').default, - apikey: require('./icons/apikey.svg').default + apikey: require('./icons/apikey.svg').default, + save: require('./icons/save.svg').default }; export type IconName = keyof typeof map; diff --git a/client/src/components/Layout/index.tsx b/client/src/components/Layout/index.tsx index 4978591e1..27f3f9601 100644 --- a/client/src/components/Layout/index.tsx +++ b/client/src/components/Layout/index.tsx @@ -14,7 +14,8 @@ import { getUnreadCount } from '@/api/user'; const pcUnShowLayoutRoute: Record = { '/': true, '/login': true, - '/chat/share': true + '/chat/share': true, + '/app/edit': true }; const phoneUnShowLayoutRoute: Record = { '/': true, @@ -60,10 +61,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { return ( <> - + {pcUnShowLayoutRoute[router.pathname] ? ( {children} diff --git a/client/src/components/Layout/navbar.tsx b/client/src/components/Layout/navbar.tsx index c8203712f..d6efa73cf 100644 --- a/client/src/components/Layout/navbar.tsx +++ b/client/src/components/Layout/navbar.tsx @@ -29,6 +29,12 @@ const Navbar = ({ unread }: { unread: number }) => { { label: '应用', icon: 'model', + link: `/app/list`, + activeLink: ['/app/list'] + }, + { + label: '旧应用', + icon: 'model', link: `/model?modelId=${lastModelId}`, activeLink: ['/model'] }, diff --git a/client/src/components/Select/index.tsx b/client/src/components/Select/index.tsx index a43f9c046..08e330adf 100644 --- a/client/src/components/Select/index.tsx +++ b/client/src/components/Select/index.tsx @@ -1,5 +1,14 @@ import React, { useRef } from 'react'; -import { Menu, MenuButton, MenuList, MenuItem, Button, useDisclosure } from '@chakra-ui/react'; +import { + Menu, + Box, + MenuList, + MenuItem, + MenuButton, + Button, + useDisclosure, + useOutsideClick +} from '@chakra-ui/react'; import type { ButtonProps } from '@chakra-ui/react'; import { ChevronDownIcon } from '@chakra-ui/icons'; interface Props extends ButtonProps { @@ -7,13 +16,14 @@ interface Props extends ButtonProps { placeholder?: string; list: { label: string; - id: string; + value: string; }[]; onchange?: (val: string) => void; } const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props }: Props) => { const ref = useRef(null); + const SelectRef = useRef(null); const menuItemStyles = { borderRadius: 'sm', py: 2, @@ -25,9 +35,16 @@ const MySelect = ({ placeholder, value, width = 'auto', list, onchange, ...props }; const { isOpen, onOpen, onClose } = useDisclosure(); + useOutsideClick({ + ref: SelectRef, + handler: () => { + onClose(); + } + }); + return ( - - + + (isOpen ? onClose() : onOpen())}> - - { - const w = ref.current?.clientWidth; - if (w) { - return `${w}px !important`; + + { + const w = ref.current?.clientWidth; + if (w) { + return `${w}px !important`; + } + return Array.isArray(width) + ? width.map((item) => `${item} !important`) + : `${width} !important`; + })()} + p={'6px'} + border={'1px solid #fff'} + boxShadow={ + '0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);' } - return Array.isArray(width) - ? width.map((item) => `${item} !important`) - : `${width} !important`; - })()} - p={'6px'} - border={'1px solid #fff'} - boxShadow={'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'} - zIndex={99} - > - {list.map((item) => ( - + {list.map((item) => ( + { + if (onchange && value !== item.value) { + onchange(item.value); } - : {})} - onClick={() => { - if (onchange && value !== item.id) { - onchange(item.id); - } - }} - > - {item.label} - - ))} - + }} + > + {item.label} + + ))} + + ); }; diff --git a/client/src/components/Slider/index.tsx b/client/src/components/Slider/index.tsx index 87fff3c80..065479f34 100644 --- a/client/src/components/Slider/index.tsx +++ b/client/src/components/Slider/index.tsx @@ -10,8 +10,8 @@ import { const MySlider = ({ markList = [], - setVal, - activeVal, + onChange, + value, max = 100, min = 0, step = 1, @@ -21,8 +21,8 @@ const MySlider = ({ label: string | number; value: number; }[]; - activeVal: number; - setVal: (index: number) => void; + value: number; + onChange?: (index: number) => void; max?: number; min?: number; step?: number; @@ -40,10 +40,6 @@ const MySlider = ({ top: 0, transform: 'translateY(-3px)' }; - const value = useMemo(() => { - const index = markList.findIndex((item) => item.value === activeVal); - return index > -1 ? index : 0; - }, [activeVal, markList]); return ( {markList?.map((item, i) => ( ))} - {activeVal} + {value} ({ +export const answerModule = ({ id }: { id: string }) => ({ moduleId: id, type: AppModuleItemTypeEnum.answer, - body: {}, + flowType: FlowModuleTypeEnum.answerNode, inputs: [ { - key: SpecificInputEnum.answerText, - value: defaultText + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '触发器', + connected: true }, - ...(defaultText !== undefined - ? [ - { - key: SystemInputEnum.start, - value: undefined - } - ] - : []) + { + key: SpecificInputEnum.answerText, + value: '', + type: FlowInputItemTypeEnum.input, + label: '响应内容', + connected: true + } ], outputs: [] }); +export const chatModule = ({ + id, + systemPrompt = '', + limitPrompt = '', + history = 10 +}: { + id: string; + systemPrompt?: string; + limitPrompt?: string; + history?: number; +}) => { + return { + moduleId: id, + flowType: FlowModuleTypeEnum.chatNode, + type: AppModuleItemTypeEnum.http, + url: '/openapi/modules/chat/gpt', + inputs: [ + { + key: 'model', + type: FlowInputItemTypeEnum.select, + label: '对话模型', + value: chatModelList[0].value, + list: chatModelList + }, + { + key: 'temperature', + type: FlowInputItemTypeEnum.slider, + label: '温度', + value: 0, + min: 0, + max: 10, + step: 1, + markList: [ + { label: '严谨', value: 0 }, + { label: '发散', value: 10 } + ] + }, + { + key: 'maxToken', + type: FlowInputItemTypeEnum.slider, + label: '回复上限', + value: 3000, + min: 0, + max: 4000, + step: 50, + markList: [ + { label: '0', value: 0 }, + { label: '4000', value: 4000 } + ] + }, + { + key: 'systemPrompt', + type: FlowInputItemTypeEnum.textarea, + label: '系统提示词', + description: + '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', + placeholder: + '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', + value: systemPrompt + }, + { + key: 'limitPrompt', + type: FlowInputItemTypeEnum.textarea, + label: '限定词', + description: + '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', + placeholder: + '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', + value: limitPrompt + }, + { + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '触发器', + connected: true + }, + { + key: 'quotePrompt', + type: FlowInputItemTypeEnum.target, + label: '引用内容(字符串)', + connected: true + }, + { + key: SystemInputEnum.history, + type: FlowInputItemTypeEnum.numberInput, + label: '最长上下文', + description: '为 0 时,代表不需要上下文。', + value: history, + min: 0, + max: 50 + }, + { + key: SystemInputEnum.userChatInput, + type: FlowInputItemTypeEnum.none, + label: '用户输入(系统自动填写)', + description: '' + } + ], + outputs: [ + { + key: 'answer', + label: '模型回复', + type: FlowOutputItemTypeEnum.answer, + targets: [] + } + ] + }; +}; export const chatAppDemo: AppItemType = { id: 'chat', + name: '', // 标记字段 - modules: [ - { - moduleId: '1', - type: AppModuleItemTypeEnum.http, - url: '/openapi/modules/chat/gpt', - body: { - model: 'gpt-3.5-turbo-16k', - temperature: 5, - maxToken: 4000 - }, - inputs: [ - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'answer', - answer: true, - value: undefined, - targets: [] - } - ] - } - ] + modules: [chatModule({ id: 'chat' })] }; export const kbChatAppDemo: AppItemType = { id: 'kbchat', + name: 'kbchat', // 标记字段 modules: [ { - moduleId: '1', - type: 'http', + moduleId: 'kbsearch', + flowType: FlowModuleTypeEnum.kbSearchNode, + type: AppModuleItemTypeEnum.http, url: '/openapi/modules/kb/search', - body: { - kb_ids: ['646627f4f7b896cfd8910e38'], - similarity: 0.82, - limit: 2, - maxToken: 2500 - }, + position: { x: -500, y: 0 }, inputs: [ + { + key: 'kb_ids', + type: FlowInputItemTypeEnum.custom, + label: '关联的知识库', + value: ['646627f4f7b896cfd8910e38'], + list: [] + }, + + { + key: 'similarity', + type: FlowInputItemTypeEnum.slider, + label: '相似度', + value: 0.8, + min: 0, + max: 1, + step: 0.01, + markList: [ + { label: '0', value: 0 }, + { label: '1', value: 1 } + ] + }, + { + key: 'limit', + type: FlowInputItemTypeEnum.slider, + label: '单次搜索上限', + value: 5, + min: 1, + max: 20, + step: 1, + markList: [ + { label: '1', value: 1 }, + { label: '20', value: 20 } + ] + }, { key: SystemInputEnum.history, - value: undefined + type: FlowInputItemTypeEnum.hidden, + label: '引用复用数量', + value: 1 }, { key: SystemInputEnum.userChatInput, - value: undefined + type: FlowInputItemTypeEnum.none, + label: '用户输入(系统自动填写)', + description: '' } ], outputs: [ { key: 'rawSearch', - value: undefined, + label: '源搜索数据', + type: FlowOutputItemTypeEnum.none, + response: true, targets: [] }, { key: 'isEmpty', - value: undefined, + label: '无搜索结果', + type: FlowOutputItemTypeEnum.source, targets: [ { - moduleId: '4', - key: 'switch' + moduleId: 'tfswitch', + key: SystemInputEnum.switch } ] }, { key: 'quotePrompt', - response: true, - value: undefined, + label: '引用内容(字符串)', + type: FlowOutputItemTypeEnum.source, targets: [ { - moduleId: '2', + moduleId: 'chat', key: 'quotePrompt' } ] @@ -251,636 +247,610 @@ export const kbChatAppDemo: AppItemType = { ] }, { - moduleId: '4', - type: 'switch', - body: {}, + moduleId: 'tfswitch', + type: AppModuleItemTypeEnum.switch, + flowType: FlowModuleTypeEnum.tfSwitchNode, + position: { x: 0, y: 510 }, inputs: [ { - key: 'switch', - value: undefined + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '触发器', + connected: true } ], outputs: [ { key: 'true', - value: undefined, + label: '无搜索数据', + type: FlowOutputItemTypeEnum.source, targets: [ { - moduleId: '3', - key: SystemInputEnum.start + moduleId: 'answer', + key: SystemInputEnum.switch } ] }, { key: 'false', - value: undefined, + label: '有搜索数据', + type: FlowOutputItemTypeEnum.source, targets: [ { - moduleId: '2', - key: SystemInputEnum.start + moduleId: 'chat', + key: SystemInputEnum.switch } ] } ] }, { - moduleId: '2', - type: 'http', - url: '/openapi/modules/chat/gpt', - body: { - model: 'gpt-3.5-turbo-16k', - temperature: 5, - maxToken: 4000, - systemPrompt: '知识库是关于电影玲芽之旅的介绍。', - limitPrompt: '你仅回答关于电影《玲芽之旅的问题》' - }, - inputs: [ - { - key: SystemInputEnum.start, - value: undefined - }, - { - key: 'quotePrompt', - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'answer', - value: undefined, - answer: true, - targets: [] - } - ] - }, - answerModule({ id: '3', defaultText: '你好,我可以回答你关于电影《玲芽之旅》的问题。' }) - ] -}; - -export const classifyQuestionDemo: AppItemType = { - id: 'classifyQuestionDemo', - // 标记字段 - modules: [ - { - moduleId: '1', - type: AppModuleItemTypeEnum.http, - url: '/openapi/modules/agent/classifyQuestion', - body: { - systemPrompt: - 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。', - agents: [ - { - desc: '打招呼、问候、身份询问等问题', - key: 'a' - }, - { - desc: "询问 'laf 使用和介绍的问题'", - key: 'b' - }, - { - desc: "询问 'laf 代码问题'", - key: 'c' - }, - { - desc: '其他问题', - key: 'd' - } - ] - }, - inputs: [ - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'a', - value: undefined, - targets: [ - { - moduleId: 'a', - key: SystemInputEnum.start - } - ] - }, - { - key: 'b', - value: undefined, - targets: [ - { - moduleId: 'b', - key: SystemInputEnum.start - } - ] - }, - { - key: 'c', - value: undefined, - targets: [ - { - moduleId: 'c', - key: SystemInputEnum.start - } - ] - }, - { - key: 'd', - value: undefined, - targets: [ - { - moduleId: 'd', - key: SystemInputEnum.start - } - ] - } - ] + ...chatModule({ id: 'chat', limitPrompt: '参考知识库内容进行回答', history: 5 }), + position: { x: 300, y: 240 } }, { - moduleId: 'a', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '你好,我是 Laf 助手,有什么可以帮助你的?' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - // laf 知识库 - { - moduleId: 'b', - type: 'http', - url: '/openapi/modules/kb/search', - body: { - kb_ids: ['646627f4f7b896cfd8910e24'], - similarity: 0.82, - limit: 4, - maxToken: 2500 - }, - inputs: [ - { - key: SystemInputEnum.start, - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'rawSearch', - value: undefined, - response: true, - targets: [] - }, - { - key: 'quotePrompt', - value: undefined, - targets: [ - { - moduleId: 'lafchat', - key: 'quotePrompt' - } - ] - } - ] - }, - // laf 对话 - { - moduleId: 'lafchat', - type: 'http', - url: '/openapi/modules/chat/gpt', - body: { - model: 'gpt-3.5-turbo-16k', - temperature: 5, - maxToken: 4000, - systemPrompt: '知识库是关于 Laf 的内容。', - limitPrompt: '你仅能参考知识库的内容回答问题,不能超出知识库范围。' - }, - inputs: [ - { - key: 'quotePrompt', - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'answer', - answer: true, - value: undefined, - targets: [] - } - ] - }, - // laf 代码知识库 - { - moduleId: 'c', - type: 'http', - url: '/openapi/modules/kb/search', - body: { - kb_ids: ['646627f4f7b896cfd8910e26'], - similarity: 0.8, - limit: 4, - maxToken: 2500 - }, - inputs: [ - { - key: SystemInputEnum.start, - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'rawSearch', - value: undefined, - response: true, - targets: [] - }, - { - key: 'quotePrompt', - value: undefined, - targets: [ - { - moduleId: 'lafcodechat', - key: 'quotePrompt' - } - ] - } - ] - }, - // laf代码对话 - { - moduleId: 'lafcodechat', - type: 'http', - url: '/openapi/modules/chat/gpt', - body: { - model: 'gpt-3.5-turbo-16k', - temperature: 5, - maxToken: 4000, - systemPrompt: `下例是laf结构\n~~~ts\nimport cloud from '@lafjs/cloud'\nexport default async function(ctx: FunctionContext){\nreturn \"success\"\n};\n~~~\n下例是@lafjs/cloud的api\n~~~\ncloud.fetch//完全等同axios\ncloud.database()// 获取操作数据库实例,和mongo语法相似.\ncloud.getToken(payload)//获取token\ncloud.parseToken(token)//解析token\n// 下面是持久化缓存Api\ncloud.shared.set(key,val); //设置缓存,仅能设置值,无法设置过期时间\ncloud.shared.get(key);\ncloud.shared.has(key); \ncloud.shared.delete(key); \ncloud.shared.clear(); \n~~~\n下例是ctx对象\n~~~\nctx.requestId\nctx.method\nctx.headers//请求的 headers, ctx.headers.get('Content-Type')获取Content-Type的值\nctx.user//Http Bearer Token 认证时,获取token值\nctx.query\nctx.body\nctx.request//同express的Request\nctx.response//同express的Response\nctx.socket/WebSocket 实例\nctx.files//上传的文件 (File对象数组)\nctx.env//自定义的环境变量\n~~~\n下例是数据库获取数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {minMemory} = ctx.query\nconst _ = db.command;\nconst {data: users,total} = collection(\"users\")\n .where({//条件查询\n category: \"computer\",\n type: {\n memory: _gt(minMemory), \n }\n }) \n .skip(10)//跳过10条-分页时使用\n .limit(10)//仅返回10条\n .orderBy(\"name\", \"asc\") \n .orderBy(\"age\", \"desc\")\n .field({age:true,name: false})//返回age不返回name\n}\nconst {data:user} = db.where({phone:req.body.phone}).getOne()//获取一个满足条件的用户\nreturn {users,total}\n~~~\n下例是数据库添加数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext) {\n const {username} = ctx.body\n const {id:userId, ok} = await collection(\"users\")\n .add({\n username, \n })\n if(ok) return {userId}\n return {code:500,message:\"失败\"}\n}\n~~~\n下例是数据库更新数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\n//id直接修改\nawait collection(\"user\").doc(\"id\").update({\n name: \"Hey\",\n});\n//批量更新\nawait collection\n .where({name:\"1234\"})\n .update({\n age:18\n })\nconst _ = db.command;\nawait collection(\"user\")\n .doc(id)\n .set({\n count: _.inc(1)\n count: _.mul(2)\n count: _.remove()\n users: _.push([\"aaa\", \"bbb\"])\n users: _.push(\"aaa\")\n users: _.pop()\n users: _.unshift()\n users: _.shift()\n })\n}\n~~~\n下例是删除数据库记录\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\ncollection(\"user\").doc(id).remove();\n//批量删除\ncollection\n .where({age:18}) \n .remove({multi: true})\nreturn \"success\"\n}\n~~~\n你只需返回 ts 代码块!不需要说明.\n用户的问题与 Laf 代码无关时,你直接回答: \"我不确定,我只会写 Laf 代码。\"`, - limitPrompt: - '你是由 Laf 团队开发的代码助手,把我的需求用 Laf 代码实现.参考知识库中 Laf 的例子.' - }, - inputs: [ - { - key: 'quotePrompt', - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'answer', - answer: true, - value: undefined, - targets: [] - } - ] - }, - { - moduleId: 'd', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '你好,我没有理解你的意思,请问你有什么 Laf 相关的问题么?' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] + ...answerModule({ id: 'answer' }), + position: { x: 300, y: 0 } } ] }; -export const lafClassifyQuestionDemo: AppItemType = { - id: 'test', - // 标记字段 - modules: [ - { - moduleId: '1', - type: AppModuleItemTypeEnum.http, - url: '/openapi/modules/agent/classifyQuestion', - body: { - systemPrompt: - 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。\nsealos是一个 k8s 云平台,可以让用户快速部署云服务。', - agents: [ - { - desc: '打招呼、问候、身份询问等问题', - key: 'a' - }, - { - desc: "询问 'laf 的使用和介绍'", - key: 'b' - }, - { - desc: "询问 'laf 代码相关问题'", - key: 'c' - }, - { - desc: "用户希望运行或知道 'laf 代码' 运行结果", - key: 'g' - }, - { - desc: "询问 'sealos 相关问题'", - key: 'd' - }, - { - desc: '其他问题', - key: 'e' - }, - { - desc: '商务类问题', - key: 'f' - } - ] - }, - inputs: [ - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'a', - value: undefined, - targets: [ - { - moduleId: 'a', - key: SystemInputEnum.start - } - ] - }, - { - key: 'b', - value: undefined, - targets: [ - { - moduleId: 'b', - key: SystemInputEnum.start - } - ] - }, - { - key: 'c', - value: undefined, - targets: [ - { - moduleId: 'c', - key: SystemInputEnum.start - } - ] - }, - { - key: 'd', - value: undefined, - targets: [ - { - moduleId: 'd', - key: SystemInputEnum.start - } - ] - }, - { - key: 'e', - value: undefined, - targets: [ - { - moduleId: 'e', - key: SystemInputEnum.start - } - ] - }, - { - key: 'f', - value: undefined, - targets: [ - { - moduleId: 'f', - key: SystemInputEnum.start - } - ] - }, - { - key: 'g', - value: undefined, - targets: [ - { - moduleId: 'g', - key: SystemInputEnum.start - } - ] - } - ] - }, - { - moduleId: 'a', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '你好,我是 环界云 助手,你有什么 Laf 或者 sealos 的 问题么?' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'b', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '查询 Laf 通用知识库:xxxxx' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'c', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '查询 Laf 代码知识库:xxxxx' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'd', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '查询 sealos 通用知识库: xxxx' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'e', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '其他问题。回复引导语:xxxx' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'f', - type: 'answer', - body: {}, - inputs: [ - { - key: SpecificInputEnum.answerText, - value: '商务类问题,联系方式:xxxxx' - }, - { - key: SystemInputEnum.start, - value: undefined - } - ], - outputs: [] - }, - { - moduleId: 'g', - type: 'http', - url: '/openapi/modules/agent/extract', - body: { - description: '运行 laf 代码', - agents: [ - { - desc: '代码内容', - key: 'code' - } - ] - }, - inputs: [ - { - key: SystemInputEnum.start, - value: undefined - }, - { - key: SystemInputEnum.history, - value: undefined - }, - { - key: SystemInputEnum.userChatInput, - value: undefined - } - ], - outputs: [ - { - key: 'code', - value: undefined, - targets: [ - { - moduleId: 'code_run', - key: 'code' - } - ] - } - ] - }, - { - moduleId: 'code_run', - type: AppModuleItemTypeEnum.http, - url: 'https://v1cde7.laf.run/tess', - body: {}, - inputs: [ - { - key: 'code', - value: undefined - } - ], - outputs: [ - { - key: 'star', - value: undefined, - targets: [] - } - ] - } - ] -}; +// export const classifyQuestionDemo: AppItemType = { +// id: 'classifyQuestionDemo', +// // 标记字段 +// modules: [ +// { +// moduleId: '1', +// type: AppModuleItemTypeEnum.http, +// url: '/openapi/modules/agent/classifyQuestion', +// body: { +// systemPrompt: +// 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。', +// agents: [ +// { +// desc: '打招呼、问候、身份询问等问题', +// key: 'a' +// }, +// { +// desc: "询问 'laf 使用和介绍的问题'", +// key: 'b' +// }, +// { +// desc: "询问 'laf 代码问题'", +// key: 'c' +// }, +// { +// desc: '其他问题', +// key: 'd' +// } +// ] +// }, +// inputs: [ +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'a', +// value: undefined, +// targets: [ +// { +// moduleId: 'a', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'b', +// value: undefined, +// targets: [ +// { +// moduleId: 'b', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'c', +// value: undefined, +// targets: [ +// { +// moduleId: 'c', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'd', +// value: undefined, +// targets: [ +// { +// moduleId: 'd', +// key: SystemInputEnum.switch +// } +// ] +// } +// ] +// }, +// { +// moduleId: 'a', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '你好,我是 Laf 助手,有什么可以帮助你的?' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// // laf 知识库 +// { +// moduleId: 'b', +// type: 'http', +// url: '/openapi/modules/kb/search', +// body: { +// kb_ids: ['646627f4f7b896cfd8910e24'], +// similarity: 0.82, +// limit: 4, +// maxToken: 2500 +// }, +// inputs: [ +// { +// key: SystemInputEnum.switch, +// value: undefined +// }, +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'rawSearch', +// value: undefined, +// response: true, +// targets: [] +// }, +// { +// key: 'quotePrompt', +// value: undefined, +// targets: [ +// { +// moduleId: 'lafchat', +// key: 'quotePrompt' +// } +// ] +// } +// ] +// }, +// // laf 对话 +// { +// moduleId: 'lafchat', +// type: 'http', +// url: '/openapi/modules/chat/gpt', +// body: { +// model: 'gpt-3.5-turbo-16k', +// temperature: 5, +// maxToken: 4000, +// systemPrompt: '知识库是关于 Laf 的内容。', +// limitPrompt: '你仅能参考知识库的内容回答问题,不能超出知识库范围。' +// }, +// inputs: [ +// { +// key: 'quotePrompt', +// value: undefined +// }, +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'answer', +// answer: true, +// value: undefined, +// targets: [] +// } +// ] +// }, +// // laf 代码知识库 +// { +// moduleId: 'c', +// type: 'http', +// url: '/openapi/modules/kb/search', +// body: { +// kb_ids: ['646627f4f7b896cfd8910e26'], +// similarity: 0.8, +// limit: 4, +// maxToken: 2500 +// }, +// inputs: [ +// { +// key: SystemInputEnum.switch, +// value: undefined +// }, +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'rawSearch', +// value: undefined, +// response: true, +// targets: [] +// }, +// { +// key: 'quotePrompt', +// value: undefined, +// targets: [ +// { +// moduleId: 'lafcodechat', +// key: 'quotePrompt' +// } +// ] +// } +// ] +// }, +// // laf代码对话 +// { +// moduleId: 'lafcodechat', +// type: 'http', +// url: '/openapi/modules/chat/gpt', +// body: { +// model: 'gpt-3.5-turbo-16k', +// temperature: 5, +// maxToken: 4000, +// systemPrompt: `下例是laf结构\n~~~ts\nimport cloud from '@lafjs/cloud'\nexport default async function(ctx: FunctionContext){\nreturn \"success\"\n};\n~~~\n下例是@lafjs/cloud的api\n~~~\ncloud.fetch//完全等同axios\ncloud.database()// 获取操作数据库实例,和mongo语法相似.\ncloud.getToken(payload)//获取token\ncloud.parseToken(token)//解析token\n// 下面是持久化缓存Api\ncloud.shared.set(key,val); //设置缓存,仅能设置值,无法设置过期时间\ncloud.shared.get(key);\ncloud.shared.has(key); \ncloud.shared.delete(key); \ncloud.shared.clear(); \n~~~\n下例是ctx对象\n~~~\nctx.requestId\nctx.method\nctx.headers//请求的 headers, ctx.headers.get('Content-Type')获取Content-Type的值\nctx.user//Http Bearer Token 认证时,获取token值\nctx.query\nctx.body\nctx.request//同express的Request\nctx.response//同express的Response\nctx.socket/WebSocket 实例\nctx.files//上传的文件 (File对象数组)\nctx.env//自定义的环境变量\n~~~\n下例是数据库获取数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {minMemory} = ctx.query\nconst _ = db.command;\nconst {data: users,total} = collection(\"users\")\n .where({//条件查询\n category: \"computer\",\n type: {\n memory: _gt(minMemory), \n }\n }) \n .skip(10)//跳过10条-分页时使用\n .limit(10)//仅返回10条\n .orderBy(\"name\", \"asc\") \n .orderBy(\"age\", \"desc\")\n .field({age:true,name: false})//返回age不返回name\n}\nconst {data:user} = db.where({phone:req.body.phone}).getOne()//获取一个满足条件的用户\nreturn {users,total}\n~~~\n下例是数据库添加数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext) {\n const {username} = ctx.body\n const {id:userId, ok} = await collection(\"users\")\n .add({\n username, \n })\n if(ok) return {userId}\n return {code:500,message:\"失败\"}\n}\n~~~\n下例是数据库更新数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\n//id直接修改\nawait collection(\"user\").doc(\"id\").update({\n name: \"Hey\",\n});\n//批量更新\nawait collection\n .where({name:\"1234\"})\n .update({\n age:18\n })\nconst _ = db.command;\nawait collection(\"user\")\n .doc(id)\n .set({\n count: _.inc(1)\n count: _.mul(2)\n count: _.remove()\n users: _.push([\"aaa\", \"bbb\"])\n users: _.push(\"aaa\")\n users: _.pop()\n users: _.unshift()\n users: _.shift()\n })\n}\n~~~\n下例是删除数据库记录\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\ncollection(\"user\").doc(id).remove();\n//批量删除\ncollection\n .where({age:18}) \n .remove({multi: true})\nreturn \"success\"\n}\n~~~\n你只需返回 ts 代码块!不需要说明.\n用户的问题与 Laf 代码无关时,你直接回答: \"我不确定,我只会写 Laf 代码。\"`, +// limitPrompt: +// '你是由 Laf 团队开发的代码助手,把我的需求用 Laf 代码实现.参考知识库中 Laf 的例子.' +// }, +// inputs: [ +// { +// key: 'quotePrompt', +// value: undefined +// }, +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'answer', +// answer: true, +// value: undefined, +// targets: [] +// } +// ] +// }, +// { +// moduleId: 'd', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '你好,我没有理解你的意思,请问你有什么 Laf 相关的问题么?' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// } +// ] +// }; + +// export const lafClassifyQuestionDemo: AppItemType = { +// id: 'test', +// // 标记字段 +// modules: [ +// { +// moduleId: '1', +// type: AppModuleItemTypeEnum.http, +// url: '/openapi/modules/agent/classifyQuestion', +// body: { +// systemPrompt: +// 'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。\nsealos是一个 k8s 云平台,可以让用户快速部署云服务。', +// agents: [ +// { +// desc: '打招呼、问候、身份询问等问题', +// key: 'a' +// }, +// { +// desc: "询问 'laf 的使用和介绍'", +// key: 'b' +// }, +// { +// desc: "询问 'laf 代码相关问题'", +// key: 'c' +// }, +// { +// desc: "用户希望运行或知道 'laf 代码' 运行结果", +// key: 'g' +// }, +// { +// desc: "询问 'sealos 相关问题'", +// key: 'd' +// }, +// { +// desc: '其他问题', +// key: 'e' +// }, +// { +// desc: '商务类问题', +// key: 'f' +// } +// ] +// }, +// inputs: [ +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'a', +// value: undefined, +// targets: [ +// { +// moduleId: 'a', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'b', +// value: undefined, +// targets: [ +// { +// moduleId: 'b', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'c', +// value: undefined, +// targets: [ +// { +// moduleId: 'c', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'd', +// value: undefined, +// targets: [ +// { +// moduleId: 'd', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'e', +// value: undefined, +// targets: [ +// { +// moduleId: 'e', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'f', +// value: undefined, +// targets: [ +// { +// moduleId: 'f', +// key: SystemInputEnum.switch +// } +// ] +// }, +// { +// key: 'g', +// value: undefined, +// targets: [ +// { +// moduleId: 'g', +// key: SystemInputEnum.switch +// } +// ] +// } +// ] +// }, +// { +// moduleId: 'a', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '你好,我是 环界云 助手,你有什么 Laf 或者 sealos 的 问题么?' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'b', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '查询 Laf 通用知识库:xxxxx' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'c', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '查询 Laf 代码知识库:xxxxx' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'd', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '查询 sealos 通用知识库: xxxx' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'e', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '其他问题。回复引导语:xxxx' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'f', +// type: 'answer', +// body: {}, +// inputs: [ +// { +// key: SpecificInputEnum.answerText, +// value: '商务类问题,联系方式:xxxxx' +// }, +// { +// key: SystemInputEnum.switch, +// value: undefined +// } +// ], +// outputs: [] +// }, +// { +// moduleId: 'g', +// type: 'http', +// url: '/openapi/modules/agent/extract', +// body: { +// description: '运行 laf 代码', +// agents: [ +// { +// desc: '代码内容', +// key: 'code' +// } +// ] +// }, +// inputs: [ +// { +// key: SystemInputEnum.switch, +// value: undefined +// }, +// { +// key: SystemInputEnum.history, +// value: undefined +// }, +// { +// key: SystemInputEnum.userChatInput, +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'code', +// value: undefined, +// targets: [ +// { +// moduleId: 'code_run', +// key: 'code' +// } +// ] +// } +// ] +// }, +// { +// moduleId: 'code_run', +// type: AppModuleItemTypeEnum.http, +// url: 'https://v1cde7.laf.run/tess', +// body: {}, +// inputs: [ +// { +// key: 'code', +// value: undefined +// } +// ], +// outputs: [ +// { +// key: 'star', +// value: undefined, +// targets: [] +// } +// ] +// } +// ] +// }; diff --git a/client/src/constants/data.ts b/client/src/constants/data.ts new file mode 100644 index 000000000..e72f987fb --- /dev/null +++ b/client/src/constants/data.ts @@ -0,0 +1,12 @@ +export enum ChatModelEnum { + 'GPT35' = 'gpt-3.5-turbo', + 'GPT3516k' = 'gpt-3.5-turbo-16k', + 'GPT4' = 'gpt-4', + 'GPT432k' = 'gpt-4-32k' +} + +export const chatModelList = [ + { label: 'Gpt35-16k', value: ChatModelEnum.GPT3516k }, + { label: 'Gpt35-4k', value: ChatModelEnum.GPT35 }, + { label: 'Gpt4-8k', value: ChatModelEnum.GPT4 } +]; diff --git a/client/src/constants/flow/ModuleTemplate.ts b/client/src/constants/flow/ModuleTemplate.ts new file mode 100644 index 000000000..42d5d70cd --- /dev/null +++ b/client/src/constants/flow/ModuleTemplate.ts @@ -0,0 +1,311 @@ +import { AppModuleItemTypeEnum, SystemInputEnum } from '../app'; +import { FlowModuleTypeEnum, FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from './index'; +import type { AppModuleTemplateItemType } from '@/types/app'; +import { chatModelList } from '../data'; +import { + Input_Template_History, + Input_Template_TFSwitch, + Input_Template_UserChatInput +} from './inputTemplate'; + +export const UserInputModule: AppModuleTemplateItemType = { + logo: '', + name: '用户问题', + intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', + type: AppModuleItemTypeEnum.initInput, + flowType: FlowModuleTypeEnum.questionInputNode, + url: '/openapi/modules/init/userChatInput', + inputs: [ + { + key: SystemInputEnum.userChatInput, + type: FlowInputItemTypeEnum.systemInput, + label: '用户问题' + } + ], + outputs: [ + { + key: SystemInputEnum.userChatInput, + label: '用户问题', + type: FlowOutputItemTypeEnum.source, + targets: [] + } + ] +}; +export const HistoryModule: AppModuleTemplateItemType = { + logo: '', + name: '聊天记录', + intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。', + type: AppModuleItemTypeEnum.initInput, + flowType: FlowModuleTypeEnum.historyNode, + url: '/openapi/modules/init/history', + inputs: [ + { + key: 'maxContext', + type: FlowInputItemTypeEnum.numberInput, + label: '最长记录数', + value: 4, + min: 0, + max: 50 + }, + { + key: SystemInputEnum.history, + type: FlowInputItemTypeEnum.hidden, + label: '聊天记录' + } + ], + outputs: [ + { + key: SystemInputEnum.history, + label: '聊天记录', + type: FlowOutputItemTypeEnum.source, + targets: [] + } + ] +}; + +export const ChatModule: AppModuleTemplateItemType = { + logo: '', + name: 'AI 对话', + intro: 'OpenAI GPT 大模型对话。', + flowType: FlowModuleTypeEnum.chatNode, + type: AppModuleItemTypeEnum.http, + url: '/openapi/modules/chat/gpt', + inputs: [ + { + key: 'model', + type: FlowInputItemTypeEnum.select, + label: '对话模型', + value: chatModelList[0].value, + list: chatModelList + }, + { + key: 'temperature', + type: FlowInputItemTypeEnum.slider, + label: '温度', + value: 0, + min: 0, + max: 10, + step: 1, + markList: [ + { label: '严谨', value: 0 }, + { label: '发散', value: 10 } + ] + }, + { + key: 'maxToken', + type: FlowInputItemTypeEnum.slider, + label: '回复上限', + value: 3000, + min: 0, + max: 4000, + step: 50, + markList: [ + { label: '0', value: 0 }, + { label: '4000', value: 4000 } + ] + }, + { + key: 'systemPrompt', + type: FlowInputItemTypeEnum.textarea, + label: '系统提示词', + description: + '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', + placeholder: + '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。', + value: '' + }, + { + key: 'limitPrompt', + type: FlowInputItemTypeEnum.textarea, + label: '限定词', + description: + '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', + placeholder: + '限定模型对话范围,会被放置在本次提问前,拥有强引导和限定性。例如:\n1. 知识库是关于 Laf 的介绍,参考知识库回答问题,与 "Laf" 无关内容,直接回复: "我不知道"。\n2. 你仅回答关于 "xxx" 的问题,其他问题回复: "xxxx"', + value: '' + }, + Input_Template_TFSwitch, + { + key: 'quotePrompt', + type: FlowInputItemTypeEnum.target, + label: '引用内容' + }, + Input_Template_History, + Input_Template_UserChatInput + ], + outputs: [ + { + key: 'answer', + label: '模型回复', + description: '直接响应,无需配置', + type: FlowOutputItemTypeEnum.hidden, + targets: [] + } + ] +}; + +export const KBSearchModule: AppModuleTemplateItemType = { + logo: '', + name: '知识库搜索', + intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。', + flowType: FlowModuleTypeEnum.kbSearchNode, + type: AppModuleItemTypeEnum.http, + url: '/openapi/modules/kb/search', + inputs: [ + { + key: 'kb_ids', + type: FlowInputItemTypeEnum.custom, + label: '关联的知识库', + value: [], + list: [] + }, + { + key: 'similarity', + type: FlowInputItemTypeEnum.slider, + label: '相似度', + value: 0.8, + min: 0, + max: 1, + step: 0.01, + markList: [ + { label: '0', value: 0 }, + { label: '1', value: 1 } + ] + }, + { + key: 'limit', + type: FlowInputItemTypeEnum.slider, + label: '单次搜索上限', + value: 5, + min: 1, + max: 20, + step: 1, + markList: [ + { label: '1', value: 1 }, + { label: '20', value: 20 } + ] + }, + Input_Template_TFSwitch, + Input_Template_UserChatInput + ], + outputs: [ + { + key: 'rawSearch', + label: '源搜索数据', + type: FlowOutputItemTypeEnum.hidden, + response: true, + targets: [] + }, + { + key: 'isEmpty', + label: '搜索结果为空', + type: FlowOutputItemTypeEnum.source, + targets: [] + }, + { + key: 'quotePrompt', + label: '引用内容', + description: '搜索结果为空时不触发', + type: FlowOutputItemTypeEnum.source, + targets: [] + } + ] +}; + +export const AnswerModule: AppModuleTemplateItemType = { + logo: '', + name: '指定回复', + intro: '该模块可以直接回复一段指定的内容。常用于引导、提示。', + type: AppModuleItemTypeEnum.answer, + flowType: FlowModuleTypeEnum.answerNode, + inputs: [ + Input_Template_TFSwitch, + { + key: 'answerText', + value: '', + type: FlowInputItemTypeEnum.input, + label: '回复的内容' + } + ], + outputs: [] +}; +export const TFSwitchModule: AppModuleTemplateItemType = { + logo: '', + name: 'TF开关', + intro: '可以判断输入的内容为 True 或者 False,从而执行不同操作。', + type: AppModuleItemTypeEnum.switch, + flowType: FlowModuleTypeEnum.tfSwitchNode, + inputs: [ + { + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '输入' + } + ], + outputs: [ + { + key: 'true', + label: 'True', + type: FlowOutputItemTypeEnum.source, + targets: [] + }, + { + key: 'false', + label: 'False', + type: FlowOutputItemTypeEnum.source, + targets: [] + } + ] +}; + +export const ClassifyQuestionModule: AppModuleTemplateItemType = { + logo: '', + name: '意图识别', + intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。', + type: AppModuleItemTypeEnum.switch, + flowType: FlowModuleTypeEnum.tfSwitchNode, + inputs: [ + { + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '输入' + } + ], + outputs: [ + { + key: 'true', + label: 'True', + type: FlowOutputItemTypeEnum.source, + targets: [] + }, + { + key: 'false', + label: 'False', + type: FlowOutputItemTypeEnum.source, + targets: [] + } + ] +}; + +export const ModuleTemplates = [ + { + label: '输入模块', + list: [UserInputModule, HistoryModule] + }, + { + label: '对话模块', + list: [ChatModule] + }, + { + label: '知识库模块', + list: [KBSearchModule] + }, + { + label: '工具', + list: [AnswerModule, TFSwitchModule] + }, + { + label: 'Agent', + list: [ClassifyQuestionModule] + } +]; diff --git a/client/src/constants/flow/defaultModule.ts b/client/src/constants/flow/defaultModule.ts new file mode 100644 index 000000000..fa6d725f4 --- /dev/null +++ b/client/src/constants/flow/defaultModule.ts @@ -0,0 +1,5 @@ +import { CSSProperties } from 'react'; + +export const nodeDefaultStyle: CSSProperties = { + border: '1px solid #DEE0E2' +}; diff --git a/client/src/constants/flow/index.ts b/client/src/constants/flow/index.ts new file mode 100644 index 000000000..db6e6c6f8 --- /dev/null +++ b/client/src/constants/flow/index.ts @@ -0,0 +1,36 @@ +export enum FlowInputItemTypeEnum { + systemInput = 'systemInput', // history, userChatInput + input = 'input', + textarea = 'textarea', + numberInput = 'numberInput', + select = 'select', + slider = 'slider', + custom = 'custom', + target = 'target', + none = 'none', + hidden = 'hidden' +} + +export enum FlowOutputItemTypeEnum { + answer = 'answer', + source = 'source', + none = 'none', + hidden = 'hidden' +} + +export enum FlowModuleTypeEnum { + questionInputNode = 'questionInput', + historyNode = 'historyNode', + chatNode = 'chatNode', + kbSearchNode = 'kbSearchNode', + tfSwitchNode = 'tfSwitchNode', + answerNode = 'answerNode' +} + +export const edgeOptions = { + style: { + strokeWidth: 1, + stroke: '#5A646Es' + } +}; +export const connectionLineStyle = { strokeWidth: 1, stroke: '#5A646Es' }; diff --git a/client/src/constants/flow/inputTemplate.ts b/client/src/constants/flow/inputTemplate.ts new file mode 100644 index 000000000..912c783fc --- /dev/null +++ b/client/src/constants/flow/inputTemplate.ts @@ -0,0 +1,21 @@ +import { FlowInputItemType } from '@/types/flow'; +import { SystemInputEnum } from '../app'; +import { FlowInputItemTypeEnum } from './index'; + +export const Input_Template_TFSwitch: FlowInputItemType = { + key: SystemInputEnum.switch, + type: FlowInputItemTypeEnum.target, + label: '触发器' +}; + +export const Input_Template_History: FlowInputItemType = { + key: SystemInputEnum.history, + type: FlowInputItemTypeEnum.target, + label: '聊天记录' +}; + +export const Input_Template_UserChatInput: FlowInputItemType = { + key: SystemInputEnum.userChatInput, + type: FlowInputItemTypeEnum.target, + label: '用户问题' +}; diff --git a/client/src/constants/model.ts b/client/src/constants/model.ts index 696d7486f..4e9df2d1d 100644 --- a/client/src/constants/model.ts +++ b/client/src/constants/model.ts @@ -1,5 +1,5 @@ import type { ShareChatEditType } from '@/types/model'; -import type { ModelSchema } from '@/types/mongoSchema'; +import type { AppSchema } from '@/types/mongoSchema'; export const embeddingModel = 'text-embedding-ada-002'; export const embeddingPrice = 0.1; @@ -64,8 +64,8 @@ export const chatModelList: ChatModelItemType[] = [ ChatModelMap[OpenAiChatEnum.GPT4] ]; -export const defaultModel: ModelSchema = { - _id: 'modelId', +export const defaultApp: AppSchema = { + _id: '', userId: 'userId', name: '模型名称', avatar: '/icon/logo.png', @@ -86,7 +86,8 @@ export const defaultModel: ModelSchema = { isShare: false, isShareDetail: false, collection: 0 - } + }, + modules: [] }; export const defaultShareChat: ShareChatEditType = { diff --git a/client/src/constants/theme.ts b/client/src/constants/theme.ts index b2fce8cb7..38c9de768 100644 --- a/client/src/constants/theme.ts +++ b/client/src/constants/theme.ts @@ -1,6 +1,6 @@ import { extendTheme, defineStyleConfig, ComponentStyleConfig } from '@chakra-ui/react'; // @ts-ignore -import { modalAnatomy, switchAnatomy, selectAnatomy, checkboxAnatomy } from '@chakra-ui/anatomy'; +import { modalAnatomy, switchAnatomy, selectAnatomy, numberInputAnatomy } from '@chakra-ui/anatomy'; // @ts-ignore import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system'; @@ -11,6 +11,8 @@ const { definePartsStyle: switchPart, defineMultiStyleConfig: switchMultiStyle } createMultiStyleConfigHelpers(switchAnatomy.keys); const { definePartsStyle: selectPart, defineMultiStyleConfig: selectMultiStyle } = createMultiStyleConfigHelpers(selectAnatomy.keys); +const { definePartsStyle: numInputPart, defineMultiStyleConfig: numInputMultiStyle } = + createMultiStyleConfigHelpers(numberInputAnatomy.keys); // modal 弹窗 const ModalTheme = defineMultiStyleConfig({ @@ -122,6 +124,39 @@ const Input: ComponentStyleConfig = { } }; +const NumberInput = numInputMultiStyle({ + variants: { + outline: numInputPart({ + field: { + bg: 'myWhite.300', + border: '1px solid', + borderRadius: 'base', + borderColor: 'myGray.200', + _focus: { + borderColor: 'myBlue.600 !important', + boxShadow: '0px 0px 4px #A8DBFF !important', + bg: 'transparent' + }, + _disabled: { + color: 'myGray.400 !important', + bg: 'myWhite.300 !important' + } + }, + stepper: { + bg: 'transparent', + border: 'none', + color: 'myGray.600', + _active: { + color: 'myBlue.600' + } + } + }) + }, + defaultProps: { + variant: 'outline' + } +}); + const Textarea: ComponentStyleConfig = { variants: { outline: { @@ -260,6 +295,7 @@ export const theme = extendTheme({ Input, Textarea, Switch, - Select + Select, + NumberInput } }); diff --git a/client/src/hooks/useRequest.tsx b/client/src/hooks/useRequest.tsx index a3184657d..29775a1ed 100644 --- a/client/src/hooks/useRequest.tsx +++ b/client/src/hooks/useRequest.tsx @@ -1,6 +1,7 @@ import { useToast } from '@/hooks/useToast'; import { useMutation } from '@tanstack/react-query'; import type { UseMutationOptions } from '@tanstack/react-query'; +import { getErrText } from '@/utils/tools'; interface Props extends UseMutationOptions { successToast?: string; @@ -23,7 +24,7 @@ export const useRequest = ({ successToast, errorToast, onSuccess, onError, ...pr onError?.(err, variables, context); errorToast && toast({ - title: typeof err === 'string' ? err : err?.message || errorToast, + title: getErrText(err, errorToast), status: 'error' }); } diff --git a/client/src/pages/api/chat/init.ts b/client/src/pages/api/chat/init.ts index 7d73462c8..65e2ce6ab 100644 --- a/client/src/pages/api/chat/init.ts +++ b/client/src/pages/api/chat/init.ts @@ -4,9 +4,9 @@ import { connectToDatabase, Chat, Model } from '@/service/mongo'; import type { InitChatResponse } from '@/api/response/chat'; import { authUser } from '@/service/utils/auth'; import { ChatItemType } from '@/types/chat'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; import mongoose from 'mongoose'; -import type { ModelSchema } from '@/types/mongoSchema'; +import type { AppSchema } from '@/types/mongoSchema'; /* 初始化我的聊天框,需要身份验证 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await connectToDatabase(); // 没有 modelId 时,直接获取用户的第一个id - const model = await (async () => { + const app = await (async () => { if (!modelId) { const myModel = await Model.findOne({ userId }); if (!myModel) { @@ -29,23 +29,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) name: '应用1', userId }); - return (await Model.findById(_id)) as ModelSchema; + return (await Model.findById(_id)) as AppSchema; } else { return myModel; } } else { // 校验使用权限 - const authRes = await authModel({ - modelId, + const authRes = await authApp({ + appId: modelId, userId, authUser: false, authOwner: false }); - return authRes.model; + return authRes.app; } })(); - modelId = modelId || model._id; + modelId = modelId || app._id; // 历史记录 let history: ChatItemType[] = []; @@ -87,21 +87,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) ]); } - const isOwner = String(model.userId) === userId; + const isOwner = String(app.userId) === userId; jsonRes(res, { data: { chatId: chatId || '', modelId: modelId, model: { - name: model.name, - avatar: model.avatar, - intro: model.intro, - canUse: model.share.isShare || isOwner + name: app.name, + avatar: app.avatar, + intro: app.intro, + canUse: app.share.isShare || isOwner }, - chatModel: model.chat.chatModel, - systemPrompt: isOwner ? model.chat.systemPrompt : '', - limitPrompt: isOwner ? model.chat.limitPrompt : '', + chatModel: app.chat.chatModel, + systemPrompt: isOwner ? app.chat.systemPrompt : '', + limitPrompt: isOwner ? app.chat.limitPrompt : '', history } }); diff --git a/client/src/pages/api/chat/saveChat.ts b/client/src/pages/api/chat/saveChat.ts index e196cc5b3..387fc7e42 100644 --- a/client/src/pages/api/chat/saveChat.ts +++ b/client/src/pages/api/chat/saveChat.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { ChatItemType } from '@/types/chat'; import { connectToDatabase, Chat, Model } from '@/service/mongo'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth'; import { Types } from 'mongoose'; @@ -49,7 +49,7 @@ export async function saveChat({ userId }: Props & { newChatId?: Types.ObjectId; userId: string }): Promise<{ newChatId: string }> { await connectToDatabase(); - const { model } = await authModel({ modelId, userId, authOwner: false }); + const { app } = await authApp({ appId: modelId, userId, authOwner: false }); const content = prompts.map((item) => ({ _id: item._id, @@ -59,7 +59,7 @@ export async function saveChat({ quote: item.quote || [] })); - if (String(model.userId) === userId) { + if (String(app.userId) === userId) { await Model.findByIdAndUpdate(modelId, { updateTime: new Date() }); @@ -93,8 +93,8 @@ export async function saveChat({ newChatId: String(res._id) })) ]), - // update model - ...(String(model.userId) === userId + // update app + ...(String(app.userId) === userId ? [ Model.findByIdAndUpdate(modelId, { updateTime: new Date() diff --git a/client/src/pages/api/chat/shareChat/create.ts b/client/src/pages/api/chat/shareChat/create.ts index 1318591fa..197f7c0c1 100644 --- a/client/src/pages/api/chat/shareChat/create.ts +++ b/client/src/pages/api/chat/shareChat/create.ts @@ -1,7 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { connectToDatabase, ShareChat } from '@/service/mongo'; -import { authModel, authUser } from '@/service/utils/auth'; +import { authApp, authUser } from '@/service/utils/auth'; import type { ShareChatEditType } from '@/types/model'; /* create a shareChat */ @@ -14,8 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) await connectToDatabase(); const { userId } = await authUser({ req, authToken: true }); - await authModel({ - modelId, + await authApp({ + appId: modelId, userId, authOwner: false }); diff --git a/client/src/pages/api/chat/shareChat/init.ts b/client/src/pages/api/chat/shareChat/init.ts index 3906b5658..124ebf816 100644 --- a/client/src/pages/api/chat/shareChat/init.ts +++ b/client/src/pages/api/chat/shareChat/init.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { connectToDatabase, ShareChat, User } from '@/service/mongo'; import type { InitShareChatResponse } from '@/api/response/chat'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; import { hashPassword } from '@/service/utils/tools'; import { HUMAN_ICON } from '@/constants/chat'; @@ -35,8 +35,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } // 校验使用权限 - const { model } = await authModel({ - modelId: shareChat.modelId, + const { app } = await authApp({ + appId: shareChat.modelId, userId: String(shareChat.userId), authOwner: false }); @@ -48,11 +48,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) maxContext: shareChat.maxContext, userAvatar: user?.avatar || HUMAN_ICON, model: { - name: model.name, - avatar: model.avatar, - intro: model.intro + name: app.name, + avatar: app.avatar, + intro: app.intro }, - chatModel: model.chat.chatModel + chatModel: app.chat.chatModel } }); } catch (err) { diff --git a/client/src/pages/api/model/del.ts b/client/src/pages/api/model/del.ts index 972746644..9eee33348 100644 --- a/client/src/pages/api/model/del.ts +++ b/client/src/pages/api/model/del.ts @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { Chat, Model, connectToDatabase, Collection, ShareChat } from '@/service/mongo'; import { authUser } from '@/service/utils/auth'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -19,8 +19,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); // 验证是否是该用户的 model - await authModel({ - modelId, + await authApp({ + appId: modelId, userId }); diff --git a/client/src/pages/api/model/detail.tsx b/client/src/pages/api/model/detail.tsx index 7836b1fd9..5b20d78cd 100644 --- a/client/src/pages/api/model/detail.tsx +++ b/client/src/pages/api/model/detail.tsx @@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/service/response'; import { connectToDatabase } from '@/service/mongo'; import { authUser } from '@/service/utils/auth'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -18,14 +18,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); - const { model } = await authModel({ - modelId, + const { app } = await authApp({ + appId: modelId, userId, authOwner: false }); jsonRes(res, { - data: model + data: app }); } catch (err) { jsonRes(res, { diff --git a/client/src/pages/api/model/list.ts b/client/src/pages/api/model/list.ts index a6856d16f..d229e4f89 100644 --- a/client/src/pages/api/model/list.ts +++ b/client/src/pages/api/model/list.ts @@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); // 根据 userId 获取模型信息 - const [myModels, myCollections] = await Promise.all([ + const [myApps, myCollections] = await Promise.all([ Model.find( { userId @@ -33,20 +33,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< jsonRes(res, { data: { - myModels: myModels.map((item) => ({ + myApps: myApps.map((item) => ({ _id: item._id, name: item.name, avatar: item.avatar, intro: item.intro })), - myCollectionModels: myCollections + myCollectionApps: myCollections .map((item: any) => ({ _id: item.modelId?._id, name: item.modelId?.name, avatar: item.modelId?.avatar, intro: item.modelId?.intro })) - .filter((item) => !myModels.find((model) => String(model._id) === String(item._id))) // 去重 + .filter((item) => !myApps.find((model) => String(model._id) === String(item._id))) // 去重 } }); } catch (err) { diff --git a/client/src/pages/api/model/update.ts b/client/src/pages/api/model/update.ts index c0b536d8b..00c365a1f 100644 --- a/client/src/pages/api/model/update.ts +++ b/client/src/pages/api/model/update.ts @@ -4,16 +4,16 @@ import { connectToDatabase } from '@/service/mongo'; import { authUser } from '@/service/utils/auth'; import { Model } from '@/service/models/model'; import type { ModelUpdateParams } from '@/types/model'; -import { authModel } from '@/service/utils/auth'; +import { authApp } from '@/service/utils/auth'; /* 获取我的模型 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { - const { name, avatar, chat, share, intro } = req.body as ModelUpdateParams; - const { modelId } = req.query as { modelId: string }; + const { name, avatar, chat, share, intro, modules } = req.body as ModelUpdateParams; + const { appId } = req.query as { appId: string }; - if (!modelId) { - throw new Error('参数错误'); + if (!appId) { + throw new Error('appId is empty'); } // 凭证校验 @@ -21,15 +21,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); - await authModel({ - modelId, + await authApp({ + appId, userId }); // 更新模型 await Model.updateOne( { - _id: modelId, + _id: appId, userId }, { @@ -40,7 +40,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< ...(share && { 'share.isShare': share.isShare, 'share.isShareDetail': share.isShareDetail - }) + }), + ...(modules && { modules }) } ); diff --git a/client/src/pages/api/openapi/chat/chat.ts b/client/src/pages/api/openapi/chat/chat.ts index 210cac9a4..4933f860e 100644 --- a/client/src/pages/api/openapi/chat/chat.ts +++ b/client/src/pages/api/openapi/chat/chat.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { connectToDatabase } from '@/service/mongo'; -import { authUser, authModel, getApiKey } from '@/service/utils/auth'; +import { authUser, authApp, getApiKey } from '@/service/utils/auth'; import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat'; import { ChatItemType } from '@/types/chat'; import { jsonRes } from '@/service/response'; @@ -50,19 +50,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex /* 凭证校验 */ const { userId } = await authUser({ req }); - const { model } = await authModel({ + const { app } = await authApp({ userId, - modelId + appId: modelId }); /* get api key */ const { systemAuthKey: apiKey } = await getApiKey({ - model: model.chat.chatModel, + model: app.chat.chatModel, userId, mustPay: true }); - const modelConstantsData = ChatModelMap[model.chat.chatModel]; + const modelConstantsData = ChatModelMap[app.chat.chatModel]; const prompt = prompts[prompts.length - 1]; const { @@ -71,14 +71,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex quotePrompt = [] } = await (async () => { // 使用了知识库搜索 - if (model.chat.relatedKbs?.length > 0) { + if (app.chat.relatedKbs?.length > 0) { const { quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({ - model, + model: app, userId, fixedQuote: [], prompt: prompt, - similarity: model.chat.searchSimilarity, - limit: model.chat.searchLimit + similarity: app.chat.searchSimilarity, + limit: app.chat.searchLimit }); return { @@ -88,19 +88,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }; } return { - userSystemPrompt: model.chat.systemPrompt + userSystemPrompt: app.chat.systemPrompt ? [ { obj: ChatRoleEnum.System, - value: model.chat.systemPrompt + value: app.chat.systemPrompt } ] : [], - userLimitPrompt: model.chat.limitPrompt + userLimitPrompt: app.chat.limitPrompt ? [ { obj: ChatRoleEnum.Human, - value: model.chat.limitPrompt + value: app.chat.limitPrompt } ] : [] @@ -108,8 +108,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex })(); // search result is empty - if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) { - const response = model.chat.searchEmptyText; + if (app.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && app.chat.searchEmptyText) { + const response = app.chat.searchEmptyText; return res.end(response); } @@ -123,14 +123,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ]; // 计算温度 - const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed( + const temperature = (modelConstantsData.maxTemperature * (app.chat.temperature / 10)).toFixed( 2 ); // 发出请求 const { streamResponse, responseMessages, responseText, totalTokens } = await modelServiceToolMap.chatCompletion({ - model: model.chat.chatModel, + model: app.chat.chatModel, apiKey, temperature: +temperature, messages: completePrompts, @@ -146,7 +146,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex if (isStream) { try { const { finishMessages, totalTokens } = await resStreamResponse({ - model: model.chat.chatModel, + model: app.chat.chatModel, res, chatResponse: streamResponse, prompts: responseMessages @@ -173,7 +173,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex pushChatBill({ isPay: true, - chatModel: model.chat.chatModel, + chatModel: app.chat.chatModel, userId, textLen, tokens, diff --git a/client/src/pages/api/openapi/kb/appKbSearch.ts b/client/src/pages/api/openapi/kb/appKbSearch.ts index 77bdde4f4..ad6876c11 100644 --- a/client/src/pages/api/openapi/kb/appKbSearch.ts +++ b/client/src/pages/api/openapi/kb/appKbSearch.ts @@ -4,8 +4,8 @@ import { authUser } from '@/service/utils/auth'; import { PgClient } from '@/service/pg'; import { withNextCors } from '@/service/utils/tools'; import type { ChatItemType } from '@/types/chat'; -import type { ModelSchema } from '@/types/mongoSchema'; -import { authModel } from '@/service/utils/auth'; +import type { AppSchema } from '@/types/mongoSchema'; +import { authApp } from '@/service/utils/auth'; import { ChatModelMap } from '@/constants/model'; import { ChatRoleEnum } from '@/constants/chat'; import { openaiEmbedding } from '../plugin/openaiEmbedding'; @@ -54,13 +54,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } // auth model - const { model } = await authModel({ - modelId: appId, + const { app } = await authApp({ + appId, userId }); const result = await appKbSearch({ - model, + model: app, userId, fixedQuote: [], prompt: prompts[prompts.length - 1], @@ -88,7 +88,7 @@ export async function appKbSearch({ similarity = 0.8, limit = 5 }: { - model: ModelSchema; + model: AppSchema; userId: string; fixedQuote?: QuoteItemType[]; prompt: ChatItemType; diff --git a/client/src/pages/api/openapi/modules/agent/classifyQuestion.ts b/client/src/pages/api/openapi/modules/agent/classifyQuestion.ts index c4274b5e4..330bb1a30 100644 --- a/client/src/pages/api/openapi/modules/agent/classifyQuestion.ts +++ b/client/src/pages/api/openapi/modules/agent/classifyQuestion.ts @@ -106,7 +106,7 @@ export async function classifyQuestion({ if (!arg.type) { throw new Error(''); } - console.log(adaptMessages, arg.type); + console.log(arg.type); return { [arg.type]: 1 diff --git a/client/src/pages/api/openapi/modules/chat/gpt.ts b/client/src/pages/api/openapi/modules/chat/gpt.ts index 9351785ef..eb5ccee70 100644 --- a/client/src/pages/api/openapi/modules/chat/gpt.ts +++ b/client/src/pages/api/openapi/modules/chat/gpt.ts @@ -5,9 +5,8 @@ import { sseResponse } from '@/service/utils/tools'; import { ChatModelMap, OpenAiChatEnum } from '@/constants/model'; import { adaptChatItem_openAI } from '@/utils/plugin/openai'; import { modelToolMap } from '@/utils/plugin'; -import { ChatCompletionType, ChatContextFilter } from '@/service/utils/chat/index'; +import { ChatContextFilter } from '@/service/utils/chat/index'; import type { ChatItemType } from '@/types/chat'; -import { getSystemOpenAiKey } from '@/service/utils/auth'; import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt'; import { getOpenAIApi, axiosConfig } from '@/service/ai/openai'; @@ -23,7 +22,7 @@ export type Props = { systemPrompt?: string; limitPrompt?: string; }; -export type Response = { history: ChatItemType[] }; +export type Response = { answer: string }; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -89,7 +88,7 @@ export async function chatCompletion({ userChatInput, systemPrompt, limitPrompt -}: Props & { res: NextApiResponse }) { +}: Props & { res: NextApiResponse }): Promise { const messages: ChatItemType[] = [ ...(quotePrompt ? [ @@ -131,7 +130,6 @@ export async function chatCompletion({ const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false }); const chatAPI = getOpenAIApi(); - console.log(adaptMessages); /* count response max token */ const promptsToken = modelToolMap[model].countTokens({ @@ -156,37 +154,35 @@ export async function chatCompletion({ } ); - const { answer, totalTokens } = await (async () => { + const { answer } = await (async () => { if (stream) { // sse response const { answer } = await streamResponse({ res, response }); // count tokens - const finishMessages = filterMessages.concat({ - obj: ChatRoleEnum.AI, - value: answer - }); + // const finishMessages = filterMessages.concat({ + // obj: ChatRoleEnum.AI, + // value: answer + // }); - const totalTokens = modelToolMap[model].countTokens({ - messages: finishMessages - }); + // const totalTokens = modelToolMap[model].countTokens({ + // messages: finishMessages + // }); return { - answer, - totalTokens + answer + // totalTokens }; } else { const answer = stream ? '' : response.data.choices?.[0].message?.content || ''; - const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0; + // const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0; return { - answer, - totalTokens + answer + // totalTokens }; } })(); - // count price - const unitPrice = ChatModelMap[model]?.price || 3; return { answer }; diff --git a/client/src/pages/api/openapi/modules/init/history.tsx b/client/src/pages/api/openapi/modules/init/history.tsx new file mode 100644 index 000000000..a6fd9ef92 --- /dev/null +++ b/client/src/pages/api/openapi/modules/init/history.tsx @@ -0,0 +1,20 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@/service/response'; +import { SystemInputEnum } from '@/constants/app'; +import { ChatItemType } from '@/types/chat'; + +export type Props = { + maxContext: number; + [SystemInputEnum.history]: ChatItemType[]; +}; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { maxContext = 5, history } = req.body as Props; + + jsonRes(res, { + data: { + history: history.slice(-maxContext) + } + }); +} diff --git a/client/src/pages/api/openapi/modules/init/userChatInput.tsx b/client/src/pages/api/openapi/modules/init/userChatInput.tsx new file mode 100644 index 000000000..33de8cb19 --- /dev/null +++ b/client/src/pages/api/openapi/modules/init/userChatInput.tsx @@ -0,0 +1,17 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction +import type { NextApiRequest, NextApiResponse } from 'next'; +import { jsonRes } from '@/service/response'; +import { SystemInputEnum } from '@/constants/app'; + +export type Props = { + [SystemInputEnum.userChatInput]: string; +}; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { userChatInput } = req.body as Props; + jsonRes(res, { + data: { + userChatInput + } + }); +} diff --git a/client/src/pages/api/openapi/modules/kb/search.ts b/client/src/pages/api/openapi/modules/kb/search.ts index ea6f1127a..983fa5758 100644 --- a/client/src/pages/api/openapi/modules/kb/search.ts +++ b/client/src/pages/api/openapi/modules/kb/search.ts @@ -25,7 +25,7 @@ type Props = { type Response = { rawSearch: QuoteItemType[]; isEmpty?: boolean; - quotePrompt: string; + quotePrompt?: string; }; export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -43,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex throw new Error('params is error'); } - const result = await appKbSearch({ + const result = await kbSearch({ kb_ids, history, similarity, @@ -64,7 +64,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } }); -export async function appKbSearch({ +export async function kbSearch({ kb_ids = [], history = [], similarity = 0.8, @@ -108,8 +108,8 @@ export async function appKbSearch({ const rawSearch = searchRes.slice(0, sliceResult.length); return { - isEmpty: rawSearch.length === 0, + isEmpty: rawSearch.length === 0 ? true : undefined, rawSearch, - quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : '' + quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : undefined }; } diff --git a/client/src/pages/api/openapi/modules/tools/httpRequest.ts b/client/src/pages/api/openapi/modules/tools/httpRequest.ts deleted file mode 100644 index 7f11aeff5..000000000 --- a/client/src/pages/api/openapi/modules/tools/httpRequest.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type Props = { - url: string; - body: Record; -}; diff --git a/client/src/pages/api/openapi/plugin/openaiEmbedding.ts b/client/src/pages/api/openapi/plugin/openaiEmbedding.ts index 4a215d1ee..841100d80 100644 --- a/client/src/pages/api/openapi/plugin/openaiEmbedding.ts +++ b/client/src/pages/api/openapi/plugin/openaiEmbedding.ts @@ -96,7 +96,7 @@ export async function openaiEmbedding_system({ input }: Props) { input }, { - timeout: 60000, + timeout: 20000, ...axiosConfig(apiKey) } ) diff --git a/client/src/pages/api/openapi/v1/chat/completions.ts b/client/src/pages/api/openapi/v1/chat/completions.ts index 0bcda9e66..5b8c3540f 100644 --- a/client/src/pages/api/openapi/v1/chat/completions.ts +++ b/client/src/pages/api/openapi/v1/chat/completions.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { connectToDatabase } from '@/service/mongo'; -import { authUser, authModel, getApiKey, authShareChat } from '@/service/utils/auth'; +import { authUser, authApp, getApiKey, authShareChat } from '@/service/utils/auth'; import { modelServiceToolMap, V2_StreamResponse } from '@/service/utils/chat'; import { jsonRes } from '@/service/response'; import { ChatModelMap } from '@/constants/model'; @@ -79,9 +79,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } // auth app permission - const { model, showModelDetail } = await authModel({ + const { app, showModelDetail } = await authApp({ userId, - modelId: appId, + appId, authOwner: false, reserveDetail: true }); @@ -90,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex /* get api key */ const { systemAuthKey: apiKey, userOpenAiKey } = await getApiKey({ - model: model.chat.chatModel, + model: app.chat.chatModel, userId, mustPay: authType !== 'token' }); @@ -112,14 +112,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex quotePrompt = [] } = await (async () => { // 使用了知识库搜索 - if (model.chat.relatedKbs?.length > 0) { + if (app.chat.relatedKbs?.length > 0) { const { rawSearch, quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({ - model, + model: app, userId, fixedQuote: history[history.length - 1]?.quote, prompt, - similarity: model.chat.searchSimilarity, - limit: model.chat.searchLimit + similarity: app.chat.searchSimilarity, + limit: app.chat.searchLimit }); return { @@ -130,19 +130,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }; } return { - userSystemPrompt: model.chat.systemPrompt + userSystemPrompt: app.chat.systemPrompt ? [ { obj: ChatRoleEnum.System, - value: model.chat.systemPrompt + value: app.chat.systemPrompt } ] : [], - userLimitPrompt: model.chat.limitPrompt + userLimitPrompt: app.chat.limitPrompt ? [ { obj: ChatRoleEnum.Human, - value: model.chat.limitPrompt + value: app.chat.limitPrompt } ] : [] @@ -150,15 +150,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex })(); // search result is empty - if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) { - const response = model.chat.searchEmptyText; + if (app.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && app.chat.searchEmptyText) { + const response = app.chat.searchEmptyText; if (stream) { sseResponse({ res, event: sseResponseEventEnum.answer, data: textAdaptGptResponse({ text: response, - model: model.chat.chatModel, + model: app.chat.chatModel, finish_reason: 'stop' }) }); @@ -166,9 +166,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex } else { return res.json({ id: chatId || '', - model: model.chat.chatModel, object: 'chat.completion', created: 1688608930, + model: app.chat.chatModel, usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, choices: [ { message: { role: 'assistant', content: response }, finish_reason: 'stop', index: 0 } @@ -186,9 +186,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex prompt ]; // chat temperature - const modelConstantsData = ChatModelMap[model.chat.chatModel]; + const modelConstantsData = ChatModelMap[app.chat.chatModel]; // FastGpt temperature range: 1~10 - const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed( + const temperature = (modelConstantsData.maxTemperature * (app.chat.temperature / 10)).toFixed( 2 ); @@ -196,13 +196,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex input: `${userSystemPrompt[0]?.value}\n${userLimitPrompt[0]?.value}\n${prompt.value}` }); - // start model api. responseText and totalTokens: valid only if stream = false + // start app api. responseText and totalTokens: valid only if stream = false const { streamResponse, responseMessages, responseText, totalTokens } = await modelServiceToolMap.chatCompletion({ - model: model.chat.chatModel, + model: app.chat.chatModel, apiKey: userOpenAiKey || apiKey, temperature: +temperature, - maxToken: model.chat.maxToken, + maxToken: app.chat.maxToken, messages: completePrompts, stream, res @@ -242,7 +242,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex }); // response answer const { finishMessages, totalTokens, responseContent } = await V2_StreamResponse({ - model: model.chat.chatModel, + model: app.chat.chatModel, res, chatResponse: streamResponse, prompts: responseMessages @@ -300,7 +300,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex id: chatId || '', object: 'chat.completion', created: 1688608930, - model: model.chat.chatModel, + model: app.chat.chatModel, usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: tokens }, choices: [ { message: { role: 'assistant', content: answer }, finish_reason: 'stop', index: 0 } @@ -310,7 +310,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex pushChatBill({ isPay: !userOpenAiKey, - chatModel: model.chat.chatModel, + chatModel: app.chat.chatModel, userId, textLen, tokens, diff --git a/client/src/pages/api/openapi/v1/chat/test.ts b/client/src/pages/api/openapi/v1/chat/completions2.ts similarity index 65% rename from client/src/pages/api/openapi/v1/chat/test.ts rename to client/src/pages/api/openapi/v1/chat/completions2.ts index 938efb5d5..79bf4eba8 100644 --- a/client/src/pages/api/openapi/v1/chat/test.ts +++ b/client/src/pages/api/openapi/v1/chat/completions2.ts @@ -1,6 +1,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { connectToDatabase } from '@/service/mongo'; -import { authUser, authModel, getApiKey, authShareChat } from '@/service/utils/auth'; +import { authUser, authApp, getApiKey, authShareChat } from '@/service/utils/auth'; import { sseErrRes, jsonRes } from '@/service/response'; import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat'; import { withNextCors } from '@/service/utils/tools'; @@ -13,14 +13,14 @@ import { type ChatCompletionRequestMessage } from 'openai'; import { kbChatAppDemo, chatAppDemo, - lafClassifyQuestionDemo, - classifyQuestionDemo, SpecificInputEnum, AppModuleItemTypeEnum } from '@/constants/app'; -import { Types } from 'mongoose'; +import { model, Types } from 'mongoose'; import { moduleFetch } from '@/service/api/request'; -import { AppModuleItemType } from '@/types/app'; +import { AppModuleItemType, RunningModuleItemType } from '@/types/app'; +import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from '@/constants/flow'; +import { SystemInputEnum } from '@/constants/app'; export type MessageItemType = ChatCompletionRequestMessage & { _id?: string }; type FastGptWebChatProps = { @@ -82,8 +82,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex throw new Error('appId is empty'); } - // get history - const { history } = await getChatHistory({ chatId, userId }); + // auth app, get history + const [{ app }, { history }] = await Promise.all([ + authApp({ + appId, + userId + }), + getChatHistory({ chatId, userId }) + ]); + const prompts = history.concat(gptMessage2ChatType(messages)); if (prompts[prompts.length - 1].obj === 'AI') { prompts.pop(); @@ -95,12 +102,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex throw new Error('Question is empty'); } - /* start process */ - const modules = JSON.parse(JSON.stringify(classifyQuestionDemo.modules)); + const newChatId = chatId === '' ? new Types.ObjectId() : undefined; + if (stream && newChatId) { + res.setHeader('newChatId', String(newChatId)); + } + /* start process */ const { responseData, answerText } = await dispatchModules({ res, - modules, + modules: app.modules, params: { history: prompts, userChatInput: prompt.value @@ -110,8 +120,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex // save chat if (typeof chatId === 'string') { - const { newChatId } = await saveChat({ + await saveChat({ chatId, + newChatId, modelId: appId, prompts: [ prompt, @@ -124,19 +135,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex ], userId }); - - if (newChatId) { - sseResponse({ - res, - event: sseResponseEventEnum.chatResponse, - data: JSON.stringify({ - newChatId - }) - }); - } } if (stream) { + sseResponse({ + res, + event: sseResponseEventEnum.answer, + data: '[DONE]' + }); sseResponse({ res, event: sseResponseEventEnum.appStreamResponse, @@ -145,7 +151,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex res.end(); } else { res.json({ - data: responseData, + data: { + newChatId, + ...responseData + }, id: chatId || '', model: '', usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, @@ -183,6 +192,7 @@ async function dispatchModules({ params?: Record; stream?: boolean; }) { + const runningModules = loadModules(modules); let storeData: Record = {}; let responseData: Record = {}; let answerText = ''; @@ -212,7 +222,10 @@ async function dispatchModules({ ...data }; } - function moduleInput(module: AppModuleItemType, data: Record = {}): Promise { + function moduleInput( + module: RunningModuleItemType, + data: Record = {} + ): Promise { const checkInputFinish = () => { return !module.inputs.find((item: any) => item.value === undefined); }; @@ -222,50 +235,58 @@ async function dispatchModules({ module.inputs[index].value = value; }; + const set = new Set(); + return Promise.all( Object.entries(data).map(([key, val]: any) => { updateInputValue(key, val); - if (checkInputFinish()) { + + if (!set.has(module.moduleId) && checkInputFinish()) { + set.add(module.moduleId); return moduleRun(module); } }) ); } - function moduleOutput(module: AppModuleItemType, result: Record = {}): Promise { + function moduleOutput( + module: RunningModuleItemType, + result: Record = {} + ): Promise { return Promise.all( - module.outputs.map((item) => { - if (result[item.key] === undefined) return; + module.outputs.map((outputItem) => { + if (result[outputItem.key] === undefined) return; /* update output value */ - item.value = result[item.key]; + outputItem.value = result[outputItem.key]; pushStore({ - isResponse: item.response, - answer: item.answer ? item.value : '', + isResponse: outputItem.response, + answer: outputItem.answer ? outputItem.value : '', data: { - [item.key]: item.value + [outputItem.key]: outputItem.value } }); /* update target */ return Promise.all( - item.targets.map((target: any) => { + outputItem.targets.map((target: any) => { // find module - const targetModule = modules.find((item) => item.moduleId === target.moduleId); + const targetModule = runningModules.find((item) => item.moduleId === target.moduleId); if (!targetModule) return; - return moduleInput(targetModule, { [target.key]: item.value }); + return moduleInput(targetModule, { [target.key]: outputItem.value }); }) ); }) ); } - async function moduleRun(module: AppModuleItemType): Promise { + async function moduleRun(module: RunningModuleItemType): Promise { + if (res.closed) return Promise.resolve(); console.log('run=========', module.type, module.url); if (module.type === AppModuleItemTypeEnum.answer) { pushStore({ - answer: module.inputs[0].value + answer: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value || '' }); - return AnswerResponse({ + return StreamAnswer({ res, stream, text: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value @@ -276,16 +297,19 @@ async function dispatchModules({ return moduleOutput(module, switchResponse(module)); } - if (module.type === AppModuleItemTypeEnum.http && module.url) { + if ( + (module.type === AppModuleItemTypeEnum.http || + module.type === AppModuleItemTypeEnum.initInput) && + module.url + ) { // get fetch params - const inputParams: Record = {}; + const params: Record = {}; module.inputs.forEach((item: any) => { - inputParams[item.key] = item.value; + params[item.key] = item.value; }); const data = { stream, - ...module.body, - ...inputParams + ...params }; // response data @@ -299,8 +323,12 @@ async function dispatchModules({ } } - // 从填充 params 开始进入递归 - await Promise.all(modules.map((module) => moduleInput(module, params))); + // start process width initInput + const initModules = runningModules.filter( + (item) => item.type === AppModuleItemTypeEnum.initInput + ); + + await Promise.all(initModules.map((module) => moduleInput(module, params))); return { responseData, @@ -308,7 +336,29 @@ async function dispatchModules({ }; } -function AnswerResponse({ +function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] { + return modules.map((module) => { + return { + moduleId: module.moduleId, + type: module.type, + url: module.url, + inputs: module.inputs + .filter((item) => item.type !== FlowInputItemTypeEnum.target || item.connected) // filter unconnected target input + .map((item) => ({ + key: item.key, + value: item.value + })), + outputs: module.outputs.map((item) => ({ + key: item.key, + answer: item.type === FlowOutputItemTypeEnum.answer, + response: item.response, + value: undefined, + targets: item.targets + })) + }; + }); +} +function StreamAnswer({ res, stream = false, text = '' @@ -322,13 +372,13 @@ function AnswerResponse({ res, event: sseResponseEventEnum.answer, data: textAdaptGptResponse({ - text + text: text.replace(/\\n/g, '\n') }) }); } return text; } -function switchResponse(module: any) { +function switchResponse(module: RunningModuleItemType) { const val = module?.inputs?.[0]?.value; if (val) { diff --git a/client/src/pages/app/detail/components/API.tsx b/client/src/pages/app/detail/components/API.tsx new file mode 100644 index 000000000..0b805112e --- /dev/null +++ b/client/src/pages/app/detail/components/API.tsx @@ -0,0 +1,85 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Divider, Flex, useTheme, Button, Skeleton, useDisclosure } from '@chakra-ui/react'; +import { useCopyData } from '@/utils/tools'; +import dynamic from 'next/dynamic'; +import MyIcon from '@/components/Icon'; + +const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), { + ssr: false +}); + +const API = ({ modelId }: { modelId: string }) => { + const theme = useTheme(); + const { copyData } = useCopyData(); + const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi'); + const { + isOpen: isOpenAPIModal, + onOpen: onOpenAPIModal, + onClose: onCloseAPIModal + } = useDisclosure(); + const [isLoaded, setIsLoaded] = useState(false); + + useEffect(() => { + setBaseUrl(`${location.origin}/api/openapi`); + }, []); + + return ( + + + + AppId: + copyData(modelId, '已复制 AppId')} + > + {modelId} + + + copyData(baseUrl, '已复制 API 地址')} + > + + API服务器 + + + {baseUrl} + + + + + + + +