feat: add copy function for mermaid diagram

This commit is contained in:
shiquda 2025-03-15 19:07:11 +08:00 committed by 亢奋猫
parent 9cb127f14e
commit 7d048872e1
6 changed files with 48 additions and 2 deletions

View File

@ -402,6 +402,7 @@
"citations": "References", "citations": "References",
"copied": "Copied!", "copied": "Copied!",
"copy.success": "Copied!", "copy.success": "Copied!",
"copy.failed": "Copy failed",
"error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size", "error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size",
"error.dimension_too_large": "Content size is too large", "error.dimension_too_large": "Content size is too large",
"error.enter.api.host": "Please enter your API host first", "error.enter.api.host": "Please enter your API host first",

View File

@ -402,6 +402,7 @@
"citations": "参考文献", "citations": "参考文献",
"copied": "コピーしました!", "copied": "コピーしました!",
"copy.success": "コピーしました!", "copy.success": "コピーしました!",
"copy.failed": "コピーに失敗しました",
"error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません", "error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません",
"error.dimension_too_large": "内容のサイズが大きすぎます", "error.dimension_too_large": "内容のサイズが大きすぎます",
"error.enter.api.host": "APIホストを入力してください", "error.enter.api.host": "APIホストを入力してください",

View File

@ -408,6 +408,7 @@
"citations": "Источники", "citations": "Источники",
"copied": "Скопировано!", "copied": "Скопировано!",
"copy.success": "Скопировано!", "copy.success": "Скопировано!",
"copy.failed": "Не удалось скопировать",
"error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.", "error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.",
"error.dimension_too_large": "Размер содержимого слишком велик", "error.dimension_too_large": "Размер содержимого слишком велик",
"error.enter.api.host": "Пожалуйста, введите ваш API хост", "error.enter.api.host": "Пожалуйста, введите ваш API хост",

View File

@ -402,6 +402,7 @@
"citations": "引用内容", "citations": "引用内容",
"copied": "已复制", "copied": "已复制",
"copy.success": "复制成功", "copy.success": "复制成功",
"copy.failed": "复制失败",
"error.chunk_overlap_too_large": "分段重叠不能大于分段大小", "error.chunk_overlap_too_large": "分段重叠不能大于分段大小",
"error.dimension_too_large": "内容尺寸过大", "error.dimension_too_large": "内容尺寸过大",
"error.enter.api.host": "请输入您的 API 地址", "error.enter.api.host": "请输入您的 API 地址",

View File

@ -400,8 +400,9 @@
"backup.success": "備份成功", "backup.success": "備份成功",
"chat.completion.paused": "聊天完成已暫停", "chat.completion.paused": "聊天完成已暫停",
"citations": "參考文獻", "citations": "參考文獻",
"copied": "已複製", "copied": "已複製!",
"copy.success": "複製成功", "copy.success": "已複製!",
"copy.failed": "複製失敗",
"error.chunk_overlap_too_large": "分段重疊不能大於分段大小", "error.chunk_overlap_too_large": "分段重疊不能大於分段大小",
"error.dimension_too_large": "內容尺寸過大", "error.dimension_too_large": "內容尺寸過大",
"error.enter.api.host": "請先輸入您的 API 主機地址", "error.enter.api.host": "請先輸入您的 API 主機地址",

View File

@ -51,6 +51,46 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
} }
} }
const handleCopyImage = async () => {
try {
const element = document.getElementById(mermaidId)
if (!element) return
const svgElement = element.querySelector('svg')
if (!svgElement) return
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.crossOrigin = 'anonymous'
const viewBox = svgElement.getAttribute('viewBox')?.split(' ').map(Number) || []
const width = viewBox[2] || svgElement.clientWidth || svgElement.getBoundingClientRect().width
const height = viewBox[3] || svgElement.clientHeight || svgElement.getBoundingClientRect().height
const svgData = new XMLSerializer().serializeToString(svgElement)
const svgBase64 = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`
img.onload = async () => {
const scale = 3
canvas.width = width * scale
canvas.height = height * scale
if (ctx) {
ctx.scale(scale, scale)
ctx.drawImage(img, 0, 0, width, height)
const blob = await new Promise<Blob>((resolve) => canvas.toBlob((b) => resolve(b!), 'image/png'))
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
window.message.success(t('message.copy.success'))
}
}
img.src = svgBase64
} catch (error) {
console.error('Copy failed:', error)
window.message.error(t('message.copy.failed'))
}
}
const handleDownload = async (format: 'svg' | 'png') => { const handleDownload = async (format: 'svg' | 'png') => {
try { try {
const element = document.getElementById(mermaidId) const element = document.getElementById(mermaidId)
@ -132,6 +172,7 @@ const PopupContainer: React.FC<Props> = ({ resolve, chart }) => {
<> <>
<Button onClick={() => handleZoom(0.1)}>{t('mermaid.resize.zoom-in')}</Button> <Button onClick={() => handleZoom(0.1)}>{t('mermaid.resize.zoom-in')}</Button>
<Button onClick={() => handleZoom(-0.1)}>{t('mermaid.resize.zoom-out')}</Button> <Button onClick={() => handleZoom(-0.1)}>{t('mermaid.resize.zoom-out')}</Button>
<Button onClick={() => handleCopyImage()}>{t('common.copy')}</Button>
<Button onClick={() => handleDownload('svg')}>{t('mermaid.download.svg')}</Button> <Button onClick={() => handleDownload('svg')}>{t('mermaid.download.svg')}</Button>
<Button onClick={() => handleDownload('png')}>{t('mermaid.download.png')}</Button> <Button onClick={() => handleDownload('png')}>{t('mermaid.download.png')}</Button>
</> </>