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:
SuYao 2025-03-04 15:37:29 +08:00 committed by GitHub
parent 846e7ca097
commit a592fdc550
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 812 additions and 812 deletions

View File

@ -27,7 +27,6 @@ files:
- '!node_modules/@tavily/core/node_modules/js-tiktoken' - '!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/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/mammoth/{mammoth.browser.js,mammoth.browser.min.js}'
- '!node_modules/html2canvas/dist/{html2canvas.min.js,html2canvas.esm.js}'
asarUnpack: asarUnpack:
- resources/** - resources/**
- '**/*.{node,dll,metal,exp,lib}' - '**/*.{node,dll,metal,exp,lib}'

View File

@ -74,7 +74,7 @@
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"epub": "^1.3.0", "epub": "^1.3.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"html2canvas": "^1.4.1", "html-to-image": "^1.11.13",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"officeparser": "^4.1.1", "officeparser": "^4.1.1",
"tokenx": "^0.4.1", "tokenx": "^0.4.1",

View File

@ -387,6 +387,7 @@
"error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured", "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.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.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.content": "Deleting a group message will delete the user's question and all assistant's answers",
"group.delete.title": "Delete Group Message", "group.delete.title": "Delete Group Message",
"ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base", "ignore.knowledge.base": "Web search mode is enabled, ignore knowledge base",

View File

@ -387,6 +387,7 @@
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません", "error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください", "error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください",
"error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません", "error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません",
"error.dimension_too_large": "内容のサイズが大きすぎます",
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます", "group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
"group.delete.title": "分組メッセージを削除", "group.delete.title": "分組メッセージを削除",
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します", "ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",

View File

@ -387,6 +387,7 @@
"error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен", "error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен",
"error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации", "error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации",
"error.yuque.no_config": "Yuque Token или Yuque Url не настроен", "error.yuque.no_config": "Yuque Token или Yuque Url не настроен",
"error.dimension_too_large": "Размер содержимого слишком велик",
"group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника", "group.delete.content": "Удаление группы сообщений удалит пользовательский вопрос и все ответы помощника",
"group.delete.title": "Удалить группу сообщений", "group.delete.title": "Удалить группу сообщений",
"ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний", "ignore.knowledge.base": "Режим сети включен, игнорировать базу знаний",

View File

@ -387,6 +387,7 @@
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID", "error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
"error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置", "error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置",
"error.yuque.no_config": "未配置语雀 Token 或 知识库 URL", "error.yuque.no_config": "未配置语雀 Token 或 知识库 URL",
"error.dimension_too_large": "内容尺寸过大",
"group.delete.content": "删除分组消息会删除用户提问和所有助手的回答", "group.delete.content": "删除分组消息会删除用户提问和所有助手的回答",
"group.delete.title": "删除分组消息", "group.delete.title": "删除分组消息",
"ignore.knowledge.base": "联网模式开启,忽略知识库", "ignore.knowledge.base": "联网模式开启,忽略知识库",

View File

@ -387,6 +387,7 @@
"error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID", "error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID",
"error.yuque.export": "導出語雀錯誤,請檢查連接狀態並對照文檔檢查配置", "error.yuque.export": "導出語雀錯誤,請檢查連接狀態並對照文檔檢查配置",
"error.yuque.no_config": "未配置語雀 Token 或知識庫 Url", "error.yuque.no_config": "未配置語雀 Token 或知識庫 Url",
"error.dimension_too_large": "內容尺寸過大",
"group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答", "group.delete.content": "刪除分組消息會刪除用戶提問和所有助手的回答",
"group.delete.title": "刪除分組消息", "group.delete.title": "刪除分組消息",
"ignore.knowledge.base": "網路模式開啟,忽略知識庫", "ignore.knowledge.base": "網路模式開啟,忽略知識庫",

View File

@ -1,7 +1,8 @@
import i18n from '@renderer/i18n'
import { Model } from '@renderer/types' import { Model } from '@renderer/types'
import { ModalFuncProps } from 'antd/es/modal/interface' import { ModalFuncProps } from 'antd/es/modal/interface'
import imageCompression from 'browser-image-compression' import imageCompression from 'browser-image-compression'
import html2canvas from 'html2canvas' import * as htmlToImage from 'html-to-image'
// @ts-ignore next-line` // @ts-ignore next-line`
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
@ -279,7 +280,7 @@ export function getFileExtension(filePath: string) {
export async function captureDiv(divRef: React.RefObject<HTMLDivElement>) { export async function captureDiv(divRef: React.RefObject<HTMLDivElement>) {
if (divRef.current) { if (divRef.current) {
try { try {
const canvas = await html2canvas(divRef.current) const canvas = await htmlToImage.toCanvas(divRef.current)
const imageData = canvas.toDataURL('image/png') const imageData = canvas.toDataURL('image/png')
return imageData return imageData
} catch (error) { } catch (error) {
@ -311,40 +312,47 @@ export const captureScrollableDiv = async (divRef: React.RefObject<HTMLDivElemen
div.style.overflow = 'visible' div.style.overflow = 'visible'
div.style.position = 'static' div.style.position = 'static'
// Configure html2canvas options // calculate the size of the div
const canvas = await html2canvas(div, { const totalWidth = div.scrollWidth
scrollY: -window.scrollY, const totalHeight = div.scrollHeight
windowHeight: document.documentElement.scrollHeight,
useCORS: true, // Allow cross-origin images // check if the size of the div is too large
allowTaint: true, // Allow cross-origin images const MAX_ALLOWED_DIMENSION = 32767 // the maximum allowed pixel size
logging: false, // Disable logging if (totalHeight > MAX_ALLOWED_DIMENSION || totalWidth > MAX_ALLOWED_DIMENSION) {
imageTimeout: 0, // Disable image timeout // restore the original styles
backgroundColor: getComputedStyle(div).getPropertyValue('--color-background'), div.style.height = originalStyle.height
onclone: (clonedDoc) => { div.style.maxHeight = originalStyle.maxHeight
// 克隆时保留原始样式 div.style.overflow = originalStyle.overflow
if (div.id) { div.style.position = originalStyle.position
const clonedDiv = clonedDoc.querySelector(`#${div.id}`) as HTMLElement
if (clonedDiv) { // restore the original scroll position
const computedStyle = getComputedStyle(div) setTimeout(() => {
clonedDiv.style.backgroundColor = computedStyle.backgroundColor div.scrollTop = originalScrollTop
clonedDiv.style.color = computedStyle.color }, 0)
}
window.message.error({
content: i18n.t('message.error.dimension_too_large'),
key: 'export-error'
})
return Promise.reject()
} }
// Ensure all images in cloned document are loaded const canvas = await new Promise<HTMLCanvasElement>((resolve, reject) => {
const images = clonedDoc.getElementsByTagName('img') htmlToImage
return Promise.all( .toCanvas(div, {
Array.from(images).map((img) => { backgroundColor: getComputedStyle(div).getPropertyValue('--color-background'),
if (img.complete) { cacheBust: true,
return Promise.resolve() pixelRatio: window.devicePixelRatio,
skipAutoScale: true,
canvasWidth: div.scrollWidth,
canvasHeight: div.scrollHeight,
style: {
backgroundColor: getComputedStyle(div).backgroundColor,
color: getComputedStyle(div).color
} }
return new Promise((resolve) => {
img.onload = resolve
img.onerror = resolve
}) })
}) .then((canvas) => resolve(canvas))
) .catch((error) => reject(error))
}
}) })
// Restore original styles // Restore original styles

1538
yarn.lock

File diff suppressed because it is too large Load Diff