refactor: Replace html2canvas with html-to-image for improved sup element (#2739)
* refactor: Replace html2canvas with html-to-image for improved screenshot capture * refactor: Simplify scrollable div capture method * refactor: Simplify captureScrollableDivAsBlob method * fix: Specify PNG format in captureScrollableDivAsBlob method * feat: Add error handling for large content dimensions in screenshot capture * fix: Reorder error messages in en-us.json locale file
This commit is contained in:
parent
846e7ca097
commit
a592fdc550
@ -27,7 +27,6 @@ files:
|
||||
- '!node_modules/@tavily/core/node_modules/js-tiktoken'
|
||||
- '!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}'
|
||||
- '!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}'
|
||||
- '!node_modules/html2canvas/dist/{html2canvas.min.js,html2canvas.esm.js}'
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
- '**/*.{node,dll,metal,exp,lib}'
|
||||
|
||||
@ -74,7 +74,7 @@
|
||||
"electron-window-state": "^5.0.3",
|
||||
"epub": "^1.3.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"html-to-image": "^1.11.13",
|
||||
"markdown-it": "^14.1.0",
|
||||
"officeparser": "^4.1.1",
|
||||
"tokenx": "^0.4.1",
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured",
|
||||
"error.yuque.export": "Failed to export to Yuque. Please check connection status and configuration according to documentation",
|
||||
"error.yuque.no_config": "Yuque Token or Yuque Url is not configured",
|
||||
"error.dimension_too_large": "Content size is too large",
|
||||
"group.delete.content": "Deleting a group message will delete the user's question and all assistant's answers",
|
||||
"group.delete.title": "Delete Group Message",
|
||||
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
|
||||
"error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください",
|
||||
"error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません",
|
||||
"error.dimension_too_large": "内容のサイズが大きすぎます",
|
||||
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
|
||||
"group.delete.title": "分組メッセージを削除",
|
||||
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
|
||||
"error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации",
|
||||
"error.yuque.no_config": "Yuque Token или Yuque Url не настроен",
|
||||
"error.dimension_too_large": "Размер содержимого слишком велик",
|
||||
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
|
||||
"group.delete.title": "Удалить группу сообщений",
|
||||
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
|
||||
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
|
||||
"error.yuque.no_config": "未配置语雀 Token 或 知识库 URL",
|
||||
"error.dimension_too_large": "内容尺寸过大",
|
||||
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
|
||||
"group.delete.title": "删除分组消息",
|
||||
"ignore.knowledge.base": "联网模式开启,忽略知识库",
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
|
||||
"error.yuque.export": "導出語雀錯誤,請檢查連接狀態並對照文檔檢查配置",
|
||||
"error.yuque.no_config": "未配置語雀 Token 或知識庫 Url",
|
||||
"error.dimension_too_large": "內容尺寸過大",
|
||||
"group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答",
|
||||
"group.delete.title": "刪除分組消息",
|
||||
"ignore.knowledge.base": "網路模式開啟,忽略知識庫",
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import i18n from '@renderer/i18n'
|
||||
import { Model } from '@renderer/types'
|
||||
import { ModalFuncProps } from 'antd/es/modal/interface'
|
||||
import imageCompression from 'browser-image-compression'
|
||||
import html2canvas from 'html2canvas'
|
||||
import * as htmlToImage from 'html-to-image'
|
||||
// @ts-ignore next-line`
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
@ -279,7 +280,7 @@ export function getFileExtension(filePath: string) {
|
||||
export async function captureDiv(divRef: React.RefObject<HTMLDivElement>) {
|
||||
if (divRef.current) {
|
||||
try {
|
||||
const canvas = await html2canvas(divRef.current)
|
||||
const canvas = await htmlToImage.toCanvas(divRef.current)
|
||||
const imageData = canvas.toDataURL('image/png')
|
||||
return imageData
|
||||
} catch (error) {
|
||||
@ -311,40 +312,47 @@ export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElemen
|
||||
div.style.overflow = 'visible'
|
||||
div.style.position = 'static'
|
||||
|
||||
// Configure html2canvas options
|
||||
const canvas = await html2canvas(div, {
|
||||
scrollY: -window.scrollY,
|
||||
windowHeight: document.documentElement.scrollHeight,
|
||||
useCORS: true, // Allow cross-origin images
|
||||
allowTaint: true, // Allow cross-origin images
|
||||
logging: false, // Disable logging
|
||||
imageTimeout: 0, // Disable image timeout
|
||||
backgroundColor: getComputedStyle(div).getPropertyValue('--color-background'),
|
||||
onclone: (clonedDoc) => {
|
||||
// 克隆时保留原始样式
|
||||
if (div.id) {
|
||||
const clonedDiv = clonedDoc.querySelector(`#${div.id}`) as HTMLElement
|
||||
if (clonedDiv) {
|
||||
const computedStyle = getComputedStyle(div)
|
||||
clonedDiv.style.backgroundColor = computedStyle.backgroundColor
|
||||
clonedDiv.style.color = computedStyle.color
|
||||
}
|
||||
}
|
||||
// calculate the size of the div
|
||||
const totalWidth = div.scrollWidth
|
||||
const totalHeight = div.scrollHeight
|
||||
|
||||
// Ensure all images in cloned document are loaded
|
||||
const images = clonedDoc.getElementsByTagName('img')
|
||||
return Promise.all(
|
||||
Array.from(images).map((img) => {
|
||||
if (img.complete) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
img.onload = resolve
|
||||
img.onerror = resolve
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
// check if the size of the div is too large
|
||||
const MAX_ALLOWED_DIMENSION = 32767 // the maximum allowed pixel size
|
||||
if (totalHeight > MAX_ALLOWED_DIMENSION || totalWidth > MAX_ALLOWED_DIMENSION) {
|
||||
// restore the original styles
|
||||
div.style.height = originalStyle.height
|
||||
div.style.maxHeight = originalStyle.maxHeight
|
||||
div.style.overflow = originalStyle.overflow
|
||||
div.style.position = originalStyle.position
|
||||
|
||||
// restore the original scroll position
|
||||
setTimeout(() => {
|
||||
div.scrollTop = originalScrollTop
|
||||
}, 0)
|
||||
|
||||
window.message.error({
|
||||
content: i18n.t('message.error.dimension_too_large'),
|
||||
key: 'export-error'
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
const canvas = await new Promise<HTMLCanvasElement>((resolve, reject) => {
|
||||
htmlToImage
|
||||
.toCanvas(div, {
|
||||
backgroundColor: getComputedStyle(div).getPropertyValue('--color-background'),
|
||||
cacheBust: true,
|
||||
pixelRatio: window.devicePixelRatio,
|
||||
skipAutoScale: true,
|
||||
canvasWidth: div.scrollWidth,
|
||||
canvasHeight: div.scrollHeight,
|
||||
style: {
|
||||
backgroundColor: getComputedStyle(div).backgroundColor,
|
||||
color: getComputedStyle(div).color
|
||||
}
|
||||
})
|
||||
.then((canvas) => resolve(canvas))
|
||||
.catch((error) => reject(error))
|
||||
})
|
||||
|
||||
// Restore original styles
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user