添加了通用问答和历史回答的pdf导出功能,能导出对话,并保存为pdf形式

This commit is contained in:
moon 2025-10-13 18:05:46 +08:00
parent dd1fdd16ec
commit 2d4eeab6a2
5 changed files with 529 additions and 61 deletions

View File

@ -30,6 +30,7 @@
"element-plus": "^2.3.8",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
"html2pdf.js": "^0.12.1",
"image-conversion": "^2.1.1",
"js-cookie": "^3.0.5",
"js-message": "^2.1.0",
@ -3752,6 +3753,11 @@
"integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
"dev": true
},
"node_modules/@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
@ -3769,6 +3775,12 @@
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
@ -6391,6 +6403,14 @@
"node": ">=0.10.0"
}
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@ -7450,6 +7470,25 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/capital-case": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz",
@ -8419,6 +8458,14 @@
"node": ">=12 || >=16"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-loader": {
"version": "6.11.0",
"resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz",
@ -11280,6 +11327,21 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fast-png": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
"dependencies": {
"@types/pako": "^2.0.3",
"iobuffer": "^5.3.2",
"pako": "^2.1.0"
}
},
"node_modules/fast-png/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
},
"node_modules/fast-uri": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz",
@ -11363,6 +11425,11 @@
"pend": "~1.2.0"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
},
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz",
@ -12965,6 +13032,27 @@
"node": ">=6"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/html2pdf.js": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.12.1.tgz",
"integrity": "sha512-3rBWQ96H5oOU9jtoz3MnE/epGi27ig9h8aonBk4JTpvUERM3lMRxhIRckhJZEi4wE0YfRINoYOIDY0hLY0CHgQ==",
"dependencies": {
"html2canvas": "^1.0.0",
"jspdf": "^3.0.0"
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz",
@ -13715,6 +13803,11 @@
"node": ">=4"
}
},
"node_modules/iobuffer": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA=="
},
"node_modules/ipaddr.js": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
@ -15661,6 +15754,22 @@
"node": ">=0.10.0"
}
},
"node_modules/jspdf": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
"integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
"dependencies": {
"@babel/runtime": "^7.26.9",
"fast-png": "^6.2.0",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^3.2.4",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@ -18641,6 +18750,12 @@
"dev": true,
"optional": true
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -20408,6 +20523,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@ -20735,6 +20859,12 @@
"node": ">=4"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"node_modules/regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz",
@ -21139,6 +21269,15 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
@ -22461,6 +22600,15 @@
"node": ">=8"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz",
@ -23564,6 +23712,15 @@
"node": ">=0.10.0"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/svg-sprite-loader": {
"version": "6.0.11",
"resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz",
@ -24023,6 +24180,14 @@
"node": ">=8"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@ -25294,6 +25459,14 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
@ -29618,6 +29791,11 @@
"integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
"dev": true
},
"@types/pako": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw=="
},
"@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
@ -29635,6 +29813,12 @@
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true
},
"@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"optional": true
},
"@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
@ -31566,6 +31750,11 @@
"pascalcase": "^0.1.1"
}
},
"base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@ -32391,6 +32580,22 @@
}
}
},
"canvg": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
"optional": true,
"requires": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
}
},
"capital-case": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz",
@ -33122,6 +33327,14 @@
"integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==",
"dev": true
},
"css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"requires": {
"utrie": "^1.0.2"
}
},
"css-loader": {
"version": "6.11.0",
"resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz",
@ -35305,6 +35518,23 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"fast-png": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
"requires": {
"@types/pako": "^2.0.3",
"iobuffer": "^5.3.2",
"pako": "^2.1.0"
},
"dependencies": {
"pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
}
}
},
"fast-uri": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz",
@ -35363,6 +35593,11 @@
"pend": "~1.2.0"
}
},
"fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
},
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz",
@ -36545,6 +36780,24 @@
}
}
},
"html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"requires": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
}
},
"html2pdf.js": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.12.1.tgz",
"integrity": "sha512-3rBWQ96H5oOU9jtoz3MnE/epGi27ig9h8aonBk4JTpvUERM3lMRxhIRckhJZEi4wE0YfRINoYOIDY0hLY0CHgQ==",
"requires": {
"html2canvas": "^1.0.0",
"jspdf": "^3.0.0"
}
},
"htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz",
@ -37106,6 +37359,11 @@
"p-is-promise": "^1.1.0"
}
},
"iobuffer": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA=="
},
"ipaddr.js": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
@ -38517,6 +38775,20 @@
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
"dev": true
},
"jspdf": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
"integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
"requires": {
"@babel/runtime": "^7.26.9",
"canvg": "^3.0.11",
"core-js": "^3.6.0",
"dompurify": "^3.2.4",
"fast-png": "^6.2.0",
"fflate": "^0.8.1",
"html2canvas": "^1.0.0-rc.5"
}
},
"jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@ -40791,6 +41063,12 @@
"dev": true,
"optional": true
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"optional": true
},
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -42005,6 +42283,15 @@
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true
},
"raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"optional": true,
"requires": {
"performance-now": "^2.1.0"
}
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@ -42253,6 +42540,12 @@
"regenerate": "^1.4.2"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"optional": true
},
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz",
@ -42554,6 +42847,12 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"optional": true
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
@ -43553,6 +43852,12 @@
}
}
},
"stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"optional": true
},
"stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz",
@ -44373,6 +44678,12 @@
}
}
},
"svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"optional": true
},
"svg-sprite-loader": {
"version": "6.0.11",
"resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz",
@ -44713,6 +45024,14 @@
"minimatch": "^3.0.4"
}
},
"text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"requires": {
"utrie": "^1.0.2"
}
},
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@ -45672,6 +45991,14 @@
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"dev": true
},
"utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"requires": {
"base64-arraybuffer": "^1.0.2"
}
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",

