feat: 增加保存会话为图片的功能 (#374)
* feat: 增加保存会话为图片的功能 * feat: 异常处理和增加导出动画 --------- Co-authored-by: ChenZhaoYu <790348264@qq.com>
This commit is contained in:
parent
ecc2afd164
commit
a2ffa3cb3a
|
@ -26,6 +26,7 @@
|
|||
"@traptitech/markdown-it-katex": "^3.6.0",
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"katex": "^0.16.4",
|
||||
"markdown-it": "^13.0.1",
|
||||
"naive-ui": "^2.34.3",
|
||||
|
|
|
@ -17,6 +17,7 @@ specifiers:
|
|||
crypto-js: ^4.1.1
|
||||
eslint: ^8.35.0
|
||||
highlight.js: ^11.7.0
|
||||
html2canvas: ^1.4.1
|
||||
husky: ^8.0.3
|
||||
katex: ^0.16.4
|
||||
less: ^4.1.3
|
||||
|
@ -39,6 +40,7 @@ dependencies:
|
|||
'@traptitech/markdown-it-katex': 3.6.0
|
||||
'@vueuse/core': 9.13.0_vue@3.2.47
|
||||
highlight.js: 11.7.0
|
||||
html2canvas: 1.4.1
|
||||
katex: 0.16.4
|
||||
markdown-it: 13.0.1
|
||||
naive-ui: 2.34.3_vue@3.2.47
|
||||
|
@ -1345,6 +1347,11 @@ packages:
|
|||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
|
||||
/base64-arraybuffer/1.0.2:
|
||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
dev: false
|
||||
|
||||
/binary-extensions/2.2.0:
|
||||
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -1666,6 +1673,12 @@ packages:
|
|||
resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==}
|
||||
dev: true
|
||||
|
||||
/css-line-break/2.1.0:
|
||||
resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==}
|
||||
dependencies:
|
||||
utrie: 1.0.2
|
||||
dev: false
|
||||
|
||||
/css-render/0.15.12:
|
||||
resolution: {integrity: sha512-eWzS66patiGkTTik+ipO9qNGZ+uNuGyTmnz6/+EJIiFg8+3yZRpnMwgFo8YdXhQRsiePzehnusrxVvugNjXzbw==}
|
||||
dependencies:
|
||||
|
@ -2757,6 +2770,14 @@ packages:
|
|||
lru-cache: 6.0.0
|
||||
dev: true
|
||||
|
||||
/html2canvas/1.4.1:
|
||||
resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
dependencies:
|
||||
css-line-break: 2.1.0
|
||||
text-segmentation: 1.0.3
|
||||
dev: false
|
||||
|
||||
/htmlparser2/8.0.1:
|
||||
resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
|
||||
dependencies:
|
||||
|
@ -4488,6 +4509,12 @@ packages:
|
|||
engines: {node: '>=0.10'}
|
||||
dev: true
|
||||
|
||||
/text-segmentation/1.0.3:
|
||||
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
|
||||
dependencies:
|
||||
utrie: 1.0.2
|
||||
dev: false
|
||||
|
||||
/text-table/0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
dev: true
|
||||
|
@ -4670,6 +4697,12 @@ packages:
|
|||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
||||
/utrie/1.0.2:
|
||||
resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==}
|
||||
dependencies:
|
||||
base64-arraybuffer: 1.0.2
|
||||
dev: false
|
||||
|
||||
/v8-compile-cache-lib/3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
dev: true
|
||||
|
|
|
@ -21,6 +21,10 @@ export default {
|
|||
copyCode: 'Copy Code',
|
||||
clearChat: 'Clear Chat',
|
||||
clearChatConfirm: 'Are you sure to clear this chat?',
|
||||
exportImage: 'Export Image',
|
||||
exportImageConfirm: 'Are you sure to export this chat to png?',
|
||||
exportSuccess: 'Export Success',
|
||||
exportFailed: 'Export Failed',
|
||||
deleteMessage: 'Delete Message',
|
||||
deleteMessageConfirm: 'Are you sure to delete this message?',
|
||||
deleteHistoryConfirm: 'Are you sure to clear this history?',
|
||||
|
|
|
@ -21,6 +21,10 @@ export default {
|
|||
copyCode: '复制代码',
|
||||
clearChat: '清空会话',
|
||||
clearChatConfirm: '是否清空会话?',
|
||||
exportImage: '保存会话到图片',
|
||||
exportImageConfirm: '是否将会话保存为图片?',
|
||||
exportSuccess: '保存成功',
|
||||
exportFailed: '保存失败',
|
||||
deleteMessage: '删除消息',
|
||||
deleteMessageConfirm: '是否删除此消息?',
|
||||
deleteHistoryConfirm: '确定删除此记录?',
|
||||
|
|
|
@ -21,6 +21,10 @@ export default {
|
|||
copyCode: '複製代碼',
|
||||
clearChat: '清空對話',
|
||||
clearChatConfirm: '是否清空對話?',
|
||||
exportImage: '儲存對話為圖片',
|
||||
exportImageConfirm: '是否將對話儲存為圖片?',
|
||||
exportSuccess: '儲存成功',
|
||||
exportFailed: '儲存失敗',
|
||||
deleteMessage: '刪除訊息',
|
||||
deleteMessageConfirm: '是否刪除此訊息?',
|
||||
deleteHistoryConfirm: '確定刪除此紀錄?',
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script setup lang='ts'>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { NButton, NInput, useDialog } from 'naive-ui'
|
||||
import { NButton, NInput, useDialog, useMessage } from 'naive-ui'
|
||||
import html2canvas from 'html2canvas'
|
||||
import { Message } from './components'
|
||||
import { useScroll } from './hooks/useScroll'
|
||||
import { useChat } from './hooks/useChat'
|
||||
|
@ -16,6 +17,7 @@ let controller = new AbortController()
|
|||
|
||||
const route = useRoute()
|
||||
const dialog = useDialog()
|
||||
const ms = useMessage()
|
||||
|
||||
const chatStore = useChatStore()
|
||||
|
||||
|
@ -268,6 +270,46 @@ async function onRegenerate(index: number) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
if (loading.value)
|
||||
return
|
||||
|
||||
const d = dialog.warning({
|
||||
title: t('chat.exportImage'),
|
||||
content: t('chat.exportImageConfirm'),
|
||||
positiveText: t('common.yes'),
|
||||
negativeText: t('common.no'),
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
d.loading = true
|
||||
const ele = document.getElementById('image-wrapper')
|
||||
const canvas = await html2canvas(ele as HTMLDivElement)
|
||||
const imgUrl = canvas.toDataURL('image/png')
|
||||
const tempLink = document.createElement('a')
|
||||
tempLink.style.display = 'none'
|
||||
tempLink.href = imgUrl
|
||||
tempLink.setAttribute('download', 'chat-shot.png')
|
||||
if (typeof tempLink.download === 'undefined')
|
||||
tempLink.setAttribute('target', '_blank')
|
||||
|
||||
document.body.appendChild(tempLink)
|
||||
tempLink.click()
|
||||
document.body.removeChild(tempLink)
|
||||
window.URL.revokeObjectURL(imgUrl)
|
||||
d.loading = false
|
||||
ms.success(t('chat.exportSuccess'))
|
||||
Promise.resolve()
|
||||
}
|
||||
catch (error: any) {
|
||||
ms.error(t('chat.exportFailed'))
|
||||
}
|
||||
finally {
|
||||
d.loading = false
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(index: number) {
|
||||
if (loading.value)
|
||||
return
|
||||
|
@ -360,9 +402,8 @@ onUnmounted(() => {
|
|||
id="scrollRef"
|
||||
ref="scrollRef"
|
||||
class="h-full overflow-hidden overflow-y-auto"
|
||||
:class="[isMobile ? 'p-2' : 'p-4']"
|
||||
>
|
||||
<div class="w-full max-w-screen-xl m-auto">
|
||||
<div id="image-wrapper" class="w-full max-w-screen-xl m-auto" :class="[isMobile ? 'p-2' : 'p-4']">
|
||||
<template v-if="!dataSources.length">
|
||||
<div class="flex items-center justify-center mt-4 text-center text-neutral-300">
|
||||
<SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
|
||||
|
@ -403,6 +444,11 @@ onUnmounted(() => {
|
|||
<SvgIcon icon="ri:delete-bin-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
<HoverButton @click="handleExport">
|
||||
<span class="text-xl text-[#4f555e] dark:text-white">
|
||||
<SvgIcon icon="ri:download-2-line" />
|
||||
</span>
|
||||
</HoverButton>
|
||||
<NInput
|
||||
v-model:value="prompt"
|
||||
type="textarea"
|
||||
|
|
Loading…
Reference in New Issue