首页对话相关
This commit is contained in:
Generated
+103
-11
@@ -23,17 +23,21 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"countup.js": "^2.8.0",
|
||||
"cropperjs": "^1.6.0",
|
||||
"dompurify": "^3.4.13",
|
||||
"echarts": "^5.5.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"element-plus": "^2.10.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"highlight.js": "^11.12.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"json-schema-editor": "github:wusij/json-schema#8827d0d6a360c37bc32a8945fb979260bd4a4c85",
|
||||
"jsplumb": "^2.15.6",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^18.0.9",
|
||||
"marked-highlight": "^2.2.4",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
@@ -266,6 +270,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
|
||||
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
@@ -929,6 +934,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@interactjs/core/-/core-1.10.27.tgz",
|
||||
"integrity": "sha512-SliUr/3ZbLAdED8LokzYzWHWMdCB5Cq+UnpXuRy+BIod1j97m4IUFf/D1iIKUBBjBcucgXbz28z96WnenVCB7Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@interactjs/utils": "1.10.27"
|
||||
}
|
||||
@@ -999,6 +1005,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@interactjs/modifiers/-/modifiers-1.10.27.tgz",
|
||||
"integrity": "sha512-ei/qfoQ+9/8k6WzNzdNqHI6cWkIV576N4Ap16r5CoqOWwhA6Xzj3OMHf1g0t1O4eSq2HdJsVJn3eLNfw9HsbeQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@interactjs/snappers": "1.10.27"
|
||||
},
|
||||
@@ -1065,7 +1072,8 @@
|
||||
"version": "1.10.27",
|
||||
"resolved": "https://registry.npmjs.org/@interactjs/utils/-/utils-1.10.27.tgz",
|
||||
"integrity": "sha512-+qfLOio2OxQqg1cXSnRaCl+N8MQDQLDS9w+aOGxH8YLAhIMyt7Asxx/46//sT8orgsi16pmlBPtngPHT9s8zKw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "11.1.2",
|
||||
@@ -1157,6 +1165,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@logicflow/core/-/core-2.2.1.tgz",
|
||||
"integrity": "sha512-VzLPrCrT4eXnOLjoGQ5v4GUSay3+6rd3YNZD0qOJw4vME5e4WjQ5fd+hKK2zlIzgdRI4D54dXiEFJrS6xdV6yQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
@@ -1910,6 +1919,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
@@ -1920,6 +1930,7 @@
|
||||
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
@@ -1945,6 +1956,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||
@@ -1991,6 +2009,7 @@
|
||||
"integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "7.18.0",
|
||||
"@typescript-eslint/types": "7.18.0",
|
||||
@@ -2166,6 +2185,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@uppy/core/-/core-2.3.4.tgz",
|
||||
"integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@transloadit/prettier-bytes": "0.0.7",
|
||||
"@uppy/store-default": "^2.1.1",
|
||||
@@ -2197,6 +2217,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz",
|
||||
"integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@uppy/companion-client": "^2.2.2",
|
||||
"@uppy/utils": "^4.1.2",
|
||||
@@ -2274,6 +2295,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.48.2.tgz",
|
||||
"integrity": "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.5.0",
|
||||
"d3-drag": "^3.0.0",
|
||||
@@ -2517,6 +2539,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
|
||||
"integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-url": "^1.2.4"
|
||||
},
|
||||
@@ -2549,6 +2572,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@wangeditor/core/-/core-1.1.19.tgz",
|
||||
"integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/event-emitter": "^0.3.3",
|
||||
"event-emitter": "^0.3.5",
|
||||
@@ -2667,6 +2691,7 @@
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -2922,6 +2947,7 @@
|
||||
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz",
|
||||
"integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.0.0",
|
||||
"@codemirror/commands": "^6.0.0",
|
||||
@@ -3105,6 +3131,7 @@
|
||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
@@ -3244,10 +3271,20 @@
|
||||
"resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz",
|
||||
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ssr-window": "^3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.6.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
||||
@@ -3280,6 +3317,7 @@
|
||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz",
|
||||
"integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.1"
|
||||
@@ -3320,6 +3358,7 @@
|
||||
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.11.8.tgz",
|
||||
"integrity": "sha512-2wzSj2uubFU1f0t/gHkkE1d09mUgV18fSZX5excw3Ar6hyWcxph4E57U8dgYLDt7HwkKYv1BiqPyBdy0WqWlOA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
@@ -3508,6 +3547,7 @@
|
||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -4170,6 +4210,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.12.0.tgz",
|
||||
"integrity": "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
|
||||
@@ -4300,7 +4349,8 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz",
|
||||
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
@@ -4498,13 +4548,15 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash-unified": {
|
||||
"version": "1.0.3",
|
||||
@@ -4521,32 +4573,37 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.foreach": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
|
||||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
@@ -4559,13 +4616,15 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.toarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
|
||||
"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
@@ -4576,6 +4635,28 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "18.0.9",
|
||||
"resolved": "https://registry.npmmirror.com/marked/-/marked-18.0.9.tgz",
|
||||
"integrity": "sha512-/Sa4qiiHZxf0/FQdBBowr9q4r10krCwMvpK48FUBdXdUXScDxiQGR9zCPrFgRVR5LU3iySOiIjy09ZQvADir1w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/marked-highlight": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/marked-highlight/-/marked-highlight-2.2.4.tgz",
|
||||
"integrity": "sha512-PZxisNMJDduSjc0q6uvjsnqqHCXc9s0eyzxDO9sB1eNGJnd/H1/Fu+z6g/liC1dfJdFW4SftMwMlLvsBhUPrqQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"marked": ">=4 <19"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@@ -4678,6 +4759,7 @@
|
||||
"resolved": "https://registry.npmjs.org/mobx/-/mobx-5.15.7.tgz",
|
||||
"integrity": "sha512-wyM3FghTkhmC+hQjyPGGFdpehrcX1KOXsDuERhfK2YbJemkUhEB+6wzEN639T21onxlfYBmriA1PFnvxTUhcKw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mobx"
|
||||
@@ -4742,6 +4824,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -5028,6 +5111,7 @@
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz",
|
||||
"integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
@@ -5288,6 +5372,7 @@
|
||||
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
@@ -5506,6 +5591,7 @@
|
||||
"resolved": "https://registry.npmjs.org/slate/-/slate-0.72.8.tgz",
|
||||
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
@@ -5529,6 +5615,7 @@
|
||||
"resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.6.3.tgz",
|
||||
"integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
@@ -5694,6 +5781,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -5771,6 +5859,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -5840,6 +5929,7 @@
|
||||
"integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -6008,6 +6098,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -6027,6 +6118,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz",
|
||||
"integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.25",
|
||||
"@vue/compiler-sfc": "3.5.25",
|
||||
|
||||
@@ -28,17 +28,21 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"countup.js": "^2.8.0",
|
||||
"cropperjs": "^1.6.0",
|
||||
"dompurify": "^3.4.13",
|
||||
"echarts": "^5.5.0",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"element-plus": "^2.10.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"highlight.js": "^11.12.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"json-schema-editor": "github:wusij/json-schema#8827d0d6a360c37bc32a8945fb979260bd4a4c85",
|
||||
"jsplumb": "^2.15.6",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^18.0.9",
|
||||
"marked-highlight": "^2.2.4",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
|
||||
@@ -8,10 +8,41 @@
|
||||
>
|
||||
<div class="bubble-wrap">
|
||||
<div class="bubble">
|
||||
<template v-if="msg.loading">
|
||||
<span class="loading-text">思考中</span><span class="loading-dots"><i></i><i></i><i></i></span>
|
||||
<template v-if="msg.isUser">{{ msg.content }}</template>
|
||||
<template v-else>
|
||||
<!-- 初始加载动画 -->
|
||||
<template v-if="msg.loading && !msg.thinking && !msg.content">
|
||||
<span class="loading-text">思考中</span><span class="loading-dots"><i></i><i></i><i></i></span>
|
||||
</template>
|
||||
<!-- 思考区:DeepSeek 风格折叠块 -->
|
||||
<div v-if="msg.thinking" class="thinking-block">
|
||||
<button class="thinking-toggle" type="button" @click="toggleThinking(msg)">
|
||||
<span class="thinking-icon">🧠</span>
|
||||
<span class="thinking-title">{{ thinkingTitle(msg) }}</span>
|
||||
<span class="thinking-arrow" :class="{ collapsed: !isThinkingExpanded(msg) }">▾</span>
|
||||
</button>
|
||||
<div v-if="isThinkingExpanded(msg)" class="thinking-content">
|
||||
<div class="thinking-text">{{ msg.thinking }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 回答区:Markdown 渲染 -->
|
||||
<div
|
||||
v-if="msg.content"
|
||||
class="answer-block"
|
||||
v-html="renderAnswer(msg.content)"
|
||||
@click="handleAnswerClick"
|
||||
></div>
|
||||
<!-- 失败重试 -->
|
||||
<button v-if="isErrorMsg(msg)" type="button" class="retry-btn" @click="emit('retry', msg)">
|
||||
<span class="retry-icon">↻</span> 重新生成
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>{{ msg.content }}</template>
|
||||
</div>
|
||||
<!-- AI 消息操作栏(hover 显示) -->
|
||||
<div v-if="!msg.isUser && msg.content && !isErrorMsg(msg)" class="msg-actions">
|
||||
<button class="msg-action" title="复制回答" @click="copyText(msg.content)">
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="time">{{ msg.time }}</div>
|
||||
</div>
|
||||
@@ -20,7 +51,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue';
|
||||
import { nextTick, reactive, ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { DocumentCopy } from '@element-plus/icons-vue';
|
||||
import 'highlight.js/styles/github-dark.css';
|
||||
import { renderMarkdown } from '../utils/markdown';
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
@@ -28,6 +63,13 @@ interface ChatMessage {
|
||||
time: string;
|
||||
isUser: boolean;
|
||||
loading?: boolean;
|
||||
thinking?: string;
|
||||
thinkingSeconds?: number;
|
||||
retryQuestion?: string;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'retry', msg: ChatMessage): void;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@@ -35,13 +77,63 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
const chatListRef = ref<HTMLElement | null>(null);
|
||||
|
||||
const isErrorMsg = (msg: ChatMessage): boolean => !msg.isUser && String(msg.content || '').startsWith('❌');
|
||||
|
||||
// 思考区折叠:未手动操作时,流式中展开、完成(出现答案)后自动收起(DeepSeek 形态)
|
||||
const thinkingExpanded = reactive<Record<string, boolean>>({});
|
||||
|
||||
const isThinkingExpanded = (msg: ChatMessage): boolean =>
|
||||
msg.id in thinkingExpanded ? thinkingExpanded[msg.id] : !msg.content;
|
||||
|
||||
const toggleThinking = (msg: ChatMessage) => {
|
||||
thinkingExpanded[msg.id] = !isThinkingExpanded(msg);
|
||||
};
|
||||
|
||||
// Markdown 渲染缓存(content 在作答完成后固定,避免重复解析)
|
||||
const mdCache = new Map<string, string>();
|
||||
const renderAnswer = (content: string): string => {
|
||||
if (!mdCache.has(content)) mdCache.set(content, renderMarkdown(content));
|
||||
return mdCache.get(content)!;
|
||||
};
|
||||
|
||||
// 复制文本(剪贴板 API + 兼容回退)
|
||||
const copyText = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.value = text;
|
||||
ta.style.position = 'fixed';
|
||||
ta.style.opacity = '0';
|
||||
document.body.appendChild(ta);
|
||||
ta.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
ElMessage.success('已复制');
|
||||
};
|
||||
|
||||
// 代码块复制(事件委托:点击渲染出的 .code-copy-btn)
|
||||
const handleAnswerClick = (e: MouseEvent) => {
|
||||
const el = e.target as HTMLElement;
|
||||
const btn = el.closest('.code-copy-btn');
|
||||
if (!btn) return;
|
||||
const code = btn.closest('.code-wrapper')?.querySelector('code');
|
||||
if (code) copyText(code.textContent || '');
|
||||
};
|
||||
|
||||
// 思考区折叠标题:完成(有答案)显示"已深度思考(用时 X 秒)",流式中显示"思考中…"
|
||||
const thinkingTitle = (msg: ChatMessage): string => {
|
||||
if (!msg.content) return '思考中…';
|
||||
return msg.thinkingSeconds != null ? `已深度思考(用时 ${msg.thinkingSeconds} 秒)` : '已深度思考';
|
||||
};
|
||||
|
||||
// 消息新增或内容变化时自动滚动到底部
|
||||
watch(
|
||||
() => props.messages.map((m) => `${m.id}|${m.content}|${m.loading ? 1 : 0}`).join('\n'),
|
||||
() => props.messages.map((m) => `${m.id}|${m.content}|${m.thinking || ''}|${m.loading ? 1 : 0}`).join('\n'),
|
||||
() => {
|
||||
nextTick(() => {
|
||||
const el = chatListRef.value;
|
||||
@@ -79,35 +171,352 @@ watch(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
max-width: min(84%, 1120px);
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
|
||||
.message-row.is-user & {
|
||||
align-items: flex-end;
|
||||
/* 明确宽度基准:打断 fit-content 与百分比 max-width 的循环,避免气泡被压得过窄导致文字竖排 */
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bubble {
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
line-height: 1.75;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
word-break: break-word;
|
||||
color: #1f2937;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.96) 0%, rgba(248, 250, 255, 0.92) 100%);
|
||||
border: 1px solid rgba(226, 233, 244, 0.95);
|
||||
backdrop-filter: blur(8px);
|
||||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.05);
|
||||
color: #1f2328;
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
.message-row.is-user & {
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%);
|
||||
border-color: rgba(59, 130, 246, 0.4);
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px rgba(37, 99, 235, 0.35);
|
||||
width: fit-content;
|
||||
max-width: 85%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 16px 16px 4px 16px;
|
||||
background: #d3f5d8;
|
||||
color: #1f2328;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.message-row.is-error & {
|
||||
background: #fef2f2;
|
||||
border-color: rgba(254, 226, 226, 0.95);
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
/* AI 消息操作栏(hover 显示) */
|
||||
.msg-actions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
|
||||
.message-row:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #f1f2f4;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 失败重试 */
|
||||
.retry-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-top: 10px;
|
||||
padding: 5px 12px;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 8px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #fee2e2;
|
||||
border-color: #fca5a5;
|
||||
}
|
||||
|
||||
.retry-icon {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 思考区:DeepSeek 风格折叠块 */
|
||||
.thinking-block {
|
||||
margin: 2px 0 10px;
|
||||
background: #f5f5f6;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thinking-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #8b8b98;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
transition: color 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
|
||||
.thinking-icon {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.thinking-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.thinking-arrow {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
opacity: 0.7;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&.collapsed {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.thinking-content {
|
||||
padding: 0 12px 12px;
|
||||
animation: thinking-fade-in 0.2s ease;
|
||||
}
|
||||
|
||||
.thinking-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
color: #9a9aab;
|
||||
font-style: italic;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
border-top: 1px solid #ececee;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@keyframes thinking-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 回答区:Markdown 渲染(DeepSeek 风格排版) */
|
||||
.answer-block {
|
||||
white-space: normal;
|
||||
line-height: 1.75;
|
||||
font-size: 15px;
|
||||
word-break: break-word;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
& > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(p) {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
:deep(h1),
|
||||
:deep(h2),
|
||||
:deep(h3),
|
||||
:deep(h4),
|
||||
:deep(h5),
|
||||
:deep(h6) {
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
margin: 20px 0 10px;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
:deep(h1) {
|
||||
font-size: 20px;
|
||||
border-bottom: 1px solid #eaecef;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep(h2) {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
:deep(h3) {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(h4),
|
||||
:deep(h5),
|
||||
:deep(h6) {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
margin: 0 0 12px;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
:deep(li > ul),
|
||||
:deep(li > ol) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(blockquote) {
|
||||
margin: 0 0 12px;
|
||||
padding: 6px 14px;
|
||||
border-left: 4px solid #e0e0e0;
|
||||
color: #6a737d;
|
||||
background: #fafafa;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
:deep(code) {
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||
font-size: 13px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: #f2f3f5;
|
||||
color: #c7254e;
|
||||
}
|
||||
|
||||
:deep(.code-wrapper) {
|
||||
position: relative;
|
||||
margin: 0 0 14px;
|
||||
|
||||
&:hover .code-copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.code-copy-btn) {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 2;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #8b949e;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
color: #c9d1d9;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(pre) {
|
||||
margin: 0;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
background: #0d1117;
|
||||
overflow-x: auto;
|
||||
line-height: 1.6;
|
||||
|
||||
code {
|
||||
display: block;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(table) {
|
||||
width: 100%;
|
||||
margin: 0 0 14px;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(a) {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(hr) {
|
||||
border: none;
|
||||
border-top: 1px solid #eaecef;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 执行中占位 */
|
||||
.loading-text {
|
||||
color: #94a3b8;
|
||||
|
||||
@@ -177,7 +177,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.input-card {
|
||||
max-width: 860px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border: 1.5px solid #e2e8f0;
|
||||
@@ -347,7 +347,7 @@ onMounted(() => {
|
||||
|
||||
/* 快捷工作流胶囊 */
|
||||
.workflow-shortcuts {
|
||||
max-width: 860px;
|
||||
max-width: 800px;
|
||||
margin: 10px auto 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -131,71 +131,42 @@
|
||||
|
||||
<!-- 对话页 — 无工作流但有消息时展示消息流 -->
|
||||
<div v-else-if="hasMessages" class="content-body chat-body">
|
||||
<ChatList :messages="messages" />
|
||||
<ChatList :messages="messages" @retry="emit('retry', $event)" />
|
||||
</div>
|
||||
|
||||
<!-- 默认占位 — 无工作流时显示引导 -->
|
||||
<div v-else class="content-body placeholder-body">
|
||||
<div class="placeholder-content">
|
||||
<div class="placeholder-icon">
|
||||
<div class="icon-ring ring-outer">
|
||||
<div class="icon-ring ring-inner">
|
||||
<div class="icon-ring ring-core">
|
||||
<svg class="icon-sparkle" width="28" height="28" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2l1.09 6.26L18 4.5l-3.41 4.74L20 10l-5.41.76L18 15.5l-4.91-2.76L12 19l-1.09-6.26L6 15.5l3.41-4.74L4 10l5.41-.76L6 4.5l4.91 2.74L12 2z" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="placeholder-title">你好,我能帮你解决什么问题?</h2>
|
||||
<p class="placeholder-desc">选择一个工作流,填写参数后一键生成</p>
|
||||
|
||||
<h2 class="placeholder-title">请选择下方工作流进行创作</h2>
|
||||
<p class="placeholder-desc">点击工作流胶囊快速切换,填写参数后一键生成内容</p>
|
||||
|
||||
<div class="placeholder-steps">
|
||||
<div class="step-item">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-text">
|
||||
<span class="step-label">选择工作流</span>
|
||||
<span class="step-hint">点击胶囊切换不同创作模式</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-arrow">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-text">
|
||||
<span class="step-label">填写参数</span>
|
||||
<span class="step-hint">按需配置内容与样式选项</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-arrow">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M5 12h14M13 5l7 7-7 7"/></svg>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-text">
|
||||
<span class="step-label">一键生成</span>
|
||||
<span class="step-hint">AI 自动产出,结果同步到工作空间</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="placeholder-footer">
|
||||
<span class="footer-hint">还可从左侧「对话记录」查看历史或「工作空间」管理作品</span>
|
||||
<div v-if="placeWorkflows.length" class="workflow-cards">
|
||||
<button
|
||||
v-for="wf in placeWorkflows"
|
||||
:key="wf.id"
|
||||
type="button"
|
||||
class="workflow-card"
|
||||
@click="handlePlaceSelect(wf)"
|
||||
>
|
||||
<el-icon class="wf-card-icon"><Promotion /></el-icon>
|
||||
<span class="wf-card-name">{{ wf.name }}</span>
|
||||
<span v-if="wf.isTemplate" class="wf-card-tag">模板</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="placeholder-empty">暂无工作流,可在工作流管理中创建</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import PatchTemplateEditor from '/@/components/patchTemplate/PatchTemplateEditor.vue';
|
||||
import { uploadFile } from '/@/api/common/upload';
|
||||
import { collectHomeFormFields } from '../utils/flowDsl';
|
||||
import { getWorkflowList } from '/@/api/settings/creation';
|
||||
import { Promotion } from '@element-plus/icons-vue';
|
||||
import ChatList from './ChatList.vue';
|
||||
|
||||
interface Props {
|
||||
@@ -205,10 +176,16 @@ interface Props {
|
||||
messages?: any[];
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'retry', msg: any): void;
|
||||
(e: 'workflow-select', id: string, isTemplate?: boolean): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
activeHistoryId: null,
|
||||
messages: () => [],
|
||||
});
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const hasMessages = computed(() => Array.isArray(props.messages) && props.messages.length > 0);
|
||||
const formValues = reactive<Record<string, any>>({});
|
||||
@@ -427,6 +404,28 @@ watch(
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// 占位页工作流卡片(DeepSeek 式引导:居中标题 + 工作流卡片)
|
||||
const placeWorkflows = ref<any[]>([]);
|
||||
const loadPlaceWorkflows = async () => {
|
||||
try {
|
||||
const res: any = await getWorkflowList();
|
||||
const userList = res.data?.listFlowUserRes?.list || [];
|
||||
const tplList = res.data?.listFlowTemplateRes?.list || [];
|
||||
placeWorkflows.value = [
|
||||
...userList.map((w: any) => ({ id: String(w.id), name: w.flowName || '未命名', isTemplate: false })),
|
||||
...tplList.map((w: any) => ({ id: String(w.id), name: w.flowTemplateName || '未命名', isTemplate: true })),
|
||||
];
|
||||
} catch {
|
||||
placeWorkflows.value = [];
|
||||
}
|
||||
};
|
||||
const handlePlaceSelect = (wf: any) => {
|
||||
emit('workflow-select', wf.id, wf.isTemplate);
|
||||
};
|
||||
onMounted(() => {
|
||||
loadPlaceWorkflows();
|
||||
});
|
||||
|
||||
defineExpose({ formValues, fieldFiles, templates, validateFormFields });
|
||||
</script>
|
||||
|
||||
@@ -447,7 +446,7 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
|
||||
|
||||
/* ===== 工作流表单 ===== */
|
||||
.workflow-form-body {
|
||||
width: min(820px, 100%);
|
||||
width: min(800px, 100%);
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -570,12 +569,12 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
|
||||
|
||||
/* ===== 对话页 ===== */
|
||||
.chat-body {
|
||||
width: min(1060px, 82%);
|
||||
width: min(800px, 92%);
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 20px;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
@@ -583,84 +582,62 @@ defineExpose({ formValues, fieldFiles, templates, validateFormFields });
|
||||
}
|
||||
|
||||
.placeholder-body { display: flex; align-items: center; justify-content: center; }
|
||||
.placeholder-content { text-align: center; padding: 20px; max-width: 480px; }
|
||||
|
||||
/* ===== 图标:层叠圆环 + 星形 ===== */
|
||||
.placeholder-icon { margin-bottom: 28px; display: flex; justify-content: center; }
|
||||
.icon-ring {
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.ring-outer {
|
||||
width: 88px; height: 88px;
|
||||
background: radial-gradient(circle at 35% 30%, rgba(59, 130, 246, 0.10) 0%, rgba(139, 92, 246, 0.06) 100%);
|
||||
border: 1.5px solid rgba(59, 130, 246, 0.15);
|
||||
&:hover { transform: scale(1.05); }
|
||||
}
|
||||
.ring-inner {
|
||||
width: 62px; height: 62px;
|
||||
background: radial-gradient(circle at 35% 30%, rgba(59, 130, 246, 0.14) 0%, rgba(139, 92, 246, 0.08) 100%);
|
||||
border: 1.5px solid rgba(59, 130, 246, 0.20);
|
||||
}
|
||||
.ring-core {
|
||||
width: 42px; height: 42px;
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.35);
|
||||
}
|
||||
.icon-sparkle { color: #fff; }
|
||||
.placeholder-content { text-align: center; padding: 24px 20px; width: 100%; max-width: 640px; }
|
||||
|
||||
/* ===== 标题与描述 ===== */
|
||||
.placeholder-title { font-size: 18px; font-weight: 700; color: #0f172a; margin: 0 0 6px; letter-spacing: -0.2px; line-height: 1.4; }
|
||||
.placeholder-desc { font-size: 14px; color: #94a3b8; margin: 0 0 32px; line-height: 1.5; }
|
||||
.placeholder-title { font-size: 20px; font-weight: 700; color: #0f172a; margin: 0 0 8px; letter-spacing: -0.2px; line-height: 1.4; }
|
||||
.placeholder-desc { font-size: 14px; color: #94a3b8; margin: 0 0 28px; line-height: 1.5; }
|
||||
|
||||
/* ===== 步骤引导 ===== */
|
||||
.placeholder-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
margin-bottom: 28px;
|
||||
/* ===== 工作流功能卡片(DeepSeek 式引导) ===== */
|
||||
.workflow-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.step-item {
|
||||
.workflow-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.step-num {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #eff6ff 0%, #f0f0ff 100%);
|
||||
border: 1.5px solid #bfdbfe;
|
||||
color: #3b82f6;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s;
|
||||
.step-item:hover & {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
outline: none;
|
||||
&:hover {
|
||||
border-color: #93c5fd;
|
||||
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
.step-text { display: flex; flex-direction: column; gap: 1px; }
|
||||
.step-label { font-size: 13px; font-weight: 600; color: #334155; line-height: 1.3; white-space: nowrap; }
|
||||
.step-hint { font-size: 11px; color: #94a3b8; line-height: 1.3; white-space: nowrap; }
|
||||
.step-arrow {
|
||||
color: #cbd5e1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 6px;
|
||||
.wf-card-icon {
|
||||
font-size: 18px;
|
||||
color: #3b82f6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ===== 底部辅助提示 ===== */
|
||||
.placeholder-footer { margin-top: 4px; }
|
||||
.footer-hint { font-size: 12px; color: #cbd5e1; line-height: 1.5; }
|
||||
.wf-card-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.wf-card-tag {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: #fff7e6;
|
||||
color: #d97706;
|
||||
border: 1px solid #fde68a;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.placeholder-empty {
|
||||
font-size: 13px;
|
||||
color: #cbd5e1;
|
||||
padding: 24px 0;
|
||||
}
|
||||
</style>
|
||||
+101
-26
@@ -21,6 +21,8 @@
|
||||
:workflow-detail="selectedWorkflowDetail"
|
||||
:active-history-id="activeHistoryId"
|
||||
:messages="currentMessages"
|
||||
@retry="handleRetry"
|
||||
@workflow-select="handlePlaceWorkflowSelect"
|
||||
/>
|
||||
<InputBar ref="inputBarRef" :send-disabled="isSendDisabled" :workflow-locked="isHistoryWorkflow" @send="handleSend" @workflow-select="handleWorkflowSelect" />
|
||||
</div>
|
||||
@@ -60,6 +62,7 @@ import TemplateCompleteDialog from './components/TemplateCompleteDialog.vue';
|
||||
import { applyHomeFormValues } from './utils/flowDsl';
|
||||
import { getChatModel } from '/@/api/settings/modelConfigV2';
|
||||
import { openWsExecute } from './utils/wsExecute';
|
||||
import { parseWsMessage, getDelta, getAnswer, getErrorText } from './utils/wsMessage';
|
||||
import { getSessionMessages } from '/@/api/settings/session';
|
||||
import type { ExecutionTreeItem } from '/@/api/settings/creation';
|
||||
import {
|
||||
@@ -86,6 +89,9 @@ interface ChatMessage {
|
||||
time: string;
|
||||
isUser: boolean;
|
||||
loading?: boolean;
|
||||
thinking?: string;
|
||||
thinkingSeconds?: number;
|
||||
retryQuestion?: string;
|
||||
}
|
||||
|
||||
interface TreeNode {
|
||||
@@ -331,6 +337,15 @@ const handleTemplateSaved = async (newId: string) => {
|
||||
ib?.selectWorkflow?.(newId);
|
||||
};
|
||||
|
||||
// 占位页工作流卡片点击:模板走补全弹窗;用户工作流同步选中输入框,进入表单页
|
||||
const handlePlaceWorkflowSelect = (id: string, isTemplate?: boolean) => {
|
||||
if (isTemplate) {
|
||||
handleWorkflowSelect(id, true);
|
||||
} else {
|
||||
inputBarRef.value?.selectWorkflow?.(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuChange = (menu: string) => {
|
||||
activeMenu.value = menu;
|
||||
};
|
||||
@@ -427,6 +442,47 @@ const handleSend = async (message: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// ===== 失败重试:重新发送同一条用户消息 =====
|
||||
const handleRetry = async (msg: ChatMessage) => {
|
||||
let sid = '';
|
||||
for (const [k, list] of sessionMessages.value) {
|
||||
if (list.some((m) => m.id === msg.id)) {
|
||||
sid = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sid || sendingSessions[sid]) return;
|
||||
const session = historyList.value.find((h) => h.id === sid);
|
||||
if (!session) return;
|
||||
const list = sessionMessages.value.get(sid) || [];
|
||||
const idx = list.findIndex((m) => m.id === msg.id);
|
||||
if (idx < 0) return;
|
||||
// 待重发的用户问题:优先失败消息上记录的原文,其次向上找最近一条用户消息
|
||||
let question = msg.retryQuestion || '';
|
||||
if (!question) {
|
||||
for (let i = idx - 1; i >= 0; i--) {
|
||||
if (list[i].isUser) {
|
||||
question = list[i].content;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!question) return;
|
||||
// 移除失败消息,重新执行
|
||||
list.splice(idx, 1);
|
||||
saveChatCache(sid, list);
|
||||
sendingSessions[sid] = true;
|
||||
session.status = 'executing';
|
||||
const sessionId = session.sessionId || getSessionId();
|
||||
try {
|
||||
await runChat(sid, sessionId, question);
|
||||
} catch {
|
||||
session.status = 'failed';
|
||||
} finally {
|
||||
delete sendingSessions[sid];
|
||||
}
|
||||
};
|
||||
|
||||
// ===== 工作流执行:选中工作流 → 表单页 WS 执行 → 完成后切回对话页 =====
|
||||
const runWorkflow = async (sid: string, sessionId: string, message: string, mc: any) => {
|
||||
const curSession = historyList.value.find((h) => h.id === sid);
|
||||
@@ -551,6 +607,8 @@ const runChat = async (sid: string, sessionId: string, message: string) => {
|
||||
|
||||
let done = false;
|
||||
let failed = false;
|
||||
// 思考/生成过程计时起点(收到首帧时开始)
|
||||
let thinkStart = 0;
|
||||
const finishChat = (success: boolean, errorMsg?: string) => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
@@ -561,6 +619,8 @@ const runChat = async (sid: string, sessionId: string, message: string) => {
|
||||
if (list && aiIdx >= 0) {
|
||||
list[aiIdx].loading = false;
|
||||
list[aiIdx].content = content;
|
||||
list[aiIdx].thinking = '';
|
||||
list[aiIdx].retryQuestion = message;
|
||||
} else {
|
||||
addMessage({ id: 'msg-' + Date.now() + '-fail', content, time: formatTime(new Date()), isUser: false });
|
||||
}
|
||||
@@ -612,28 +672,50 @@ const runChat = async (sid: string, sessionId: string, message: string) => {
|
||||
question: message || '',
|
||||
systemPrompt: '',
|
||||
onMessage: (raw) => {
|
||||
let data: any = raw;
|
||||
if (typeof raw === 'string') {
|
||||
try { data = JSON.parse(raw); } catch { data = raw; }
|
||||
}
|
||||
// 提取回复文本(兼容常见字段)
|
||||
let text = '';
|
||||
if (typeof data === 'string') text = data;
|
||||
else if (data && typeof data === 'object') {
|
||||
text = data.content ?? data.message ?? data.reply ?? data.text ?? data.answer ?? '';
|
||||
}
|
||||
const str = text ? text : typeof data === 'string' ? data : JSON.stringify(data || '');
|
||||
const msg = parseWsMessage(raw);
|
||||
if (!msg) return;
|
||||
const list = sessionMessages.value.get(sid);
|
||||
const aiIdx = list ? list.findIndex((m) => m.id === aiMsgId) : -1;
|
||||
if (aiIdx >= 0 && str) {
|
||||
const ai = list[aiIdx];
|
||||
if (ai.loading) ai.loading = false;
|
||||
ai.content = ai.content ? ai.content + (str.startsWith(' ') ? str : '\n' + str) : str;
|
||||
saveChatCache(sid, list || []);
|
||||
const ai = aiIdx >= 0 && list ? list[aiIdx] : null;
|
||||
|
||||
// 思考步骤:思考区为空时给出占位提示(虚化)
|
||||
if (msg.type === 'agent_step') {
|
||||
if (ai && !ai.thinking && !ai.content) {
|
||||
ai.loading = false;
|
||||
ai.thinking = '思考中…';
|
||||
}
|
||||
if (!thinkStart) thinkStart = Date.now();
|
||||
return;
|
||||
}
|
||||
if (/done|finish|complete|end|success/i.test(str)) {
|
||||
// 流式 token:思考区逐字累加(虚化显示)
|
||||
if (msg.type === 'agent_token') {
|
||||
const delta = getDelta(msg);
|
||||
if (ai && delta) {
|
||||
ai.loading = false;
|
||||
ai.thinking = ai.thinking && ai.thinking !== '思考中…' ? ai.thinking + delta : delta;
|
||||
saveChatCache(sid, list || []);
|
||||
}
|
||||
if (!thinkStart) thinkStart = Date.now();
|
||||
return;
|
||||
}
|
||||
// 作答完成:回答区输出完整答案,记录思考用时并结束
|
||||
if (msg.type === 'agent_answer') {
|
||||
const answer = getAnswer(msg);
|
||||
if (ai && answer) {
|
||||
ai.loading = false;
|
||||
ai.content = answer;
|
||||
ai.thinkingSeconds = thinkStart ? Math.max(1, Math.round((Date.now() - thinkStart) / 1000)) : undefined;
|
||||
saveChatCache(sid, list || []);
|
||||
}
|
||||
finishChat(true);
|
||||
return;
|
||||
}
|
||||
// 错误:展示错误信息并结束
|
||||
if (msg.type === 'error') {
|
||||
finishChat(false, getErrorText(msg));
|
||||
return;
|
||||
}
|
||||
// 其余(ack / agent / 未知)忽略
|
||||
},
|
||||
onError: () => {
|
||||
failed = true;
|
||||
@@ -784,12 +866,7 @@ onMounted(() => {
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(1200px 600px at 0% 0%, rgba(59, 130, 246, 0.18) 0%, rgba(59, 130, 246, 0) 45%),
|
||||
radial-gradient(1000px 500px at 100% 0%, rgba(139, 92, 246, 0.15) 0%, rgba(139, 92, 246, 0) 50%),
|
||||
radial-gradient(800px 400px at 0% 100%, rgba(236, 72, 153, 0.12) 0%, rgba(236, 72, 153, 0) 55%),
|
||||
radial-gradient(600px 300px at 100% 100%, rgba(16, 185, 129, 0.1) 0%, rgba(16, 185, 129, 0) 60%),
|
||||
linear-gradient(135deg, #f8fbff 0%, #e6f0ff 60%, #f0f4fa 100%);
|
||||
background: linear-gradient(180deg, #f7f7f8 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
@@ -805,9 +882,7 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0) 32%),
|
||||
radial-gradient(900px 360px at 15% 115%, rgba(99, 102, 241, 0.06) 0%, rgba(99, 102, 241, 0) 70%);
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ===== 首页回答区 Markdown 渲染(DeepSeek 风格)=====
|
||||
// marked 负责 Markdown 语法,自定义 renderer.code 用 highlight.js 做代码语法高亮,
|
||||
// 并为代码块包一层带"复制"按钮的结构;DOMPurify 消毒避免 v-html 注入 XSS。
|
||||
import { marked } from 'marked';
|
||||
import hljs from 'highlight.js/lib/common';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = ({ text, lang }) => {
|
||||
const language = lang && hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||
const highlighted = hljs.highlight(text, { language }).value;
|
||||
return `<div class="code-wrapper"><button type="button" class="code-copy-btn" data-copy>复制</button><pre><code class="hljs language-${language}">${highlighted}</code></pre></div>`;
|
||||
};
|
||||
marked.use({ renderer });
|
||||
|
||||
// gfm:GitHub 风格(表格、任务列表等);breaks:单换行转 <br>,贴近对话输出
|
||||
marked.setOptions({ gfm: true, breaks: true });
|
||||
|
||||
/** 将 Markdown 渲染为安全 HTML(含代码高亮与复制按钮,已消毒) */
|
||||
export function renderMarkdown(content: string): string {
|
||||
return DOMPurify.sanitize(marked.parse(content) as string);
|
||||
}
|
||||
@@ -56,7 +56,16 @@ export function openWsExecute(opts: WsExecuteOptions): WebSocket | null {
|
||||
|
||||
ws.onopen = () => {
|
||||
if (opts.flowContent) {
|
||||
// 工作流执行:连接后发送 flowContent(workflow_exec 的完整 payload 结构待后端确认)
|
||||
ws.send(JSON.stringify({ flowContent: opts.flowContent, systemPrompt: opts.systemPrompt || '' }));
|
||||
} else {
|
||||
// 普通对话:连接后需发送启动消息(type=agent),后端据此开始推流
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'agent',
|
||||
payload: { modelId: opts.modelId != null ? String(opts.modelId) : '', question: opts.question || '' },
|
||||
})
|
||||
);
|
||||
}
|
||||
opts.onOpen?.(ws);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// ===== 首页 WebSocket 流式消息解析工具 =====
|
||||
// 服务端推送统一为 JSON 文本帧:
|
||||
// {"type":"ack","message":"WebSocket连接成功"} 连接/流程确认(message 为提示文案)
|
||||
// {"type":"agent","payload":{"modelId":"...","question":"..."}} agent 启动信息
|
||||
// {"type":"agent_step","message":"模型思考中","data":{"maxStep":15,"step":1}} 思考步骤进度
|
||||
// {"type":"agent_token","message":"思考中","data":{"delta":"你"}} 流式 token 增量
|
||||
// {"type":"agent_answer","message":"作答完成","data":{"answer":"..."}} 作答完成(最终答案)
|
||||
// {"type":"error","message":"..."} 执行失败
|
||||
// 完成/失败判定均以 type 为准,不再用正则猜测文本。
|
||||
|
||||
export interface WsStreamMessage {
|
||||
type: string;
|
||||
message?: string;
|
||||
data?: any;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析服务端推送帧。
|
||||
* 非 JSON、无 type 的帧返回 null(调用方忽略)。
|
||||
*/
|
||||
export function parseWsMessage(raw: any): WsStreamMessage | null {
|
||||
let data: any = raw;
|
||||
if (typeof raw === 'string') {
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!data || typeof data !== 'object' || typeof data.type !== 'string') return null;
|
||||
return data;
|
||||
}
|
||||
|
||||
/** agent_token → data.delta 流式增量 */
|
||||
export function getDelta(msg: WsStreamMessage): string {
|
||||
return typeof msg?.data?.delta === 'string' ? msg.data.delta : '';
|
||||
}
|
||||
|
||||
/** agent_answer → data.answer 最终答案 */
|
||||
export function getAnswer(msg: WsStreamMessage): string {
|
||||
return typeof msg?.data?.answer === 'string' ? msg.data.answer : '';
|
||||
}
|
||||
|
||||
/** agent_step → { step, maxStep },缺失时 maxStep 为 0 */
|
||||
export function getStepInfo(msg: WsStreamMessage): { step: number; maxStep: number } | null {
|
||||
const step = msg?.data?.step;
|
||||
if (typeof step !== 'number') return null;
|
||||
const maxStep = msg?.data?.maxStep;
|
||||
return { step, maxStep: typeof maxStep === 'number' ? maxStep : 0 };
|
||||
}
|
||||
|
||||
/** error → 错误文案(message 优先,其次 data.message / data.error / data.msg) */
|
||||
export function getErrorText(msg: WsStreamMessage): string {
|
||||
if (typeof msg?.message === 'string' && msg.message) return msg.message;
|
||||
const d = msg?.data;
|
||||
if (typeof d === 'string') return d;
|
||||
if (d && typeof d === 'object') {
|
||||
const nested = d.message ?? d.error ?? d.msg;
|
||||
if (typeof nested === 'string' && nested) return nested;
|
||||
}
|
||||
return '执行失败,请重试';
|
||||
}
|
||||
Reference in New Issue
Block a user