View File

@ -45,6 +45,7 @@
"element-plus": "^2.3.8",
"file-saver": "^2.0.5",
"highlight.js": "^11.11.1",
"html2pdf.js": "^0.12.1",
"image-conversion": "^2.1.1",
"js-cookie": "^3.0.5",
"js-message": "^2.1.0",

View File

@ -0,0 +1,68 @@
// src/utils/exportPdf.ts
import html2pdf from 'html2pdf.js'
import { nextTick } from 'vue'
/**
* html2pdf
*/
export type Html2PdfOptions = {
margin?: number
filename?: string
image?: { type?: 'jpeg' | 'png'; quality?: number }
html2canvas?: { scale?: number; scrollY?: number; useCORS?: boolean; windowWidth?: number }
jsPDF?: {
unit?: string
format?: 'a3' | 'a4' | 'a5' | 'letter' | 'legal' | [number, number]
orientation?: 'portrait' | 'landscape'
}
}
/**
* DOM PDF
* @param element DOM
* @param fileName export.pdf
*/
export async function exportElementToPdf(
element: HTMLElement,
fileName = 'export.pdf'
): Promise<void> {
if (!element) {
console.error('❌ exportElementToPdf: 未传入有效的 DOM 元素')
return
}
// 等待 DOM 渲染完成
await nextTick()
await new Promise(resolve => setTimeout(resolve, 50))
// 获取元素尺寸
const elementWidth = element.scrollWidth
const elementHeight = element.scrollHeight
// html2pdf 配置
const opt: Html2PdfOptions = {
margin: 10, // 页面边距,单位 px
filename: fileName,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 2,
scrollY: -window.scrollY,
useCORS: true,
windowWidth: elementWidth,
},
jsPDF: {
unit: 'pt', // 使用 pt 单位更适合像素换算
format: [elementWidth, elementHeight],
orientation: 'portrait',
},
}
console.log('📄 正在导出 PDF...')
try {
await html2pdf().set(opt).from(element).save()
console.log(`✅ PDF 导出成功:${fileName}`)
} catch (err) {
console.error('❌ PDF 导出失败:', err)
}
}

