更新35.2.0

This commit is contained in:
1600822305 2025-04-26 06:27:33 +08:00
parent 8546219546
commit e5c3e57430
14 changed files with 1156 additions and 103 deletions

7
.npmrc
View File

@ -1 +1,6 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
registry=https://registry.npmmirror.com/
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
ELECTRON_CUSTOM_DIR="{{ version }}"
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/

1
.yarnrc Normal file
View File

@ -0,0 +1 @@
electron_mirror "https://npmmirror.com/mirrors/electron/"

View File

@ -0,0 +1,4 @@
const { app } = require('electron');
console.log(`Electron version: ${process.versions.electron}`);
console.log(`Chrome version: ${process.versions.chrome}`);
console.log(`Node version: ${process.versions.node}`);

View File

@ -0,0 +1,4 @@
// 这是一个启动脚本,用于加载补丁并运行 electron-builder
require('J:\Cherry\cherry-studioTTS\node_modules\app-builder-lib\out\node-module-collector\nodeModulesCollector.patch.js')
require('electron-builder/cli')

View File

@ -22,16 +22,19 @@
"dev": "electron-vite dev",
"build": "npm run typecheck && electron-vite build",
"build:check": "yarn test && yarn typecheck && yarn check:i18n",
"build:unpack": "dotenv npm run build && electron-builder --dir",
"build:win": "dotenv npm run build && electron-builder --win",
"build:win:x64": "dotenv npm run build && electron-builder --win --x64",
"build:win:arm64": "dotenv npm run build && electron-builder --win --arm64",
"build:mac": "dotenv electron-vite build && electron-builder --mac",
"build:mac:arm64": "dotenv electron-vite build && electron-builder --mac --arm64",
"build:mac:x64": "dotenv electron-vite build && electron-builder --mac --x64",
"build:linux": "dotenv electron-vite build && electron-builder --linux",
"build:linux:arm64": "dotenv electron-vite build && electron-builder --linux --arm64",
"build:linux:x64": "dotenv electron-vite build && electron-builder --linux --x64",
"fix:electron-builder": "node scripts/fix-electron-builder.js",
"update:electron": "node scripts/update-electron.js",
"update:electron:direct": "node scripts/update-electron-direct.js",
"build:unpack": "dotenv npm run build && npm run fix:electron-builder && electron-builder --dir",
"build:win": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win",
"build:win:x64": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win --x64",
"build:win:arm64": "dotenv npm run build && npm run fix:electron-builder && electron-builder --win --arm64",
"build:mac": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac",
"build:mac:arm64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac --arm64",
"build:mac:x64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --mac --x64",
"build:linux": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux",
"build:linux:arm64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux --arm64",
"build:linux:x64": "dotenv electron-vite build && npm run fix:electron-builder && electron-builder --linux --x64",
"build:npm": "node scripts/build-npm.js",
"release": "node scripts/version.js",
"publish": "yarn build:check && yarn release patch push",
@ -52,7 +55,7 @@
"test:renderer:coverage": "vitest run --coverage",
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"postinstall": "electron-builder install-app-deps",
"postinstall": "electron-builder install-app-deps && npm run fix:electron-builder",
"prepare": "husky"
},
"dependencies": {
@ -155,6 +158,7 @@
"turndown-plugin-gfm": "^1.0.2",
"undici": "^7.4.0",
"webdav": "^5.8.0",
"zipfile": "^0.5.12",
"zipread": "^1.3.3",
"zod": "^3.24.2"
},
@ -213,7 +217,7 @@
"dexie": "^4.0.8",
"dexie-react-hooks": "^1.1.7",
"dotenv-cli": "^7.4.2",
"electron": "31.7.6",
"electron": "35.2.0",
"electron-builder": "26.0.13",
"electron-devtools-installer": "^3.2.0",
"electron-icon-builder": "^2.0.1",

View File

@ -0,0 +1,109 @@
/**
* 修复 electron-builder 堆栈溢出问题的补丁脚本
*
* 这个脚本修复了 electron-builder 在处理循环依赖时导致的堆栈溢出问题
* 主要修改了以下文件
* 1. node_modules/app-builder-lib/out/node-module-collector/nodeModulesCollector.js
*/
const fs = require('fs');
const path = require('path');
// 获取 nodeModulesCollector.js 文件的路径
const nodeModulesCollectorPath = path.join(
process.cwd(),
'node_modules',
'app-builder-lib',
'out',
'node-module-collector',
'nodeModulesCollector.js'
);
// 检查文件是否存在
if (!fs.existsSync(nodeModulesCollectorPath)) {
console.error('找不到 nodeModulesCollector.js 文件,请确保已安装 electron-builder');
process.exit(1);
}
// 读取文件内容
let content = fs.readFileSync(nodeModulesCollectorPath, 'utf8');
// 修复 1: 修改 _getNodeModules 方法,添加环路检测
const oldGetNodeModulesMethod = /(_getNodeModules\(dependencies, result\) \{[\s\S]*?result\.sort\(\(a, b\) => a\.name\.localeCompare\(b\.name\)\);\s*\})/;
const newGetNodeModulesMethod = `_getNodeModules(dependencies, result, depth = 0, visited = new Set()) {
// 添加递归深度限制
if (depth > 10) {
console.log("递归深度超过10停止递归");
return;
}
if (dependencies.size === 0) {
return;
}
for (const d of dependencies.values()) {
const reference = [...d.references][0];
const moduleId = \`\${d.name}@\${reference}\`;
// 环路检测:如果已经访问过这个模块,则跳过
if (visited.has(moduleId)) {
console.log(\`检测到循环依赖: \${moduleId}\`);
continue;
}
// 标记为已访问
visited.add(moduleId);
const p = this.dependencyPathMap.get(moduleId);
if (p === undefined) {
builder_util_1.log.debug({ name: d.name, reference }, "cannot find path for dependency");
continue;
}
const node = {
name: d.name,
version: reference,
dir: p,
};
result.push(node);
if (d.dependencies.size > 0) {
node.dependencies = [];
this._getNodeModules(d.dependencies, node.dependencies, depth + 1, visited);
}
// 处理完成后,从已访问集合中移除,允许在其他路径中再次访问
visited.delete(moduleId);
}
result.sort((a, b) => a.name.localeCompare(b.name));
}`;
content = content.replace(oldGetNodeModulesMethod, newGetNodeModulesMethod);
// 修复 2: 修改 getNodeModules 方法,传递 visited 集合
const oldGetNodeModulesCall = /(this\._getNodeModules\(hoisterResult\.dependencies, this\.nodeModules\);)/;
const newGetNodeModulesCall = `// 创建一个新的 visited 集合用于环路检测
const visited = new Set();
this._getNodeModules(hoisterResult.dependencies, this.nodeModules, 0, visited);`;
content = content.replace(oldGetNodeModulesCall, newGetNodeModulesCall);
// 修复 3: 修改 convertToDependencyGraph 方法,跳过路径未定义的依赖
const oldPathCheck = /(if \(!dependencies\.path\) \{[\s\S]*?throw new Error\("unable to parse `path` during `tree\.dependencies` reduce"\);[\s\S]*?\})/;
const newPathCheck = `if (!dependencies.path) {
builder_util_1.log.error({
packageName,
data: dependencies,
parentModule: tree.name,
parentVersion: tree.version,
}, "dependency path is undefined");
// 跳过这个依赖而不是抛出错误
console.log(\`跳过路径未定义的依赖: \${packageName}\`);
return acc;
}`;
content = content.replace(oldPathCheck, newPathCheck);
// 写入修改后的内容
fs.writeFileSync(nodeModulesCollectorPath, content, 'utf8');
console.log('成功应用 electron-builder 堆栈溢出修复补丁!');

