diff --git a/package-lock.json b/package-lock.json index 7670c61..8a3af21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 14b5237..c8e9754 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/views/home/components/ChatList.vue b/src/views/home/components/ChatList.vue index 5664d93..e14c50a 100644 --- a/src/views/home/components/ChatList.vue +++ b/src/views/home/components/ChatList.vue @@ -8,10 +8,41 @@ >
- @@ -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; +} \ No newline at end of file diff --git a/src/views/home/index.vue b/src/views/home/index.vue index e24ae82..762d14b 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -21,6 +21,8 @@ :workflow-detail="selectedWorkflowDetail" :active-history-id="activeHistoryId" :messages="currentMessages" + @retry="handleRetry" + @workflow-select="handlePlaceWorkflowSelect" />
@@ -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; } } diff --git a/src/views/home/utils/markdown.ts b/src/views/home/utils/markdown.ts new file mode 100644 index 0000000..66339df --- /dev/null +++ b/src/views/home/utils/markdown.ts @@ -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 `
${highlighted}
`; +}; +marked.use({ renderer }); + +// gfm:GitHub 风格(表格、任务列表等);breaks:单换行转
,贴近对话输出 +marked.setOptions({ gfm: true, breaks: true }); + +/** 将 Markdown 渲染为安全 HTML(含代码高亮与复制按钮,已消毒) */ +export function renderMarkdown(content: string): string { + return DOMPurify.sanitize(marked.parse(content) as string); +} diff --git a/src/views/home/utils/wsExecute.ts b/src/views/home/utils/wsExecute.ts index b63e126..4da4ee9 100644 --- a/src/views/home/utils/wsExecute.ts +++ b/src/views/home/utils/wsExecute.ts @@ -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); }; diff --git a/src/views/home/utils/wsMessage.ts b/src/views/home/utils/wsMessage.ts new file mode 100644 index 0000000..62f03ea --- /dev/null +++ b/src/views/home/utils/wsMessage.ts @@ -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 '执行失败,请重试'; +}