View File

@ -76,7 +76,7 @@
<!-- 聊天消息区域 -->
<div class="chat-messages" ref="messagesContainer">
<MessageItem
v-for="(msg, index) in messages"
v-for="(msg, index) in messages"
:key="index"
:message="msg"
:index="index"
@ -304,16 +304,16 @@ const handleCreateNewChat = async () => {
if (messages.value.length > 0) {
setCurrentMessages(messages.value)
}
//
const initialMessages = [...props.initialMessages]
const newChatIndex = await createNewChat(initialMessages)
messages.value = initialMessages
//
emit('chatCreated', newChatIndex)
//
await nextTick()
throttledScrollToBottom()
@ -322,21 +322,21 @@ const handleCreateNewChat = async () => {
//
const handleSwitchChat = async (index: number) => {
if (index === currentChatIndex.value) return
//
if (messages.value.length > 0) {
setCurrentMessages(messages.value)
}
const newIndex = switchChat(index)
//
await nextTick()
messages.value = [...getCurrentMessages()]
//
emit('chatSwitched', newIndex)
//
await nextTick()
throttledScrollToBottom()
@ -345,12 +345,12 @@ const handleSwitchChat = async (index: number) => {
//
const handleSendMessage = async (messageText: string) => {
if (!messageText.trim() || isLoading.value) return
inputMessage.value = '' //
const currentSession = getCurrentSession()
const conversationId = currentSession?.conversationId
const result = await sendMessageCore(
messageText,
messages.value,
@ -371,7 +371,7 @@ const handleSendMessage = async (messageText: string) => {
streamingScrollToBottom()
}
)
//
setCurrentMessages(messages.value)
scrollToBottomForce()
@ -380,7 +380,7 @@ const handleSendMessage = async (messageText: string) => {
//
const handleStopStream = async () => {
await stopStream(props.chatType, props.userId)
//
if (messages.value.length > 0) {
const lastMessage = messages.value[messages.value.length - 1]
@ -396,18 +396,18 @@ const handleStopStream = async () => {
//
const handleRegenerateMessage = async (index: number) => {
if (index === 0) return
//
const userMessage = messages.value[index - 1]
if (!userMessage || !userMessage.isUser) return
//
messages.value = messages.value.slice(0, index)
//
const currentSession = getCurrentSession()
const conversationId = currentSession?.conversationId
await sendMessageCore(
userMessage.text,
messages.value,
@ -427,7 +427,7 @@ const handleRegenerateMessage = async (index: number) => {
streamingScrollToBottom()
}
)
setCurrentMessages(messages.value)
scrollToBottomForce()
}
@ -435,11 +435,11 @@ const handleRegenerateMessage = async (index: number) => {
//
const handleSaveEdit = async (newText: string) => {
if (!newText.trim()) return
//
editingMessageIndex.value = -1
editingMessageText.value = ''
//
await handleSendMessage(newText)
}
@ -455,13 +455,13 @@ const handleSourceClick = async (source: TraceFile, conversationId?: string, mes
// PDFURL
const baseUrl = window.location.protocol + '//' + window.location.host
const previewFileUrl = baseUrl + source.filePath
console.log('预览文件:', source)
console.log('预览URL:', previewFileUrl)
//
filePreviewModals.value?.showPdfPreview(previewFileUrl)
//
emit('sourceClick', source)
} catch (error) {
@ -475,7 +475,7 @@ const handleContextFileClick = async (fileContext: TraceContext, fileType: 'exce
try {
// \n
const processedContent = fileContext.context.replace(/\\n/g, '\n')
if (fileType === 'excel') {
// markedmarkdown
let htmlContent = ''
@ -486,15 +486,15 @@ const handleContextFileClick = async (fileContext: TraceContext, fileType: 'exce
console.warn('marked解析失败尝试手动解析:', error)
htmlContent = parseMarkdownTable(processedContent)
}
// HTMLtable
if (!htmlContent.includes('<table')) {
console.warn('未检测到表格手动解析markdown')
htmlContent = parseMarkdownTable(processedContent)
}
filePreviewModals.value?.showExcelPreview(htmlContent)
} else if (fileType === 'markdown' || fileType === 'word') {
// MarkdownWord使markdown
let htmlContent = ''
@ -506,10 +506,10 @@ const handleContextFileClick = async (fileContext: TraceContext, fileType: 'exce
// 使
htmlContent = `<pre style="white-space: pre-wrap; font-family: inherit;">${processedContent}</pre>`
}
filePreviewModals.value?.showMarkdownPreview(htmlContent, fileContext.fileName)
}
console.log(`预览${fileType}文件:`, fileContext.fileName)
console.log('文件内容:', fileContext.context)
} catch (error) {
@ -521,7 +521,7 @@ const handleContextFileClick = async (fileContext: TraceContext, fileType: 'exce
//
const handleRecommendQuestionClick = (question: string) => {
if (!question.trim() || isLoading.value) return
//
handleSendMessage(question)
}
@ -530,6 +530,11 @@ const handleRecommendQuestionClick = (question: string) => {
const getAssistantAvatar = () => {
return props.assistantAvatar || new URL('@/assets/assistant.png', import.meta.url).href
}
const messagesQuantity = computed(() => messages.value.length)
defineExpose({
messagesContainer,
messagesQuantity
})
</script>
<style scoped lang="scss">
@ -571,7 +576,7 @@ const getAssistantAvatar = () => {
background: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
@ -601,14 +606,14 @@ const getAssistantAvatar = () => {
padding: 40px 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
overflow-y: auto;
.logo-section {
margin-bottom: 20px;
}
.title-section {
margin-bottom: 30px;
.main-title {
font-size: 32px;
font-weight: 600;
@ -619,7 +624,7 @@ const getAssistantAvatar = () => {
max-width: 800px;
}
}
.quick-questions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
@ -627,7 +632,7 @@ const getAssistantAvatar = () => {
max-width: 900px;
width: 100%;
margin-bottom: 30px;
.question-card {
background: #ffffff;
border: 1px solid #e5e7eb;
@ -638,13 +643,13 @@ const getAssistantAvatar = () => {
display: flex;
align-items: flex-start;
gap: 12px;
&:hover {
border-color: #6366f1;
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.15);
transform: translateY(-2px);
}
.question-icon {
flex-shrink: 0;
width: 32px;
@ -657,7 +662,7 @@ const getAssistantAvatar = () => {
color: #6366f1;
font-size: 18px;
}
.question-text {
flex: 1;
font-size: 14px;
@ -671,13 +676,13 @@ const getAssistantAvatar = () => {
}
}
}
.centered-input-wrapper {
width: 100%;
max-width: 900px;
margin-bottom: 20px;
}
.footer-text {
font-size: 13px;
color: #9ca3af;
@ -696,7 +701,7 @@ const getAssistantAvatar = () => {
flex-direction: column;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
overflow: hidden;
.chat-messages {
flex: 1;
overflow-y: auto;
@ -705,7 +710,7 @@ const getAssistantAvatar = () => {
scroll-behavior: smooth;
width: 100%;
}
.fixed-input-section {
position: absolute;
bottom: 0;
@ -718,11 +723,11 @@ const getAssistantAvatar = () => {
display: flex;
flex-direction: column;
align-items: center;
> * {
pointer-events: auto;
}
:deep(.centered-input-wrapper) {
margin: 0 auto;
}
@ -809,58 +814,58 @@ const getAssistantAvatar = () => {
.chat-main {
border-radius: 0;
}
.sidebar-toggle-btn {
top: 12px;
left: 12px;
}
.empty-state-container {
padding: 20px 16px;
.logo-section {
margin-bottom: 16px;
:deep(.el-icon) {
font-size: 40px !important;
}
}
.title-section {
margin-bottom: 24px;
.main-title {
font-size: 24px;
}
}
.quick-questions {
grid-template-columns: 1fr;
margin-bottom: 24px;
.question-card {
padding: 14px 16px;
.question-text {
font-size: 13px;
}
}
}
.centered-input-wrapper {
margin-bottom: 12px;
}
}
.chat-content {
.chat-messages {
padding: 1rem 0.5rem;
padding-bottom: 200px;
}
.fixed-input-section {
padding: 12px;
}
}
}
</style>
</style>

View File

@ -6,11 +6,12 @@
<div class="conversation-meta">
<span class="message-count"> {{ $t('vabI18n.HistoryRecords.HistoryDetails.count') }} {{ messages.length }} {{ $t('vabI18n.HistoryRecords.HistoryDetails.messageCount') }}</span>
<el-button class="export-btn" type="primary" size="small" icon="Download" @click="handleExportMarkdown">{{ $t('vabI18n.HistoryRecords.HistoryDetails.exportMarkdown') }}</el-button>
<el-button class="export-btn" type="primary" size="small" icon="Download" @click="handleExportPdf">导出为pdf</el-button>
</div>
</div>
<!-- 时间线对话 -->
<div class="timeline-container">
<div class="timeline-container" ref="timelineContainer">
<div v-for="(message, index) in messages" :key="message.messageId" class="timeline-item">
<!-- 时间标签 -->
<div class="timeline-time">
@ -67,6 +68,8 @@
import { marked } from 'marked'
import { convertEchartsMarkdown } from '@/api/echart'
import { useI18n } from 'vue-i18n'
import html2pdf from 'html2pdf.js'
export default defineComponent({
name: 'HistoryDetails',
props: {
@ -147,6 +150,67 @@
state.showThinking[index] = !state.showThinking[index]
}
// DOM
const timelineContainer = ref<HTMLElement | null>(null)
// html2pdf
type Html2PdfOptions = {
margin?: number
filename?: string
image?: { type?: 'jpeg' | 'png'; quality?: number }
html2canvas?: { scale?: number; scrollY?: number; windowWidth?: number }
jsPDF?: {
unit?: string;
format?: 'a3' | 'a4' | 'a5' | 'letter' | 'legal' | [number, number];
orientation?: 'portrait' | 'landscape'
}
}
const handleExportPdf = async () => {
if (!timelineContainer.value) return
//
const originalMaxHeight = timelineContainer.value.style.maxHeight
const originalOverflow = timelineContainer.value.style.overflow
//
timelineContainer.value.style.maxHeight = 'none'
timelineContainer.value.style.overflow = 'visible'
Object.keys(state.showThinking).forEach(k => state.showThinking[k] = true)
await nextTick()
await new Promise(resolve => setTimeout(resolve, 50)) //
const elementHeight = timelineContainer.value.scrollHeight
const elementWidth = timelineContainer.value.scrollWidth
const opt : Html2PdfOptions = {
margin: 0.3,
filename: `${props.conversationName}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, scrollY: -window.scrollY, windowWidth: elementWidth },
jsPDF: {
unit: 'in',
format: [elementWidth / 90, elementHeight / 90] as [number, number],
orientation: 'portrait'
}
}
try {
await html2pdf().set(opt).from(timelineContainer.value).save()
} catch (err) {
console.error('PDF 导出失败:', err)
} finally {
//
timelineContainer.value.style.maxHeight = originalMaxHeight
timelineContainer.value.style.overflow = originalOverflow
}
}
//markdown
const handleExportMarkdown = async () => {
// markdown
@ -182,6 +246,7 @@
return {
...toRefs(state),
timelineContainer,
t,
locale,
conversationName: computed(() => props.conversationName),
@ -191,6 +256,7 @@
formatAnswer,
toggleThinking,
handleExportMarkdown,
handleExportPdf,
}
},
})
@ -218,6 +284,7 @@
link.click()
setTimeout(() => URL.revokeObjectURL(link.href), 10000)
}
</script>
<style scoped>