添加了通用问答和历史回答的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", "element-plus": "^2.3.8",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"html2pdf.js": "^0.12.1",
"image-conversion": "^2.1.1", "image-conversion": "^2.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"js-message": "^2.1.0", "js-message": "^2.1.0",
@ -3752,6 +3753,11 @@
"integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
"dev": true "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": { "node_modules/@types/parse-json": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
@ -3769,6 +3775,12 @@
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true "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": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
@ -6391,6 +6403,14 @@
"node": ">=0.10.0" "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": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz",
@ -7450,6 +7470,25 @@
"simple-concat": "^1.0.0" "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": { "node_modules/capital-case": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz", "resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz",
@ -8419,6 +8458,14 @@
"node": ">=12 || >=16" "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": { "node_modules/css-loader": {
"version": "6.11.0", "version": "6.11.0",
"resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz", "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz",
@ -11280,6 +11327,21 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "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": { "node_modules/fast-uri": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz",
@ -11363,6 +11425,11 @@
"pend": "~1.2.0" "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": { "node_modules/figures": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz", "resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz",
@ -12965,6 +13032,27 @@
"node": ">=6" "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": { "node_modules/htmlparser2": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz", "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz",
@ -13715,6 +13803,11 @@
"node": ">=4" "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": { "node_modules/ipaddr.js": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
@ -15661,6 +15754,22 @@
"node": ">=0.10.0" "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": { "node_modules/jszip": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@ -18641,6 +18750,12 @@
"dev": true, "dev": true,
"optional": 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": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -20408,6 +20523,15 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@ -20735,6 +20859,12 @@
"node": ">=4" "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": { "node_modules/regex-not": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz",
@ -21139,6 +21269,15 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true "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": { "node_modules/rimraf": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
@ -22461,6 +22600,15 @@
"node": ">=8" "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": { "node_modules/stackframe": {
"version": "1.3.4", "version": "1.3.4",
"resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz",
@ -23564,6 +23712,15 @@
"node": ">=0.10.0" "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": { "node_modules/svg-sprite-loader": {
"version": "6.0.11", "version": "6.0.11",
"resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz", "resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz",
@ -24023,6 +24180,14 @@
"node": ">=8" "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": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@ -25294,6 +25459,14 @@
"node": ">= 0.4.0" "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": { "node_modules/uuid": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",
@ -29618,6 +29791,11 @@
"integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==", "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
"dev": true "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": { "@types/parse-json": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz", "resolved": "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz",
@ -29635,6 +29813,12 @@
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"dev": true "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": { "@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz", "resolved": "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz",
@ -31566,6 +31750,11 @@
"pascalcase": "^0.1.1" "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": { "base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", "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": { "capital-case": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz", "resolved": "https://registry.npmmirror.com/capital-case/-/capital-case-1.0.4.tgz",
@ -33122,6 +33327,14 @@
"integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==",
"dev": true "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": { "css-loader": {
"version": "6.11.0", "version": "6.11.0",
"resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz", "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz",
@ -35305,6 +35518,23 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "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": { "fast-uri": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz", "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.0.6.tgz",
@ -35363,6 +35593,11 @@
"pend": "~1.2.0" "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": { "figures": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-3.2.0.tgz", "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": { "htmlparser2": {
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz", "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz",
@ -37106,6 +37359,11 @@
"p-is-promise": "^1.1.0" "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": { "ipaddr.js": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
@ -38517,6 +38775,20 @@
"integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
"dev": true "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": { "jszip": {
"version": "3.10.1", "version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
@ -40791,6 +41063,12 @@
"dev": true, "dev": true,
"optional": 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": { "picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@ -42005,6 +42283,15 @@
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
"dev": true "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": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz",
@ -42253,6 +42540,12 @@
"regenerate": "^1.4.2" "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": { "regex-not": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/regex-not/-/regex-not-1.0.2.tgz",
@ -42554,6 +42847,12 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true "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": { "rimraf": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", "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": { "stackframe": {
"version": "1.3.4", "version": "1.3.4",
"resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz", "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": { "svg-sprite-loader": {
"version": "6.0.11", "version": "6.0.11",
"resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz", "resolved": "https://registry.npmmirror.com/svg-sprite-loader/-/svg-sprite-loader-6.0.11.tgz",
@ -44713,6 +45024,14 @@
"minimatch": "^3.0.4" "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": { "text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
@ -45672,6 +45991,14 @@
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"dev": true "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": { "uuid": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz",

View File

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

View File

@ -6,11 +6,12 @@
<div class="conversation-meta"> <div class="conversation-meta">
<span class="message-count"> {{ $t('vabI18n.HistoryRecords.HistoryDetails.count') }} {{ messages.length }} {{ $t('vabI18n.HistoryRecords.HistoryDetails.messageCount') }}</span> <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="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> </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 v-for="(message, index) in messages" :key="message.messageId" class="timeline-item">
<!-- 时间标签 --> <!-- 时间标签 -->
<div class="timeline-time"> <div class="timeline-time">
@ -67,6 +68,8 @@
import { marked } from 'marked' import { marked } from 'marked'
import { convertEchartsMarkdown } from '@/api/echart' import { convertEchartsMarkdown } from '@/api/echart'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import html2pdf from 'html2pdf.js'
export default defineComponent({ export default defineComponent({
name: 'HistoryDetails', name: 'HistoryDetails',
props: { props: {
@ -147,6 +150,67 @@
state.showThinking[index] = !state.showThinking[index] 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 //markdown
const handleExportMarkdown = async () => { const handleExportMarkdown = async () => {
// markdown // markdown
@ -182,6 +246,7 @@
return { return {
...toRefs(state), ...toRefs(state),
timelineContainer,
t, t,
locale, locale,
conversationName: computed(() => props.conversationName), conversationName: computed(() => props.conversationName),
@ -191,6 +256,7 @@
formatAnswer, formatAnswer,
toggleThinking, toggleThinking,
handleExportMarkdown, handleExportMarkdown,
handleExportPdf,
} }
}, },
}) })
@ -218,6 +284,7 @@
link.click() link.click()
setTimeout(() => URL.revokeObjectURL(link.href), 10000) setTimeout(() => URL.revokeObjectURL(link.href), 10000)
} }
</script> </script>
<style scoped> <style scoped>