View File

@ -0,0 +1,130 @@
/**
* Electron 直接版本更新脚本
*
* 这个脚本帮助您直接更新到指定版本的 Electron
* 使用方法: node scripts/update-electron-direct.js [version]
*
* 例如: node scripts/update-electron-direct.js 32.0.0
*/
const { execSync } = require('child_process')
const fs = require('fs')
const path = require('path')
const readline = require('readline')
// 创建命令行交互界面
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// 获取当前 package.json 中的 Electron 版本
function getCurrentElectronVersion() {
const packageJsonPath = path.join(process.cwd(), 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
return packageJson.devDependencies.electron
}
// 更新 package.json 中的 Electron 版本
function updateElectronVersion(version) {
const packageJsonPath = path.join(process.cwd(), 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
// 保存旧版本
const oldVersion = packageJson.devDependencies.electron
// 更新版本
packageJson.devDependencies.electron = version
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8')
console.log(`已更新 package.json 中的 Electron 版本: ${oldVersion} -> ${version}`)
return oldVersion
}
// 安装依赖
function installDependencies() {
console.log('正在安装依赖...')
try {
execSync('yarn install', { stdio: 'inherit' })
return true
} catch (error) {
console.error('安装依赖失败:', error.message)
return false
}
}
// 测试应用
function testApp() {
console.log('正在启动开发模式测试应用...')
try {
execSync('npm run dev', { stdio: 'inherit' })
return true
} catch (error) {
console.error('测试应用失败:', error.message)
return false
}
}
// 主函数
async function main() {
const targetVersion = process.argv[2]
if (!targetVersion) {
console.error('请指定目标 Electron 版本')
console.log('使用方法: node scripts/update-electron-direct.js [version]')
console.log('例如: node scripts/update-electron-direct.js 32.0.0')
rl.close()
return
}
const currentVersion = getCurrentElectronVersion()
console.log(`当前 Electron 版本: ${currentVersion}`)
rl.question(`确定要直接更新到 Electron ${targetVersion} 吗?(y/n) `, (answer) => {
if (answer.toLowerCase() !== 'y') {
console.log('操作已取消')
rl.close()
return
}
const oldVersion = updateElectronVersion(targetVersion)
if (!installDependencies()) {
console.log(`Electron ${targetVersion} 安装依赖失败,正在恢复到原版本 ${oldVersion}`)
updateElectronVersion(oldVersion)
installDependencies()
rl.close()
return
}
rl.question('依赖安装成功,是否测试应用?(y/n) ', (answer) => {
if (answer.toLowerCase() !== 'y') {
console.log('跳过测试步骤')
rl.close()
return
}
if (!testApp()) {
console.log(`Electron ${targetVersion} 测试失败`)
rl.question(`是否恢复到原版本 ${oldVersion}(y/n) `, (answer) => {
if (answer.toLowerCase() === 'y') {
console.log(`正在恢复到原版本 ${oldVersion}`)
updateElectronVersion(oldVersion)
installDependencies()
}
rl.close()
})
return
}
console.log(`Electron ${targetVersion} 更新并测试成功!`)
rl.close()
})
})
}
// 运行主函数
main().catch((error) => {
console.error('发生错误:', error)
rl.close()
})

266
scripts/update-electron.js Normal file
View File

@ -0,0 +1,266 @@
/**
* Electron 版本更新脚本
*
* 这个脚本帮助您逐步更新到更新版本的 Electron
* 使用方法: node scripts/update-electron.js [target-version]
*
* 例如: node scripts/update-electron.js 32.0.0
*/
const { execSync } = require('child_process')
const fs = require('fs')
const path = require('path')
const readline = require('readline')
const https = require('https')
// 创建命令行交互界面
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// 获取当前 package.json 中的 Electron 版本
function getCurrentElectronVersion() {
const packageJsonPath = path.join(process.cwd(), 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
return packageJson.devDependencies.electron
}
// 更新 package.json 中的 Electron 版本
function updateElectronVersion(version) {
const packageJsonPath = path.join(process.cwd(), 'package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
// 保存旧版本
const oldVersion = packageJson.devDependencies.electron
// 更新版本
packageJson.devDependencies.electron = version
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8')
console.log(`已更新 package.json 中的 Electron 版本: ${oldVersion} -> ${version}`)
return oldVersion
}
// 安装依赖
function installDependencies() {
console.log('正在安装依赖...')
try {
execSync('yarn install', { stdio: 'inherit' })
return true
} catch (error) {
console.error('安装依赖失败:', error.message)
return false
}
}
// 测试应用
function testApp() {
console.log('正在启动开发模式测试应用...')
try {
execSync('npm run dev', { stdio: 'inherit' })
return true
} catch (error) {
console.error('测试应用失败:', error.message)
return false
}
}
// 从 npm 获取 Electron 的可用版本
function getAvailableElectronVersions() {
return new Promise((resolve, reject) => {
https
.get('https://registry.npmjs.org/electron', (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
try {
const json = JSON.parse(data)
const versions = Object.keys(json.versions)
.filter((v) => !v.includes('-')) // 过滤掉预发布版本
.sort((a, b) => compareVersions(a, b))
resolve(versions)
} catch (error) {
reject(error)
}
})
})
.on('error', (error) => {
reject(error)
})
})
}
// 比较版本号
function compareVersions(a, b) {
const partsA = a.split('.').map(Number)
const partsB = b.split('.').map(Number)
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const partA = partsA[i] || 0
const partB = partsB[i] || 0
if (partA < partB) return -1
if (partA > partB) return 1
}
return 0
}
// 获取从当前版本到目标版本的升级路径
function getUpgradePath(currentVersion, targetVersion, allVersions) {
// 清理版本号,移除可能的前缀(如 ^、~
currentVersion = currentVersion.replace(/[^0-9.]/g, '')
// 过滤出在当前版本和目标版本之间的所有版本
const relevantVersions = allVersions.filter(
(v) => compareVersions(v, currentVersion) > 0 && compareVersions(v, targetVersion) <= 0
)
// 按主要版本分组
const versionsByMajor = {}
relevantVersions.forEach((v) => {
const major = v.split('.')[0]
if (!versionsByMajor[major]) {
versionsByMajor[major] = []
}
versionsByMajor[major].push(v)
})
// 为每个主要版本选择最新的次要版本
const upgradePath = []
Object.keys(versionsByMajor)
.sort((a, b) => Number(a) - Number(b))
.forEach((major) => {
const versions = versionsByMajor[major]
// 添加该主要版本的最新版本
upgradePath.push(versions[versions.length - 1])
})
// 确保包含目标版本
if (upgradePath.length === 0 || upgradePath[upgradePath.length - 1] !== targetVersion) {
upgradePath.push(targetVersion)
}
return upgradePath
}
// 主函数
async function main() {
try {
const currentVersionFull = getCurrentElectronVersion()
const currentVersion = currentVersionFull.replace(/[^0-9.]/g, '')
console.log(`当前 Electron 版本: ${currentVersionFull} (${currentVersion})`)
// 获取可用的 Electron 版本
console.log('正在获取可用的 Electron 版本...')
const allVersions = await getAvailableElectronVersions()
// 获取最新版本
const latestVersion = allVersions[allVersions.length - 1]
console.log(`最新的 Electron 版本: ${latestVersion}`)
// 确定目标版本
let targetVersion = process.argv[2] || latestVersion
if (compareVersions(targetVersion, currentVersion) <= 0) {
console.log(`目标版本 ${targetVersion} 不高于当前版本 ${currentVersion},无需更新`)
rl.close()
return
}
// 获取升级路径
const upgradePath = getUpgradePath(currentVersion, targetVersion, allVersions)
console.log(`\n${currentVersion}${targetVersion} 的推荐升级路径:`)
upgradePath.forEach((v, i) => {
console.log(`${i + 1}. ${v}`)
})
rl.question('\n是否按照推荐路径逐步升级(y/n) ', async (answer) => {
if (answer.toLowerCase() !== 'y') {
console.log('操作已取消')
rl.close()
return
}
// 保存原始版本,以便在失败时恢复
const originalVersion = currentVersionFull
// 逐步升级
for (let i = 0; i < upgradePath.length; i++) {
const version = upgradePath[i]
console.log(`\n===== 正在升级到 Electron ${version} (${i + 1}/${upgradePath.length}) =====`)
updateElectronVersion(version)
if (!installDependencies()) {
console.log(`Electron ${version} 安装依赖失败`)
const restoreAnswer = await new Promise((resolve) => {
rl.question('是否恢复到原始版本?(y/n) ', resolve)
})
if (restoreAnswer.toLowerCase() === 'y') {
console.log(`正在恢复到原始版本 ${originalVersion}`)
updateElectronVersion(originalVersion)
installDependencies()
}
rl.close()
return
}
const testAnswer = await new Promise((resolve) => {
rl.question(`依赖安装成功,是否测试 Electron ${version}(y/n) `, resolve)
})
if (testAnswer.toLowerCase() === 'y') {
if (!testApp()) {
console.log(`Electron ${version} 测试失败`)
const restoreAnswer = await new Promise((resolve) => {
rl.question('是否恢复到原始版本?(y/n) ', resolve)
})
if (restoreAnswer.toLowerCase() === 'y') {
console.log(`正在恢复到原始版本 ${originalVersion}`)
updateElectronVersion(originalVersion)
installDependencies()
}
rl.close()
return
}
console.log(`Electron ${version} 测试成功!`)
}
if (i < upgradePath.length - 1) {
const continueAnswer = await new Promise((resolve) => {
rl.question('是否继续升级到下一个版本?(y/n) ', resolve)
})
if (continueAnswer.toLowerCase() !== 'y') {
console.log(`升级停止在 Electron ${version}`)
rl.close()
return
}
}
}
console.log(`\n成功升级到 Electron ${targetVersion}`)
rl.close()
})
} catch (error) {
console.error('发生错误:', error)
rl.close()
}
}
// 运行主函数
main()

View File

@ -7,7 +7,7 @@ import { isMac, isWin } from '@main/constant'
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
import { IpcChannel } from '@shared/IpcChannel'
import { MCPServer, Shortcut, ThemeMode } from '@types' // Import MCPServer here
import { BrowserWindow, ipcMain, session, shell } from 'electron'
import { BrowserWindow, ipcMain, session, shell, webContents } from 'electron'
import log from 'electron-log'
import { titleBarOverlayDark, titleBarOverlayLight } from './config'
@ -193,6 +193,44 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
}
})
// 销毁webContents
ipcMain.handle('browser:destroy-webcontents', async (_, webContentsId: number) => {
try {
// 尝试通过ID获取webContents
const allWebContents = webContents.getAllWebContents()
const targetWebContents = allWebContents.find((wc) => wc.id === webContentsId)
if (targetWebContents) {
// 如果找到了webContents尝试销毁它
if (!targetWebContents.isDestroyed()) {
// 先停止加载
targetWebContents.stop()
// 加载空白页面
targetWebContents.loadURL('about:blank')
// 等待一小段时间,让空白页面加载完成
await new Promise((resolve) => setTimeout(resolve, 100))
// 销毁webContents - 使用close方法
targetWebContents.close() // WebContents没有destroy方法但有close方法
log.info(`Successfully destroyed webContents with ID: ${webContentsId}`)
return { success: true }
} else {
log.info(`WebContents with ID ${webContentsId} is already destroyed`)
return { success: true, alreadyDestroyed: true }
}
} else {
log.warn(`WebContents with ID ${webContentsId} not found`)
return { success: false, error: 'WebContents not found' }
}
} catch (error: any) {
log.error(`Failed to destroy webContents with ID ${webContentsId}:`, error)
return { success: false, error: error.message }
}
})
// check for update
ipcMain.handle(IpcChannel.App_CheckForUpdate, async () => {
const update = await appUpdater.autoUpdater.checkForUpdates()

View File

@ -265,6 +265,10 @@ const api = {
getSupportedLanguages: () => ipcRenderer.invoke(IpcChannel.CodeExecutor_GetSupportedLanguages),
executeJS: (code: string) => ipcRenderer.invoke(IpcChannel.CodeExecutor_ExecuteJS, code),
executePython: (code: string) => ipcRenderer.invoke(IpcChannel.CodeExecutor_ExecutePython, code)
},
browser: {
clearData: () => ipcRenderer.invoke('browser:clear-data'),
destroyWebContents: (webContentsId: number) => ipcRenderer.invoke('browser:destroy-webcontents', webContentsId)
}
}

View File

@ -82,16 +82,23 @@ const WebviewContainer = styled.div`
.webview-wrapper {
width: 100%;
height: 100%;
display: none;
position: absolute;
top: 0;
left: 0;
visibility: hidden;
z-index: 1;
&.active {
display: block;
visibility: visible;
z-index: 2;
}
}
& webview {
width: 100%;
height: 100%;
border: none;
outline: none;
}
`
@ -134,108 +141,300 @@ interface Tab {
const Browser = () => {
const { t } = useTranslation()
// 选项卡状态管理
const [tabs, setTabs] = useState<Tab[]>([
{
id: '1',
title: 'Google',
url: 'https://www.google.com',
isLoading: false,
canGoBack: false,
canGoForward: false
// 从本地存储加载选项卡状态
const loadTabsFromStorage = (): { tabs: Tab[]; activeTabId: string } => {
try {
const savedTabs = localStorage.getItem('browser_tabs')
const savedActiveTabId = localStorage.getItem('browser_active_tab_id')
if (savedTabs && savedActiveTabId) {
// 解析保存的选项卡
const parsedTabs = JSON.parse(savedTabs) as Tab[]
// 验证选项卡数据
const validTabs = parsedTabs.filter(
(tab) => tab && tab.id && tab.url && typeof tab.id === 'string' && typeof tab.url === 'string'
)
// 确保至少有一个选项卡
if (validTabs.length > 0) {
// 验证活动选项卡ID
const isActiveTabValid = validTabs.some((tab) => tab.id === savedActiveTabId)
const finalActiveTabId = isActiveTabValid ? savedActiveTabId : validTabs[0].id
console.log('Loaded tabs from storage:', validTabs.length, 'tabs, active tab:', finalActiveTabId)
return {
tabs: validTabs,
activeTabId: finalActiveTabId
}
}
}
} catch (error) {
console.error('Failed to load tabs from storage:', error)
}
])
const [activeTabId, setActiveTabId] = useState('1')
// 默认选项卡
const defaultTabs = [
{
id: '1',
title: 'Google',
url: 'https://www.google.com',
isLoading: false,
canGoBack: false,
canGoForward: false
}
]
console.log('Using default tabs')
return {
tabs: defaultTabs,
activeTabId: '1'
}
}
// 保存选项卡状态到本地存储
const saveTabsToStorage = (tabs: Tab[], activeTabId: string) => {
try {
// 确保只保存当前有效的选项卡
const validTabs = tabs.filter((tab) => tab && tab.id && tab.url)
// 确保activeTabId是有效的
const isActiveTabValid = validTabs.some((tab) => tab.id === activeTabId)
const finalActiveTabId = isActiveTabValid ? activeTabId : validTabs.length > 0 ? validTabs[0].id : ''
// 保存到localStorage
localStorage.setItem('browser_tabs', JSON.stringify(validTabs))
localStorage.setItem('browser_active_tab_id', finalActiveTabId)
console.log('Saved tabs to storage:', validTabs.length, 'tabs, active tab:', finalActiveTabId)
} catch (error) {
console.error('Failed to save tabs to storage:', error)
}
}
// 选项卡状态管理
const initialTabState = loadTabsFromStorage()
const [tabs, setTabs] = useState<Tab[]>(initialTabState.tabs)
const [activeTabId, setActiveTabId] = useState(initialTabState.activeTabId)
// 获取当前活动选项卡
const activeTab = tabs.find((tab) => tab.id === activeTabId) || tabs[0]
// 兼容旧代码的状态
const [url, setUrl] = useState(activeTab.url)
// 兼容旧代码的状态只使用setter
const [, setUrl] = useState(activeTab.url)
const [currentUrl, setCurrentUrl] = useState('')
const [canGoBack, setCanGoBack] = useState(false)
const [canGoForward, setCanGoForward] = useState(false)
const [isLoading, setIsLoading] = useState(false)
// 使用对象存储多个webview引用
// 使用对象存储多个webview引用 - 使用useRef确保在组件重新渲染时保持引用
const webviewRefs = useRef<Record<string, WebviewTag | null>>({})
// 使用useRef保存webview的会话状态
const webviewSessionsRef = useRef<Record<string, boolean>>({})
// 使用useRef保存事件监听器清理函数
const cleanupFunctionsRef = useRef<Record<string, () => void>>({})
// 获取当前活动的webview引用
const webviewRef = {
current: webviewRefs.current[activeTabId] || null
} as React.RefObject<WebviewTag>
useEffect(() => {
const webview = webviewRef.current
if (!webview) return
// 创建一个函数来设置webview的所有事件监听器
const setupWebviewListeners = (webview: WebviewTag, tabId: string) => {
console.log('Setting up event listeners for tab:', tabId)
// 处理加载开始事件
const handleDidStartLoading = () => {
setIsLoading(true)
// 只更新当前活动标签页的UI状态
if (tabId === activeTabId) {
setIsLoading(true)
}
// 更新选项卡状态
updateTabInfo(activeTabId, { isLoading: true })
updateTabInfo(tabId, { isLoading: true })
}
// 处理加载结束事件
const handleDidStopLoading = () => {
const currentURL = webview.getURL()
setIsLoading(false)
setCurrentUrl(currentURL)
// 只更新当前活动标签页的UI状态
if (tabId === activeTabId) {
setIsLoading(false)
setCurrentUrl(currentURL)
}
// 更新选项卡状态
updateTabInfo(activeTabId, {
updateTabInfo(tabId, {
isLoading: false,
url: currentURL,
title: webview.getTitle() || currentURL
title: webview.getTitle() || currentURL,
canGoBack: webview.canGoBack(),
canGoForward: webview.canGoForward()
})
}
// 处理导航事件
const handleDidNavigate = (e: any) => {
const canGoBackStatus = webview.canGoBack()
const canGoForwardStatus = webview.canGoForward()
setCurrentUrl(e.url)
setCanGoBack(canGoBackStatus)
setCanGoForward(canGoForwardStatus)
// 只更新当前活动标签页的UI状态
if (tabId === activeTabId) {
setCurrentUrl(e.url)
setCanGoBack(canGoBackStatus)
setCanGoForward(canGoForwardStatus)
}
// 更新选项卡状态
updateTabInfo(activeTabId, {
updateTabInfo(tabId, {
url: e.url,
canGoBack: canGoBackStatus,
canGoForward: canGoForwardStatus
})
}
// 处理页内导航事件
const handleDidNavigateInPage = (e: any) => {
const canGoBackStatus = webview.canGoBack()
const canGoForwardStatus = webview.canGoForward()
setCurrentUrl(e.url)
setCanGoBack(canGoBackStatus)
setCanGoForward(canGoForwardStatus)
// 只更新当前活动标签页的UI状态
if (tabId === activeTabId) {
setCurrentUrl(e.url)
setCanGoBack(canGoBackStatus)
setCanGoForward(canGoForwardStatus)
}
// 更新选项卡状态
updateTabInfo(activeTabId, {
updateTabInfo(tabId, {
url: e.url,
canGoBack: canGoBackStatus,
canGoForward: canGoForwardStatus
})
}
// 处理页面标题变化
// 处理页面标题更新事件
const handlePageTitleUpdated = (e: any) => {
// 更新选项卡标题
updateTabInfo(activeTabId, { title: e.title })
updateTabInfo(tabId, { title: e.title })
}
// 处理网站图标更新
// 处理网站图标更新事件
const handlePageFaviconUpdated = (e: any) => {
// 更新选项卡图标
updateTabInfo(activeTabId, { favicon: e.favicons[0] })
updateTabInfo(tabId, { favicon: e.favicons[0] })
}
// 检测Cloudflare验证码
// 处理DOM就绪事件
const handleDomReady = () => {
const captchaNotice = t('browser.captcha_notice')
// 注入链接点击拦截脚本
webview.executeJavaScript(`
(function() {
// 已经注入过脚本,不再重复注入
if (window.__linkInterceptorInjected) return;
window.__linkInterceptorInjected = true;
// 创建一个全局函数,用于在控制台中调用以打开新标签页
window.__openInNewTab = function(url, title) {
console.log('OPEN_NEW_TAB:' + JSON.stringify({url: url, title: title || url}));
};
// 拦截所有链接点击
document.addEventListener('click', function(e) {
// 查找被点击的链接元素
let target = e.target;
while (target && target.tagName !== 'A') {
target = target.parentElement;
if (!target) return; // 不是链接,直接返回
}
// 找到了链接元素
if (target.tagName === 'A' && target.href) {
// 检查是否应该在新标签页中打开
const inNewTab = e.ctrlKey || e.metaKey || target.target === '_blank';
// 阻止默认行为
e.preventDefault();
e.stopPropagation();
// 使用一个特殊的数据属性来标记这个链接
const linkData = {
url: target.href,
title: target.textContent || target.title || target.href,
inNewTab: inNewTab
};
// 将数据转换为字符串并存储在自定义属性中
document.body.setAttribute('data-last-clicked-link', JSON.stringify(linkData));
// 触发一个自定义事件
const event = new CustomEvent('link-clicked', { detail: linkData });
document.dispatchEvent(event);
// 使用控制台消息通知Electron
console.log('LINK_CLICKED:' + JSON.stringify(linkData));
if (!inNewTab) {
// 在当前标签页中打开链接
window.location.href = target.href;
}
return false;
}
}, true);
// 打印一条消息,确认链接拦截脚本已经注入
console.log('Link interceptor script injected successfully');
// 每5秒测试一次链接拦截功能
setInterval(function() {
console.log('Testing link interceptor...');
// 尝试调用全局函数
if (window.__openInNewTab) {
console.log('Link interceptor is working!');
// 创建一个测试链接
const testLink = document.createElement('a');
testLink.href = 'https://www.example.com';
testLink.textContent = 'Test Link';
testLink.target = '_blank'; // 在新标签页中打开
// 添加到DOM中
if (!document.getElementById('test-link-container')) {
const container = document.createElement('div');
container.id = 'test-link-container';
container.style.position = 'fixed';
container.style.top = '10px';
container.style.right = '10px';
container.style.zIndex = '9999';
container.style.background = 'white';
container.style.padding = '10px';
container.style.border = '1px solid black';
container.appendChild(testLink);
document.body.appendChild(container);
}
// 模拟点击事件
console.log('LINK_CLICKED:' + JSON.stringify({
url: 'https://www.example.com',
title: 'Test Link',
inNewTab: true
}));
} else {
console.log('Link interceptor is NOT working!');
}
}, 5000);
})();
`)
// 注入浏览器模拟脚本
webview.executeJavaScript(`
try {
@ -431,6 +630,62 @@ const Browser = () => {
webview.executeJavaScript(finalScript)
}
// 处理新窗口打开请求
const handleNewWindow = (e: any) => {
e.preventDefault() // 阻止默认行为
// 始终在新标签页中打开
openUrlInTab(e.url, true, e.frameName || 'New Tab')
}
// 处理将要导航的事件
const handleWillNavigate = (e: any) => {
// 更新当前标签页的URL
updateTabInfo(tabId, { url: e.url })
}
// 处理控制台消息事件 - 用于链接点击拦截
const handleConsoleMessage = (event: any) => {
// 打印所有控制台消息,便于调试
console.log(`[Tab ${tabId}] Console message:`, event.message)
// 处理新的链接点击消息
if (event.message && event.message.startsWith('LINK_CLICKED:')) {
try {
const dataStr = event.message.replace('LINK_CLICKED:', '')
const data = JSON.parse(dataStr)
console.log(`[Tab ${tabId}] Link clicked:`, data)
if (data.url && data.inNewTab) {
// 在新标签页中打开链接
console.log(`[Tab ${tabId}] Opening link in new tab:`, data.url)
openUrlInTab(data.url, true, data.title || data.url)
}
} catch (error) {
console.error('Failed to parse link data:', error)
}
}
// 保留对旧消息格式的支持
else if (event.message && event.message.startsWith('OPEN_NEW_TAB:')) {
try {
const dataStr = event.message.replace('OPEN_NEW_TAB:', '')
const data = JSON.parse(dataStr)
console.log(`[Tab ${tabId}] Opening link in new tab (legacy format):`, data)
if (data.url) {
// 在新标签页中打开链接
openUrlInTab(data.url, true, data.title || data.url)
}
} catch (error) {
console.error('Failed to parse link data:', error)
}
}
}
// 添加所有事件监听器
webview.addEventListener('did-start-loading', handleDidStartLoading)
webview.addEventListener('did-stop-loading', handleDidStopLoading)
webview.addEventListener('did-navigate', handleDidNavigate)
@ -438,11 +693,13 @@ const Browser = () => {
webview.addEventListener('dom-ready', handleDomReady)
webview.addEventListener('page-title-updated', handlePageTitleUpdated)
webview.addEventListener('page-favicon-updated', handlePageFaviconUpdated)
webview.addEventListener('new-window', handleNewWindow)
webview.addEventListener('will-navigate', handleWillNavigate)
webview.addEventListener('console-message', handleConsoleMessage)
// 初始加载URL
webview.src = url
// 返回清理函数
return () => {
console.log('Cleaning up event listeners for tab:', tabId)
webview.removeEventListener('did-start-loading', handleDidStartLoading)
webview.removeEventListener('did-stop-loading', handleDidStopLoading)
webview.removeEventListener('did-navigate', handleDidNavigate)
@ -450,8 +707,73 @@ const Browser = () => {
webview.removeEventListener('dom-ready', handleDomReady)
webview.removeEventListener('page-title-updated', handlePageTitleUpdated)
webview.removeEventListener('page-favicon-updated', handlePageFaviconUpdated)
webview.removeEventListener('new-window', handleNewWindow)
webview.removeEventListener('will-navigate', handleWillNavigate)
webview.removeEventListener('console-message', handleConsoleMessage)
}
}, [url, t, activeTabId])
}
// 通用的打开URL函数
const openUrlInTab = (url: string, inNewTab: boolean = false, title: string = 'New Tab') => {
if (inNewTab) {
// 在新标签页中打开链接
const newTabId = `tab-${Date.now()}`
const newTab: Tab = {
id: newTabId,
title: title,
url: url,
isLoading: true,
canGoBack: false,
canGoForward: false
}
// 创建新的选项卡数组,确保不修改原数组
const newTabs = [...tabs, newTab]
// 更新状态
setTabs(newTabs)
setActiveTabId(newTabId)
// 保存到本地存储
saveTabsToStorage(newTabs, newTabId)
console.log('Opened URL in new tab:', url, 'tab ID:', newTabId)
} else {
// 在当前标签页中打开链接
setUrl(url)
// 更新当前选项卡的URL
updateTabInfo(activeTabId, { url: url })
}
}
// 当activeTabId变化时更新UI状态
useEffect(() => {
// 获取当前活动的webview
const webview = webviewRefs.current[activeTabId]
if (!webview) return
// 从webview获取最新状态
try {
const currentURL = webview.getURL()
if (currentURL && currentURL !== 'about:blank') {
setCurrentUrl(currentURL)
} else {
// 如果没有有效URL使用存储的URL
const tab = tabs.find((tab) => tab.id === activeTabId)
if (tab) {
setCurrentUrl(tab.url)
}
}
// 更新导航状态
setCanGoBack(webview.canGoBack())
setCanGoForward(webview.canGoForward())
setIsLoading(webview.isLoading())
} catch (error) {
console.error('Error updating UI state:', error)
}
}, [activeTabId, tabs])
const handleUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCurrentUrl(e.target.value)
@ -538,60 +860,192 @@ const Browser = () => {
}
// 选项卡管理功能
const handleAddTab = () => {
const handleAddTab = (url: string = 'https://www.google.com', title: string = 'New Tab') => {
const newTabId = `tab-${Date.now()}`
const newTab: Tab = {
id: newTabId,
title: 'New Tab',
url: 'https://www.google.com',
title: title,
url: url,
isLoading: false,
canGoBack: false,
canGoForward: false
}
setTabs([...tabs, newTab])
const newTabs = [...tabs, newTab]
setTabs(newTabs)
setActiveTabId(newTabId)
setUrl('https://www.google.com')
setUrl(url)
// 保存到本地存储
saveTabsToStorage(newTabs, newTabId)
return newTabId
}
const handleCloseTab = (tabId: string, e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation() // 防止触发选项卡切换
console.log('Closing tab:', tabId)
if (tabs.length === 1) {
// 如果只有一个选项卡,创建一个新的空白选项卡
handleAddTab()
return // 已经在handleAddTab中保存了状态这里直接返回
}
// 如果关闭的是当前活动选项卡,切换到前一个选项卡
// 计算新的活动选项卡ID
let newActiveTabId = activeTabId
if (tabId === activeTabId) {
const currentIndex = tabs.findIndex((tab) => tab.id === tabId)
const newActiveIndex = currentIndex === 0 ? 1 : currentIndex - 1
setActiveTabId(tabs[newActiveIndex].id)
newActiveTabId = tabs[newActiveIndex].id
setActiveTabId(newActiveTabId)
}
// 从选项卡列表中移除
setTabs(tabs.filter((tab) => tab.id !== tabId))
const newTabs = tabs.filter((tab) => tab.id !== tabId)
setTabs(newTabs)
// 清理不再使用的webview引用和会话状态
if (webviewRefs.current[tabId]) {
// 停止加载并清理webview
try {
const webview = webviewRefs.current[tabId]
if (webview) {
// 停止加载
webview.stop()
// 尝试获取webContentsId
try {
const webContentsId = webview.getWebContentsId()
if (webContentsId && window.api && window.api.ipcRenderer) {
// 通过IPC请求主进程销毁webContents
window.api.ipcRenderer
.invoke('browser:destroy-webcontents', webContentsId)
.then(() => {
console.log('Successfully requested destruction of webContents for tab:', tabId)
})
.catch((error) => {
console.error('Error requesting destruction of webContents:', error)
})
}
} catch (e) {
console.error('Error getting webContentsId:', e)
}
// 加载空白页面,释放资源
webview.src = 'about:blank'
// 使用保存的清理函数移除事件监听器
if (cleanupFunctionsRef.current[tabId]) {
console.log('Calling cleanup function for tab:', tabId)
cleanupFunctionsRef.current[tabId]()
delete cleanupFunctionsRef.current[tabId]
}
}
} catch (error) {
console.error('Error cleaning up webview:', error)
}
// 删除引用
delete webviewRefs.current[tabId]
console.log('Removed webview reference for tab:', tabId)
}
// 删除会话状态
delete webviewSessionsRef.current[tabId]
console.log('Removed session state for tab:', tabId)
// 保存到本地存储 - 确保不包含已关闭的选项卡
saveTabsToStorage(newTabs, newActiveTabId)
console.log('Tab closed, remaining tabs:', newTabs.length)
}
const handleTabChange = (newActiveTabId: string) => {
console.log('Switching to tab:', newActiveTabId)
// 更新活动标签页ID
setActiveTabId(newActiveTabId)
// 更新URL和其他状态
const newActiveTab = tabs.find((tab) => tab.id === newActiveTabId)
if (newActiveTab) {
setUrl(newActiveTab.url)
setCurrentUrl(newActiveTab.url)
setCanGoBack(newActiveTab.canGoBack)
setCanGoForward(newActiveTab.canGoForward)
setIsLoading(newActiveTab.isLoading)
// 获取新活动的webview
const newWebview = webviewRefs.current[newActiveTabId]
// 如果webview存在从webview获取最新状态
if (newWebview) {
try {
// 获取当前URL
const currentURL = newWebview.getURL()
if (currentURL && currentURL !== 'about:blank') {
// 使用webview的实际URL而不是存储的URL
setUrl(currentURL)
setCurrentUrl(currentURL)
// 更新选项卡信息
updateTabInfo(newActiveTabId, { url: currentURL })
} else {
// 如果没有有效URL使用存储的URL
setUrl(newActiveTab.url)
setCurrentUrl(newActiveTab.url)
}
// 更新导航状态
setCanGoBack(newWebview.canGoBack())
setCanGoForward(newWebview.canGoForward())
setIsLoading(newWebview.isLoading())
} catch (error) {
console.error('Error getting webview state:', error)
// 出错时使用存储的状态
setUrl(newActiveTab.url)
setCurrentUrl(newActiveTab.url)
setCanGoBack(newActiveTab.canGoBack)
setCanGoForward(newActiveTab.canGoForward)
setIsLoading(newActiveTab.isLoading)
}
} else {
// 如果webview不存在使用存储的状态
setUrl(newActiveTab.url)
setCurrentUrl(newActiveTab.url)
setCanGoBack(newActiveTab.canGoBack)
setCanGoForward(newActiveTab.canGoForward)
setIsLoading(newActiveTab.isLoading)
}
// 保存到本地存储
saveTabsToStorage(tabs, newActiveTabId)
}
}
// 更新选项卡信息
const updateTabInfo = (tabId: string, updates: Partial<Tab>) => {
setTabs((prevTabs) => prevTabs.map((tab) => (tab.id === tabId ? { ...tab, ...updates } : tab)))
setTabs((prevTabs) => {
const newTabs = prevTabs.map((tab) => (tab.id === tabId ? { ...tab, ...updates } : tab))
// 保存到本地存储
saveTabsToStorage(newTabs, activeTabId)
return newTabs
})
}
// 在组件挂载和卸载时处理webview会话
useEffect(() => {
// 组件挂载时确保webviewSessionsRef与tabs同步
tabs.forEach((tab) => {
if (!webviewSessionsRef.current[tab.id]) {
webviewSessionsRef.current[tab.id] = false
}
})
// 组件卸载时保存状态
return () => {
saveTabsToStorage(tabs, activeTabId)
}
}, [tabs, activeTabId])
// 检测Google登录页面
useEffect(() => {
// 检测是否是Google登录页面
@ -619,6 +1073,7 @@ const Browser = () => {
} else {
setShowGoogleLoginTip(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentUrl, activeTabId])
return (
@ -667,7 +1122,7 @@ const Browser = () => {
<Button
className="add-tab-button"
icon={<PlusOutlined />}
onClick={handleAddTab}
onClick={() => handleAddTab()}
title={t('browser.new_tab')}
/>
)
@ -714,11 +1169,52 @@ const Browser = () => {
<webview
ref={(el: any) => {
if (el) {
webviewRefs.current[tab.id] = el as WebviewTag
// 检查这个webview是否已经有引用
const existingWebview = webviewRefs.current[tab.id]
// 如果是新创建的选项卡加载初始URL
if (!el.src) {
// 只有在webview不存在或者是新创建的选项卡时才设置引用和加载URL
if (!existingWebview) {
console.log('Creating new webview for tab:', tab.id, 'URL:', tab.url)
// 保存webview引用
webviewRefs.current[tab.id] = el as WebviewTag
// 标记为已初始化
webviewSessionsRef.current[tab.id] = true
// 设置初始URL
el.src = tab.url
// 设置事件监听器并保存清理函数
const cleanup = setupWebviewListeners(el as WebviewTag, tab.id)
cleanupFunctionsRef.current[tab.id] = cleanup
} else if (existingWebview !== el) {
// 如果引用变了React重新创建了元素保留原来的状态
console.log('Webview reference changed for tab:', tab.id, 'preserving state')
// 先清理旧的事件监听器
if (cleanupFunctionsRef.current[tab.id]) {
cleanupFunctionsRef.current[tab.id]()
}
// 更新webview引用
webviewRefs.current[tab.id] = el as WebviewTag
// 不要重新设置src这会导致页面刷新
// 只有在URL明确改变时才设置src
if (existingWebview.getURL() !== tab.url && tab.url !== '') {
el.src = tab.url
}
// 重新设置事件监听器
const cleanup = setupWebviewListeners(el as WebviewTag, tab.id)
cleanupFunctionsRef.current[tab.id] = cleanup
}
} else {
// DOM元素被移除清理事件监听器
if (cleanupFunctionsRef.current[tab.id]) {
cleanupFunctionsRef.current[tab.id]()
delete cleanupFunctionsRef.current[tab.id]
}
}
}}
@ -726,7 +1222,7 @@ const Browser = () => {
partition="persist:browser"
useragent={userAgent}
preload=""
webpreferences="contextIsolation=no, javascript=yes, webgl=yes, webaudio=yes, allowRunningInsecureContent=yes"
webpreferences="contextIsolation=no, javascript=yes, webgl=yes, webaudio=yes, allowRunningInsecureContent=yes, nodeIntegration=yes, enableRemoteModule=yes"
disablewebsecurity={DISABLE_SECURITY}
plugins={true}
/>

View File

@ -1,8 +0,0 @@
// 不再自动清除回调函数,允许持续接收语音识别结果
// setTimeout(() => {
// // 发送重置命令,确保浏览器不会继续发送结果
// ASRService.cancelRecording()
//
// // 清除ASRService中的回调函数防止后续结果被处理
// ASRService.resultCallback = null
// }, 2000) // 2秒后强制取消作为安全措施

View File

@ -6166,12 +6166,12 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:^20.9.0":
version: 20.17.24
resolution: "@types/node@npm:20.17.24"
"@types/node@npm:^22.7.7":
version: 22.15.2
resolution: "@types/node@npm:22.15.2"
dependencies:
undici-types: "npm:~6.19.2"
checksum: 10c0/2a39ce4c4cd4588a05b2a485cc0a1407cbea608dd1ab03e36add59d61712718d95c84b492ca5190753f0be2bce748aeeb0f2a1412e712775462befe3820b3ff9
undici-types: "npm:~6.21.0"
checksum: 10c0/39da31d5fc63b14fabd217bb8a921c4a7fc3d99f233440209f9fc2d5d736e8773f7efc65223e2fd0e8db8390b0baab9c0cd2e951c2ece8b237f07313ab3cf295
languageName: node
linkType: hard
@ -6847,7 +6847,7 @@ __metadata:
docx: "npm:^9.0.2"
dotenv-cli: "npm:^7.4.2"
edge-tts-node: "npm:^1.5.7"
electron: "npm:31.7.6"
electron: "npm:35.2.0"
electron-builder: "npm:26.0.13"
electron-chrome-extensions: "npm:^4.7.0"
electron-devtools-installer: "npm:^3.2.0"
@ -6941,6 +6941,7 @@ __metadata:
vite: "npm:^5.0.12"
vitest: "npm:^3.1.1"
webdav: "npm:^5.8.0"
zipfile: "npm:^0.5.12"
zipread: "npm:^1.3.3"
zod: "npm:^3.24.2"
languageName: unknown
@ -10595,16 +10596,16 @@ __metadata:
languageName: node
linkType: hard
"electron@npm:31.7.6":
version: 31.7.6
resolution: "electron@npm:31.7.6"
"electron@npm:35.2.0":
version: 35.2.0
resolution: "electron@npm:35.2.0"
dependencies:
"@electron/get": "npm:^2.0.0"
"@types/node": "npm:^20.9.0"
"@types/node": "npm:^22.7.7"
extract-zip: "npm:^2.0.1"
bin:
electron: cli.js
checksum: 10c0/4b7ee31894eb3606d6a6047cd7af22d3b82331dacb96869c483bfd32ffc8581ef638ccfa027938d83d5242e7bf8b7856cad29a09fb80942a25ef3de0c888fb48
checksum: 10c0/180c00ec8418ef79889c41b690fc206c51c12267420a85bee5ff7b0ca279a988eb63c66c979b6d0e241c2a821d9d2980cd8d0104063feba58a745333e0997e7f
languageName: node
linkType: hard
@ -22947,13 +22948,6 @@ __metadata:
languageName: node
linkType: hard
"undici-types@npm:~6.19.2":
version: 6.19.8
resolution: "undici-types@npm:6.19.8"
checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344
languageName: node
linkType: hard
"undici-types@npm:~6.20.0":
version: 6.20.0
resolution: "undici-types@npm:6.20.0"
@ -22961,6 +22955,13 @@ __metadata:
languageName: node
linkType: hard
"undici-types@npm:~6.21.0":
version: 6.21.0
resolution: "undici-types@npm:6.21.0"
checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04
languageName: node
linkType: hard
"undici@npm:>=6, undici@npm:^7.4.0":
version: 7.5.0
resolution: "undici@npm:7.5.0"
@ -24164,7 +24165,7 @@ __metadata:
languageName: node
linkType: hard
"zipfile@npm:^0.5.11":
"zipfile@npm:^0.5.11, zipfile@npm:^0.5.12":
version: 0.5.12
resolution: "zipfile@npm:0.5.12"
dependencies:

View File

@ -1 +0,0 @@
我爱世界