From 8dd33a9649c5d72b71517e92288324fd6b0a8f6f Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 11:13:50 +0800
Subject: [PATCH 01/22] =?UTF-8?q?=E5=85=B1=E5=BB=BA=E8=AE=A1=E5=88=92?=
=?UTF-8?q?=E5=85=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
icons/joint_build.png | Bin 0 -> 1173 bytes
package-lock.json | 91 ++++++++------------
src/renderer/app.tsx | 2 +
src/renderer/pages/hello/index.scss | 34 ++++++++
src/renderer/pages/hello/index.tsx | 8 ++
src/renderer/pages/welcome/WelcomeModal.css | 75 ++++++++++++++++
src/renderer/pages/welcome/WelcomeModal.tsx | 59 +++++++++++++
7 files changed, 215 insertions(+), 54 deletions(-)
create mode 100644 icons/joint_build.png
create mode 100644 src/renderer/pages/welcome/WelcomeModal.css
create mode 100644 src/renderer/pages/welcome/WelcomeModal.tsx
diff --git a/icons/joint_build.png b/icons/joint_build.png
new file mode 100644
index 0000000000000000000000000000000000000000..967f3a0b487522fb53d063d7c1c72faa6adf8c51
GIT binary patch
literal 1173
zcmV;G1Zw+
u5b59P9_yM#fSc5ne^wyb0aBXJRc8^X;GYDnk3;?r
z$?2D
z@5C$u^`-^>_W+g;D-w31o{1Q?)Un#SWEKG~^mDPujn*zUXX2|e7SW-LZWaM}fc;G&
z$!DpYi`=FXejEc3iL5`35NPfpihN#7A(5NxLayhsv39Pt0gpthcjY4lxB%b|p2Ny;
zGse$bxhJnXMrY5w%a{=Y*)4$2zMIR8unR(3V@&N)DTYz0E8wF#;eEG(ZyXJbxkDj4rHUR*v
zx~Ft~I@}>H|0;o=Iaex12>f%UVOjY3d&{zX5F&RD5Q*x|R1bZ9lH616W|1kgN1{)as#Kdn?7T?KDEb>RR=z)k;=($o;i!tewj3Lh8{)7&-%tddnc9OdiEQ
zH?xo$#oc7(jWwqYq|4c9_3nIR2Pn5v)gVBAR%t(ES!H7hS82expt0JU&_!pfcju!7
z7yyJ}%GSBVaNSB<@uGX&V+$3EP-fcgp_Lo@NMcLmVrzF&4*}aw
zIq@YXL{ctHI)rPXao1?-G}kTeVgnj@B-pY@^rzwXi#-G=yFYVTTvY9HV!UT}=mHQ>
za{#wMh%76AH_uBC0kPWQT+x4N0&Ht{fZJN-i-MH`@S@gA*Q=%;0$ip=5fP^6&7ODX
z)+)b3vn(ytiddo;+socx4}mtHaxvZogpdcF=dYD&V~*zL0!bIZfO9Ffx^x{OkQFkl
z-g4zp;eOsr^20ig5E%94Jy1Yzot?dTm%bhXVsTHzxF}t&eVJQ~oE@Q>?X34+xrYFs
zv{Lsbrv|RH<;(ne=14.17"
- }
- },
"node_modules/@svgr/hast-util-to-babel-ast": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
@@ -5431,22 +5420,6 @@
}
}
},
- "node_modules/@svgr/plugin-svgo/node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
- "license": "Apache-2.0",
- "optional": true,
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
"node_modules/@svgr/webpack": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/@svgr/webpack/-/webpack-8.1.0.tgz",
@@ -5626,6 +5599,7 @@
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -5787,6 +5761,7 @@
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -5828,6 +5803,7 @@
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -6020,6 +5996,7 @@
"integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
"dev": true,
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "5.62.0",
"@typescript-eslint/types": "5.62.0",
@@ -6431,6 +6408,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6520,6 +6498,7 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -6627,7 +6606,6 @@
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -6641,6 +6619,7 @@
"integrity": "sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ant-design/colors": "^7.2.1",
"@ant-design/cssinjs": "^1.23.0",
@@ -7249,6 +7228,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -8514,7 +8494,8 @@
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/debug": {
"version": "4.4.3",
@@ -8539,7 +8520,6 @@
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9692,17 +9672,6 @@
"node": ">= 0.8"
}
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmmirror.com/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -9996,6 +9965,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -10052,6 +10022,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -10564,6 +10535,7 @@
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -14087,7 +14059,6 @@
"resolved": "https://registry.npmmirror.com/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT",
- "peer": true,
"bin": {
"mustache": "bin/mustache"
}
@@ -15154,6 +15125,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -15289,6 +15261,7 @@
"integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -16214,6 +16187,7 @@
"resolved": "https://registry.npmmirror.com/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -16223,6 +16197,7 @@
"resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -18435,6 +18410,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -18903,6 +18879,7 @@
"integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19207,6 +19184,7 @@
"integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -19280,6 +19258,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -19397,6 +19376,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -19489,6 +19469,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -19780,6 +19761,7 @@
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"devOptional": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -19812,7 +19794,8 @@
"integrity": "sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ==",
"deprecated": "This package is now deprecated. Move to @xterm/xterm instead.",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/xterm-addon-fit": {
"version": "0.5.0",
@@ -19983,6 +19966,7 @@
"resolved": "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -19992,7 +19976,6 @@
"resolved": "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz",
"integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==",
"license": "ISC",
- "peer": true,
"peerDependencies": {
"zod": "^3.25 || ^4"
}
diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index c8247c4..4859dc1 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -15,10 +15,12 @@ import PdfConvert from './pages/pdf-convert';
import PdfConfig from './pages/pdf-config';
import LLMConfig from './pages/llm-api-config';
import VoiceRTCConfig from './pages/voice-rtc-config';
+import WelcomeModal from './pages/welcome/WelcomeModal';
export default function App() {
return (
+
} />
diff --git a/src/renderer/pages/hello/index.scss b/src/renderer/pages/hello/index.scss
index e3bc9f6..fffe4d2 100644
--- a/src/renderer/pages/hello/index.scss
+++ b/src/renderer/pages/hello/index.scss
@@ -667,6 +667,40 @@ body{
transform: translateY(-2px);
}
}
+
+ .joint-build-button {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ padding: 2px 8px;
+ gap: 4px;
+ width: auto;
+ min-width: 90px;
+ min-height: 22px;
+ background: #52c41a;
+ border: 1px solid #52c41a;
+ border-radius: 6px;
+ color: #fff;
+ font-family: 'Inter', sans-serif;
+ font-style: normal;
+ font-weight: 500;
+ font-size: clamp(10px, 2vw, 12px);
+ line-height: 16px;
+ cursor: pointer;
+
+ .joint-build-icon {
+ width: 16px;
+ height: 16px;
+ }
+
+ &:hover {
+ background: #73d13d;
+ border-color: #73d13d;
+ transform: translateY(-2px);
+ }
+ }
.dev-example-link-inline {
text-decoration: none;
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 1e4c27f..95e4379 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -12,6 +12,7 @@ import wslLogo from './wslLogo.png';
// 新增导入Frame 3和Frame 8图片
import frame3 from './Frame 3.png';
import frame8 from './Frame 8.png';
+import jointBuildIcon from '../../../../icons/joint_build.png';
import './index.scss';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { useTrainingServiceShortcut } from '../../containers/use-training-service-shortcut';
@@ -581,6 +582,13 @@ export default function Hello() {
版本号:{__NPM_PACKAGE_VERSION__} 源码版本:{__COMMIT_HASH__}
+
-
window.open('https://ai-learning-assistant.com/joint-build', '_blank')}
- >
-
- 共建计划
-
+
+
+
+ 共建计划
+
+
([
+ {
+ id: 'launcher',
+ name: 'AI学习助手启动器',
+ description: '磁盘占用 200MB',
+ diskUsage: '200MB',
+ recommendLevel: 3,
+ enabled: true,
+ },
+ {
+ id: 'training-package',
+ name: '学科培训安装包',
+ description: '磁盘占用 200MB',
+ diskUsage: '200MB',
+ recommendLevel: 3,
+ enabled: false,
+ },
+ {
+ id: 'voice-model',
+ name: '学科培训语音模型',
+ description: '磁盘占用 2.1GB',
+ diskUsage: '2.1GB',
+ recommendLevel: 2,
+ enabled: false,
+ },
+ ]);
+
+ // 从 localStorage 加载配置
+ useEffect(() => {
+ const savedMasterSwitch = localStorage.getItem(STORAGE_KEY_MASTER);
+ if (savedMasterSwitch !== null) {
+ setMasterSwitch(savedMasterSwitch === 'true');
+ }
+
+ const savedModules = localStorage.getItem(STORAGE_KEY_MODULES);
+ if (savedModules) {
+ try {
+ const parsed = JSON.parse(savedModules);
+ setModules((prev) =>
+ prev.map((mod) => {
+ const saved = parsed.find((s: ModuleItem) => s.id === mod.id);
+ return saved ? { ...mod, enabled: saved.enabled } : mod;
+ })
+ );
+ } catch (e) {
+ console.error('Failed to parse saved modules:', e);
+ }
+ }
+
+ const savedDiskPath = localStorage.getItem(STORAGE_KEY_DISK_PATH);
+ if (savedDiskPath) {
+ setDiskPath(savedDiskPath);
+ }
+ }, []);
+
+ // 保存配置到 localStorage
+ const saveConfig = (master: boolean, mods: ModuleItem[]) => {
+ localStorage.setItem(STORAGE_KEY_MASTER, String(master));
+ localStorage.setItem(
+ STORAGE_KEY_MODULES,
+ JSON.stringify(mods.map((m) => ({ id: m.id, enabled: m.enabled })))
+ );
+ };
+
+ const handleMasterSwitchChange = (checked: boolean) => {
+ setMasterSwitch(checked);
+ saveConfig(checked, modules);
+ if (checked) {
+ message.success('共建计划已开启');
+ } else {
+ message.info('共建计划已关闭');
+ }
+ };
+
+ const handleModuleToggle = (id: string, checked: boolean) => {
+ const newModules = modules.map((mod) =>
+ mod.id === id ? { ...mod, enabled: checked } : mod
+ );
+ setModules(newModules);
+ saveConfig(masterSwitch, newModules);
+ };
+
+ const handleChangePath = async () => {
+ // 这里可以调用 electron 的对话框选择文件夹
+ // 暂时用 prompt 模拟
+ const newPath = window.prompt('请输入新的磁盘路径:', diskPath);
+ if (newPath) {
+ setDiskPath(newPath);
+ localStorage.setItem(STORAGE_KEY_DISK_PATH, newPath);
+ message.success('路径已更新');
+ }
+ };
+
+ const renderRecommendLevel = (level: number) => {
+ const fires = [];
+ for (let i = 0; i < 3; i++) {
+ fires.push(
+
+ 🔥
+
+ );
+ }
+ return fires;
+ };
+
+ const diskUsedPercent = Math.round((diskUsed / diskTotal) * 100);
+
+ return (
+
+
+
+
+
+

+
+
共建计划
+
通过闲时分享少量带宽,不仅能加速您的下载,也能帮助其他学习者更快获取大模型与课程资源。
+
+
+
+
+
+
+
可用磁盘空间
+
+ 磁盘路径:{diskPath}
+ 更换路径
+
+
+
+
+
+
共建参与模块设置
+
管理参与哪些模块的共建计划,请根据您电脑的磁盘空余选择开启
+
+ {modules.map((mod) => (
+
+
+
+ {mod.name}
+
+ 推荐指数 {renderRecommendLevel(mod.recommendLevel)}
+
+
+
磁盘占用 {mod.diskUsage}
+
+
handleModuleToggle(mod.id, checked)}
+ disabled={!masterSwitch}
+ />
+
+ ))}
+
+
+
+ );
+}
From 6fc2e189b671c8d90b5223aeaf207c4e87f73e40 Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 11:24:19 +0800
Subject: [PATCH 03/22] =?UTF-8?q?=E5=85=B1=E5=BB=BA=E8=AE=A1=E5=88=92?=
=?UTF-8?q?=E6=80=BB=E5=BC=80=E5=85=B3=E7=8A=B6=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/renderer/pages/joint-build/index.tsx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/renderer/pages/joint-build/index.tsx b/src/renderer/pages/joint-build/index.tsx
index 5b178a4..0037119 100644
--- a/src/renderer/pages/joint-build/index.tsx
+++ b/src/renderer/pages/joint-build/index.tsx
@@ -17,6 +17,7 @@ interface ModuleItem {
const STORAGE_KEY_MASTER = 'joint_build_master_switch';
const STORAGE_KEY_MODULES = 'joint_build_modules';
const STORAGE_KEY_DISK_PATH = 'joint_build_disk_path';
+const STORAGE_KEY_WELCOME = 'ai_learning_assistant_welcome_shown'; // 欢迎弹窗用户选择
export default function JointBuild() {
const [masterSwitch, setMasterSwitch] = useState(false);
@@ -52,9 +53,16 @@ export default function JointBuild() {
// 从 localStorage 加载配置
useEffect(() => {
+ // 优先读取共建开关的设置,如果没有则读取欢迎弹窗的用户选择
const savedMasterSwitch = localStorage.getItem(STORAGE_KEY_MASTER);
if (savedMasterSwitch !== null) {
setMasterSwitch(savedMasterSwitch === 'true');
+ } else {
+ // 如果没有单独设置过共建开关,则读取欢迎弹窗的选择
+ const welcomeChoice = localStorage.getItem(STORAGE_KEY_WELCOME);
+ if (welcomeChoice === 'true') {
+ setMasterSwitch(true);
+ }
}
const savedModules = localStorage.getItem(STORAGE_KEY_MODULES);
From 80443b8ee337fcddf0737c63d3ec8563bbe12e48 Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 11:36:05 +0800
Subject: [PATCH 04/22] =?UTF-8?q?=E8=AF=BB=E5=8F=96=E7=A3=81=E7=9B=98?=
=?UTF-8?q?=E5=89=A9=E4=BD=99=E7=A9=BA=E9=97=B4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/index.ts | 2 +
src/main/joint-build/index.ts | 68 ++++++++++++++++++++++++
src/main/joint-build/type-info.ts | 11 ++++
src/main/preload.ts | 12 +++++
src/renderer/pages/joint-build/index.tsx | 56 ++++++++++++++-----
5 files changed, 135 insertions(+), 14 deletions(-)
create mode 100644 src/main/joint-build/index.ts
create mode 100644 src/main/joint-build/type-info.ts
diff --git a/src/main/index.ts b/src/main/index.ts
index 2e400b2..c64ef7c 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -13,6 +13,7 @@ import initPdfConvert from './pdf-convert';
import initTrainingService from './training-service';
import initLogService from './backup';
import initExternalUrl from './external-url';
+import { setupJointBuildHandlers } from './joint-build';
import path from 'node:path';
import { appPath, autoAdaptEncodingForWindows } from './exec';
import { logDeviceInfo } from './logger/log-device-info';
@@ -77,6 +78,7 @@ initExampleMain(ipcMain);
initPdfConvert(ipcMain);
initTrainingService(ipcMain);
initExternalUrl(ipcMain);
+setupJointBuildHandlers(ipcMain);
updateTemplate();
const createWindow = async () => {
diff --git a/src/main/joint-build/index.ts b/src/main/joint-build/index.ts
new file mode 100644
index 0000000..2a37d53
--- /dev/null
+++ b/src/main/joint-build/index.ts
@@ -0,0 +1,68 @@
+import { dialog, IpcMain } from 'electron';
+import { selectFolderHandle, getDiskInfoHandle, DiskInfo } from './type-info';
+import { exec } from 'child_process';
+import { promisify } from 'util';
+import path from 'path';
+import { ipcHandle } from '../ipc-util';
+
+const execPromise = promisify(exec);
+
+// 获取Windows磁盘信息
+async function getWindowsDiskInfo(diskPath: string): Promise {
+ try {
+ // 获取盘符,例如 "C:" 或 "D:"
+ const driveLetter = path.parse(diskPath).root.replace('\\', '');
+
+ // 使用 wmic 命令获取磁盘信息
+ const { stdout } = await execPromise(
+ `wmic logicaldisk where "DeviceID='${driveLetter}'" get Size,FreeSpace /format:csv`,
+ { encoding: 'utf8' }
+ );
+
+ const lines = stdout.trim().split('\n').filter(line => line.trim());
+ if (lines.length < 2) {
+ throw new Error('无法获取磁盘信息');
+ }
+
+ // CSV格式: Node,FreeSpace,Size
+ const values = lines[1].split(',');
+ const freeSpace = parseInt(values[1], 10) || 0;
+ const totalSize = parseInt(values[2], 10) || 0;
+
+ return {
+ total: totalSize,
+ free: freeSpace,
+ used: totalSize - freeSpace,
+ };
+ } catch (error) {
+ console.error('获取磁盘信息失败:', error);
+ // 返回默认值
+ return {
+ total: 500 * 1024 * 1024 * 1024, // 500GB
+ free: 200 * 1024 * 1024 * 1024, // 200GB
+ used: 300 * 1024 * 1024 * 1024, // 300GB
+ };
+ }
+}
+
+export function setupJointBuildHandlers(ipcMain: IpcMain): void {
+ // 处理选择文件夹
+ ipcHandle(ipcMain, selectFolderHandle, async () => {
+ const result = await dialog.showOpenDialog({
+ properties: ['openDirectory'],
+ defaultPath: 'C:\\', // Windows默认从C盘开始
+ title: '选择共建计划存储路径',
+ });
+
+ if (result.canceled || !result.filePaths || result.filePaths.length === 0) {
+ return null;
+ }
+
+ return result.filePaths[0];
+ });
+
+ // 处理获取磁盘信息
+ ipcHandle(ipcMain, getDiskInfoHandle, async (_event, diskPath: string) => {
+ return getWindowsDiskInfo(diskPath);
+ });
+}
diff --git a/src/main/joint-build/type-info.ts b/src/main/joint-build/type-info.ts
new file mode 100644
index 0000000..5530bd7
--- /dev/null
+++ b/src/main/joint-build/type-info.ts
@@ -0,0 +1,11 @@
+export type ActionName = 'select-folder' | 'get-disk-info';
+export type ServiceName = 'joint-build';
+
+export const selectFolderHandle = 'joint-build:select-folder';
+export const getDiskInfoHandle = 'joint-build:get-disk-info';
+
+export interface DiskInfo {
+ total: number; // bytes
+ free: number; // bytes
+ used: number; // bytes
+}
diff --git a/src/main/preload.ts b/src/main/preload.ts
index c4c20cf..5c87604 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -19,6 +19,11 @@ import {
removeTrainingServiceHandle,
startTrainingServiceHandle,
} from './training-service/type-info';
+import {
+ selectFolderHandle,
+ getDiskInfoHandle,
+ DiskInfo,
+} from './joint-build/type-info';
const electronHandler = {
ipcRenderer: {
@@ -108,6 +113,13 @@ const mainHandle = {
logsTrainingServiceHandle,
);
},
+ // 共建计划相关
+ selectJointBuildFolder: async (): Promise => {
+ return ipcInvoke(selectFolderHandle);
+ },
+ getJointBuildDiskInfo: async (diskPath: string): Promise => {
+ return ipcInvoke(getDiskInfoHandle, diskPath);
+ },
};
export type MainHandle = typeof mainHandle;
diff --git a/src/renderer/pages/joint-build/index.tsx b/src/renderer/pages/joint-build/index.tsx
index 0037119..0a9e092 100644
--- a/src/renderer/pages/joint-build/index.tsx
+++ b/src/renderer/pages/joint-build/index.tsx
@@ -1,6 +1,6 @@
import { Switch, Button, Progress, message } from 'antd';
import { NavLink } from 'react-router-dom';
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useCallback } from 'react';
import { LeftOutlined } from '@ant-design/icons';
import jointBuildIcon from '../../../../icons/joint_build.png';
import './index.scss';
@@ -19,11 +19,18 @@ const STORAGE_KEY_MODULES = 'joint_build_modules';
const STORAGE_KEY_DISK_PATH = 'joint_build_disk_path';
const STORAGE_KEY_WELCOME = 'ai_learning_assistant_welcome_shown'; // 欢迎弹窗用户选择
+// 字节转GB
+const bytesToGB = (bytes: number): number => {
+ return Math.round((bytes / (1024 * 1024 * 1024)) * 10) / 10;
+};
+
export default function JointBuild() {
const [masterSwitch, setMasterSwitch] = useState(false);
const [diskPath, setDiskPath] = useState('C:\\');
- const [diskUsed, setDiskUsed] = useState(120); // GB
- const [diskTotal, setDiskTotal] = useState(500); // GB
+ const [diskUsed, setDiskUsed] = useState(0);
+ const [diskTotal, setDiskTotal] = useState(0);
+ const [diskLoading, setDiskLoading] = useState(false);
+ // todo: 这些数据从哪获取?还是写死
const [modules, setModules] = useState([
{
id: 'launcher',
@@ -51,6 +58,20 @@ export default function JointBuild() {
},
]);
+ // 获取磁盘信息
+ const fetchDiskInfo = useCallback(async (path: string) => {
+ setDiskLoading(true);
+ try {
+ const diskInfo = await window.mainHandle.getJointBuildDiskInfo(path);
+ setDiskTotal(bytesToGB(diskInfo.total));
+ setDiskUsed(bytesToGB(diskInfo.used));
+ } catch (error) {
+ console.error('获取磁盘信息失败:', error);
+ } finally {
+ setDiskLoading(false);
+ }
+ }, []);
+
// 从 localStorage 加载配置
useEffect(() => {
// 优先读取共建开关的设置,如果没有则读取欢迎弹窗的用户选择
@@ -81,10 +102,12 @@ export default function JointBuild() {
}
const savedDiskPath = localStorage.getItem(STORAGE_KEY_DISK_PATH);
- if (savedDiskPath) {
- setDiskPath(savedDiskPath);
- }
- }, []);
+ const initialPath = savedDiskPath || 'C:\\';
+ setDiskPath(initialPath);
+
+ // 初始化时获取磁盘信息
+ fetchDiskInfo(initialPath);
+ }, [fetchDiskInfo]);
// 保存配置到 localStorage
const saveConfig = (master: boolean, mods: ModuleItem[]) => {
@@ -114,13 +137,18 @@ export default function JointBuild() {
};
const handleChangePath = async () => {
- // 这里可以调用 electron 的对话框选择文件夹
- // 暂时用 prompt 模拟
- const newPath = window.prompt('请输入新的磁盘路径:', diskPath);
- if (newPath) {
- setDiskPath(newPath);
- localStorage.setItem(STORAGE_KEY_DISK_PATH, newPath);
- message.success('路径已更新');
+ try {
+ const newPath = await window.mainHandle.selectJointBuildFolder();
+ if (newPath) {
+ setDiskPath(newPath);
+ localStorage.setItem(STORAGE_KEY_DISK_PATH, newPath);
+ message.success('路径已更新');
+ // 获取新路径的磁盘信息
+ fetchDiskInfo(newPath);
+ }
+ } catch (error) {
+ console.error('选择文件夹失败:', error);
+ message.error('选择文件夹失败');
}
};
From d49c61ffeba560a52939ba5d3b2fc78067d127bb Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 11:49:47 +0800
Subject: [PATCH 05/22] =?UTF-8?q?=E6=89=98=E7=9B=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/index.ts | 9 +-
src/main/joint-build/index.ts | 118 ++++++++++++++++++++++-
src/main/joint-build/type-info.ts | 3 +-
src/main/preload.ts | 4 +
src/renderer/pages/hello/index.tsx | 23 +++++
src/renderer/pages/joint-build/index.tsx | 22 ++++-
6 files changed, 171 insertions(+), 8 deletions(-)
diff --git a/src/main/index.ts b/src/main/index.ts
index c64ef7c..2b00f42 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -13,7 +13,7 @@ import initPdfConvert from './pdf-convert';
import initTrainingService from './training-service';
import initLogService from './backup';
import initExternalUrl from './external-url';
-import { setupJointBuildHandlers } from './joint-build';
+import { setupJointBuildHandlers, setupWindowCloseHandler, isTrayEnabled } from './joint-build';
import path from 'node:path';
import { appPath, autoAdaptEncodingForWindows } from './exec';
import { logDeviceInfo } from './logger/log-device-info';
@@ -93,6 +93,9 @@ const createWindow = async () => {
autoHideMenuBar: true,
});
+ // 设置窗口关闭行为(托盘最小化)
+ setupWindowCloseHandler(mainWindow);
+
// 最大化窗口
mainWindow.maximize();
@@ -119,6 +122,10 @@ app.on('ready', createWindow);
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
+ // 如果托盘启用,不退出应用
+ if (isTrayEnabled()) {
+ return;
+ }
if (process.platform !== 'darwin') {
app.quit();
}
diff --git a/src/main/joint-build/index.ts b/src/main/joint-build/index.ts
index 2a37d53..c84e718 100644
--- a/src/main/joint-build/index.ts
+++ b/src/main/joint-build/index.ts
@@ -1,5 +1,5 @@
-import { dialog, IpcMain } from 'electron';
-import { selectFolderHandle, getDiskInfoHandle, DiskInfo } from './type-info';
+import { dialog, IpcMain, Tray, Menu, BrowserWindow, app, nativeImage } from 'electron';
+import { selectFolderHandle, getDiskInfoHandle, DiskInfo, setTrayEnabledHandle } from './type-info';
import { exec } from 'child_process';
import { promisify } from 'util';
import path from 'path';
@@ -7,6 +7,13 @@ import { ipcHandle } from '../ipc-util';
const execPromise = promisify(exec);
+// 托盘实例
+let tray: Tray | null = null;
+// 是否启用托盘(共建开关状态)
+let trayEnabled = false;
+// 是否正在强制退出
+let forceQuit = false;
+
// 获取Windows磁盘信息
async function getWindowsDiskInfo(diskPath: string): Promise {
try {
@@ -65,4 +72,111 @@ export function setupJointBuildHandlers(ipcMain: IpcMain): void {
ipcHandle(ipcMain, getDiskInfoHandle, async (_event, diskPath: string) => {
return getWindowsDiskInfo(diskPath);
});
+
+ // 处理设置托盘启用状态
+ ipcHandle(ipcMain, setTrayEnabledHandle, async (_event, enabled: boolean) => {
+ trayEnabled = enabled;
+ if (enabled) {
+ createTray();
+ } else {
+ destroyTray();
+ }
+ return true;
+ });
+}
+
+// 获取图标路径
+function getIconPath(): string {
+ // 开发环境和生产环境的路径不同
+ if (app.isPackaged) {
+ return path.join(process.resourcesPath, 'icons', 'icon.png');
+ } else {
+ return path.join(__dirname, '..', '..', 'icons', 'icon.png');
+ }
+}
+
+// 创建托盘
+function createTray(): void {
+ if (tray) return;
+
+ try {
+ const iconPath = getIconPath();
+ const icon = nativeImage.createFromPath(iconPath);
+ tray = new Tray(icon.resize({ width: 16, height: 16 }));
+
+ const contextMenu = Menu.buildFromTemplate([
+ {
+ label: '显示主窗口',
+ click: () => {
+ const windows = BrowserWindow.getAllWindows();
+ if (windows.length > 0) {
+ const mainWindow = windows[0];
+ mainWindow.show();
+ mainWindow.focus();
+ }
+ },
+ },
+ { type: 'separator' },
+ {
+ label: '退出',
+ click: () => {
+ forceQuit = true;
+ app.quit();
+ },
+ },
+ ]);
+
+ // todo: 信息识别
+ const version = app.getVersion();
+ const isJointBuilding = trayEnabled ? '正在共建中(ON)' : '共建已关闭(OFF)';
+ const currentUploadSpeed = '0KB/s';
+ const linkedTo = 0;
+ tray.setToolTip(`AI学习助手 ${version}\n${isJointBuilding}\n当前上传速度: ${currentUploadSpeed}\n已连接伙伴${linkedTo}人`);
+ tray.setContextMenu(contextMenu);
+
+ // 双击托盘图标显示窗口
+ tray.on('double-click', () => {
+ const windows = BrowserWindow.getAllWindows();
+ if (windows.length > 0) {
+ const mainWindow = windows[0];
+ mainWindow.show();
+ mainWindow.focus();
+ }
+ });
+ } catch (error) {
+ console.error('创建托盘失败:', error);
+ }
+}
+
+// 销毁托盘
+function destroyTray(): void {
+ if (tray) {
+ tray.destroy();
+ tray = null;
+ }
+}
+
+// 设置窗口关闭行为
+export function setupWindowCloseHandler(mainWindow: BrowserWindow): void {
+ mainWindow.on('close', (event) => {
+ if (trayEnabled && !forceQuit) {
+ event.preventDefault();
+ mainWindow.hide();
+ }
+ });
+}
+
+// 检查是否强制退出
+export function isForceQuit(): boolean {
+ return forceQuit;
+}
+
+// 设置强制退出标志
+export function setForceQuit(value: boolean): void {
+ forceQuit = value;
+}
+
+// 获取托盘启用状态
+export function isTrayEnabled(): boolean {
+ return trayEnabled;
}
diff --git a/src/main/joint-build/type-info.ts b/src/main/joint-build/type-info.ts
index 5530bd7..ac7f09c 100644
--- a/src/main/joint-build/type-info.ts
+++ b/src/main/joint-build/type-info.ts
@@ -1,8 +1,9 @@
-export type ActionName = 'select-folder' | 'get-disk-info';
+export type ActionName = 'select-folder' | 'get-disk-info' | 'set-tray-enabled';
export type ServiceName = 'joint-build';
export const selectFolderHandle = 'joint-build:select-folder';
export const getDiskInfoHandle = 'joint-build:get-disk-info';
+export const setTrayEnabledHandle = 'joint-build:set-tray-enabled';
export interface DiskInfo {
total: number; // bytes
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 5c87604..e5e9f74 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -22,6 +22,7 @@ import {
import {
selectFolderHandle,
getDiskInfoHandle,
+ setTrayEnabledHandle,
DiskInfo,
} from './joint-build/type-info';
@@ -120,6 +121,9 @@ const mainHandle = {
getJointBuildDiskInfo: async (diskPath: string): Promise => {
return ipcInvoke(getDiskInfoHandle, diskPath);
},
+ setTrayEnabled: async (enabled: boolean): Promise => {
+ return ipcInvoke(setTrayEnabledHandle, enabled);
+ },
};
export type MainHandle = typeof mainHandle;
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 0c712ff..841dd82 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -49,6 +49,29 @@ export default function Hello() {
};
}, [setupBackupListener]);
+ // 初始化托盘状态(根据共建开关)
+ useEffect(() => {
+ const initTrayStatus = async () => {
+ const masterSwitch = localStorage.getItem('joint_build_master_switch');
+ const welcomeChoice = localStorage.getItem('ai_learning_assistant_welcome_shown');
+
+ let trayEnabled = false;
+ if (masterSwitch !== null) {
+ trayEnabled = masterSwitch === 'true';
+ } else if (welcomeChoice === 'true') {
+ trayEnabled = true;
+ }
+
+ try {
+ await window.mainHandle.setTrayEnabled(trayEnabled);
+ } catch (error) {
+ console.error('初始化托盘状态失败:', error);
+ }
+ };
+
+ initTrayStatus();
+ }, []);
+
const [currentSlide, setCurrentSlide] = useState(0);
const slides = [
diff --git a/src/renderer/pages/joint-build/index.tsx b/src/renderer/pages/joint-build/index.tsx
index 0a9e092..4a7c32e 100644
--- a/src/renderer/pages/joint-build/index.tsx
+++ b/src/renderer/pages/joint-build/index.tsx
@@ -76,15 +76,21 @@ export default function JointBuild() {
useEffect(() => {
// 优先读取共建开关的设置,如果没有则读取欢迎弹窗的用户选择
const savedMasterSwitch = localStorage.getItem(STORAGE_KEY_MASTER);
+ let initialMasterSwitch = false;
+
if (savedMasterSwitch !== null) {
- setMasterSwitch(savedMasterSwitch === 'true');
+ initialMasterSwitch = savedMasterSwitch === 'true';
} else {
// 如果没有单独设置过共建开关,则读取欢迎弹窗的选择
const welcomeChoice = localStorage.getItem(STORAGE_KEY_WELCOME);
if (welcomeChoice === 'true') {
- setMasterSwitch(true);
+ initialMasterSwitch = true;
}
}
+
+ setMasterSwitch(initialMasterSwitch);
+ // 初始化时同步托盘状态到主进程
+ window.mainHandle.setTrayEnabled(initialMasterSwitch).catch(console.error);
const savedModules = localStorage.getItem(STORAGE_KEY_MODULES);
if (savedModules) {
@@ -118,11 +124,19 @@ export default function JointBuild() {
);
};
- const handleMasterSwitchChange = (checked: boolean) => {
+ const handleMasterSwitchChange = async (checked: boolean) => {
setMasterSwitch(checked);
saveConfig(checked, modules);
+
+ // 同步托盘状态到主进程
+ try {
+ await window.mainHandle.setTrayEnabled(checked);
+ } catch (error) {
+ console.error('设置托盘状态失败:', error);
+ }
+
if (checked) {
- message.success('共建计划已开启');
+ message.success('共建计划已开启,关闭窗口后将最小化到托盘');
} else {
message.info('共建计划已关闭');
}
From 2002c012a9972572c600db8a1492ff881bc204f6 Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 11:54:55 +0800
Subject: [PATCH 06/22] =?UTF-8?q?=E6=89=98=E7=9B=98=E9=80=BB=E8=BE=91?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/joint-build/index.ts | 44 +++++++++++++++++++++++------------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/src/main/joint-build/index.ts b/src/main/joint-build/index.ts
index c84e718..5cb94a8 100644
--- a/src/main/joint-build/index.ts
+++ b/src/main/joint-build/index.ts
@@ -73,14 +73,13 @@ export function setupJointBuildHandlers(ipcMain: IpcMain): void {
return getWindowsDiskInfo(diskPath);
});
- // 处理设置托盘启用状态
+ // 处理设置托盘启用状态(只更新状态,不销毁托盘)
ipcHandle(ipcMain, setTrayEnabledHandle, async (_event, enabled: boolean) => {
trayEnabled = enabled;
- if (enabled) {
- createTray();
- } else {
- destroyTray();
- }
+ // 确保托盘始终存在
+ createTray();
+ // 更新托盘提示信息
+ updateTrayTooltip();
return true;
});
}
@@ -126,12 +125,7 @@ function createTray(): void {
},
]);
- // todo: 信息识别
- const version = app.getVersion();
- const isJointBuilding = trayEnabled ? '正在共建中(ON)' : '共建已关闭(OFF)';
- const currentUploadSpeed = '0KB/s';
- const linkedTo = 0;
- tray.setToolTip(`AI学习助手 ${version}\n${isJointBuilding}\n当前上传速度: ${currentUploadSpeed}\n已连接伙伴${linkedTo}人`);
+ updateTrayTooltip();
tray.setContextMenu(contextMenu);
// 双击托盘图标显示窗口
@@ -148,6 +142,22 @@ function createTray(): void {
}
}
+// 更新托盘提示信息
+function updateTrayTooltip(): void {
+ if (!tray) return;
+
+ const version = app.getVersion();
+ const isJointBuilding = trayEnabled ? '正在共建中(ON)' : '共建已关闭(OFF)';
+ if (!trayEnabled) {
+ tray.setToolTip(`AI学习助手 ${version}\n共建已关闭(OFF)`);
+ return;
+ }
+ // todo:获取动态数据
+ const currentUploadSpeed = '0KB/s';
+ const linkedTo = 0;
+ tray.setToolTip(`AI学习助手 ${version}\n${isJointBuilding}\n当前上传速度: ${currentUploadSpeed}\n已连接伙伴${linkedTo}人`);
+}
+
// 销毁托盘
function destroyTray(): void {
if (tray) {
@@ -158,8 +168,12 @@ function destroyTray(): void {
// 设置窗口关闭行为
export function setupWindowCloseHandler(mainWindow: BrowserWindow): void {
+ // 确保托盘已创建
+ createTray();
+
mainWindow.on('close', (event) => {
- if (trayEnabled && !forceQuit) {
+ // 始终最小化到托盘,除非是强制退出
+ if (!forceQuit) {
event.preventDefault();
mainWindow.hide();
}
@@ -176,7 +190,7 @@ export function setForceQuit(value: boolean): void {
forceQuit = value;
}
-// 获取托盘启用状态
+// 获取托盘启用状态(始终启用托盘最小化)
export function isTrayEnabled(): boolean {
- return trayEnabled;
+ return true;
}
From 3cb825048b6bb57075bf9fea4c0567126079c457 Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 16 Jan 2026 12:10:09 +0800
Subject: [PATCH 07/22] =?UTF-8?q?=E6=89=98=E7=9B=98=E9=80=89=E9=A1=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/joint-build/index.ts | 82 +++++++++++++++++-------
src/main/preload.ts | 11 ++++
src/renderer/app.tsx | 31 ++++++++-
src/renderer/pages/joint-build/index.tsx | 13 ++++
4 files changed, 113 insertions(+), 24 deletions(-)
diff --git a/src/main/joint-build/index.ts b/src/main/joint-build/index.ts
index 5cb94a8..cf606e0 100644
--- a/src/main/joint-build/index.ts
+++ b/src/main/joint-build/index.ts
@@ -103,30 +103,8 @@ function createTray(): void {
const icon = nativeImage.createFromPath(iconPath);
tray = new Tray(icon.resize({ width: 16, height: 16 }));
- const contextMenu = Menu.buildFromTemplate([
- {
- label: '显示主窗口',
- click: () => {
- const windows = BrowserWindow.getAllWindows();
- if (windows.length > 0) {
- const mainWindow = windows[0];
- mainWindow.show();
- mainWindow.focus();
- }
- },
- },
- { type: 'separator' },
- {
- label: '退出',
- click: () => {
- forceQuit = true;
- app.quit();
- },
- },
- ]);
-
+ updateTrayMenu();
updateTrayTooltip();
- tray.setContextMenu(contextMenu);
// 双击托盘图标显示窗口
tray.on('double-click', () => {
@@ -142,6 +120,64 @@ function createTray(): void {
}
}
+// 更新托盘菜单
+function updateTrayMenu(): void {
+ if (!tray) return;
+
+ const contextMenu = Menu.buildFromTemplate([
+ {
+ label: '打开AI学习助手',
+ click: () => {
+ const windows = BrowserWindow.getAllWindows();
+ if (windows.length > 0) {
+ const mainWindow = windows[0];
+ mainWindow.show();
+ mainWindow.focus();
+ // 发送路由事件到渲染进程,导航到主界面
+ mainWindow.webContents.send('navigate-to', '/hello');
+ }
+ },
+ },
+ {
+ label: '打开共建设置',
+ click: () => {
+ const windows = BrowserWindow.getAllWindows();
+ if (windows.length > 0) {
+ const mainWindow = windows[0];
+ mainWindow.show();
+ mainWindow.focus();
+ // 发送路由事件到渲染进程,导航到共建设置界面
+ mainWindow.webContents.send('navigate-to', '/joint-build');
+ }
+ },
+ },
+ {
+ label: trayEnabled ? '停止共建计划' : '运行共建计划',
+ click: () => {
+ trayEnabled = !trayEnabled;
+ // 更新菜单显示
+ updateTrayMenu();
+ updateTrayTooltip();
+ // 通知渲染进程更新状态
+ const windows = BrowserWindow.getAllWindows();
+ if (windows.length > 0) {
+ windows[0].webContents.send('joint-build-status-changed', trayEnabled);
+ }
+ },
+ },
+ { type: 'separator' },
+ {
+ label: '退出AI学习助手',
+ click: () => {
+ forceQuit = true;
+ app.quit();
+ },
+ },
+ ]);
+
+ tray.setContextMenu(contextMenu);
+}
+
// 更新托盘提示信息
function updateTrayTooltip(): void {
if (!tray) return;
diff --git a/src/main/preload.ts b/src/main/preload.ts
index e5e9f74..2e493ba 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -124,6 +124,17 @@ const mainHandle = {
setTrayEnabled: async (enabled: boolean): Promise => {
return ipcInvoke(setTrayEnabledHandle, enabled);
},
+ // 托盘菜单事件监听
+ onNavigateTo: (callback: (route: string) => void) => {
+ const handler = (_event: IpcRendererEvent, route: string) => callback(route);
+ ipcRenderer.on('navigate-to', handler);
+ return () => ipcRenderer.removeListener('navigate-to', handler);
+ },
+ onJointBuildStatusChanged: (callback: (enabled: boolean) => void) => {
+ const handler = (_event: IpcRendererEvent, enabled: boolean) => callback(enabled);
+ ipcRenderer.on('joint-build-status-changed', handler);
+ return () => ipcRenderer.removeListener('joint-build-status-changed', handler);
+ },
};
export type MainHandle = typeof mainHandle;
diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx
index 06081f9..3906c57 100644
--- a/src/renderer/app.tsx
+++ b/src/renderer/app.tsx
@@ -1,4 +1,4 @@
-import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
+import { MemoryRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
import '@ant-design/v5-patch-for-react-19';
import Hello from './pages/hello';
import './app.css';
@@ -17,12 +17,41 @@ import LLMConfig from './pages/llm-api-config';
import VoiceRTCConfig from './pages/voice-rtc-config';
import WelcomeModal from './pages/welcome/WelcomeModal';
import JointBuild from './pages/joint-build';
+import { useEffect } from 'react';
+
+// 托盘菜单导航监听组件
+function TrayNavigationHandler() {
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ // 监听托盘菜单的导航事件
+ const unsubscribeNavigate = window.mainHandle.onNavigateTo((route: string) => {
+ navigate(route);
+ });
+
+ // 监听共建计划状态变化事件
+ const unsubscribeStatus = window.mainHandle.onJointBuildStatusChanged((enabled: boolean) => {
+ // 更新 localStorage
+ localStorage.setItem('joint_build_master_switch', String(enabled));
+ // 触发页面刷新或状态更新
+ window.dispatchEvent(new CustomEvent('joint-build-status-changed', { detail: enabled }));
+ });
+
+ return () => {
+ unsubscribeNavigate();
+ unsubscribeStatus();
+ };
+ }, [navigate]);
+
+ return null;
+}
export default function App() {
return (
+
} />
} />
diff --git a/src/renderer/pages/joint-build/index.tsx b/src/renderer/pages/joint-build/index.tsx
index 4a7c32e..2e4efc3 100644
--- a/src/renderer/pages/joint-build/index.tsx
+++ b/src/renderer/pages/joint-build/index.tsx
@@ -115,6 +115,19 @@ export default function JointBuild() {
fetchDiskInfo(initialPath);
}, [fetchDiskInfo]);
+ // 监听托盘菜单的状态变化
+ useEffect(() => {
+ const handleStatusChange = (event: CustomEvent) => {
+ setMasterSwitch(event.detail);
+ };
+
+ window.addEventListener('joint-build-status-changed', handleStatusChange as EventListener);
+
+ return () => {
+ window.removeEventListener('joint-build-status-changed', handleStatusChange as EventListener);
+ };
+ }, []);
+
// 保存配置到 localStorage
const saveConfig = (master: boolean, mods: ModuleItem[]) => {
localStorage.setItem(STORAGE_KEY_MASTER, String(master));
From 6c70518a53d85e23ed66ee7992d0d05ea02143d2 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Mon, 19 Jan 2026 11:56:39 +0800
Subject: [PATCH 08/22] feat: add launcher and P2P download updates for
Obsidian and LLMstudio
---
.gitignore | 6 +-
external-resources/dlc/dlc-index.json | 39 ++-
package-lock.json | 25 +-
package.json | 2 +
src/main/cmd/index.ts | 174 +++++++++++
src/main/dlc/index.ts | 34 +-
src/main/dlc/type-info.ts | 3 +
src/main/index.ts | 2 +
src/main/ipc-data-type.ts | 6 +-
src/main/launcher-update/index.ts | 342 +++++++++++++++++++++
src/main/launcher-update/type-info.ts | 9 +
src/main/preload.ts | 30 +-
src/main/training-service/index.ts | 11 +-
src/renderer/pages/hello/index.scss | 9 +
src/renderer/pages/hello/index.tsx | 64 +++-
src/renderer/pages/lm-service/index.scss | 51 ++-
src/renderer/pages/lm-service/index.tsx | 194 ++++++++++--
src/renderer/pages/obsidian-app/index.scss | 49 +++
src/renderer/pages/obsidian-app/index.tsx | 218 ++++++++++++-
19 files changed, 1219 insertions(+), 49 deletions(-)
create mode 100644 src/main/launcher-update/index.ts
create mode 100644 src/main/launcher-update/type-info.ts
diff --git a/.gitignore b/.gitignore
index 990de72..fbbb803 100644
--- a/.gitignore
+++ b/.gitignore
@@ -109,4 +109,8 @@ external-resources/dlc/**/*.mp4
external-resources/dlc/**/*.tar
external-resources/dlc/**/*.zip
external-resources/dlc/**/*.sql
-external-resources/dlc/**/*.torrent
\ No newline at end of file
+external-resources/dlc/**/*.torrent
+
+# 忽略启动器更新临时目录
+update-temp/
+update.bat
diff --git a/external-resources/dlc/dlc-index.json b/external-resources/dlc/dlc-index.json
index 8edbf75..4c2b326 100644
--- a/external-resources/dlc/dlc-index.json
+++ b/external-resources/dlc/dlc-index.json
@@ -6,12 +6,12 @@
"2.0.0": {
"magnet": "magnet:?xt=urn:btih:a7f8c85dcef68ae7958f0f3548603ea9016897df&dn=ala-training-v2.0.0.tar&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce",
"comment": "第一个版本",
- "riquire":{}
+ "riquire": {}
},
"2.0.2": {
"magnet": "magnet:?xt=urn:btih:98995512a1cf581a1baea19132b1a6550ec424fc&dn=ala-training-v2.0.2.tar&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce",
"comment": "相比上一个版本修复了一些问题",
- "riquire":{}
+ "riquire": {}
}
}
},
@@ -22,10 +22,43 @@
"2.0.2": {
"magnet": "magnet:?xt=urn:btih:cff0fb96747c5fc4696af4c9fa8cd30330050250&dn=ai_learning_assistant.sql&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce&tr=wss%3A%2F%2F114.66.58.95%3A17853%2Fannounce",
"comment": "第一个版本",
- "riquire":{
+ "riquire": {
"TRAINING_TAR": "^2.0.0"
}
}
}
+ },
+ {
+ "id": "AI_LEARNING_ASSISTANT_LAUNCHER",
+ "name": "AI学习助手启动器",
+ "versions": {
+ "2.0.3": {
+ "magnet": "magnet:?xt=urn:btih:5e685a7cbe4045efc40f7a593dddfdf56ae7c59d&dn=AI-Learning-Assistant-Launcher-win32-x64-2.0.3.zip&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
+ "comment": "第一个版本",
+ "riquire": {}
+ }
+ }
+ },
+ {
+ "id": "OBSIDIAN_SETUP_EXE",
+ "name": "obsidian 安装程序",
+ "versions": {
+ "1.11.4": {
+ "magnet": "magnet:?xt=urn:btih:0e35e31fcbfa86153cd155f24c5d835e977d7d8b&dn=Obsidian-1.11.4.exe&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
+ "comment": "第一个版本",
+ "riquire": {}
+ }
+ }
+ },
+ {
+ "id": "LM_STUDIO_SETUP_EXE",
+ "name": "lm studio 安装程序",
+ "versions": {
+ "0.3.39": {
+ "magnet": "magnet:?xt=urn:btih:f99095d4fc565a5e7fcf971f80817d8dbf617c97&dn=LM-Studio-0.3.39-x64.exe&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
+ "comment": "第一个版本",
+ "riquire": {}
+ }
+ }
}
]
diff --git a/package-lock.json b/package-lock.json
index 2f2c1f3..1b17429 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,7 +19,8 @@
"langchain": "^0.3.35",
"pdf-lib": "^1.17.1",
"react": "^19.1.0",
- "react-dom": "^19.1.0"
+ "react-dom": "^19.1.0",
+ "semver": "^7.7.3"
},
"devDependencies": {
"@ant-design/v5-patch-for-react-19": "^1.0.3",
@@ -34,6 +35,7 @@
"@electron/fuses": "^1.8.0",
"@stdlib/utils-convert-path": "^0.2.2",
"@svgr/webpack": "^8.1.0",
+ "@types/adm-zip": "^0.5.7",
"@types/cross-spawn": "^6.0.6",
"@types/dockerode": "^3.3.40",
"@types/form-data": "^2.2.1",
@@ -46,6 +48,7 @@
"@typescript-eslint/parser": "^5.62.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"@xterm/xterm": "^5.5.0",
+ "adm-zip": "^0.5.16",
"antd": "^5.26.1",
"copy-webpack-plugin": "^13.0.1",
"cpy": "^11.1.0",
@@ -5755,6 +5758,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/adm-zip": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmmirror.com/@types/adm-zip/-/adm-zip-0.5.7.tgz",
+ "integrity": "sha512-DNEs/QvmyRLurdQPChqq0Md4zGvPwHerAJYWk9l2jCbD1VPpnzRJorOdiq4zsw09NFbYnhfsoEhWtxIzXpn2yw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/bittorrent-protocol": {
"version": "3.1.7",
"resolved": "https://registry.npmmirror.com/@types/bittorrent-protocol/-/bittorrent-protocol-3.1.7.tgz",
@@ -6767,6 +6780,16 @@
"node": ">=12.20.0"
}
},
+ "node_modules/adm-zip": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.16.tgz",
+ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
diff --git a/package.json b/package.json
index b325b13..cb1f159 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@electron/fuses": "^1.8.0",
"@stdlib/utils-convert-path": "^0.2.2",
"@svgr/webpack": "^8.1.0",
+ "@types/adm-zip": "^0.5.7",
"@types/cross-spawn": "^6.0.6",
"@types/dockerode": "^3.3.40",
"@types/form-data": "^2.2.1",
@@ -44,6 +45,7 @@
"@typescript-eslint/parser": "^5.62.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"@xterm/xterm": "^5.5.0",
+ "adm-zip": "^0.5.16",
"antd": "^5.26.1",
"copy-webpack-plugin": "^13.0.1",
"cpy": "^11.1.0",
diff --git a/src/main/cmd/index.ts b/src/main/cmd/index.ts
index dbe8798..bd84404 100644
--- a/src/main/cmd/index.ts
+++ b/src/main/cmd/index.ts
@@ -488,6 +488,93 @@ async function checkWSLComponent() {
}
export async function installObsidian() {
+ try {
+ // 首先检查是否有P2P下载的文件
+ const { getDLCFromDLCIndex, destroyWebtorrentForInstall } = await import(
+ '../dlc'
+ );
+ const dlcInfo = getDLCFromDLCIndex('OBSIDIAN_SETUP_EXE');
+
+ if (dlcInfo) {
+ const latestVersion = Object.keys(dlcInfo.versions).sort().pop();
+ if (latestVersion) {
+ const versionInfo = dlcInfo.versions[latestVersion];
+
+ // 完全销毁 torrent 以释放文件句柄,避免 EBUSY 错误
+ if (versionInfo.magnet) {
+ try {
+ console.debug('开始销毁种子以释放文件句柄...');
+ await destroyWebtorrentForInstall(versionInfo.magnet);
+ // 等待额外的时间确保文件句柄完全释放
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ console.debug('种子已销毁,等待完成');
+ } catch (e) {
+ console.warn('销毁torrent失败:', e);
+ }
+ }
+
+ const downloadPath = path.join(
+ appPath,
+ 'external-resources',
+ 'dlc',
+ 'OBSIDIAN_SETUP_EXE',
+ latestVersion,
+ );
+
+ const fs = await import('fs');
+ if (fs.existsSync(downloadPath)) {
+ const files = fs.readdirSync(downloadPath);
+ const exeFile = files.find((f: string) => f.endsWith('.exe'));
+
+ if (exeFile) {
+ const exePath = path.join(downloadPath, exeFile);
+
+ // 使用更激进的重试机制处理 EBUSY 错误
+ const maxRetries = 8;
+ for (let retry = 0; retry < maxRetries; retry++) {
+ try {
+ // 每次重试前等待更长时间,让文件句柄有充足时间释放
+ const waitTime = retry === 0 ? 1000 : 1000 + retry * 1000;
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
+
+ console.debug(
+ `尝试执行安装程序 (${retry + 1}/${maxRetries}),等待了 ${waitTime}ms`,
+ );
+ const result = await commandLine.exec(exePath, ['/s']);
+ console.debug('安装程序执行成功:', result);
+ return true;
+ } catch (e) {
+ const isEBUSY = e.message && e.message.includes('EBUSY');
+ if (isEBUSY && retry < maxRetries - 1) {
+ console.warn(
+ `执行安装程序失败 (EBUSY),重试 ${retry + 1}/${maxRetries}...`,
+ );
+ } else if (isEBUSY) {
+ // 所有重试都失败了,尝试最后一次长等待
+ console.warn('所有重试都失败,尝试最后一次长等待(10秒)...');
+ await new Promise((resolve) => setTimeout(resolve, 10000));
+ try {
+ const result = await commandLine.exec(exePath, ['/s']);
+ console.debug('最后一次尝试成功:', result);
+ return true;
+ } catch (finalError) {
+ console.error('最后一次尝试也失败了:', finalError);
+ throw finalError;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error('尝试使用P2P下载包失败,使用默认方式:', error);
+ }
+
+ // 降级到原有的本地安装包方式
const result = await commandLine.exec(
path.join(
appPath,
@@ -520,6 +607,93 @@ export async function isObsidianInstall() {
}
export async function installLMStudio() {
+ try {
+ // 首先检查是否有P2P下载的文件
+ const { getDLCFromDLCIndex, destroyWebtorrentForInstall } = await import(
+ '../dlc'
+ );
+ const dlcInfo = getDLCFromDLCIndex('LM_STUDIO_SETUP_EXE');
+
+ if (dlcInfo) {
+ const latestVersion = Object.keys(dlcInfo.versions).sort().pop();
+ if (latestVersion) {
+ const versionInfo = dlcInfo.versions[latestVersion];
+
+ // 完全销毁 torrent 以释放文件句柄,避免 EBUSY 错误
+ if (versionInfo.magnet) {
+ try {
+ console.debug('开始销毁LM Studio种子以释放文件句柄...');
+ await destroyWebtorrentForInstall(versionInfo.magnet);
+ // 等待额外的时间确保文件句柄完全释放
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ console.debug('种子已销毁,等待完成');
+ } catch (e) {
+ console.warn('销毁torrent失败:', e);
+ }
+ }
+
+ const downloadPath = path.join(
+ appPath,
+ 'external-resources',
+ 'dlc',
+ 'LM_STUDIO_SETUP_EXE',
+ latestVersion,
+ );
+
+ const fs = await import('fs');
+ if (fs.existsSync(downloadPath)) {
+ const files = fs.readdirSync(downloadPath);
+ const exeFile = files.find((f: string) => f.endsWith('.exe'));
+
+ if (exeFile) {
+ const exePath = path.join(downloadPath, exeFile);
+
+ // 使用更激进的重试机制处理 EBUSY 错误
+ const maxRetries = 8;
+ for (let retry = 0; retry < maxRetries; retry++) {
+ try {
+ // 每次重试前等待更长时间,让文件句柄有充足时间释放
+ const waitTime = retry === 0 ? 1000 : 1000 + retry * 1000;
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
+
+ console.debug(
+ `尝试执行LM Studio安装程序 (${retry + 1}/${maxRetries}),等待了 ${waitTime}ms`,
+ );
+ const result = await commandLine.exec(exePath, []);
+ console.debug('LM Studio安装程序执行成功:', result);
+ return true;
+ } catch (e) {
+ const isEBUSY = e.message && e.message.includes('EBUSY');
+ if (isEBUSY && retry < maxRetries - 1) {
+ console.warn(
+ `执行LM Studio安装程序失败 (EBUSY),重试 ${retry + 1}/${maxRetries}...`,
+ );
+ } else if (isEBUSY) {
+ // 所有重试都失败了,尝试最后一次长等待
+ console.warn('所有重试都失败,尝试最后一次长等待(10秒)...');
+ await new Promise((resolve) => setTimeout(resolve, 10000));
+ try {
+ const result = await commandLine.exec(exePath, []);
+ console.debug('最后一次尝试成功:', result);
+ return true;
+ } catch (finalError) {
+ console.error('最后一次尝试也失败了:', finalError);
+ throw finalError;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error('尝试使用P2P下载包失败,使用默认方式:', error);
+ }
+
+ // 降级到原有的本地安装包方式
const result = await commandLine.exec(
path.join(
appPath,
diff --git a/src/main/dlc/index.ts b/src/main/dlc/index.ts
index 869cebe..5225291 100644
--- a/src/main/dlc/index.ts
+++ b/src/main/dlc/index.ts
@@ -99,9 +99,9 @@ export async function startWebtorrent(magnet: string) {
});
console.debug('种子文件已经存在,继续下载');
torrent.resume();
- return torrent;
+ return { success: true, infoHash: torrent.infoHash };
} else {
- return client.add(
+ const newTorrent = client.add(
magnet,
{
path: filePath,
@@ -114,9 +114,11 @@ export async function startWebtorrent(magnet: string) {
saveTorrentFile(filePath, torrent);
},
);
+ return { success: true, infoHash: newTorrent.infoHash };
}
} else {
console.warn('没找到链接对应的索引信息', magnet);
+ return { success: false, error: '没找到链接对应的索引信息' };
}
}
@@ -152,6 +154,34 @@ export async function pauseWebtorrent(magnet: string) {
}
}
+/**
+ * 完全销毁种子并释放文件句柄
+ * 注意:这会关闭所有文件句柄,但不会删除已下载的文件
+ */
+export async function destroyWebtorrentForInstall(
+ magnet: string,
+): Promise {
+ const torrent = await client.get(magnet);
+ if (torrent) {
+ return new Promise((resolve) => {
+ // 先暂停下载
+ torrent.pause();
+
+ // 销毁种子,这会关闭所有文件句柄
+ torrent.destroy({ destroyStore: false }, () => {
+ console.debug('种子已销毁,文件句柄已释放:', torrent.infoHash);
+ resolve();
+ });
+
+ // 设置一个超时,防止回调永远不执行
+ setTimeout(() => {
+ console.debug('destroy回调超时,强制resolve');
+ resolve();
+ }, 3000);
+ });
+ }
+}
+
export async function removeWebtorrent(magnet: string) {
const torrent = await client.get(magnet);
if (torrent) {
diff --git a/src/main/dlc/type-info.ts b/src/main/dlc/type-info.ts
index 37d0a5d..dfc5a37 100644
--- a/src/main/dlc/type-info.ts
+++ b/src/main/dlc/type-info.ts
@@ -20,6 +20,9 @@ export const dLCIds = [
'TRAINING_VOICE_TAR',
'TRAINING_COURSE',
'LMSTUDIO_WINDOWS',
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ 'OBSIDIAN_SETUP_EXE',
+ 'LM_STUDIO_SETUP_EXE',
'TEST_FILE',
] as const;
diff --git a/src/main/index.ts b/src/main/index.ts
index 3b8f105..623f96a 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -14,6 +14,7 @@ import initTrainingService from './training-service';
import initLogService from './backup';
import initExternalUrl from './external-url';
import initDLC from './dlc';
+import initLauncherUpdate from './launcher-update';
import path from 'node:path';
import { appPath, autoAdaptEncodingForWindows } from './exec';
import { logDeviceInfo } from './logger/log-device-info';
@@ -81,6 +82,7 @@ initPdfConvert(ipcMain);
initTrainingService(ipcMain);
initExternalUrl(ipcMain);
initDLC(ipcMain);
+initLauncherUpdate(ipcMain);
updateTemplate();
const createWindow = async () => {
diff --git a/src/main/ipc-data-type.ts b/src/main/ipc-data-type.ts
index acd7b57..5e606a6 100644
--- a/src/main/ipc-data-type.ts
+++ b/src/main/ipc-data-type.ts
@@ -16,7 +16,8 @@ export type Channels =
| 'training-service'
| 'backup'
| 'open-external-url'
- | 'webtorrent';
+ | 'webtorrent'
+ | 'launcher-update';
export enum MESSAGE_TYPE {
/** 阻断性错误,会把转圈中的按钮变成不转圈 */
@@ -69,12 +70,11 @@ import {
ActionName as LogActionName,
ServiceName as LogServiceName,
} from './backup/type-info';
-import {
+import {
ActionName as ActionNameUrl,
ServiceName as ServiceNameUrl,
} from './external-url/type-info';
-
export type AllAction =
| ActionNamePodman
| ActionNameCmd
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
new file mode 100644
index 0000000..80867d2
--- /dev/null
+++ b/src/main/launcher-update/index.ts
@@ -0,0 +1,342 @@
+import { IpcMain, app, dialog } from 'electron';
+import { ipcHandle } from '../ipc-util';
+import {
+ checkLauncherUpdateHandle,
+ downloadLauncherUpdateHandle,
+ installLauncherUpdateHandle,
+} from './type-info';
+import {
+ getLatestVersion,
+ isLatestVersion,
+ startWebtorrent,
+ waitTorrentDone,
+} from '../dlc';
+import path from 'path';
+import { appPath } from '../exec';
+import {
+ existsSync,
+ mkdirSync,
+ readdirSync,
+ statSync,
+ rmSync,
+ writeFileSync,
+ readFileSync,
+} from 'fs';
+import { spawn } from 'child_process';
+import AdmZip from 'adm-zip';
+
+import packageJson from '../../../package.json';
+const currentVersion = packageJson.version;
+
+// 在 Electron 中使用 original-fs 处理 asar 打包后的文件操作
+// original-fs 是 Electron 内置的,需要通过 require 动态导入
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const originalFs = require('original-fs');
+
+export default async function init(ipcMain: IpcMain) {
+ ipcHandle(ipcMain, checkLauncherUpdateHandle, async (_event) =>
+ checkLauncherUpdate(),
+ );
+ ipcHandle(ipcMain, downloadLauncherUpdateHandle, async (_event) =>
+ downloadLauncherUpdate(),
+ );
+ ipcHandle(ipcMain, installLauncherUpdateHandle, async (_event) =>
+ installLauncherUpdate(),
+ );
+}
+
+export async function checkLauncherUpdate() {
+ try {
+ const latestVersionInfo = getLatestVersion(
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+ const latestVersion = latestVersionInfo.version;
+ const haveNew = !isLatestVersion(
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ currentVersion,
+ );
+
+ console.debug('检查启动器更新:', {
+ currentVersion,
+ latestVersion,
+ haveNew,
+ });
+
+ return {
+ currentVersion,
+ latestVersion,
+ haveNew,
+ };
+ } catch (error) {
+ console.error('检查启动器更新失败:', error);
+ return {
+ currentVersion,
+ latestVersion: currentVersion,
+ haveNew: false,
+ };
+ }
+}
+
+export async function downloadLauncherUpdate() {
+ try {
+ const latestVersionInfo = getLatestVersion(
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+
+ console.debug('开始下载启动器更新:', latestVersionInfo);
+
+ await startWebtorrent(latestVersionInfo.dlcInfo.magnet);
+
+ const torrent = await waitTorrentDone(
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ latestVersionInfo.version,
+ );
+
+ console.debug('启动器更新下载完成');
+
+ // 检查是否为本地开发环境
+ // const isDev = !app.isPackaged;
+ // if (isDev) {
+ // console.warn('当前为本地开发环境,启动器更新功能可能无法正常工作');
+ // }
+
+ return {
+ success: true,
+ version: latestVersionInfo.version,
+ filePath: path.join(torrent.path, torrent.files[0].name),
+ // isDev,
+ };
+ } catch (error) {
+ console.error('下载启动器更新失败:', error);
+ throw error;
+ }
+}
+
+export async function installLauncherUpdate() {
+ console.debug('[installLauncherUpdate] 开始执行安装更新');
+
+ // 如果是开发模式,不执行更新
+ // const isPackaged = app.isPackaged;
+ // if (!isPackaged) {
+ // return {
+ // success: false,
+ // message: '开发模式下不支持自动更新,请手动解压',
+ // };
+ // }
+
+ try {
+ console.debug('[installLauncherUpdate] 获取最新版本信息...');
+ const latestVersionInfo = getLatestVersion(
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+ const version = latestVersionInfo.version;
+ console.debug('[installLauncherUpdate] 最新版本:', version);
+
+ const downloadPath = path.join(
+ appPath,
+ 'external-resources',
+ 'dlc',
+ 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ version,
+ );
+ console.debug('[installLauncherUpdate] 下载路径:', downloadPath);
+ console.debug(
+ '[installLauncherUpdate] 下载路径是否存在:',
+ existsSync(downloadPath),
+ );
+
+ const files = readdirSync(downloadPath);
+ console.debug('[installLauncherUpdate] 下载目录文件列表:', files);
+ const zipFile = files.find((f) => f.endsWith('.zip'));
+
+ if (!zipFile) {
+ throw new Error('未找到下载的更新包');
+ }
+
+ const zipPath = path.join(downloadPath, zipFile);
+ console.debug('[installLauncherUpdate] zip文件路径:', zipPath);
+ console.debug(
+ '[installLauncherUpdate] zip文件是否存在:',
+ existsSync(zipPath),
+ );
+
+ const tempDir = path.join(appPath, 'update-temp');
+ console.debug('[installLauncherUpdate] 临时目录:', tempDir);
+ if (existsSync(tempDir)) {
+ console.debug('[installLauncherUpdate] 清理已存在的临时目录...');
+ rmSync(tempDir, { recursive: true, force: true });
+ console.debug('[installLauncherUpdate] 临时目录清理完成');
+ }
+ mkdirSync(tempDir, { recursive: true });
+ console.debug('[installLauncherUpdate] 临时目录已创建');
+
+ // 使用 adm-zip 解压 zip 文件
+ console.log('[installLauncherUpdate] 正在解压文件...');
+ console.log('[installLauncherUpdate] ZIP路径:', zipPath);
+ console.log('[installLauncherUpdate] 目标路径:', tempDir);
+
+ try {
+ const zip = new AdmZip(zipPath);
+ const zipEntries = zip.getEntries();
+ console.log(
+ '[installLauncherUpdate] ZIP包含的文件数量:',
+ zipEntries.length,
+ );
+ console.log(
+ '[installLauncherUpdate] ZIP包含的文件列表:',
+ zipEntries.slice(0, 5).map((e) => e.entryName),
+ );
+
+ // 手动解压每个文件,使用 original-fs 避免 asar 打包问题和 Windows 下的 chmod 问题
+ for (const entry of zipEntries) {
+ const entryPath = path.join(tempDir, entry.entryName);
+ if (entry.isDirectory) {
+ // 创建目录
+ if (!originalFs.existsSync(entryPath)) {
+ originalFs.mkdirSync(entryPath, { recursive: true });
+ }
+ } else {
+ // 确保父目录存在
+ const parentDir = path.dirname(entryPath);
+ if (!originalFs.existsSync(parentDir)) {
+ originalFs.mkdirSync(parentDir, { recursive: true });
+ }
+ // 写入文件
+ const content = entry.getData();
+ originalFs.writeFileSync(entryPath, content);
+ }
+ }
+ console.log('[installLauncherUpdate] 解压完成');
+ } catch (extractError) {
+ console.error('[installLauncherUpdate] 解压失败:', extractError);
+ console.error(
+ '[installLauncherUpdate] 解压错误堆栈:',
+ extractError instanceof Error ? extractError.stack : '无堆栈信息',
+ );
+ throw extractError;
+ }
+
+ // 查找解压后的目录(通常会有一个子目录)
+ const extractedItems = readdirSync(tempDir);
+ console.log(
+ '[installLauncherUpdate] 解压后的文件数量:',
+ extractedItems.length,
+ );
+ console.log('[installLauncherUpdate] 解压后的文件列表:', extractedItems);
+
+ let sourceDir = tempDir;
+ if (extractedItems.length === 1) {
+ const subPath = path.join(tempDir, extractedItems[0]);
+ const subStat = statSync(subPath);
+ console.log('[installLauncherUpdate] 检查子路径:', subPath);
+ console.log('[installLauncherUpdate] 是否为目录:', subStat.isDirectory());
+ if (subStat.isDirectory()) {
+ sourceDir = subPath;
+ const subItems = readdirSync(sourceDir);
+ console.log('[installLauncherUpdate] 子目录文件列表:', subItems);
+ }
+ }
+
+ console.log('[installLauncherUpdate] 解压源目录:', sourceDir);
+
+ const findExe = (dir: string): string | null => {
+ const items = readdirSync(dir);
+ for (const item of items) {
+ const fullPath = path.join(dir, item);
+ const stat = statSync(fullPath);
+ if (stat.isDirectory()) {
+ const result = findExe(fullPath);
+ if (result) return result;
+ } else if (
+ item.endsWith('.exe') &&
+ item.includes('AI-Learning-Assistant-Launcher')
+ ) {
+ return fullPath;
+ }
+ }
+ return null;
+ };
+
+ const newExePath = findExe(tempDir);
+
+ if (!newExePath) {
+ throw new Error('未在更新包中找到启动器可执行文件');
+ }
+
+ console.debug('找到新版本可执行文件:', newExePath);
+
+ const currentExePath = app.getPath('exe');
+ const currentExeDir = path.dirname(currentExePath);
+ const currentExeName = path.basename(currentExePath);
+
+ const backupPath = path.join(currentExeDir, `${currentExeName}.old`);
+
+ const updateScriptPath = path.join(appPath, 'update.bat');
+ const updateScript = `@echo off
+chcp 65001
+echo 正在更新启动器...
+timeout /t 2 /nobreak >nul
+
+REM 删除旧备份
+if exist "${backupPath}" del /f /q "${backupPath}"
+
+REM 备份当前版本
+move /y "${currentExePath}" "${backupPath}"
+
+REM 复制新版本
+copy /y "${newExePath}" "${currentExePath}"
+
+REM 清理临时文件
+rmdir /s /q "${tempDir}"
+
+REM 启动新版本
+start "" "${currentExePath}"
+
+REM 删除更新脚本自身
+(goto) 2>nul & del "%~f0"
+`;
+
+ writeFileSync(updateScriptPath, updateScript, { encoding: 'utf8' });
+
+ console.debug('更新脚本已创建:', updateScriptPath);
+
+ const result = await dialog.showMessageBox({
+ type: 'info',
+ title: '准备更新',
+ message: '启动器将在关闭后自动更新',
+ detail: `当前版本: ${currentVersion}\n新版本: ${version}\n\n点击"确定"后程序将关闭并自动更新,更新完成后会自动重启。`,
+ buttons: ['确定', '取消'],
+ defaultId: 0,
+ cancelId: 1,
+ });
+
+ if (result.response === 0) {
+ spawn('cmd.exe', ['/c', updateScriptPath], {
+ detached: true,
+ stdio: 'ignore',
+ windowsHide: true,
+ });
+
+ setTimeout(() => {
+ app.quit();
+ }, 1000);
+
+ return {
+ success: true,
+ message: '正在更新启动器...',
+ };
+ } else {
+ return {
+ success: false,
+ message: '用户取消更新',
+ };
+ }
+ } catch (error) {
+ console.error('[installLauncherUpdate] 安装启动器更新失败:', error);
+ console.error(
+ '[installLauncherUpdate] 错误堆栈:',
+ error instanceof Error ? error.stack : '无堆栈信息',
+ );
+ throw error;
+ }
+}
diff --git a/src/main/launcher-update/type-info.ts b/src/main/launcher-update/type-info.ts
new file mode 100644
index 0000000..d43ca0e
--- /dev/null
+++ b/src/main/launcher-update/type-info.ts
@@ -0,0 +1,9 @@
+import type { Channels } from '../ipc-data-type';
+
+export const channel: Channels = 'launcher-update';
+
+export const checkLauncherUpdateHandle = `${channel}checkUpdate`;
+
+export const downloadLauncherUpdateHandle = `${channel}downloadUpdate`;
+
+export const installLauncherUpdateHandle = `${channel}installUpdate`;
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 7dc73b2..5956736 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -30,6 +30,11 @@ import {
startWebtorrentHandle,
DLCId,
} from './dlc/type-info';
+import {
+ checkLauncherUpdateHandle,
+ downloadLauncherUpdateHandle,
+ installLauncherUpdateHandle,
+} from './launcher-update/type-info';
const electronHandler = {
ipcRenderer: {
@@ -130,7 +135,9 @@ const mainHandle = {
);
},
startWebtorrentHandle: async (url: string) => {
- return ipcInvoke(startWebtorrentHandle, url);
+ return ipcInvoke<
+ { success: true; infoHash: string } | { success: false; error: string }
+ >(startWebtorrentHandle, url);
},
queryWebtorrentHandle: async () => {
return ipcInvoke(queryWebtorrentHandle);
@@ -144,6 +151,27 @@ const mainHandle = {
logsWebtorrentHandle: async (url: string) => {
return ipcInvoke(logsWebtorrentHandle, url);
},
+ checkLauncherUpdateHandle: async () => {
+ return ipcInvoke<{
+ currentVersion: string;
+ latestVersion: string;
+ haveNew: boolean;
+ }>(checkLauncherUpdateHandle);
+ },
+ downloadLauncherUpdateHandle: async () => {
+ return ipcInvoke<{
+ success: boolean;
+ version: string;
+ filePath: string;
+ isDev: boolean;
+ }>(downloadLauncherUpdateHandle);
+ },
+ installLauncherUpdateHandle: async () => {
+ return ipcInvoke<{
+ success: boolean;
+ message: string;
+ }>(installLauncherUpdateHandle);
+ },
};
export type MainHandle = typeof mainHandle;
diff --git a/src/main/training-service/index.ts b/src/main/training-service/index.ts
index 562e30c..8f7aace 100644
--- a/src/main/training-service/index.ts
+++ b/src/main/training-service/index.ts
@@ -79,8 +79,8 @@ const createWindow = (): void => {
export async function installTrainingService() {
const latestVersion = getLatestVersion('TRAINING_TAR');
- const torrent = await startWebtorrent(latestVersion.dlcInfo.magnet);
- await waitTorrentDone('TRAINING_TAR', latestVersion.version);
+ await startWebtorrent(latestVersion.dlcInfo.magnet);
+ const torrent = await waitTorrentDone('TRAINING_TAR', latestVersion.version);
const tarPath = path.join(torrent.path, torrent.files[0].name);
return installService('TRAINING', null, tarPath);
}
@@ -117,8 +117,11 @@ export async function updateCourseTrainingService() {
}
if ((await courseHaveNewVersionTrainingService()).haveNew) {
const latestVersion = getLatestVersion('TRAINING_TAR');
- const torrent = await startWebtorrent(latestVersion.dlcInfo.magnet);
- await waitTorrentDone('TRAINING_TAR', latestVersion.version);
+ await startWebtorrent(latestVersion.dlcInfo.magnet);
+ const torrent = await waitTorrentDone(
+ 'TRAINING_TAR',
+ latestVersion.version,
+ );
try {
await stopService('TRAINING');
await removeService('TRAINING');
diff --git a/src/renderer/pages/hello/index.scss b/src/renderer/pages/hello/index.scss
index e3bc9f6..fec4dba 100644
--- a/src/renderer/pages/hello/index.scss
+++ b/src/renderer/pages/hello/index.scss
@@ -621,6 +621,15 @@ body{
transform: translateY(-2px);
}
+ // 更新按钮使用蓝色
+ &.update-button {
+ background: #1677ff;
+
+ &:hover {
+ background: #1677ff;
+ }
+ }
+
.log-text {
display: flex;
flex-direction: row;
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 1497ae8..49e812c 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -152,6 +152,7 @@ export default function Hello() {
useEffect(() => {
startAutoSlide();
+ checkLauncherUpdate();
return () => {
clearSlideInterval();
@@ -189,6 +190,12 @@ export default function Hello() {
};
const [trainingServiceRemoving, setTrainingServiceRemoving] = useState(false);
+ const [launcherUpdateInfo, setLauncherUpdateInfo] = useState<{
+ currentVersion: string;
+ latestVersion: string;
+ haveNew: boolean;
+ } | null>(null);
+ const [launcherUpdating, setLauncherUpdating] = useState(false);
const removeTrainingService = async () => {
setTrainingServiceRemoving(true);
@@ -205,6 +212,50 @@ export default function Hello() {
setTrainingServiceRemoving(false);
};
+ const checkLauncherUpdate = async () => {
+ try {
+ const info = await window.mainHandle.checkLauncherUpdateHandle();
+ setLauncherUpdateInfo(info);
+ } catch (error) {
+ console.error('检查启动器更新失败:', error);
+ }
+ };
+
+ const handleLauncherUpdate = async () => {
+ if (!launcherUpdateInfo || !launcherUpdateInfo.haveNew) {
+ message.info('已是最新版本');
+ return;
+ }
+
+ setLauncherUpdating(true);
+ const hideLoading = message.loading('正在下载更新包...', 0);
+ try {
+ const downloadResult =
+ await window.mainHandle.downloadLauncherUpdateHandle();
+ hideLoading();
+
+ // 开发模式下不执行安装,直接提示
+ if (downloadResult.isDev) {
+ message.warning('开发模式下不支持自动更新,请手动解压');
+ return;
+ }
+
+ message.success('下载完成,准备安装...');
+ const result = await window.mainHandle.installLauncherUpdateHandle();
+ if (result.success) {
+ message.success(result.message);
+ } else {
+ message.warning(result.message);
+ }
+ } catch (error) {
+ hideLoading();
+ console.error('更新启动器失败:', error);
+ message.error('更新失败:' + error.message);
+ } finally {
+ setLauncherUpdating(false);
+ }
+ };
+
const wslStatusText = () => {
if (!vTReady) {
return '请在BIOS开启虚拟化';
@@ -623,9 +674,18 @@ export default function Hello() {
版本号:{__NPM_PACKAGE_VERSION__} 源码版本:{__COMMIT_HASH__}
-
+ {/*
P2P测试
-
+ */}
+
+
+ {launcherUpdateInfo?.haveNew ? '更新' : ''}
+
+
(null);
+
+ useEffect(() => {
+ checkLMStudioUpdate();
+ }, []);
+
+ useEffect(() => {
+ const intervalId = setInterval(() => {
+ if (downloading && !isDownloadComplete) {
+ updateDownloadProgress();
+ }
+ }, 2000);
+
+ return () => clearInterval(intervalId);
+ }, [downloading, isDownloadComplete]);
+
+ const checkLMStudioUpdate = async () => {
+ // 检查是否已经有下载完成的文件
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const lmStudioDLC = dlcIndex.find(
+ (item) => item.id === 'LM_STUDIO_SETUP_EXE',
+ );
+ if (lmStudioDLC) {
+ const version = Object.keys(lmStudioDLC.versions).sort().pop();
+ if (version) {
+ setLatestVersion(version);
+ const versionInfo = lmStudioDLC.versions[version];
+ if (versionInfo.progress && versionInfo.progress.progress >= 1) {
+ setIsDownloadComplete(true);
+ setDownloadProgress(100);
+ }
+ }
+ }
+ } catch (error) {
+ console.error('检查LM Studio下载状态失败:', error);
+ }
+ };
+
+ const updateDownloadProgress = async () => {
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const lmStudioDLC = dlcIndex.find(
+ (item) => item.id === 'LM_STUDIO_SETUP_EXE',
+ );
+ if (lmStudioDLC) {
+ const version = Object.keys(lmStudioDLC.versions).sort().pop();
+ if (version) {
+ const versionInfo = lmStudioDLC.versions[version];
+ if (versionInfo.progress) {
+ const progress = versionInfo.progress.progress || 0;
+ setDownloadProgress(Math.floor(progress * 100));
+ if (progress >= 1) {
+ setDownloading(false);
+ setIsDownloadComplete(true);
+ message.success('LM Studio下载完成,可以点击安装按钮进行安装');
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error('获取LM Studio下载进度失败:', error);
+ }
+ };
+
+ const handleDownloadOrInstallLMStudio = async () => {
+ // 如果已经下载完成,直接安装
+ if (isDownloadComplete) {
+ clickCmd('install', 'lm-studio');
+ return;
+ }
+
+ // 否则开始下载
+ try {
+ setDownloading(true);
+ setDownloadProgress(0);
+ message.info('正在启动下载LM Studio...');
+
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const lmStudioDLC = dlcIndex.find(
+ (item) => item.id === 'LM_STUDIO_SETUP_EXE',
+ );
+
+ if (!lmStudioDLC) {
+ throw new Error('未找到LM Studio安装包信息');
+ }
+
+ const version = Object.keys(lmStudioDLC.versions).sort().pop();
+ if (!version) {
+ throw new Error('未找到可用版本');
+ }
+
+ setLatestVersion(version);
+ const versionInfo = lmStudioDLC.versions[version];
+ const magnet = versionInfo.magnet;
+
+ const result = await window.mainHandle.startWebtorrentHandle(magnet);
+ if (result.success) {
+ message.success(`开始下载LM Studio ${version}`);
+ } else {
+ throw new Error('error' in result ? result.error : '启动下载失败');
+ }
+ } catch (error) {
+ console.error('下载LM Studio失败:', error);
+ message.error('下载失败:' + (error as Error).message);
+ setDownloading(false);
+ }
+ };
// const llmContainer = containers.filter(
// (item) => item.Names.indexOf() >= 0,
// )[0];
@@ -144,22 +265,59 @@ export default function LMService() {
-
clickCmd('install', 'lm-studio')}
- >
- {isInstallLMStudio
- ? '已安装LMStudio'
- : '开启本地大模型前请点我安装LMStudio'}
-
+ {/* 更新/安装 LM Studio */}
+ {isInstallLMStudio ? (
+
+ 已安装LMStudio
+
+ ) : (
+
+
+ {isDownloadComplete
+ ? `安装 ${latestVersion || ''}`
+ : downloading
+ ? '下载中...'
+ : '下载并安装LMStudio'}
+
+ {(downloading || isDownloadComplete) && (
+
+
+ {isDownloadComplete
+ ? '下载完成'
+ : `${downloadProgress}%`}
+
+
+
+ )}
+
+ )}
}
diff --git a/src/renderer/pages/obsidian-app/index.scss b/src/renderer/pages/obsidian-app/index.scss
index 7af0edb..c81c88d 100644
--- a/src/renderer/pages/obsidian-app/index.scss
+++ b/src/renderer/pages/obsidian-app/index.scss
@@ -13,4 +13,53 @@
display: flex;
justify-content: space-between;
}
+
+ .download-wrapper {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 6px 12px;
+ background: #FFFFFF;
+ border: 1px solid #E5E5E5;
+ border-radius: 8px;
+ box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
+ transition: all 0.3s ease;
+
+ &:hover {
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
+ }
+
+ &.downloading {
+ border-color: #1677ff;
+ background: #f0f5ff;
+ }
+
+ &.complete {
+ border-color: #52c41a;
+ background: #f6ffed;
+ }
+
+ .download-progress-container {
+ display: flex;
+ flex-direction: column;
+ min-width: 100px;
+
+ .progress-text {
+ font-family: 'Inter', sans-serif;
+ font-size: 12px;
+ font-weight: 500;
+ color: #737373;
+ margin-bottom: 2px;
+ text-align: center;
+ }
+
+ .ant-progress {
+ margin: 0;
+
+ .ant-progress-inner {
+ background-color: #E5E5E5;
+ }
+ }
+ }
+ }
}
diff --git a/src/renderer/pages/obsidian-app/index.tsx b/src/renderer/pages/obsidian-app/index.tsx
index ead9183..245f6df 100644
--- a/src/renderer/pages/obsidian-app/index.tsx
+++ b/src/renderer/pages/obsidian-app/index.tsx
@@ -1,5 +1,5 @@
-import { useCallback } from 'react';
-import { Button, List, Skeleton } from 'antd';
+import { useCallback, useEffect, useState } from 'react';
+import { Button, List, Skeleton, message, Progress } from 'antd';
import { Link, NavLink } from 'react-router-dom';
import './index.scss';
import { channel } from '../../../main/cmd/type-info';
@@ -19,6 +19,145 @@ export default function ObsidianApp() {
loading: configsLoading,
} = useConfigs();
+ const [downloading, setDownloading] = useState(false);
+ const [downloadProgress, setDownloadProgress] = useState(0);
+ const [isDownloadComplete, setIsDownloadComplete] = useState(false);
+
+ useEffect(() => {
+ checkObsidianUpdate();
+ }, []);
+
+ useEffect(() => {
+ const intervalId = setInterval(() => {
+ if (downloading && !isDownloadComplete) {
+ updateDownloadProgress();
+ }
+ }, 2000);
+
+ return () => clearInterval(intervalId);
+ }, [downloading, isDownloadComplete]);
+
+ const checkObsidianUpdate = async () => {
+ // 检查是否已经有下载完成的文件
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const obsidianDLC = dlcIndex.find(
+ (item) => item.id === 'OBSIDIAN_SETUP_EXE',
+ );
+ if (obsidianDLC) {
+ const latestVersion = Object.keys(obsidianDLC.versions).sort().pop();
+ if (latestVersion) {
+ const versionInfo = obsidianDLC.versions[latestVersion];
+ if (versionInfo.progress && versionInfo.progress.progress >= 1) {
+ setIsDownloadComplete(true);
+ setDownloadProgress(100);
+ }
+ }
+ }
+ } catch (error) {
+ console.error('检查下载状态失败:', error);
+ }
+ };
+
+ const updateDownloadProgress = async () => {
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const obsidianDLC = dlcIndex.find(
+ (item) => item.id === 'OBSIDIAN_SETUP_EXE',
+ );
+ if (obsidianDLC) {
+ const latestVersion = Object.keys(obsidianDLC.versions).sort().pop();
+ if (latestVersion) {
+ const versionInfo = obsidianDLC.versions[latestVersion];
+ if (versionInfo.progress) {
+ const progress = versionInfo.progress.progress || 0;
+ setDownloadProgress(Math.floor(progress * 100));
+ if (progress >= 1) {
+ setDownloading(false);
+ setIsDownloadComplete(true);
+ message.success('下载完成,可以点击安装按钮进行安装');
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error('获取下载进度失败:', error);
+ }
+ };
+
+ const handleDownloadOrInstall = async () => {
+ // 如果已经下载完成,直接安装
+ if (isDownloadComplete) {
+ handleInstallObsidian();
+ return;
+ }
+
+ // 否则开始下载
+ try {
+ setDownloading(true);
+ setDownloadProgress(0);
+ message.info('正在启动下载...');
+
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const obsidianDLC = dlcIndex.find(
+ (item) => item.id === 'OBSIDIAN_SETUP_EXE',
+ );
+
+ if (!obsidianDLC) {
+ throw new Error('未找到Obsidian安装包信息');
+ }
+
+ const latestVersion = Object.keys(obsidianDLC.versions).sort().pop();
+ if (!latestVersion) {
+ throw new Error('未找到可用版本');
+ }
+
+ const versionInfo = obsidianDLC.versions[latestVersion];
+ const magnet = versionInfo.magnet;
+
+ const result = await window.mainHandle.startWebtorrentHandle(magnet);
+ if (result.success) {
+ message.success(`开始下载Obsidian ${latestVersion}`);
+ } else {
+ throw new Error('error' in result ? result.error : '启动下载失败');
+ }
+ } catch (error) {
+ console.error('下载Obsidian失败:', error);
+ message.error('下载失败:' + error.message);
+ setDownloading(false);
+ }
+ };
+
+ const handleInstallObsidian = async () => {
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const obsidianDLC = dlcIndex.find(
+ (item) => item.id === 'OBSIDIAN_SETUP_EXE',
+ );
+
+ if (!obsidianDLC) {
+ throw new Error('未找到Obsidian安装包');
+ }
+
+ const latestVersion = Object.keys(obsidianDLC.versions).sort().pop();
+ if (!latestVersion) {
+ throw new Error('未找到可用版本');
+ }
+
+ const versionInfo = obsidianDLC.versions[latestVersion];
+ if (!versionInfo.progress || versionInfo.progress.progress < 1) {
+ message.warning('请先完成下载');
+ return;
+ }
+
+ message.info('正在打开安装程序...');
+ cmdAction('install', 'obsidianApp');
+ } catch (error) {
+ console.error('打开安装程序失败:', error);
+ message.error('失败:' + error.message);
+ }
+ };
+
return (
cmdAction('install', 'obsidianApp')}
+
- 安装阅读器
-
+
+ {isDownloadComplete
+ ? '安装Obsidian'
+ : downloading
+ ? '下载中...'
+ : '更新Obsidian'}
+
+ {(downloading || isDownloadComplete) && (
+
+
+ {isDownloadComplete ? '下载完成' : `${downloadProgress}%`}
+
+
+
+ )}
+
),
configsAction('update', 'obsidianApp')}
>
- {isInstallObsidian ? '重新定位阅读器' : '定位阅读器'}
+ {isInstallObsidian ? '重新定位Obsidian' : '定位Obsidian'}
,
isInstallObsidian && (
cmdAction('start', 'obsidianApp')}
>
- 运行阅读器
+ 运行Obsidian
+
+ ),
+ ].filter((item) => item)}
+ >
+
+
+ {/* 老版本兼容 - 直接安装 */}
+ {/* 老版本兼容 - 直接安装 */}
+ cmdAction('install', 'obsidianApp')}
+ >
+ 本地安装
),
].filter((item) => item)}
>
From 17472b568ffa22a2762a7bf2d520f3c95dd970c4 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Fri, 23 Jan 2026 15:16:28 +0800
Subject: [PATCH 09/22] fix: prevent duplicate resolve and file handle leaks in
WebTorrent operations
---
src/main/cmd/index.ts | 420 ++++++++++++++++--------------
src/main/dlc/index.ts | 22 +-
src/main/launcher-update/index.ts | 20 +-
3 files changed, 245 insertions(+), 217 deletions(-)
diff --git a/src/main/cmd/index.ts b/src/main/cmd/index.ts
index bd84404..2a429c6 100644
--- a/src/main/cmd/index.ts
+++ b/src/main/cmd/index.ts
@@ -24,6 +24,211 @@ import { podMachineName } from '../podman-desktop/type-info';
import { isVTReady, isWSLInstall, wslVersion } from './is-wsl-install';
import { loggerFactory } from '../terminal-log';
+/**
+ * 安装程序相关的时间配置(单位:毫秒)
+ */
+const INSTALL_TIMING_CONFIG = {
+ /** 销毁 torrent 后等待文件句柄释放的时间 */
+ TORRENT_DESTROY_WAIT: 2000,
+ /** 重试的基础等待时间 */
+ RETRY_BASE_WAIT: 1000,
+ /** 每次重试增加的等待时间 */
+ RETRY_INCREMENT: 1000,
+ /** 所有重试失败后的最终等待时间 */
+ FINAL_RETRY_WAIT: 10000,
+ /** 最大重试次数 */
+ MAX_RETRIES: 8,
+ /** LM Studio 安装检测命令超时时间 */
+ LM_STUDIO_CHECK_TIMEOUT: 15000,
+} as const;
+
+/**
+ * 判断错误是否为 EBUSY 错误(文件被占用)
+ */
+function isEBUSYError(error: unknown): boolean {
+ if (error instanceof Error) {
+ return error.message.includes('EBUSY');
+ }
+ if (typeof error === 'object' && error !== null && 'message' in error) {
+ return String((error as { message: unknown }).message).includes('EBUSY');
+ }
+ return false;
+}
+
+/**
+ * 获取错误信息
+ */
+function getErrorMessage(error: unknown): string {
+ if (error instanceof Error) {
+ return error.message;
+ }
+ if (typeof error === 'string') {
+ return error;
+ }
+ return String(error);
+}
+
+interface RetryExecuteOptions {
+ /** 执行的程序路径 */
+ exePath: string;
+ /** 执行参数 */
+ args: string[];
+ /** 日志前缀,用于区分不同的安装程序 */
+ logPrefix: string;
+}
+
+/**
+ * 带重试机制执行安装程序
+ * 处理 EBUSY 错误(文件被占用)的情况
+ */
+async function executeWithRetry(
+ options: RetryExecuteOptions,
+): Promise {
+ const { exePath, args, logPrefix } = options;
+ const maxRetries = INSTALL_TIMING_CONFIG.MAX_RETRIES;
+
+ for (let retry = 0; retry < maxRetries; retry++) {
+ try {
+ // 每次重试前等待更长时间,让文件句柄有充足时间释放
+ const waitTime =
+ retry === 0
+ ? INSTALL_TIMING_CONFIG.RETRY_BASE_WAIT
+ : INSTALL_TIMING_CONFIG.RETRY_BASE_WAIT +
+ retry * INSTALL_TIMING_CONFIG.RETRY_INCREMENT;
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
+
+ console.debug(
+ `[${logPrefix}] 尝试执行安装程序 (${retry + 1}/${maxRetries}),等待了 ${waitTime}ms`,
+ );
+ const result = await commandLine.exec(exePath, args);
+ console.debug(`[${logPrefix}] 安装程序执行成功:`, result);
+ return true;
+ } catch (e) {
+ if (isEBUSYError(e) && retry < maxRetries - 1) {
+ console.warn(
+ `[${logPrefix}] 执行安装程序失败 (EBUSY),重试 ${retry + 1}/${maxRetries}...`,
+ );
+ } else if (isEBUSYError(e)) {
+ // 所有重试都失败了,尝试最后一次长等待
+ console.warn(`[${logPrefix}] 所有重试都失败,尝试最后一次长等待...`);
+ await new Promise((resolve) =>
+ setTimeout(resolve, INSTALL_TIMING_CONFIG.FINAL_RETRY_WAIT),
+ );
+ try {
+ const result = await commandLine.exec(exePath, args);
+ console.debug(`[${logPrefix}] 最后一次尝试成功:`, result);
+ return true;
+ } catch (finalError) {
+ console.error(`[${logPrefix}] 最后一次尝试也失败了:`, finalError);
+ throw finalError;
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ return false;
+}
+
+interface P2PInstallOptions {
+ /** DLC 索引中的 key */
+ dlcKey:
+ | 'OBSIDIAN_SETUP_EXE'
+ | 'LM_STUDIO_SETUP_EXE'
+ | 'PDF_TAR'
+ | 'VOICE_TAR'
+ | 'TRAINING_TAR'
+ | 'TRAINING_VOICE_TAR'
+ | 'TRAINING_COURSE'
+ | 'LMSTUDIO_WINDOWS'
+ | 'AI_LEARNING_ASSISTANT_LAUNCHER'
+ | 'TEST_FILE';
+ /** 安装程序参数 */
+ installerArgs: string[];
+ /** 日志前缀 */
+ logPrefix: string;
+ /** 降级时使用的本地安装包相对路径 */
+ fallbackInstallerPath: string;
+}
+
+/**
+ * 通用的 P2P 下载安装逻辑
+ * 优先使用 P2P 下载的安装包,失败时降级到本地安装包
+ */
+async function installFromP2POrFallback(
+ options: P2PInstallOptions,
+): Promise {
+ const { dlcKey, installerArgs, logPrefix, fallbackInstallerPath } = options;
+
+ try {
+ // 首先检查是否有P2P下载的文件
+ const { getDLCFromDLCIndex, destroyWebtorrentForInstall } = await import(
+ '../dlc'
+ );
+ const dlcInfo = getDLCFromDLCIndex(dlcKey);
+
+ if (dlcInfo) {
+ const latestVersion = Object.keys(dlcInfo.versions).sort().pop();
+ if (latestVersion) {
+ const versionInfo = dlcInfo.versions[latestVersion];
+
+ // 完全销毁 torrent 以释放文件句柄,避免 EBUSY 错误
+ if (versionInfo.magnet) {
+ try {
+ console.debug(`[${logPrefix}] 开始销毁种子以释放文件句柄...`);
+ await destroyWebtorrentForInstall(versionInfo.magnet);
+ // 等待额外的时间确保文件句柄完全释放
+ await new Promise((resolve) =>
+ setTimeout(resolve, INSTALL_TIMING_CONFIG.TORRENT_DESTROY_WAIT),
+ );
+ console.debug(`[${logPrefix}] 种子已销毁,等待完成`);
+ } catch (e) {
+ console.warn(`[${logPrefix}] 销毁torrent失败:`, e);
+ }
+ }
+
+ const downloadPath = path.join(
+ appPath,
+ 'external-resources',
+ 'dlc',
+ dlcKey,
+ latestVersion,
+ );
+
+ const fs = await import('fs');
+ if (fs.existsSync(downloadPath)) {
+ const files = fs.readdirSync(downloadPath);
+ const exeFile = files.find((f: string) => f.endsWith('.exe'));
+
+ if (exeFile) {
+ const exePath = path.join(downloadPath, exeFile);
+ return await executeWithRetry({
+ exePath,
+ args: installerArgs,
+ logPrefix,
+ });
+ }
+ }
+ }
+ }
+ } catch (error) {
+ console.error(`[${logPrefix}] 尝试使用P2P下载包失败,使用默认方式:`, error);
+ }
+
+ // 降级到原有的本地安装包方式
+ const result = await commandLine.exec(
+ path.join(
+ appPath,
+ 'external-resources',
+ 'ai-assistant-backend',
+ fallbackInstallerPath,
+ ),
+ installerArgs,
+ );
+ console.debug(`[${logPrefix}] 本地安装包执行结果:`, result);
+ return true;
+}
+
const commandLine = new Exec();
export default async function init(ipcMain: IpcMain) {
@@ -488,104 +693,12 @@ async function checkWSLComponent() {
}
export async function installObsidian() {
- try {
- // 首先检查是否有P2P下载的文件
- const { getDLCFromDLCIndex, destroyWebtorrentForInstall } = await import(
- '../dlc'
- );
- const dlcInfo = getDLCFromDLCIndex('OBSIDIAN_SETUP_EXE');
-
- if (dlcInfo) {
- const latestVersion = Object.keys(dlcInfo.versions).sort().pop();
- if (latestVersion) {
- const versionInfo = dlcInfo.versions[latestVersion];
-
- // 完全销毁 torrent 以释放文件句柄,避免 EBUSY 错误
- if (versionInfo.magnet) {
- try {
- console.debug('开始销毁种子以释放文件句柄...');
- await destroyWebtorrentForInstall(versionInfo.magnet);
- // 等待额外的时间确保文件句柄完全释放
- await new Promise((resolve) => setTimeout(resolve, 2000));
- console.debug('种子已销毁,等待完成');
- } catch (e) {
- console.warn('销毁torrent失败:', e);
- }
- }
-
- const downloadPath = path.join(
- appPath,
- 'external-resources',
- 'dlc',
- 'OBSIDIAN_SETUP_EXE',
- latestVersion,
- );
-
- const fs = await import('fs');
- if (fs.existsSync(downloadPath)) {
- const files = fs.readdirSync(downloadPath);
- const exeFile = files.find((f: string) => f.endsWith('.exe'));
-
- if (exeFile) {
- const exePath = path.join(downloadPath, exeFile);
-
- // 使用更激进的重试机制处理 EBUSY 错误
- const maxRetries = 8;
- for (let retry = 0; retry < maxRetries; retry++) {
- try {
- // 每次重试前等待更长时间,让文件句柄有充足时间释放
- const waitTime = retry === 0 ? 1000 : 1000 + retry * 1000;
- await new Promise((resolve) => setTimeout(resolve, waitTime));
-
- console.debug(
- `尝试执行安装程序 (${retry + 1}/${maxRetries}),等待了 ${waitTime}ms`,
- );
- const result = await commandLine.exec(exePath, ['/s']);
- console.debug('安装程序执行成功:', result);
- return true;
- } catch (e) {
- const isEBUSY = e.message && e.message.includes('EBUSY');
- if (isEBUSY && retry < maxRetries - 1) {
- console.warn(
- `执行安装程序失败 (EBUSY),重试 ${retry + 1}/${maxRetries}...`,
- );
- } else if (isEBUSY) {
- // 所有重试都失败了,尝试最后一次长等待
- console.warn('所有重试都失败,尝试最后一次长等待(10秒)...');
- await new Promise((resolve) => setTimeout(resolve, 10000));
- try {
- const result = await commandLine.exec(exePath, ['/s']);
- console.debug('最后一次尝试成功:', result);
- return true;
- } catch (finalError) {
- console.error('最后一次尝试也失败了:', finalError);
- throw finalError;
- }
- } else {
- throw e;
- }
- }
- }
- }
- }
- }
- }
- } catch (error) {
- console.error('尝试使用P2P下载包失败,使用默认方式:', error);
- }
-
- // 降级到原有的本地安装包方式
- const result = await commandLine.exec(
- path.join(
- appPath,
- 'external-resources',
- 'ai-assistant-backend',
- 'install_obsidian.exe',
- ),
- ['/s'],
- );
- console.debug(result);
- return true;
+ return installFromP2POrFallback({
+ dlcKey: 'OBSIDIAN_SETUP_EXE',
+ installerArgs: ['/s'],
+ logPrefix: 'Obsidian',
+ fallbackInstallerPath: 'install_obsidian.exe',
+ });
}
export async function isObsidianInstall() {
@@ -607,113 +720,24 @@ export async function isObsidianInstall() {
}
export async function installLMStudio() {
- try {
- // 首先检查是否有P2P下载的文件
- const { getDLCFromDLCIndex, destroyWebtorrentForInstall } = await import(
- '../dlc'
- );
- const dlcInfo = getDLCFromDLCIndex('LM_STUDIO_SETUP_EXE');
-
- if (dlcInfo) {
- const latestVersion = Object.keys(dlcInfo.versions).sort().pop();
- if (latestVersion) {
- const versionInfo = dlcInfo.versions[latestVersion];
-
- // 完全销毁 torrent 以释放文件句柄,避免 EBUSY 错误
- if (versionInfo.magnet) {
- try {
- console.debug('开始销毁LM Studio种子以释放文件句柄...');
- await destroyWebtorrentForInstall(versionInfo.magnet);
- // 等待额外的时间确保文件句柄完全释放
- await new Promise((resolve) => setTimeout(resolve, 2000));
- console.debug('种子已销毁,等待完成');
- } catch (e) {
- console.warn('销毁torrent失败:', e);
- }
- }
-
- const downloadPath = path.join(
- appPath,
- 'external-resources',
- 'dlc',
- 'LM_STUDIO_SETUP_EXE',
- latestVersion,
- );
-
- const fs = await import('fs');
- if (fs.existsSync(downloadPath)) {
- const files = fs.readdirSync(downloadPath);
- const exeFile = files.find((f: string) => f.endsWith('.exe'));
-
- if (exeFile) {
- const exePath = path.join(downloadPath, exeFile);
-
- // 使用更激进的重试机制处理 EBUSY 错误
- const maxRetries = 8;
- for (let retry = 0; retry < maxRetries; retry++) {
- try {
- // 每次重试前等待更长时间,让文件句柄有充足时间释放
- const waitTime = retry === 0 ? 1000 : 1000 + retry * 1000;
- await new Promise((resolve) => setTimeout(resolve, waitTime));
-
- console.debug(
- `尝试执行LM Studio安装程序 (${retry + 1}/${maxRetries}),等待了 ${waitTime}ms`,
- );
- const result = await commandLine.exec(exePath, []);
- console.debug('LM Studio安装程序执行成功:', result);
- return true;
- } catch (e) {
- const isEBUSY = e.message && e.message.includes('EBUSY');
- if (isEBUSY && retry < maxRetries - 1) {
- console.warn(
- `执行LM Studio安装程序失败 (EBUSY),重试 ${retry + 1}/${maxRetries}...`,
- );
- } else if (isEBUSY) {
- // 所有重试都失败了,尝试最后一次长等待
- console.warn('所有重试都失败,尝试最后一次长等待(10秒)...');
- await new Promise((resolve) => setTimeout(resolve, 10000));
- try {
- const result = await commandLine.exec(exePath, []);
- console.debug('最后一次尝试成功:', result);
- return true;
- } catch (finalError) {
- console.error('最后一次尝试也失败了:', finalError);
- throw finalError;
- }
- } else {
- throw e;
- }
- }
- }
- }
- }
- }
- }
- } catch (error) {
- console.error('尝试使用P2P下载包失败,使用默认方式:', error);
- }
-
- // 降级到原有的本地安装包方式
- const result = await commandLine.exec(
- path.join(
- appPath,
- 'external-resources',
- 'ai-assistant-backend',
- 'install_lm_studio.exe',
- ),
- ['/s'],
- );
- console.debug(result);
- return true;
+ return installFromP2POrFallback({
+ dlcKey: 'LM_STUDIO_SETUP_EXE',
+ installerArgs: [],
+ logPrefix: 'LM Studio',
+ fallbackInstallerPath: 'install_lm_studio.exe',
+ });
}
export async function isLMStudioInstall() {
try {
const result = await Promise.race([
new Promise((resolve, reject) =>
- setTimeout(() => reject('isLMStudioInstall命令超时'), 15000),
+ setTimeout(
+ () => reject('isLMStudioInstall命令超时'),
+ INSTALL_TIMING_CONFIG.LM_STUDIO_CHECK_TIMEOUT,
+ ),
),
- // 如果用户安装lmstudio然后又卸载了lmstudio,那么这个命令会一直卡着,也不报错,所以要用一个10000ms的报错promise与它竞赛
+ // 如果用户安装lmstudio然后又卸载了lmstudio,那么这个命令会一直卡着,也不报错,所以要用一个超时promise与它竞赛
commandLine.exec('lms', ['ls']),
]);
console.debug('isLMStudioInstall', result);
diff --git a/src/main/dlc/index.ts b/src/main/dlc/index.ts
index 5225291..ff5c825 100644
--- a/src/main/dlc/index.ts
+++ b/src/main/dlc/index.ts
@@ -164,20 +164,18 @@ export async function destroyWebtorrentForInstall(
const torrent = await client.get(magnet);
if (torrent) {
return new Promise((resolve) => {
- // 先暂停下载
- torrent.pause();
+ let resolved = false;
+ const safeResolve = () => {
+ if (!resolved) {
+ resolved = true;
+ resolve();
+ }
+ };
- // 销毁种子,这会关闭所有文件句柄
- torrent.destroy({ destroyStore: false }, () => {
- console.debug('种子已销毁,文件句柄已释放:', torrent.infoHash);
- resolve();
- });
+ torrent.pause();
+ torrent.destroy({ destroyStore: false }, safeResolve);
- // 设置一个超时,防止回调永远不执行
- setTimeout(() => {
- console.debug('destroy回调超时,强制resolve');
- resolve();
- }, 3000);
+ setTimeout(safeResolve, 3000);
});
}
}
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index 80867d2..4563f23 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -7,10 +7,11 @@ import {
} from './type-info';
import {
getLatestVersion,
- isLatestVersion,
startWebtorrent,
waitTorrentDone,
+ destroyWebtorrentForInstall,
} from '../dlc';
+import semver from 'semver';
import path from 'path';
import { appPath } from '../exec';
import {
@@ -29,9 +30,10 @@ import packageJson from '../../../package.json';
const currentVersion = packageJson.version;
// 在 Electron 中使用 original-fs 处理 asar 打包后的文件操作
-// original-fs 是 Electron 内置的,需要通过 require 动态导入
+// original-fs 是 Electron 内置模块,API 与 Node.js fs 模块相同
+// 使用类型注解确保类型安全
// eslint-disable-next-line @typescript-eslint/no-var-requires
-const originalFs = require('original-fs');
+const originalFs: typeof import('fs') = require('original-fs');
export default async function init(ipcMain: IpcMain) {
ipcHandle(ipcMain, checkLauncherUpdateHandle, async (_event) =>
@@ -51,10 +53,7 @@ export async function checkLauncherUpdate() {
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
const latestVersion = latestVersionInfo.version;
- const haveNew = !isLatestVersion(
- 'AI_LEARNING_ASSISTANT_LAUNCHER',
- currentVersion,
- );
+ const haveNew = semver.lt(currentVersion, latestVersion);
console.debug('检查启动器更新:', {
currentVersion,
@@ -153,6 +152,13 @@ export async function installLauncherUpdate() {
throw new Error('未找到下载的更新包');
}
+ // 销毁 WebTorrent 以释放文件句柄,避免后续读取 zip 文件时出现 EBUSY 错误
+ console.debug('[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...');
+ await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
+ // 等待文件句柄完全释放
+ await new Promise((resolve) => setTimeout(resolve, 500));
+ console.debug('[installLauncherUpdate] 文件句柄已释放');
+
const zipPath = path.join(downloadPath, zipFile);
console.debug('[installLauncherUpdate] zip文件路径:', zipPath);
console.debug(
From 726b3b5659c7de9ed43e0494156787e30b25ce29 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Fri, 23 Jan 2026 16:57:46 +0800
Subject: [PATCH 10/22] feat(webtorrent): add upload control capability
---
src/main/dlc/index.ts | 124 ++++++++++++++++++++++++++++++++++++++
src/main/dlc/type-info.ts | 6 ++
src/main/preload.ts | 20 ++++++
3 files changed, 150 insertions(+)
diff --git a/src/main/dlc/index.ts b/src/main/dlc/index.ts
index ff5c825..fcc4b4d 100644
--- a/src/main/dlc/index.ts
+++ b/src/main/dlc/index.ts
@@ -8,6 +8,9 @@ import {
DLCIndex,
DLCId,
OneDLCInfo,
+ setUploadEnabledHandle,
+ getUploadEnabledHandle,
+ getUploadStatsHandle,
} from './type-info';
import { ipcHandle } from '../ipc-util';
import WebTorrent, * as allExports from 'webtorrent';
@@ -17,6 +20,45 @@ import fs, { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
import { compare } from 'semver';
let client: WebTorrent.Instance | null = null;
+let uploadEnabled = true; // 默认开启上传
+
+// 上传配置文件路径
+const uploadConfigPath = path.join(
+ appPath,
+ 'external-resources',
+ 'dlc',
+ 'upload-config.json',
+);
+
+// 读取上传配置
+function loadUploadConfig(): boolean {
+ try {
+ if (existsSync(uploadConfigPath)) {
+ const config = JSON.parse(readFileSync(uploadConfigPath, 'utf8'));
+ return config.uploadEnabled ?? true;
+ }
+ } catch (error) {
+ console.error('读取上传配置失败:', error);
+ }
+ return true;
+}
+
+// 保存上传配置
+function saveUploadConfig(enabled: boolean): void {
+ try {
+ const dir = path.dirname(uploadConfigPath);
+ if (!existsSync(dir)) {
+ mkdirSync(dir, { recursive: true });
+ }
+ fs.writeFileSync(
+ uploadConfigPath,
+ JSON.stringify({ uploadEnabled: enabled }, null, 2),
+ );
+ } catch (error) {
+ console.error('保存上传配置失败:', error);
+ }
+}
+
const getWebTorrent = async () => {
const WebTorrentClass: WebTorrent.WebTorrent =
// @ts-ignore
@@ -59,7 +101,14 @@ const getWebTorrent = async () => {
// };
export default async function init(ipcMain: IpcMain) {
+ // 加载上传配置
+ uploadEnabled = loadUploadConfig();
+
client = await getWebTorrent();
+
+ // 根据配置应用上传限制
+ applyUploadThrottle();
+
ipcHandle(ipcMain, startWebtorrentHandle, async (_event, magnet: string) =>
startWebtorrent(magnet),
);
@@ -75,6 +124,13 @@ export default async function init(ipcMain: IpcMain) {
ipcHandle(ipcMain, logsWebtorrentHandle, async (_event, magnet: string) =>
logsWebtorrent(magnet),
);
+ ipcHandle(ipcMain, setUploadEnabledHandle, async (_event, enabled: boolean) =>
+ setUploadEnabled(enabled),
+ );
+ ipcHandle(ipcMain, getUploadEnabledHandle, async (_event) =>
+ getUploadEnabled(),
+ );
+ ipcHandle(ipcMain, getUploadStatsHandle, async (_event) => getUploadStats());
// 检查种子文件和对应的实际文件,如果有文件,就添加到webtorrent中
await restoreTorrentsFromFiles();
}
@@ -578,3 +634,71 @@ async function restoreTorrentsFromFiles() {
console.error('恢复种子过程中发生错误:', error);
}
}
+
+/** 应用上传限速设置 */
+function applyUploadThrottle(): void {
+ if (!client) return;
+
+ if (uploadEnabled) {
+ // 不限制上传速度 (-1 表示无限制)
+ client.throttleUpload(-1);
+ console.debug('上传已启用,不限速');
+ } else {
+ // 限制上传速度为 0(禁用上传)
+ client.throttleUpload(0);
+ console.debug('上传已禁用,限速为 0');
+ }
+}
+
+/** 设置上传开关 */
+export function setUploadEnabled(enabled: boolean): {
+ success: boolean;
+ enabled: boolean;
+} {
+ uploadEnabled = enabled;
+ saveUploadConfig(enabled);
+ applyUploadThrottle();
+ console.debug(`上传设置已更新: ${enabled ? '启用' : '禁用'}`);
+ return { success: true, enabled };
+}
+
+/** 获取上传开关状态 */
+export function getUploadEnabled(): { enabled: boolean } {
+ return { enabled: uploadEnabled };
+}
+
+/** 获取上传统计信息 */
+export function getUploadStats(): {
+ enabled: boolean;
+ totalUploaded: number;
+ uploadSpeed: number;
+ activeTorrents: number;
+} {
+ if (!client) {
+ return {
+ enabled: uploadEnabled,
+ totalUploaded: 0,
+ uploadSpeed: 0,
+ activeTorrents: 0,
+ };
+ }
+
+ let totalUploaded = 0;
+ let uploadSpeed = 0;
+ let activeTorrents = 0;
+
+ for (const torrent of client.torrents) {
+ totalUploaded += torrent.uploaded || 0;
+ uploadSpeed += torrent.uploadSpeed || 0;
+ if (torrent.progress === 1 && !torrent.paused) {
+ activeTorrents++;
+ }
+ }
+
+ return {
+ enabled: uploadEnabled,
+ totalUploaded,
+ uploadSpeed,
+ activeTorrents,
+ };
+}
diff --git a/src/main/dlc/type-info.ts b/src/main/dlc/type-info.ts
index dfc5a37..7770c7f 100644
--- a/src/main/dlc/type-info.ts
+++ b/src/main/dlc/type-info.ts
@@ -13,6 +13,12 @@ export const queryWebtorrentHandle = `${channel}query`;
export const logsWebtorrentHandle = `${channel}logs`;
+export const setUploadEnabledHandle = `${channel}setUploadEnabled`;
+
+export const getUploadEnabledHandle = `${channel}getUploadEnabled`;
+
+export const getUploadStatsHandle = `${channel}getUploadStats`;
+
export const dLCIds = [
'PDF_TAR',
'VOICE_TAR',
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 5956736..fe191f3 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -29,6 +29,9 @@ import {
removeWebtorrentHandle,
startWebtorrentHandle,
DLCId,
+ setUploadEnabledHandle,
+ getUploadEnabledHandle,
+ getUploadStatsHandle,
} from './dlc/type-info';
import {
checkLauncherUpdateHandle,
@@ -172,6 +175,23 @@ const mainHandle = {
message: string;
}>(installLauncherUpdateHandle);
},
+ setUploadEnabledHandle: async (enabled: boolean) => {
+ return ipcInvoke<{ success: boolean; enabled: boolean }>(
+ setUploadEnabledHandle,
+ enabled,
+ );
+ },
+ getUploadEnabledHandle: async () => {
+ return ipcInvoke<{ enabled: boolean }>(getUploadEnabledHandle);
+ },
+ getUploadStatsHandle: async () => {
+ return ipcInvoke<{
+ enabled: boolean;
+ totalUploaded: number;
+ uploadSpeed: number;
+ activeTorrents: number;
+ }>(getUploadStatsHandle);
+ },
};
export type MainHandle = typeof mainHandle;
From 902a45fcd11d86e257219aa27fb3ede655d889cb Mon Sep 17 00:00:00 2001
From: forceOfPx <3239735744@qq.com>
Date: Fri, 23 Jan 2026 18:45:35 +0800
Subject: [PATCH 11/22] merge fix
---
src/main/preload.ts | 2 ++
src/renderer/pages/hello/index.tsx | 1 +
2 files changed, 3 insertions(+)
diff --git a/src/main/preload.ts b/src/main/preload.ts
index 573f4a5..e456e93 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -27,6 +27,7 @@ import {
setTrayEnabledHandle,
DiskInfo,
} from './joint-build/type-info';
+import {
DLCIndex,
logsWebtorrentHandle,
pauseWebtorrentHandle,
@@ -154,6 +155,7 @@ const mainHandle = {
const handler = (_event: IpcRendererEvent, enabled: boolean) => callback(enabled);
ipcRenderer.on('joint-build-status-changed', handler);
return () => ipcRenderer.removeListener('joint-build-status-changed', handler);
+ },
startWebtorrentHandle: async (url: string) => {
return ipcInvoke(startWebtorrentHandle, url);
},
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 720bc4d..6730778 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -652,6 +652,7 @@ export default function Hello() {
共建计划
+
P2P测试
From 9f22a843744ba088f47084053f2fc8b1e2e9b922 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Fri, 23 Jan 2026 18:56:06 +0800
Subject: [PATCH 12/22] feat: replace linear progress bars with circular
progress
---
src/main/launcher-update/index.ts | 25 ++--
src/renderer/pages/hello/index.scss | 41 ++++++-
src/renderer/pages/hello/index.tsx | 132 +++++++++++++++++----
src/renderer/pages/lm-service/index.scss | 60 +++++-----
src/renderer/pages/lm-service/index.tsx | 37 ++----
src/renderer/pages/obsidian-app/index.scss | 60 +++++-----
src/renderer/pages/obsidian-app/index.tsx | 35 ++----
7 files changed, 248 insertions(+), 142 deletions(-)
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index 4563f23..9567da0 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -94,16 +94,16 @@ export async function downloadLauncherUpdate() {
console.debug('启动器更新下载完成');
// 检查是否为本地开发环境
- // const isDev = !app.isPackaged;
- // if (isDev) {
- // console.warn('当前为本地开发环境,启动器更新功能可能无法正常工作');
- // }
+ const isDev = !app.isPackaged;
+ if (isDev) {
+ console.warn('当前为本地开发环境,启动器更新功能可能无法正常工作');
+ }
return {
success: true,
version: latestVersionInfo.version,
filePath: path.join(torrent.path, torrent.files[0].name),
- // isDev,
+ isDev,
};
} catch (error) {
console.error('下载启动器更新失败:', error);
@@ -115,13 +115,14 @@ export async function installLauncherUpdate() {
console.debug('[installLauncherUpdate] 开始执行安装更新');
// 如果是开发模式,不执行更新
- // const isPackaged = app.isPackaged;
- // if (!isPackaged) {
- // return {
- // success: false,
- // message: '开发模式下不支持自动更新,请手动解压',
- // };
- // }
+ const isPackaged = app.isPackaged;
+ if (!isPackaged) {
+ console.warn('[installLauncherUpdate] 开发模式下不支持自动更新');
+ return {
+ success: false,
+ message: '开发模式下不支持自动更新,请手动解压',
+ };
+ }
try {
console.debug('[installLauncherUpdate] 获取最新版本信息...');
diff --git a/src/renderer/pages/hello/index.scss b/src/renderer/pages/hello/index.scss
index fec4dba..4054172 100644
--- a/src/renderer/pages/hello/index.scss
+++ b/src/renderer/pages/hello/index.scss
@@ -596,11 +596,27 @@ body{
padding: 8px 0;
gap: 10px;
width: auto;
- max-width: 300px;
+ max-width: 400px;
min-height: 53px;
flex: none;
order: 1;
+ .launcher-update-wrapper {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 8px;
+ background: linear-gradient(135deg, rgba(22, 119, 255, 0.1) 0%, rgba(22, 119, 255, 0.05) 100%);
+ border-radius: 8px;
+ box-shadow: 0 2px 6px rgba(22, 119, 255, 0.15);
+
+ .ant-progress {
+ .ant-progress-inner {
+ background-color: rgba(22, 119, 255, 0.15);
+ }
+ }
+ }
+
.status-indicator {
box-sizing: border-box;
display: flex;
@@ -626,7 +642,16 @@ body{
background: #1677ff;
&:hover {
- background: #1677ff;
+ background: #4096ff;
+ }
+
+ &.download-complete {
+ background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
+ animation: pulse-green 2s infinite;
+
+ &:hover {
+ background: linear-gradient(135deg, #73d13d 0%, #95de64 100%);
+ }
}
}
@@ -1118,6 +1143,18 @@ body{
}
}
+@keyframes pulse-green {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.4);
+ }
+ 70% {
+ box-shadow: 0 0 0 8px rgba(82, 196, 26, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
+ }
+}
+
@media screen and (max-width: 1200px) {
.hello-root .hello-container .hello-content .features-section .features-container {
flex-wrap: wrap;
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 49e812c..2f9d4d3 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -1,4 +1,12 @@
-import { Button, message, Space, Modal, notification, Popconfirm } from 'antd';
+import {
+ Button,
+ message,
+ Space,
+ Modal,
+ notification,
+ Popconfirm,
+ Progress,
+} from 'antd';
import { NavLink } from 'react-router-dom';
import { useEffect, useState, useRef } from 'react'; // 添加 useRef 导入
import obsidianLogo from './2023_Obsidian_logo.png';
@@ -196,6 +204,43 @@ export default function Hello() {
haveNew: boolean;
} | null>(null);
const [launcherUpdating, setLauncherUpdating] = useState(false);
+ const [launcherDownloadProgress, setLauncherDownloadProgress] = useState(0);
+ const [launcherDownloadComplete, setLauncherDownloadComplete] =
+ useState(false);
+
+ // 轮询下载进度
+ useEffect(() => {
+ if (!launcherUpdating || !launcherUpdateInfo?.haveNew) return;
+
+ let mounted = true;
+ const fetchProgress = async () => {
+ try {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const dlc = dlcIndex.find(
+ (item) => item.id === 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+ if (!dlc || !mounted) return;
+
+ const versionInfo = dlc.versions[launcherUpdateInfo.latestVersion];
+ if (versionInfo?.progress) {
+ const progress = (versionInfo.progress.progress || 0) * 100;
+ if (mounted) {
+ setLauncherDownloadProgress(progress);
+ }
+ }
+ } catch (error) {
+ console.error('获取下载进度失败:', error);
+ }
+ };
+
+ fetchProgress();
+ const intervalId = setInterval(fetchProgress, 1000);
+
+ return () => {
+ mounted = false;
+ clearInterval(intervalId);
+ };
+ }, [launcherUpdating, launcherUpdateInfo]);
const removeTrainingService = async () => {
setTrainingServiceRemoving(true);
@@ -216,6 +261,21 @@ export default function Hello() {
try {
const info = await window.mainHandle.checkLauncherUpdateHandle();
setLauncherUpdateInfo(info);
+
+ // 检查是否已有下载完成的更新包
+ if (info?.haveNew) {
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const dlc = dlcIndex.find(
+ (item) => item.id === 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+ if (dlc) {
+ const versionInfo = dlc.versions[info.latestVersion];
+ if (versionInfo?.progress?.progress >= 1) {
+ setLauncherDownloadComplete(true);
+ setLauncherDownloadProgress(100);
+ }
+ }
+ }
} catch (error) {
console.error('检查启动器更新失败:', error);
}
@@ -227,32 +287,49 @@ export default function Hello() {
return;
}
+ // 如果下载已完成,直接执行安装
+ if (launcherDownloadComplete) {
+ setLauncherUpdating(true);
+ try {
+ const result = await window.mainHandle.installLauncherUpdateHandle();
+ if (result.success) {
+ message.success(result.message);
+ } else {
+ message.warning(result.message);
+ }
+ } catch (error) {
+ console.error('安装启动器更新失败:', error);
+ message.error('安装失败:' + error.message);
+ } finally {
+ setLauncherUpdating(false);
+ }
+ return;
+ }
+
setLauncherUpdating(true);
- const hideLoading = message.loading('正在下载更新包...', 0);
+ setLauncherDownloadProgress(0);
+ setLauncherDownloadComplete(false);
+
try {
const downloadResult =
await window.mainHandle.downloadLauncherUpdateHandle();
- hideLoading();
// 开发模式下不执行安装,直接提示
if (downloadResult.isDev) {
message.warning('开发模式下不支持自动更新,请手动解压');
+ setLauncherUpdating(false);
return;
}
- message.success('下载完成,准备安装...');
- const result = await window.mainHandle.installLauncherUpdateHandle();
- if (result.success) {
- message.success(result.message);
- } else {
- message.warning(result.message);
- }
+ setLauncherDownloadProgress(100);
+ setLauncherDownloadComplete(true);
+ setLauncherUpdating(false);
+ message.success('下载完成,点击按钮重启并更新');
} catch (error) {
- hideLoading();
console.error('更新启动器失败:', error);
message.error('更新失败:' + error.message);
- } finally {
setLauncherUpdating(false);
+ setLauncherDownloadProgress(0);
}
};
@@ -677,15 +754,28 @@ export default function Hello() {
{/*
P2P测试
*/}
-
-
- {launcherUpdateInfo?.haveNew ? '更新' : ''}
-
-
+ {launcherUpdateInfo?.haveNew && (
+
+ {launcherUpdating && !launcherDownloadComplete && (
+
+ )}
+
+
+ {launcherDownloadComplete ? '重启并更新' : '更新'}
+
+
+
+ )}
+ {downloading && !isDownloadComplete && (
+
+ )}
- {(downloading || isDownloadComplete) && (
-
-
- {isDownloadComplete
- ? '下载完成'
- : `${downloadProgress}%`}
-
-
-
- )}
)}
diff --git a/src/renderer/pages/obsidian-app/index.scss b/src/renderer/pages/obsidian-app/index.scss
index c81c88d..b2095b8 100644
--- a/src/renderer/pages/obsidian-app/index.scss
+++ b/src/renderer/pages/obsidian-app/index.scss
@@ -17,49 +17,49 @@
.download-wrapper {
display: inline-flex;
align-items: center;
- gap: 10px;
- padding: 6px 12px;
- background: #FFFFFF;
- border: 1px solid #E5E5E5;
+ gap: 8px;
+ padding: 4px 8px;
+ background: linear-gradient(135deg, rgba(22, 119, 255, 0.1) 0%, rgba(22, 119, 255, 0.05) 100%);
border-radius: 8px;
- box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: 0 2px 6px rgba(22, 119, 255, 0.15);
transition: all 0.3s ease;
- &:hover {
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
+ .ant-progress {
+ .ant-progress-inner {
+ background-color: rgba(22, 119, 255, 0.15);
+ }
}
&.downloading {
border-color: #1677ff;
- background: #f0f5ff;
+ background: linear-gradient(135deg, rgba(22, 119, 255, 0.15) 0%, rgba(22, 119, 255, 0.08) 100%);
}
- &.complete {
- border-color: #52c41a;
- background: #f6ffed;
- }
+ &.download-complete {
+ background: linear-gradient(135deg, rgba(82, 196, 26, 0.15) 0%, rgba(82, 196, 26, 0.08) 100%);
+ box-shadow: 0 2px 6px rgba(82, 196, 26, 0.2);
- .download-progress-container {
- display: flex;
- flex-direction: column;
- min-width: 100px;
+ .download-complete-btn {
+ background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
+ border-color: #52c41a;
+ animation: pulse-green 2s infinite;
- .progress-text {
- font-family: 'Inter', sans-serif;
- font-size: 12px;
- font-weight: 500;
- color: #737373;
- margin-bottom: 2px;
- text-align: center;
- }
-
- .ant-progress {
- margin: 0;
-
- .ant-progress-inner {
- background-color: #E5E5E5;
+ &:hover {
+ background: linear-gradient(135deg, #73d13d 0%, #95de64 100%);
}
}
}
}
}
+
+@keyframes pulse-green {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.4);
+ }
+ 70% {
+ box-shadow: 0 0 0 8px rgba(82, 196, 26, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(82, 196, 26, 0);
+ }
+}
diff --git a/src/renderer/pages/obsidian-app/index.tsx b/src/renderer/pages/obsidian-app/index.tsx
index 245f6df..a78ea23 100644
--- a/src/renderer/pages/obsidian-app/index.tsx
+++ b/src/renderer/pages/obsidian-app/index.tsx
@@ -204,12 +204,22 @@ export default function ObsidianApp() {
key="download"
className={`download-wrapper ${
downloading ? 'downloading' : ''
- } ${isDownloadComplete ? 'complete' : ''}`}
+ } ${isDownloadComplete ? 'download-complete' : ''}`}
>
+ {downloading && !isDownloadComplete && (
+
+ )}
{isDownloadComplete
@@ -218,27 +228,6 @@ export default function ObsidianApp() {
? '下载中...'
: '更新Obsidian'}
- {(downloading || isDownloadComplete) && (
-
-
- {isDownloadComplete ? '下载完成' : `${downloadProgress}%`}
-
-
-
- )}
),
Date: Fri, 30 Jan 2026 10:02:01 +0800
Subject: [PATCH 13/22] refactor: centralize WebTorrent configuration
management
---
external-resources/dlc/dlc-index.json | 12 +++++-----
package.json | 2 +-
src/main/cmd/index.ts | 33 +++++++--------------------
src/main/dlc/index.ts | 14 ++++++++----
src/main/webtorrent-config.ts | 32 ++++++++++++++++++++++++++
5 files changed, 56 insertions(+), 37 deletions(-)
create mode 100644 src/main/webtorrent-config.ts
diff --git a/external-resources/dlc/dlc-index.json b/external-resources/dlc/dlc-index.json
index 4c2b326..1c4d1d0 100644
--- a/external-resources/dlc/dlc-index.json
+++ b/external-resources/dlc/dlc-index.json
@@ -6,12 +6,12 @@
"2.0.0": {
"magnet": "magnet:?xt=urn:btih:a7f8c85dcef68ae7958f0f3548603ea9016897df&dn=ala-training-v2.0.0.tar&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce",
"comment": "第一个版本",
- "riquire": {}
+ "require": {}
},
"2.0.2": {
"magnet": "magnet:?xt=urn:btih:98995512a1cf581a1baea19132b1a6550ec424fc&dn=ala-training-v2.0.2.tar&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce",
"comment": "相比上一个版本修复了一些问题",
- "riquire": {}
+ "require": {}
}
}
},
@@ -22,7 +22,7 @@
"2.0.2": {
"magnet": "magnet:?xt=urn:btih:cff0fb96747c5fc4696af4c9fa8cd30330050250&dn=ai_learning_assistant.sql&tr=wss%3A%2F%2Flearning.panchuantech.cn%2Fannounce&tr=wss%3A%2F%2F114.66.58.95%3A17853%2Fannounce",
"comment": "第一个版本",
- "riquire": {
+ "require": {
"TRAINING_TAR": "^2.0.0"
}
}
@@ -35,7 +35,7 @@
"2.0.3": {
"magnet": "magnet:?xt=urn:btih:5e685a7cbe4045efc40f7a593dddfdf56ae7c59d&dn=AI-Learning-Assistant-Launcher-win32-x64-2.0.3.zip&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
"comment": "第一个版本",
- "riquire": {}
+ "require": {}
}
}
},
@@ -46,7 +46,7 @@
"1.11.4": {
"magnet": "magnet:?xt=urn:btih:0e35e31fcbfa86153cd155f24c5d835e977d7d8b&dn=Obsidian-1.11.4.exe&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
"comment": "第一个版本",
- "riquire": {}
+ "require": {}
}
}
},
@@ -57,7 +57,7 @@
"0.3.39": {
"magnet": "magnet:?xt=urn:btih:f99095d4fc565a5e7fcf971f80817d8dbf617c97&dn=LM-Studio-0.3.39-x64.exe&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
"comment": "第一个版本",
- "riquire": {}
+ "require": {}
}
}
}
diff --git a/package.json b/package.json
index cb1f159..07041ae 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,6 @@
"@typescript-eslint/parser": "^5.62.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"@xterm/xterm": "^5.5.0",
- "adm-zip": "^0.5.16",
"antd": "^5.26.1",
"copy-webpack-plugin": "^13.0.1",
"cpy": "^11.1.0",
@@ -90,6 +89,7 @@
"@langchain/ollama": "^0.2.4",
"@langchain/openai": "^0.6.14",
"@types/webtorrent": "^0.110.1",
+ "adm-zip": "^0.5.16",
"electron-squirrel-startup": "^1.0.1",
"langchain": "^0.3.35",
"pdf-lib": "^1.17.1",
diff --git a/src/main/cmd/index.ts b/src/main/cmd/index.ts
index 2a429c6..629e8cb 100644
--- a/src/main/cmd/index.ts
+++ b/src/main/cmd/index.ts
@@ -23,24 +23,7 @@ import { RunResult } from '@podman-desktop/api';
import { podMachineName } from '../podman-desktop/type-info';
import { isVTReady, isWSLInstall, wslVersion } from './is-wsl-install';
import { loggerFactory } from '../terminal-log';
-
-/**
- * 安装程序相关的时间配置(单位:毫秒)
- */
-const INSTALL_TIMING_CONFIG = {
- /** 销毁 torrent 后等待文件句柄释放的时间 */
- TORRENT_DESTROY_WAIT: 2000,
- /** 重试的基础等待时间 */
- RETRY_BASE_WAIT: 1000,
- /** 每次重试增加的等待时间 */
- RETRY_INCREMENT: 1000,
- /** 所有重试失败后的最终等待时间 */
- FINAL_RETRY_WAIT: 10000,
- /** 最大重试次数 */
- MAX_RETRIES: 8,
- /** LM Studio 安装检测命令超时时间 */
- LM_STUDIO_CHECK_TIMEOUT: 15000,
-} as const;
+import { WEBTORRENT_CONFIG } from '../webtorrent-config';
/**
* 判断错误是否为 EBUSY 错误(文件被占用)
@@ -85,16 +68,16 @@ async function executeWithRetry(
options: RetryExecuteOptions,
): Promise {
const { exePath, args, logPrefix } = options;
- const maxRetries = INSTALL_TIMING_CONFIG.MAX_RETRIES;
+ const maxRetries = WEBTORRENT_CONFIG.MAX_RETRIES;
for (let retry = 0; retry < maxRetries; retry++) {
try {
// 每次重试前等待更长时间,让文件句柄有充足时间释放
const waitTime =
retry === 0
- ? INSTALL_TIMING_CONFIG.RETRY_BASE_WAIT
- : INSTALL_TIMING_CONFIG.RETRY_BASE_WAIT +
- retry * INSTALL_TIMING_CONFIG.RETRY_INCREMENT;
+ ? WEBTORRENT_CONFIG.RETRY_BASE_WAIT
+ : WEBTORRENT_CONFIG.RETRY_BASE_WAIT +
+ retry * WEBTORRENT_CONFIG.RETRY_INCREMENT;
await new Promise((resolve) => setTimeout(resolve, waitTime));
console.debug(
@@ -112,7 +95,7 @@ async function executeWithRetry(
// 所有重试都失败了,尝试最后一次长等待
console.warn(`[${logPrefix}] 所有重试都失败,尝试最后一次长等待...`);
await new Promise((resolve) =>
- setTimeout(resolve, INSTALL_TIMING_CONFIG.FINAL_RETRY_WAIT),
+ setTimeout(resolve, WEBTORRENT_CONFIG.FINAL_RETRY_WAIT),
);
try {
const result = await commandLine.exec(exePath, args);
@@ -179,7 +162,7 @@ async function installFromP2POrFallback(
await destroyWebtorrentForInstall(versionInfo.magnet);
// 等待额外的时间确保文件句柄完全释放
await new Promise((resolve) =>
- setTimeout(resolve, INSTALL_TIMING_CONFIG.TORRENT_DESTROY_WAIT),
+ setTimeout(resolve, WEBTORRENT_CONFIG.TORRENT_DESTROY_WAIT),
);
console.debug(`[${logPrefix}] 种子已销毁,等待完成`);
} catch (e) {
@@ -734,7 +717,7 @@ export async function isLMStudioInstall() {
new Promise((resolve, reject) =>
setTimeout(
() => reject('isLMStudioInstall命令超时'),
- INSTALL_TIMING_CONFIG.LM_STUDIO_CHECK_TIMEOUT,
+ WEBTORRENT_CONFIG.LM_STUDIO_CHECK_TIMEOUT,
),
),
// 如果用户安装lmstudio然后又卸载了lmstudio,那么这个命令会一直卡着,也不报错,所以要用一个超时promise与它竞赛
diff --git a/src/main/dlc/index.ts b/src/main/dlc/index.ts
index fcc4b4d..511c865 100644
--- a/src/main/dlc/index.ts
+++ b/src/main/dlc/index.ts
@@ -18,6 +18,7 @@ import path from 'path';
import { appPath } from '../exec';
import fs, { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
import { compare } from 'semver';
+import { WEBTORRENT_CONFIG } from '../webtorrent-config';
let client: WebTorrent.Instance | null = null;
let uploadEnabled = true; // 默认开启上传
@@ -231,7 +232,10 @@ export async function destroyWebtorrentForInstall(
torrent.pause();
torrent.destroy({ destroyStore: false }, safeResolve);
- setTimeout(safeResolve, 3000);
+ setTimeout(
+ safeResolve,
+ WEBTORRENT_CONFIG.TORRENT_DESTROY_CALLBACK_TIMEOUT,
+ );
});
}
}
@@ -471,8 +475,8 @@ export async function waitTorrentDone(id: DLCId, version: string) {
// 轮询检查种子是否完成
return new Promise((resolve, reject) => {
- const checkInterval = 1000; // 1秒检查一次
- const maxAttempts = 3600; // 最多检查1小时(3600秒)
+ const checkInterval = WEBTORRENT_CONFIG.DOWNLOAD_CHECK_INTERVAL;
+ const maxAttempts = WEBTORRENT_CONFIG.DOWNLOAD_MAX_ATTEMPTS;
let attempts = 0;
const intervalId = setInterval(() => {
@@ -503,8 +507,8 @@ export async function waitTorrentDone(id: DLCId, version: string) {
return;
}
- // 输出进度信息(可选,每10秒输出一次)
- if (attempts % 10 === 0) {
+ // 输出进度信息(可选,每N秒输出一次)
+ if (attempts % WEBTORRENT_CONFIG.DOWNLOAD_PROGRESS_LOG_INTERVAL === 0) {
console.debug(
`种子 ${currentTorrent.name} 下载进度: ${(currentTorrent.progress * 100).toFixed(2)}%`,
);
diff --git a/src/main/webtorrent-config.ts b/src/main/webtorrent-config.ts
new file mode 100644
index 0000000..473deda
--- /dev/null
+++ b/src/main/webtorrent-config.ts
@@ -0,0 +1,32 @@
+/**
+ * WebTorrent 相关的统一配置(单位:毫秒,除非另有说明)
+ */
+export const WEBTORRENT_CONFIG = {
+ // ============ 种子销毁与安装相关 ============
+ /** 销毁种子后等待文件句柄释放的时间 */
+ TORRENT_DESTROY_WAIT: 2000,
+ /** 销毁种子回调的超时时间 */
+ TORRENT_DESTROY_CALLBACK_TIMEOUT: 3000,
+
+ // ============ 重试机制相关 ============
+ /** 重试的基础等待时间 */
+ RETRY_BASE_WAIT: 1000,
+ /** 每次重试增加的等待时间 */
+ RETRY_INCREMENT: 1000,
+ /** 所有重试失败后的最终等待时间 */
+ FINAL_RETRY_WAIT: 10000,
+ /** 最大重试次数 */
+ MAX_RETRIES: 8,
+
+ // ============ 种子下载轮询相关 ============
+ /** 检查种子完成状态的轮询间隔 */
+ DOWNLOAD_CHECK_INTERVAL: 1000,
+ /** 最大轮询次数(3600 = 1小时) */
+ DOWNLOAD_MAX_ATTEMPTS: 3600,
+ /** 输出进度信息的间隔次数(每 N 次输出一次) */
+ DOWNLOAD_PROGRESS_LOG_INTERVAL: 10,
+
+ // ============ 其他超时配置 ============
+ /** LM Studio 安装检测命令超时时间 */
+ LM_STUDIO_CHECK_TIMEOUT: 15000,
+} as const;
From 40234d11dd1e3df477df8184a8e80652805fce18 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=94=9A=E5=8B=83=E9=B9=8F?= <2032806273@qq.com>
Date: Fri, 30 Jan 2026 14:31:48 +0800
Subject: [PATCH 14/22] feat: add cancel download button for Obsidian, LM
Studio and launcher updates
---
src/renderer/pages/hello/index.tsx | 60 +++++++++++++++++----
src/renderer/pages/lm-service/index.tsx | 66 ++++++++++++++++-------
src/renderer/pages/obsidian-app/index.tsx | 50 ++++++++++++-----
3 files changed, 135 insertions(+), 41 deletions(-)
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index 2f9d4d3..db8ebd1 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -207,6 +207,7 @@ export default function Hello() {
const [launcherDownloadProgress, setLauncherDownloadProgress] = useState(0);
const [launcherDownloadComplete, setLauncherDownloadComplete] =
useState(false);
+ const [launcherMagnet, setLauncherMagnet] = useState(null);
// 轮询下载进度
useEffect(() => {
@@ -311,6 +312,18 @@ export default function Hello() {
setLauncherDownloadComplete(false);
try {
+ // 获取 DLC 信息并保存 magnet
+ const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
+ const dlc = dlcIndex.find(
+ (item) => item.id === 'AI_LEARNING_ASSISTANT_LAUNCHER',
+ );
+ if (dlc && launcherUpdateInfo) {
+ const versionInfo = dlc.versions[launcherUpdateInfo.latestVersion];
+ if (versionInfo?.magnet) {
+ setLauncherMagnet(versionInfo.magnet);
+ }
+ }
+
const downloadResult =
await window.mainHandle.downloadLauncherUpdateHandle();
@@ -324,12 +337,31 @@ export default function Hello() {
setLauncherDownloadProgress(100);
setLauncherDownloadComplete(true);
setLauncherUpdating(false);
+ setLauncherMagnet(null);
message.success('下载完成,点击按钮重启并更新');
} catch (error) {
console.error('更新启动器失败:', error);
message.error('更新失败:' + error.message);
setLauncherUpdating(false);
setLauncherDownloadProgress(0);
+ setLauncherMagnet(null);
+ }
+ };
+
+ const handleCancelLauncherUpdate = async () => {
+ if (!launcherMagnet) {
+ message.warning('没有正在进行的下载');
+ return;
+ }
+ try {
+ await window.mainHandle.pauseWebtorrentHandle(launcherMagnet);
+ setLauncherUpdating(false);
+ setLauncherDownloadProgress(0);
+ setLauncherMagnet(null);
+ message.info('已取消下载');
+ } catch (error) {
+ console.error('取消下载失败:', error);
+ message.error('取消下载失败');
}
};
@@ -765,15 +797,25 @@ export default function Hello() {
strokeColor="#1677ff"
/>
)}
-
-
- {launcherDownloadComplete ? '重启并更新' : '更新'}
-
-
+ {launcherUpdating && !launcherDownloadComplete ? (
+
+ 取消下载
+
+ ) : (
+
+
+ {launcherDownloadComplete ? '重启并更新' : '更新'}
+
+
+ )}
)}
(null);
+ const [lmStudioMagnet, setLmStudioMagnet] = useState(null);
useEffect(() => {
checkLMStudioUpdate();
@@ -169,6 +170,7 @@ export default function LMService() {
setLatestVersion(version);
const versionInfo = lmStudioDLC.versions[version];
const magnet = versionInfo.magnet;
+ setLmStudioMagnet(magnet);
const result = await window.mainHandle.startWebtorrentHandle(magnet);
if (result.success) {
@@ -180,6 +182,24 @@ export default function LMService() {
console.error('下载LM Studio失败:', error);
message.error('下载失败:' + (error as Error).message);
setDownloading(false);
+ setLmStudioMagnet(null);
+ }
+ };
+
+ const handleCancelLMStudioDownload = async () => {
+ if (!lmStudioMagnet) {
+ message.warning('没有正在进行的下载');
+ return;
+ }
+ try {
+ await window.mainHandle.pauseWebtorrentHandle(lmStudioMagnet);
+ setDownloading(false);
+ setDownloadProgress(0);
+ setLmStudioMagnet(null);
+ message.info('已取消下载');
+ } catch (error) {
+ console.error('取消下载失败:', error);
+ message.error('取消下载失败');
}
};
// const llmContainer = containers.filter(
@@ -285,26 +305,34 @@ export default function LMService() {
strokeColor="#1677ff"
/>
)}
-
- {isDownloadComplete
- ? `安装 ${latestVersion || ''}`
- : downloading
- ? '下载中...'
+ {downloading && !isDownloadComplete ? (
+
+ 取消下载
+
+ ) : (
+
+ {isDownloadComplete
+ ? `安装 ${latestVersion || ''}`
: '下载并安装LMStudio'}
-
+
+ )}
)}
diff --git a/src/renderer/pages/obsidian-app/index.tsx b/src/renderer/pages/obsidian-app/index.tsx
index a78ea23..f4c0091 100644
--- a/src/renderer/pages/obsidian-app/index.tsx
+++ b/src/renderer/pages/obsidian-app/index.tsx
@@ -22,6 +22,7 @@ export default function ObsidianApp() {
const [downloading, setDownloading] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [isDownloadComplete, setIsDownloadComplete] = useState(false);
+ const [currentMagnet, setCurrentMagnet] = useState(null);
useEffect(() => {
checkObsidianUpdate();
@@ -114,6 +115,7 @@ export default function ObsidianApp() {
const versionInfo = obsidianDLC.versions[latestVersion];
const magnet = versionInfo.magnet;
+ setCurrentMagnet(magnet);
const result = await window.mainHandle.startWebtorrentHandle(magnet);
if (result.success) {
@@ -125,6 +127,24 @@ export default function ObsidianApp() {
console.error('下载Obsidian失败:', error);
message.error('下载失败:' + error.message);
setDownloading(false);
+ setCurrentMagnet(null);
+ }
+ };
+
+ const handleCancelDownload = async () => {
+ if (!currentMagnet) {
+ message.warning('没有正在进行的下载');
+ return;
+ }
+ try {
+ await window.mainHandle.pauseWebtorrentHandle(currentMagnet);
+ setDownloading(false);
+ setDownloadProgress(0);
+ setCurrentMagnet(null);
+ message.info('已取消下载');
+ } catch (error) {
+ console.error('取消下载失败:', error);
+ message.error('取消下载失败');
}
};
@@ -215,19 +235,23 @@ export default function ObsidianApp() {
strokeColor="#1677ff"
/>
)}
-
- {isDownloadComplete
- ? '安装Obsidian'
- : downloading
- ? '下载中...'
- : '更新Obsidian'}
-
+ {downloading && !isDownloadComplete ? (
+
+ 取消下载
+
+ ) : (
+
+ {isDownloadComplete ? '安装Obsidian' : '更新Obsidian'}
+
+ )}
),
Date: Fri, 30 Jan 2026 14:55:16 +0800
Subject: [PATCH 15/22] feat: support background download state recovery for
WebTorrent downloads
---
src/renderer/pages/hello/index.tsx | 19 +++++++++++++++----
src/renderer/pages/lm-service/index.tsx | 19 +++++++++++++++----
src/renderer/pages/obsidian-app/index.tsx | 19 +++++++++++++++----
3 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/src/renderer/pages/hello/index.tsx b/src/renderer/pages/hello/index.tsx
index db8ebd1..25c4de6 100644
--- a/src/renderer/pages/hello/index.tsx
+++ b/src/renderer/pages/hello/index.tsx
@@ -263,7 +263,7 @@ export default function Hello() {
const info = await window.mainHandle.checkLauncherUpdateHandle();
setLauncherUpdateInfo(info);
- // 检查是否已有下载完成的更新包
+ // 检查是否已有下载完成的更新包,或正在下载中(用于恢复后台下载状态)
if (info?.haveNew) {
const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
const dlc = dlcIndex.find(
@@ -271,9 +271,20 @@ export default function Hello() {
);
if (dlc) {
const versionInfo = dlc.versions[info.latestVersion];
- if (versionInfo?.progress?.progress >= 1) {
- setLauncherDownloadComplete(true);
- setLauncherDownloadProgress(100);
+ if (versionInfo?.progress) {
+ const progress = versionInfo.progress.progress || 0;
+ if (progress >= 1) {
+ // 下载已完成
+ setLauncherDownloadComplete(true);
+ setLauncherDownloadProgress(100);
+ setLauncherUpdating(false);
+ } else if (progress > 0) {
+ // 正在下载中,恢复下载状态
+ setLauncherUpdating(true);
+ setLauncherDownloadProgress(progress * 100);
+ setLauncherMagnet(versionInfo.magnet);
+ setLauncherDownloadComplete(false);
+ }
}
}
}
diff --git a/src/renderer/pages/lm-service/index.tsx b/src/renderer/pages/lm-service/index.tsx
index 31cf156..e373e73 100644
--- a/src/renderer/pages/lm-service/index.tsx
+++ b/src/renderer/pages/lm-service/index.tsx
@@ -92,7 +92,7 @@ export default function LMService() {
}, [downloading, isDownloadComplete]);
const checkLMStudioUpdate = async () => {
- // 检查是否已经有下载完成的文件
+ // 检查是否已经有下载完成的文件,或正在下载中(用于恢复后台下载状态)
try {
const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
const lmStudioDLC = dlcIndex.find(
@@ -103,9 +103,20 @@ export default function LMService() {
if (version) {
setLatestVersion(version);
const versionInfo = lmStudioDLC.versions[version];
- if (versionInfo.progress && versionInfo.progress.progress >= 1) {
- setIsDownloadComplete(true);
- setDownloadProgress(100);
+ if (versionInfo.progress) {
+ const progress = versionInfo.progress.progress || 0;
+ if (progress >= 1) {
+ // 下载已完成
+ setIsDownloadComplete(true);
+ setDownloadProgress(100);
+ setDownloading(false);
+ } else if (progress > 0) {
+ // 正在下载中,恢复下载状态
+ setDownloading(true);
+ setDownloadProgress(Math.floor(progress * 100));
+ setLmStudioMagnet(versionInfo.magnet);
+ setIsDownloadComplete(false);
+ }
}
}
}
diff --git a/src/renderer/pages/obsidian-app/index.tsx b/src/renderer/pages/obsidian-app/index.tsx
index f4c0091..c3e8a4a 100644
--- a/src/renderer/pages/obsidian-app/index.tsx
+++ b/src/renderer/pages/obsidian-app/index.tsx
@@ -39,7 +39,7 @@ export default function ObsidianApp() {
}, [downloading, isDownloadComplete]);
const checkObsidianUpdate = async () => {
- // 检查是否已经有下载完成的文件
+ // 检查是否已经有下载完成的文件,或正在下载中(用于恢复后台下载状态)
try {
const dlcIndex = await window.mainHandle.queryWebtorrentHandle();
const obsidianDLC = dlcIndex.find(
@@ -49,9 +49,20 @@ export default function ObsidianApp() {
const latestVersion = Object.keys(obsidianDLC.versions).sort().pop();
if (latestVersion) {
const versionInfo = obsidianDLC.versions[latestVersion];
- if (versionInfo.progress && versionInfo.progress.progress >= 1) {
- setIsDownloadComplete(true);
- setDownloadProgress(100);
+ if (versionInfo.progress) {
+ const progress = versionInfo.progress.progress || 0;
+ if (progress >= 1) {
+ // 下载已完成
+ setIsDownloadComplete(true);
+ setDownloadProgress(100);
+ setDownloading(false);
+ } else if (progress > 0) {
+ // 正在下载中,恢复下载状态
+ setDownloading(true);
+ setDownloadProgress(Math.floor(progress * 100));
+ setCurrentMagnet(versionInfo.magnet);
+ setIsDownloadComplete(false);
+ }
}
}
}
From adc3787be50791ad04879ef52466ef0367eb569e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=94=9A=E5=8B=83=E9=B9=8F?= <2032806273@qq.com>
Date: Fri, 30 Jan 2026 15:49:19 +0800
Subject: [PATCH 16/22] fix(lm-service): fix incorrect LMStudio installation
status display on page load
---
src/renderer/containers/use-cmd/index.tsx | 2 +-
src/renderer/pages/lm-service/index.tsx | 9 ++++++---
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/renderer/containers/use-cmd/index.tsx b/src/renderer/containers/use-cmd/index.tsx
index 433cc44..f205415 100644
--- a/src/renderer/containers/use-cmd/index.tsx
+++ b/src/renderer/containers/use-cmd/index.tsx
@@ -8,7 +8,7 @@ export default function useCmd() {
const [wslVersion, setWSLVersion] = useState('');
const [checkingWsl, setCheckingWsl] = useState(true);
const [isInstallObsidian, setIsInstallObsidian] = useState(true);
- const [isInstallLMStudio, setIsInstallLMStudio] = useState(true);
+ const [isInstallLMStudio, setIsInstallLMStudio] = useState(false);
const [loading, setLoading] = useState(false);
function action(
actionName: ActionName,
diff --git a/src/renderer/pages/lm-service/index.tsx b/src/renderer/pages/lm-service/index.tsx
index e373e73..6424f33 100644
--- a/src/renderer/pages/lm-service/index.tsx
+++ b/src/renderer/pages/lm-service/index.tsx
@@ -337,11 +337,14 @@ export default function LMService() {
cmdOperating.serviceName === 'lm-studio' &&
cmdOperating.actionName === 'install')
}
+ disabled={checkingWsl}
onClick={handleDownloadOrInstallLMStudio}
>
- {isDownloadComplete
- ? `安装 ${latestVersion || ''}`
- : '下载并安装LMStudio'}
+ {checkingWsl
+ ? '检测安装状态...'
+ : isDownloadComplete
+ ? `安装 ${latestVersion || ''}`
+ : '下载并安装LMStudio'}
)}
From a5bb6b7d9083ac42eb730f9044c1a3e8788a5471 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=94=9A=E5=8B=83=E9=B9=8F?= <2032806273@qq.com>
Date: Fri, 30 Jan 2026 18:01:58 +0800
Subject: [PATCH 17/22] feat: add persistent logging to launcher update module
---
src/main/launcher-update/index.ts | 376 ++++++++++++++++++++++++------
1 file changed, 303 insertions(+), 73 deletions(-)
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index 9567da0..63b70ea 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -21,7 +21,7 @@ import {
statSync,
rmSync,
writeFileSync,
- readFileSync,
+ appendFileSync,
} from 'fs';
import { spawn } from 'child_process';
import AdmZip from 'adm-zip';
@@ -35,6 +35,70 @@ const currentVersion = packageJson.version;
// eslint-disable-next-line @typescript-eslint/no-var-requires
const originalFs: typeof import('fs') = require('original-fs');
+// 更新日志文件路径
+const updateLogPath = path.join(appPath, 'launcher-update.log');
+
+/**
+ * 写入更新日志到文件
+ * @param level 日志级别
+ * @param message 日志消息
+ * @param data 附加数据
+ */
+function writeUpdateLog(
+ level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
+ message: string,
+ data?: unknown,
+) {
+ const timestamp = new Date().toISOString();
+ let logLine = `[${timestamp}] [${level}] ${message}`;
+ if (data !== undefined) {
+ try {
+ logLine += ` ${JSON.stringify(data)}`;
+ } catch {
+ logLine += ` [无法序列化的数据]`;
+ }
+ }
+ logLine += '\n';
+
+ try {
+ appendFileSync(updateLogPath, logLine, { encoding: 'utf8' });
+ } catch (err) {
+ console.error('写入更新日志失败:', err);
+ }
+
+ // 同时输出到控制台
+ switch (level) {
+ case 'DEBUG':
+ console.debug(message, data ?? '');
+ break;
+ case 'INFO':
+ console.log(message, data ?? '');
+ break;
+ case 'WARN':
+ console.warn(message, data ?? '');
+ break;
+ case 'ERROR':
+ console.error(message, data ?? '');
+ break;
+ }
+}
+
+/**
+ * 初始化日志文件(清空旧日志或添加分隔符)
+ */
+function initUpdateLog() {
+ const separator = `
+${'='.repeat(60)}
+[${new Date().toISOString()}] 启动器更新日志开始
+${'='.repeat(60)}
+`;
+ try {
+ appendFileSync(updateLogPath, separator, { encoding: 'utf8' });
+ } catch {
+ // 忽略错误
+ }
+}
+
export default async function init(ipcMain: IpcMain) {
ipcHandle(ipcMain, checkLauncherUpdateHandle, async (_event) =>
checkLauncherUpdate(),
@@ -48,6 +112,9 @@ export default async function init(ipcMain: IpcMain) {
}
export async function checkLauncherUpdate() {
+ initUpdateLog();
+ writeUpdateLog('INFO', '[checkLauncherUpdate] 开始检查启动器更新');
+
try {
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
@@ -55,7 +122,7 @@ export async function checkLauncherUpdate() {
const latestVersion = latestVersionInfo.version;
const haveNew = semver.lt(currentVersion, latestVersion);
- console.debug('检查启动器更新:', {
+ writeUpdateLog('INFO', '[checkLauncherUpdate] 检查启动器更新完成', {
currentVersion,
latestVersion,
haveNew,
@@ -67,7 +134,13 @@ export async function checkLauncherUpdate() {
haveNew,
};
} catch (error) {
- console.error('检查启动器更新失败:', error);
+ writeUpdateLog(
+ 'ERROR',
+ '[checkLauncherUpdate] 检查启动器更新失败',
+ error instanceof Error
+ ? { message: error.message, stack: error.stack }
+ : error,
+ );
return {
currentVersion,
latestVersion: currentVersion,
@@ -77,47 +150,66 @@ export async function checkLauncherUpdate() {
}
export async function downloadLauncherUpdate() {
+ writeUpdateLog('INFO', '[downloadLauncherUpdate] 开始下载启动器更新');
+
try {
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
- console.debug('开始下载启动器更新:', latestVersionInfo);
+ writeUpdateLog('DEBUG', '[downloadLauncherUpdate] 获取到最新版本信息', {
+ version: latestVersionInfo.version,
+ magnet: latestVersionInfo.dlcInfo?.magnet?.substring(0, 50) + '...',
+ });
await startWebtorrent(latestVersionInfo.dlcInfo.magnet);
+ writeUpdateLog('DEBUG', '[downloadLauncherUpdate] WebTorrent 已启动');
const torrent = await waitTorrentDone(
'AI_LEARNING_ASSISTANT_LAUNCHER',
latestVersionInfo.version,
);
- console.debug('启动器更新下载完成');
+ const filePath = path.join(torrent.path, torrent.files[0].name);
+ writeUpdateLog('INFO', '[downloadLauncherUpdate] 启动器更新下载完成', {
+ path: torrent.path,
+ fileName: torrent.files[0].name,
+ });
// 检查是否为本地开发环境
const isDev = !app.isPackaged;
if (isDev) {
- console.warn('当前为本地开发环境,启动器更新功能可能无法正常工作');
+ writeUpdateLog(
+ 'WARN',
+ '[downloadLauncherUpdate] 当前为本地开发环境,启动器更新功能可能无法正常工作',
+ );
}
return {
success: true,
version: latestVersionInfo.version,
- filePath: path.join(torrent.path, torrent.files[0].name),
+ filePath,
isDev,
};
} catch (error) {
- console.error('下载启动器更新失败:', error);
+ writeUpdateLog(
+ 'ERROR',
+ '[downloadLauncherUpdate] 下载启动器更新失败',
+ error instanceof Error
+ ? { message: error.message, stack: error.stack }
+ : error,
+ );
throw error;
}
}
export async function installLauncherUpdate() {
- console.debug('[installLauncherUpdate] 开始执行安装更新');
+ writeUpdateLog('INFO', '[installLauncherUpdate] 开始执行安装更新');
// 如果是开发模式,不执行更新
const isPackaged = app.isPackaged;
if (!isPackaged) {
- console.warn('[installLauncherUpdate] 开发模式下不支持自动更新');
+ writeUpdateLog('WARN', '[installLauncherUpdate] 开发模式下不支持自动更新');
return {
success: false,
message: '开发模式下不支持自动更新,请手动解压',
@@ -125,12 +217,12 @@ export async function installLauncherUpdate() {
}
try {
- console.debug('[installLauncherUpdate] 获取最新版本信息...');
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 获取最新版本信息...');
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
const version = latestVersionInfo.version;
- console.debug('[installLauncherUpdate] 最新版本:', version);
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 最新版本:', version);
const downloadPath = path.join(
appPath,
@@ -139,57 +231,68 @@ export async function installLauncherUpdate() {
'AI_LEARNING_ASSISTANT_LAUNCHER',
version,
);
- console.debug('[installLauncherUpdate] 下载路径:', downloadPath);
- console.debug(
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 下载路径:', downloadPath);
+ writeUpdateLog(
+ 'DEBUG',
'[installLauncherUpdate] 下载路径是否存在:',
existsSync(downloadPath),
);
const files = readdirSync(downloadPath);
- console.debug('[installLauncherUpdate] 下载目录文件列表:', files);
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 下载目录文件列表:', files);
const zipFile = files.find((f) => f.endsWith('.zip'));
if (!zipFile) {
+ writeUpdateLog('ERROR', '[installLauncherUpdate] 未找到下载的更新包');
throw new Error('未找到下载的更新包');
}
// 销毁 WebTorrent 以释放文件句柄,避免后续读取 zip 文件时出现 EBUSY 错误
- console.debug('[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...');
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...',
+ );
await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
// 等待文件句柄完全释放
- await new Promise((resolve) => setTimeout(resolve, 500));
- console.debug('[installLauncherUpdate] 文件句柄已释放');
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 文件句柄已释放');
const zipPath = path.join(downloadPath, zipFile);
- console.debug('[installLauncherUpdate] zip文件路径:', zipPath);
- console.debug(
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] zip文件路径:', zipPath);
+ writeUpdateLog(
+ 'DEBUG',
'[installLauncherUpdate] zip文件是否存在:',
existsSync(zipPath),
);
const tempDir = path.join(appPath, 'update-temp');
- console.debug('[installLauncherUpdate] 临时目录:', tempDir);
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录:', tempDir);
if (existsSync(tempDir)) {
- console.debug('[installLauncherUpdate] 清理已存在的临时目录...');
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 清理已存在的临时目录...',
+ );
rmSync(tempDir, { recursive: true, force: true });
- console.debug('[installLauncherUpdate] 临时目录清理完成');
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录清理完成');
}
mkdirSync(tempDir, { recursive: true });
- console.debug('[installLauncherUpdate] 临时目录已创建');
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录已创建');
// 使用 adm-zip 解压 zip 文件
- console.log('[installLauncherUpdate] 正在解压文件...');
- console.log('[installLauncherUpdate] ZIP路径:', zipPath);
- console.log('[installLauncherUpdate] 目标路径:', tempDir);
+ writeUpdateLog('INFO', '[installLauncherUpdate] 正在解压文件...');
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] ZIP路径:', zipPath);
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 目标路径:', tempDir);
try {
const zip = new AdmZip(zipPath);
const zipEntries = zip.getEntries();
- console.log(
+ writeUpdateLog(
+ 'DEBUG',
'[installLauncherUpdate] ZIP包含的文件数量:',
zipEntries.length,
);
- console.log(
+ writeUpdateLog(
+ 'DEBUG',
'[installLauncherUpdate] ZIP包含的文件列表:',
zipEntries.slice(0, 5).map((e) => e.entryName),
);
@@ -213,38 +316,53 @@ export async function installLauncherUpdate() {
originalFs.writeFileSync(entryPath, content);
}
}
- console.log('[installLauncherUpdate] 解压完成');
+ writeUpdateLog('INFO', '[installLauncherUpdate] 解压完成');
} catch (extractError) {
- console.error('[installLauncherUpdate] 解压失败:', extractError);
- console.error(
- '[installLauncherUpdate] 解压错误堆栈:',
- extractError instanceof Error ? extractError.stack : '无堆栈信息',
+ writeUpdateLog(
+ 'ERROR',
+ '[installLauncherUpdate] 解压失败',
+ extractError instanceof Error
+ ? { message: extractError.message, stack: extractError.stack }
+ : extractError,
);
throw extractError;
}
// 查找解压后的目录(通常会有一个子目录)
const extractedItems = readdirSync(tempDir);
- console.log(
+ writeUpdateLog(
+ 'DEBUG',
'[installLauncherUpdate] 解压后的文件数量:',
extractedItems.length,
);
- console.log('[installLauncherUpdate] 解压后的文件列表:', extractedItems);
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 解压后的文件列表:',
+ extractedItems,
+ );
let sourceDir = tempDir;
if (extractedItems.length === 1) {
const subPath = path.join(tempDir, extractedItems[0]);
const subStat = statSync(subPath);
- console.log('[installLauncherUpdate] 检查子路径:', subPath);
- console.log('[installLauncherUpdate] 是否为目录:', subStat.isDirectory());
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 检查子路径:', subPath);
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 是否为目录:',
+ subStat.isDirectory(),
+ );
if (subStat.isDirectory()) {
sourceDir = subPath;
const subItems = readdirSync(sourceDir);
- console.log('[installLauncherUpdate] 子目录文件列表:', subItems);
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 子目录文件列表:',
+ subItems,
+ );
}
}
- console.log('[installLauncherUpdate] 解压源目录:', sourceDir);
+ writeUpdateLog('DEBUG', '[installLauncherUpdate] 解压源目录:', sourceDir);
const findExe = (dir: string): string | null => {
const items = readdirSync(dir);
@@ -264,13 +382,21 @@ export async function installLauncherUpdate() {
return null;
};
- const newExePath = findExe(tempDir);
+ const newExePath = findExe(sourceDir);
if (!newExePath) {
+ writeUpdateLog(
+ 'ERROR',
+ '[installLauncherUpdate] 未在更新包中找到启动器可执行文件',
+ );
throw new Error('未在更新包中找到启动器可执行文件');
}
- console.debug('找到新版本可执行文件:', newExePath);
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 找到新版本可执行文件:',
+ newExePath,
+ );
const currentExePath = app.getPath('exe');
const currentExeDir = path.dirname(currentExePath);
@@ -278,54 +404,155 @@ export async function installLauncherUpdate() {
const backupPath = path.join(currentExeDir, `${currentExeName}.old`);
- const updateScriptPath = path.join(appPath, 'update.bat');
- const updateScript = `@echo off
+ // 找到新版本可执行文件所在目录的所有文件
+ const newExeDir = path.dirname(newExePath);
+ writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 新版本文件目录:',
+ newExeDir,
+ );
+ writeUpdateLog('INFO', '[installLauncherUpdate] 更新路径信息', {
+ currentExePath,
+ currentExeDir,
+ backupPath,
+ newExeDir,
+ });
+
+ const result = await dialog.showMessageBox({
+ type: 'info',
+ title: '准备更新',
+ message: '启动器将在关闭后自动更新',
+ detail: `当前版本: ${currentVersion}\n新版本: ${version}\n\n点击"确定"后程序将关闭并自动更新,更新完成后会自动重启。`,
+ buttons: ['确定', '取消'],
+ defaultId: 0,
+ cancelId: 1,
+ });
+
+ if (result.response === 0) {
+ writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 用户确认更新,准备执行更新',
+ );
+
+ // 创建更新脚本,在应用退出后执行
+ const updateScriptPath = path.join(appPath, 'update.bat');
+ // bat 脚本也写入日志文件,方便追踪更新过程(Windows BAT 脚本路径不需要转义)
+ const batLogPath = updateLogPath;
+ const updateScript = `@echo off
chcp 65001
+echo ===================================
echo 正在更新启动器...
-timeout /t 2 /nobreak >nul
+echo ===================================
-REM 删除旧备份
-if exist "${backupPath}" del /f /q "${backupPath}"
+REM 写入日志函数
+set "LOG_FILE=${batLogPath}"
+echo [%date% %time%] [BAT] 更新脚本开始执行 >> "%LOG_FILE%"
-REM 备份当前版本
-move /y "${currentExePath}" "${backupPath}"
+REM 等待应用完全退出并释放文件句柄
+echo 等待应用退出...
+echo [%date% %time%] [BAT] 等待应用退出... >> "%LOG_FILE%"
+timeout /t 3 /nobreak >nul
-REM 复制新版本
-copy /y "${newExePath}" "${currentExePath}"
+REM 删除旧备份
+if exist "${backupPath}" (
+ echo 删除旧备份...
+ echo [%date% %time%] [BAT] 删除旧备份: ${backupPath} >> "%LOG_FILE%"
+ del /f /q "${backupPath}"
+)
+
+REM 备份当前版本的可执行文件
+if exist "${currentExePath}" (
+ echo 备份当前版本...
+ echo [%date% %time%] [BAT] 备份当前版本: ${currentExePath} -^> ${backupPath} >> "%LOG_FILE%"
+ move /y "${currentExePath}" "${backupPath}"
+ if errorlevel 1 (
+ echo 备份失败!
+ echo [%date% %time%] [BAT] [ERROR] 备份失败! >> "%LOG_FILE%"
+ pause
+ exit /b 1
+ )
+ echo [%date% %time%] [BAT] 备份成功 >> "%LOG_FILE%"
+)
+
+REM 使用 robocopy 复制新版本的所有文件到当前目录
+echo 正在复制文件...
+echo [%date% %time%] [BAT] 开始复制文件: ${newExeDir} -^> ${currentExeDir} >> "%LOG_FILE%"
+robocopy "${newExeDir}" "${currentExeDir}" /E /IS /IT /NFL /NDL /NP
+
+REM robocopy 返回值说明:
+REM 0 = 没有文件被复制
+REM 1 = 所有文件复制成功
+REM 2 = 有额外的文件或目录
+REM 3 = 有文件被复制,也有不匹配的文件
+REM 4 = 有不匹配的文件或目录
+REM 5 = 有文件被复制,也有不匹配的文件或目录
+REM 6 = 有额外的文件/目录和不匹配的文件
+REM 7 = 文件被复制,有额外的文件和不匹配的文件
+REM 8+ = 有错误
+
+if %ERRORLEVEL% GEQ 8 (
+ echo ===================================
+ echo 复制文件失败!错误码: %ERRORLEVEL%
+ echo 正在恢复原版本...
+ echo ===================================
+ echo [%date% %time%] [BAT] [ERROR] 复制文件失败!错误码: %ERRORLEVEL% >> "%LOG_FILE%"
+ if exist "${backupPath}" (
+ move /y "${backupPath}" "${currentExePath}"
+ echo [%date% %time%] [BAT] 已恢复原版本 >> "%LOG_FILE%"
+ )
+ echo 更新失败,已恢复原版本
+ pause
+ exit /b 1
+)
+
+echo 文件复制完成,错误码: %ERRORLEVEL%
+echo [%date% %time%] [BAT] 文件复制完成,错误码: %ERRORLEVEL% >> "%LOG_FILE%"
REM 清理临时文件
-rmdir /s /q "${tempDir}"
+echo 清理临时文件...
+echo [%date% %time%] [BAT] 清理临时文件: ${tempDir} >> "%LOG_FILE%"
+if exist "${tempDir}" (
+ rmdir /s /q "${tempDir}" 2>nul
+)
+echo [%date% %time%] [BAT] 临时文件清理完成 >> "%LOG_FILE%"
REM 启动新版本
+echo ===================================
+echo 启动新版本...
+echo ===================================
+echo [%date% %time%] [BAT] 启动新版本: ${currentExePath} >> "%LOG_FILE%"
start "" "${currentExePath}"
+REM 等待一下确保应用启动
+timeout /t 2 /nobreak >nul
+echo [%date% %time%] [BAT] 更新脚本执行完成 >> "%LOG_FILE%"
+
REM 删除更新脚本自身
(goto) 2>nul & del "%~f0"
`;
- writeFileSync(updateScriptPath, updateScript, { encoding: 'utf8' });
-
- console.debug('更新脚本已创建:', updateScriptPath);
-
- const result = await dialog.showMessageBox({
- type: 'info',
- title: '准备更新',
- message: '启动器将在关闭后自动更新',
- detail: `当前版本: ${currentVersion}\n新版本: ${version}\n\n点击"确定"后程序将关闭并自动更新,更新完成后会自动重启。`,
- buttons: ['确定', '取消'],
- defaultId: 0,
- cancelId: 1,
- });
+ writeFileSync(updateScriptPath, updateScript, { encoding: 'utf8' });
+ writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 更新脚本已创建:',
+ updateScriptPath,
+ );
- if (result.response === 0) {
- spawn('cmd.exe', ['/c', updateScriptPath], {
+ // 使用 detached 模式启动更新脚本
+ const updateProcess = spawn('cmd.exe', ['/c', updateScriptPath], {
detached: true,
stdio: 'ignore',
- windowsHide: true,
+ windowsHide: false,
});
+ updateProcess.unref();
+ writeUpdateLog('INFO', '[installLauncherUpdate] 更新脚本已启动');
+
+ // 延迟退出,确保更新脚本已经启动
+ // 注意:这里不使用 app.relaunch(),而是让更新脚本完成后手动启动新版本
setTimeout(() => {
- app.quit();
+ writeUpdateLog('INFO', '[installLauncherUpdate] 准备退出应用');
+ app.exit(0);
}, 1000);
return {
@@ -333,16 +560,19 @@ REM 删除更新脚本自身
message: '正在更新启动器...',
};
} else {
+ writeUpdateLog('INFO', '[installLauncherUpdate] 用户取消更新');
return {
success: false,
message: '用户取消更新',
};
}
} catch (error) {
- console.error('[installLauncherUpdate] 安装启动器更新失败:', error);
- console.error(
- '[installLauncherUpdate] 错误堆栈:',
- error instanceof Error ? error.stack : '无堆栈信息',
+ writeUpdateLog(
+ 'ERROR',
+ '[installLauncherUpdate] 安装启动器更新失败',
+ error instanceof Error
+ ? { message: error.message, stack: error.stack }
+ : error,
);
throw error;
}
From d2442a7afb6f85c9ca3db630e927f6746356650a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=94=9A=E5=8B=83=E9=B9=8F?= <2032806273@qq.com>
Date: Sat, 31 Jan 2026 09:23:47 +0800
Subject: [PATCH 18/22] feat: configure Squirrel builder for auto-updates
---
forge.config.ts | 8 +-
package-lock.json | 1892 ++++++++++++++++++++-------------------------
2 files changed, 848 insertions(+), 1052 deletions(-)
diff --git a/forge.config.ts b/forge.config.ts
index 8105eaa..e64f757 100644
--- a/forge.config.ts
+++ b/forge.config.ts
@@ -20,7 +20,13 @@ const config: ForgeConfig = {
},
rebuildConfig: {},
makers: [
- // new MakerSquirrel({}),
+ new MakerSquirrel({
+ name: 'AILearningAssistantLauncher',
+ authors: 'James Zhang',
+ description: 'AI Learning Assistant Launcher',
+ // 设置图标路径
+ setupIcon: path.join(__dirname, 'icons', 'icon.ico'),
+ }),
new MakerZIP({}, ['darwin', 'win32']),
// new MakerRpm({}),
// new MakerDeb({})
diff --git a/package-lock.json b/package-lock.json
index 1b17429..c799fac 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@langchain/ollama": "^0.2.4",
"@langchain/openai": "^0.6.14",
"@types/webtorrent": "^0.110.1",
+ "adm-zip": "^0.5.16",
"electron-squirrel-startup": "^1.0.1",
"langchain": "^0.3.35",
"pdf-lib": "^1.17.1",
@@ -48,7 +49,6 @@
"@typescript-eslint/parser": "^5.62.0",
"@vercel/webpack-asset-relocator-loader": "1.7.3",
"@xterm/xterm": "^5.5.0",
- "adm-zip": "^0.5.16",
"antd": "^5.26.1",
"copy-webpack-plugin": "^13.0.1",
"cpy": "^11.1.0",
@@ -227,13 +227,13 @@
}
},
"node_modules/@babel/code-frame": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz",
- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.28.6.tgz",
+ "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
@@ -242,9 +242,9 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.5.tgz",
- "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.6.tgz",
+ "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -252,21 +252,21 @@
}
},
"node_modules/@babel/core": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.5.tgz",
- "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.6.tgz",
+ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.28.3",
- "@babel/helpers": "^7.28.4",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.5",
- "@babel/types": "^7.28.5",
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
@@ -293,14 +293,14 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.5.tgz",
- "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.6.tgz",
+ "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
@@ -323,13 +323,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
- "version": "7.27.2",
- "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.27.2",
+ "@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
@@ -350,18 +350,18 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz",
- "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
+ "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
- "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/traverse": "^7.28.5",
+ "@babel/traverse": "^7.28.6",
"semver": "^6.3.1"
},
"engines": {
@@ -410,17 +410,17 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.5",
- "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz",
- "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==",
+ "version": "0.6.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz",
+ "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-plugin-utils": "^7.27.1",
- "debug": "^4.4.1",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "debug": "^4.4.3",
"lodash.debounce": "^4.0.8",
- "resolve": "^1.22.10"
+ "resolve": "^1.22.11"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -451,29 +451,29 @@
}
},
"node_modules/@babel/helper-module-imports": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.28.3",
- "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
- "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.28.3"
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -496,9 +496,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"dev": true,
"license": "MIT",
"engines": {
@@ -524,15 +524,15 @@
}
},
"node_modules/@babel/helper-replace-supers": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
- "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
+ "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
- "@babel/traverse": "^7.27.1"
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -586,42 +586,42 @@
}
},
"node_modules/@babel/helper-wrap-function": {
- "version": "7.28.3",
- "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz",
- "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz",
+ "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/traverse": "^7.28.3",
- "@babel/types": "^7.28.2"
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
- "version": "7.28.4",
- "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.4.tgz",
- "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.4"
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
- "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.6.tgz",
+ "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.28.5"
+ "@babel/types": "^7.28.6"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -698,14 +698,14 @@
}
},
"node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
- "version": "7.28.3",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz",
- "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz",
+ "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/traverse": "^7.28.3"
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -728,13 +728,13 @@
}
},
"node_modules/@babel/plugin-syntax-import-assertions": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz",
- "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz",
+ "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -744,13 +744,13 @@
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
- "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -760,13 +760,13 @@
}
},
"node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
- "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
+ "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -776,13 +776,13 @@
}
},
"node_modules/@babel/plugin-syntax-typescript": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
- "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
+ "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -825,15 +825,15 @@
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
- "version": "7.28.0",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz",
- "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz",
+ "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1",
- "@babel/traverse": "^7.28.0"
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -843,14 +843,14 @@
}
},
"node_modules/@babel/plugin-transform-async-to-generator": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz",
- "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz",
+ "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1"
},
"engines": {
@@ -877,13 +877,13 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz",
- "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz",
+ "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -893,14 +893,14 @@
}
},
"node_modules/@babel/plugin-transform-class-properties": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
- "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz",
+ "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -910,14 +910,14 @@
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
- "version": "7.28.3",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz",
- "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz",
+ "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.28.3",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -927,18 +927,18 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.28.4",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
- "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz",
+ "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-globals": "^7.28.0",
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/helper-replace-supers": "^7.27.1",
- "@babel/traverse": "^7.28.4"
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-replace-supers": "^7.28.6",
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -948,14 +948,14 @@
}
},
"node_modules/@babel/plugin-transform-computed-properties": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz",
- "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz",
+ "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/template": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/template": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -982,14 +982,14 @@
}
},
"node_modules/@babel/plugin-transform-dotall-regex": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz",
- "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz",
+ "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1015,14 +1015,14 @@
}
},
"node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz",
- "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz",
+ "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1048,14 +1048,14 @@
}
},
"node_modules/@babel/plugin-transform-explicit-resource-management": {
- "version": "7.28.0",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz",
- "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz",
+ "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-transform-destructuring": "^7.28.0"
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1065,13 +1065,13 @@
}
},
"node_modules/@babel/plugin-transform-exponentiation-operator": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz",
- "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz",
+ "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1132,13 +1132,13 @@
}
},
"node_modules/@babel/plugin-transform-json-strings": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz",
- "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz",
+ "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1164,13 +1164,13 @@
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz",
- "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz",
+ "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1213,14 +1213,14 @@
}
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
- "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
+ "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-module-transforms": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1299,13 +1299,13 @@
}
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
- "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz",
+ "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1315,13 +1315,13 @@
}
},
"node_modules/@babel/plugin-transform-numeric-separator": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz",
- "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz",
+ "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1331,17 +1331,17 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.28.4",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
- "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz",
+ "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-parameters": "^7.27.7",
- "@babel/traverse": "^7.28.4"
+ "@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1368,13 +1368,13 @@
}
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz",
- "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz",
+ "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1384,13 +1384,13 @@
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
- "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz",
+ "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
@@ -1417,14 +1417,14 @@
}
},
"node_modules/@babel/plugin-transform-private-methods": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz",
- "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz",
+ "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-class-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1434,15 +1434,15 @@
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz",
- "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz",
+ "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "@babel/helper-create-class-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1500,17 +1500,17 @@
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz",
- "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
+ "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "@babel/helper-module-imports": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-syntax-jsx": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-syntax-jsx": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1553,13 +1553,13 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
- "version": "7.28.4",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
- "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz",
+ "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1569,14 +1569,14 @@
}
},
"node_modules/@babel/plugin-transform-regexp-modifiers": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz",
- "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz",
+ "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1618,13 +1618,13 @@
}
},
"node_modules/@babel/plugin-transform-spread": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz",
- "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz",
+ "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
@@ -1683,17 +1683,17 @@
}
},
"node_modules/@babel/plugin-transform-typescript": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz",
- "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
+ "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
- "@babel/helper-create-class-features-plugin": "^7.28.5",
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
- "@babel/plugin-syntax-typescript": "^7.27.1"
+ "@babel/plugin-syntax-typescript": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1719,14 +1719,14 @@
}
},
"node_modules/@babel/plugin-transform-unicode-property-regex": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz",
- "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz",
+ "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1753,14 +1753,14 @@
}
},
"node_modules/@babel/plugin-transform-unicode-sets-regex": {
- "version": "7.27.1",
- "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz",
- "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz",
+ "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-create-regexp-features-plugin": "^7.27.1",
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1770,76 +1770,76 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.28.5.tgz",
- "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.28.6.tgz",
+ "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.28.5",
- "@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
"@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
- "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6",
"@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
- "@babel/plugin-syntax-import-assertions": "^7.27.1",
- "@babel/plugin-syntax-import-attributes": "^7.27.1",
+ "@babel/plugin-syntax-import-assertions": "^7.28.6",
+ "@babel/plugin-syntax-import-attributes": "^7.28.6",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.27.1",
- "@babel/plugin-transform-async-generator-functions": "^7.28.0",
- "@babel/plugin-transform-async-to-generator": "^7.27.1",
+ "@babel/plugin-transform-async-generator-functions": "^7.28.6",
+ "@babel/plugin-transform-async-to-generator": "^7.28.6",
"@babel/plugin-transform-block-scoped-functions": "^7.27.1",
- "@babel/plugin-transform-block-scoping": "^7.28.5",
- "@babel/plugin-transform-class-properties": "^7.27.1",
- "@babel/plugin-transform-class-static-block": "^7.28.3",
- "@babel/plugin-transform-classes": "^7.28.4",
- "@babel/plugin-transform-computed-properties": "^7.27.1",
+ "@babel/plugin-transform-block-scoping": "^7.28.6",
+ "@babel/plugin-transform-class-properties": "^7.28.6",
+ "@babel/plugin-transform-class-static-block": "^7.28.6",
+ "@babel/plugin-transform-classes": "^7.28.6",
+ "@babel/plugin-transform-computed-properties": "^7.28.6",
"@babel/plugin-transform-destructuring": "^7.28.5",
- "@babel/plugin-transform-dotall-regex": "^7.27.1",
+ "@babel/plugin-transform-dotall-regex": "^7.28.6",
"@babel/plugin-transform-duplicate-keys": "^7.27.1",
- "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6",
"@babel/plugin-transform-dynamic-import": "^7.27.1",
- "@babel/plugin-transform-explicit-resource-management": "^7.28.0",
- "@babel/plugin-transform-exponentiation-operator": "^7.28.5",
+ "@babel/plugin-transform-explicit-resource-management": "^7.28.6",
+ "@babel/plugin-transform-exponentiation-operator": "^7.28.6",
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
"@babel/plugin-transform-for-of": "^7.27.1",
"@babel/plugin-transform-function-name": "^7.27.1",
- "@babel/plugin-transform-json-strings": "^7.27.1",
+ "@babel/plugin-transform-json-strings": "^7.28.6",
"@babel/plugin-transform-literals": "^7.27.1",
- "@babel/plugin-transform-logical-assignment-operators": "^7.28.5",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.28.6",
"@babel/plugin-transform-member-expression-literals": "^7.27.1",
"@babel/plugin-transform-modules-amd": "^7.27.1",
- "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.28.6",
"@babel/plugin-transform-modules-systemjs": "^7.28.5",
"@babel/plugin-transform-modules-umd": "^7.27.1",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
"@babel/plugin-transform-new-target": "^7.27.1",
- "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
- "@babel/plugin-transform-numeric-separator": "^7.27.1",
- "@babel/plugin-transform-object-rest-spread": "^7.28.4",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6",
+ "@babel/plugin-transform-numeric-separator": "^7.28.6",
+ "@babel/plugin-transform-object-rest-spread": "^7.28.6",
"@babel/plugin-transform-object-super": "^7.27.1",
- "@babel/plugin-transform-optional-catch-binding": "^7.27.1",
- "@babel/plugin-transform-optional-chaining": "^7.28.5",
+ "@babel/plugin-transform-optional-catch-binding": "^7.28.6",
+ "@babel/plugin-transform-optional-chaining": "^7.28.6",
"@babel/plugin-transform-parameters": "^7.27.7",
- "@babel/plugin-transform-private-methods": "^7.27.1",
- "@babel/plugin-transform-private-property-in-object": "^7.27.1",
+ "@babel/plugin-transform-private-methods": "^7.28.6",
+ "@babel/plugin-transform-private-property-in-object": "^7.28.6",
"@babel/plugin-transform-property-literals": "^7.27.1",
- "@babel/plugin-transform-regenerator": "^7.28.4",
- "@babel/plugin-transform-regexp-modifiers": "^7.27.1",
+ "@babel/plugin-transform-regenerator": "^7.28.6",
+ "@babel/plugin-transform-regexp-modifiers": "^7.28.6",
"@babel/plugin-transform-reserved-words": "^7.27.1",
"@babel/plugin-transform-shorthand-properties": "^7.27.1",
- "@babel/plugin-transform-spread": "^7.27.1",
+ "@babel/plugin-transform-spread": "^7.28.6",
"@babel/plugin-transform-sticky-regex": "^7.27.1",
"@babel/plugin-transform-template-literals": "^7.27.1",
"@babel/plugin-transform-typeof-symbol": "^7.27.1",
"@babel/plugin-transform-unicode-escapes": "^7.27.1",
- "@babel/plugin-transform-unicode-property-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-property-regex": "^7.28.6",
"@babel/plugin-transform-unicode-regex": "^7.27.1",
- "@babel/plugin-transform-unicode-sets-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.28.6",
"@babel/preset-modules": "0.1.6-no-external-plugins",
"babel-plugin-polyfill-corejs2": "^0.4.14",
"babel-plugin-polyfill-corejs3": "^0.13.0",
@@ -1921,42 +1921,42 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.28.4",
- "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz",
- "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz",
+ "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
- "version": "7.27.2",
- "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz",
- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/parser": "^7.27.2",
- "@babel/types": "^7.27.1"
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.5.tgz",
- "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.6.tgz",
+ "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.28.5",
+ "@babel/code-frame": "^7.28.6",
+ "@babel/generator": "^7.28.6",
"@babel/helper-globals": "^7.28.0",
- "@babel/parser": "^7.28.5",
- "@babel/template": "^7.27.2",
- "@babel/types": "^7.28.5",
+ "@babel/parser": "^7.28.6",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6",
"debug": "^4.3.1"
},
"engines": {
@@ -1964,9 +1964,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.28.5",
- "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
- "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "version": "7.28.6",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.6.tgz",
+ "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2016,9 +2016,9 @@
}
},
"node_modules/@electron-forge/cli": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/cli/-/cli-7.10.2.tgz",
- "integrity": "sha512-X1RtS5IqNgzGDS2rr1q0Y74wU/m3DbU4vSgllNun1ZQv1BfMpDcKLhnKi3aeetoA0huLTpMVU9eWJ7bziI9fxA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/cli/-/cli-7.11.1.tgz",
+ "integrity": "sha512-pk8AoLsr7t7LBAt0cFD06XFA6uxtPdvtLx06xeal7O9o7GHGCbj29WGwFoJ8Br/ENM0Ho868S3PrAn1PtBXt5g==",
"dev": true,
"funding": [
{
@@ -2032,9 +2032,9 @@
],
"license": "MIT",
"dependencies": {
- "@electron-forge/core": "7.10.2",
- "@electron-forge/core-utils": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/core": "7.11.1",
+ "@electron-forge/core-utils": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
"@electron/get": "^3.0.0",
"@inquirer/prompts": "^6.0.1",
"@listr2/prompt-adapter-inquirer": "^2.0.22",
@@ -2056,9 +2056,9 @@
}
},
"node_modules/@electron-forge/core": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/core/-/core-7.10.2.tgz",
- "integrity": "sha512-HAIuOtpOfGjA0cd55tbEV2gAv+A7tSZg9bonmVDYFEe6dBgbLk8a3+/1fJUdWW8fyFkg1wa8zK7pjP751bAXsA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/core/-/core-7.11.1.tgz",
+ "integrity": "sha512-YtuPLzggPKPabFAD2rOZFE0s7f4KaUTpGRduhSMbZUqpqD1TIPyfoDBpYiZvao3Ht8pyZeOJjbzcC0LpFs9gIQ==",
"dev": true,
"funding": [
{
@@ -2072,17 +2072,17 @@
],
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.10.2",
- "@electron-forge/maker-base": "7.10.2",
- "@electron-forge/plugin-base": "7.10.2",
- "@electron-forge/publisher-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/template-base": "7.10.2",
- "@electron-forge/template-vite": "7.10.2",
- "@electron-forge/template-vite-typescript": "7.10.2",
- "@electron-forge/template-webpack": "7.10.2",
- "@electron-forge/template-webpack-typescript": "7.10.2",
- "@electron-forge/tracer": "7.10.2",
+ "@electron-forge/core-utils": "7.11.1",
+ "@electron-forge/maker-base": "7.11.1",
+ "@electron-forge/plugin-base": "7.11.1",
+ "@electron-forge/publisher-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/template-base": "7.11.1",
+ "@electron-forge/template-vite": "7.11.1",
+ "@electron-forge/template-vite-typescript": "7.11.1",
+ "@electron-forge/template-webpack": "7.11.1",
+ "@electron-forge/template-webpack-typescript": "7.11.1",
+ "@electron-forge/tracer": "7.11.1",
"@electron/get": "^3.0.0",
"@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0",
@@ -2112,13 +2112,13 @@
}
},
"node_modules/@electron-forge/core-utils": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/core-utils/-/core-utils-7.10.2.tgz",
- "integrity": "sha512-JXrk2hWR4q8KgZFABpojjuqql3tYeVIH6qmtbkNEkZEQq7YIxajJBCct7J7bWfNQTmHotsQ3k5KLknhyhTaBMw==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/core-utils/-/core-utils-7.11.1.tgz",
+ "integrity": "sha512-9UxRWVsfcziBsbAA2MS0Oz4yYovQCO2BhnGIfsbKNTBtMc/RcVSxAS0NMyymce44i43p1ZC/FqWhnt1XqYw3bQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
"@electron/rebuild": "^3.7.0",
"@malept/cross-spawn-promise": "^2.0.0",
"chalk": "^4.0.0",
@@ -2134,13 +2134,13 @@
}
},
"node_modules/@electron-forge/maker-base": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/maker-base/-/maker-base-7.10.2.tgz",
- "integrity": "sha512-1QN4qnPVTjo+qWYG+s0kYv7XcuIowsPVvbl718FgJUcvkxyRjUA6kWHjFxRvdV6g7Sa2PzZBF+/Mrjpws1lehQ==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/maker-base/-/maker-base-7.11.1.tgz",
+ "integrity": "sha512-yhZrCGoN6bDeiB5DHFaueZ1h84AReElEj+f0hl2Ph4UbZnO0cnLpbx+Bs+XfMLAiA+beC8muB5UDK5ysfuT9BQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
"fs-extra": "^10.0.0",
"which": "^2.0.2"
},
@@ -2149,14 +2149,14 @@
}
},
"node_modules/@electron-forge/maker-deb": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/maker-deb/-/maker-deb-7.10.2.tgz",
- "integrity": "sha512-4MPr9NW5UbEUbf9geZn5R/0O/QVIiy2EgUXOYOeKkA7oR8U6I1I3+BytYFHYcxbY6+PGhi1H1VTLJLITbHGVWw==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/maker-deb/-/maker-deb-7.11.1.tgz",
+ "integrity": "sha512-QTYiryQLYPDkq6pIfBmx0GQ6D8QatUkowH7rTlW5MnCUa0uumX0Xu7yGIjesuwW37fxT3Lv4xi+FSXMCm2eC1w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/maker-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -2166,14 +2166,14 @@
}
},
"node_modules/@electron-forge/maker-rpm": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/maker-rpm/-/maker-rpm-7.10.2.tgz",
- "integrity": "sha512-LQoeYzbY/z1yuBBA+bNutCJmhCA4NcXUbFO4OTqsIX8B6y1zNTYZT4JEuhoK7eBsP4/Rz6u/JnNp0XOyjftOUQ==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/maker-rpm/-/maker-rpm-7.11.1.tgz",
+ "integrity": "sha512-iEfJPRQQyaTqk2EbUfZgulChNWvxGXeYUH0xBX/r5cj1pL4vcJXt3jLMQBVn3mk/0Ytv9UWRs8R/XuNWX6sf2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/maker-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -2183,14 +2183,14 @@
}
},
"node_modules/@electron-forge/maker-squirrel": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/maker-squirrel/-/maker-squirrel-7.10.2.tgz",
- "integrity": "sha512-Y5EhNSBXf4a7qcq+BK/x5qVDlQ1Gez5V+arUpDvVxf1zwvsB1aSyAjmoBrOKGYD9A5pJzjkMWMDw95MStl1W4A==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/maker-squirrel/-/maker-squirrel-7.11.1.tgz",
+ "integrity": "sha512-oSg7fgad6l+X0DjtRkSpMzB0AjzyDO4mb2gzM4kTodkP1ADeiMi08bxy0ZeCESqLm5+fG72cAPmEr3BAPvI1yw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/maker-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -2201,14 +2201,14 @@
}
},
"node_modules/@electron-forge/maker-zip": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/maker-zip/-/maker-zip-7.10.2.tgz",
- "integrity": "sha512-APRqVPM+O1rj4O7sk5f8tqJpS5UgxcUJEsCnXN4JRpdRvsOlMopzYZdazlCLH9l7S+r4ZKirjtMluIGeYq8YOg==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/maker-zip/-/maker-zip-7.11.1.tgz",
+ "integrity": "sha512-30rcp0AbJLfkFBX2hmO14LKXx7z9V61LffTVbTCFMh5vUB2kZvcA5xAhsBk2oUJWfGVxe1DuSEU0rDR9bUMHUg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/maker-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/maker-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
"cross-zip": "^4.0.0",
"fs-extra": "^10.0.0",
"got": "^11.8.5"
@@ -2218,41 +2218,41 @@
}
},
"node_modules/@electron-forge/plugin-auto-unpack-natives": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.10.2.tgz",
- "integrity": "sha512-uQnahm1DECwqI8hBC7PKccyfovY/YqHNz8de3OxyjQDmwsqQfCA8Ucyh1E9n4NMEpw6Co8KLn+qF2BuIOsftLA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-auto-unpack-natives/-/plugin-auto-unpack-natives-7.11.1.tgz",
+ "integrity": "sha512-5uRM3WNv7jIeDt8pLP3V4U2puWHPGJ/3qRuSE47RKgTp5qxpZidWHSYcEJJxjoqOL/7KFwSqKSQ/a36GoZV4Fg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/plugin-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/plugin-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/plugin-base": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-base/-/plugin-base-7.10.2.tgz",
- "integrity": "sha512-+4YLmkLZxvS6JFXYNI4dHt8Il8iIvwk2o6lCJGwNysOUq2KOZ3Wu1He4Ko8HhKcO1VWbFvslbh57oQn963Aryw==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-base/-/plugin-base-7.11.1.tgz",
+ "integrity": "sha512-lKpSOV1GA3FoYiD9k05i6v4KaQVmojnRgCr7d6VL1bFp13QOtXSaAWhFI9mtSY7rGElOacX6Zt7P7rPoB8T9eQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/plugin-fuses": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-fuses/-/plugin-fuses-7.10.2.tgz",
- "integrity": "sha512-X8FaBL5pVvKCTBNaa9EjbH6vuaeIU7UcPSmP9501XF4zcKPCfTbQKz49LTMl7gd5YzUm82IlqRjte12LLpcSDQ==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-fuses/-/plugin-fuses-7.11.1.tgz",
+ "integrity": "sha512-Td517mHf+RjQAayFDM2kKb7NaGdRXrZfPbc7KOHlGbXthp5YTkFu2cCZGWokiqt1y1wsFaAodULhqBIg7vbbbw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/plugin-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/plugin-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
@@ -2262,16 +2262,16 @@
}
},
"node_modules/@electron-forge/plugin-webpack": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-webpack/-/plugin-webpack-7.10.2.tgz",
- "integrity": "sha512-DUv82kOWsZQ8DQmwZ7SlM2BdDQDwAzTYcplEu2dMuk/kEjEyykzvULS+LPHW5pR8qeq3usJlZdPGwf5/oJ4y4w==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/plugin-webpack/-/plugin-webpack-7.11.1.tgz",
+ "integrity": "sha512-WeP65H68/81FLiEENjq7XIwCc+ZUTlj1+Xz8Ue/uqKZeBnH3B4bjE6SaMWFgkbl/BgIGs2m61huKfl84z0w6fA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.10.2",
- "@electron-forge/plugin-base": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/web-multi-logger": "7.10.2",
+ "@electron-forge/core-utils": "7.11.1",
+ "@electron-forge/plugin-base": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/web-multi-logger": "7.11.1",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"fast-glob": "^3.2.7",
@@ -2287,26 +2287,26 @@
}
},
"node_modules/@electron-forge/publisher-base": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/publisher-base/-/publisher-base-7.10.2.tgz",
- "integrity": "sha512-2k2VOY0wOoAgQoQXn/u3EJ2Ka2v363+wC/+zUMTWGeRHW8pRwX84WX2SpsTttRzbsqAEMJYw5FAzgMBEQUTfpg==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/publisher-base/-/publisher-base-7.11.1.tgz",
+ "integrity": "sha512-rXE9oMFGMtdQrixnumWYH5TTGsp99iPHZb3jI74YWq518ctCh6DlIgWlhf6ok2X0+lhWovcIb45KJucUFAQ13w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2"
+ "@electron-forge/shared-types": "7.11.1"
},
"engines": {
"node": ">= 16.4.0"
}
},
"node_modules/@electron-forge/shared-types": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/shared-types/-/shared-types-7.10.2.tgz",
- "integrity": "sha512-e2pd9RsdbKwsNf6UtKoolmJGy92Nc0/XO4SI91doV8cM954hM2XSYz3VHoqXebMFAF1JDfXoEUt6UCRbEDgMgw==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/shared-types/-/shared-types-7.11.1.tgz",
+ "integrity": "sha512-vvBWdAEh53UJlDGUevpaJk1+sqDMQibfrbHR+0IPA4MPyQex7/Uhv3vYH9oGHujBVAChQahjAuJt0fG6IJBLZg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/tracer": "7.10.2",
+ "@electron-forge/tracer": "7.11.1",
"@electron/packager": "^18.3.5",
"@electron/rebuild": "^3.7.0",
"listr2": "^7.0.2"
@@ -2316,14 +2316,14 @@
}
},
"node_modules/@electron-forge/template-base": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/template-base/-/template-base-7.10.2.tgz",
- "integrity": "sha512-D9DbEx3rtikIhUyn4tcz2pJqHNU/+FXKNnzSvmrJoJ9LusR3C42OU9GtbU8oT3nawpnCGgPFIOGXrzexFPp6DA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/template-base/-/template-base-7.11.1.tgz",
+ "integrity": "sha512-XpTaEf+EfQw+0BlSAtSpZKYIKYvKu4raNzSGHZZoSYHp+HDC7R+MlpFQmSJiGdYQzQ14C+uxO42tVjgM0DMbpw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/core-utils": "7.10.2",
- "@electron-forge/shared-types": "7.10.2",
+ "@electron-forge/core-utils": "7.11.1",
+ "@electron-forge/shared-types": "7.11.1",
"@malept/cross-spawn-promise": "^2.0.0",
"debug": "^4.3.1",
"fs-extra": "^10.0.0",
@@ -2335,14 +2335,14 @@
}
},
"node_modules/@electron-forge/template-vite": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/template-vite/-/template-vite-7.10.2.tgz",
- "integrity": "sha512-hR9HBOM902yq7zhFl8bO3w5ufMgitdd5ZwDzAdKITFh2ttZemHy9ha5S0K+R+4GoXHz8t7hUTHk8+iPy09qrpA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/template-vite/-/template-vite-7.11.1.tgz",
+ "integrity": "sha512-Or8Lxf4awoeUZoMTKJEw5KQDIhqOFs24WhVka3yZXxc6VgVWN79KmYKYM6uM/YMQttmafhsBhY2t1Lxo1WR/ug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/template-base": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/template-base": "7.11.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -2350,14 +2350,14 @@
}
},
"node_modules/@electron-forge/template-vite-typescript": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.10.2.tgz",
- "integrity": "sha512-df7rpxxIOIyZn0RfQ1GIlLW7dXhxkerc9uZ3ozO4C7zfvip3z0Mg+wS1synktPfr4WISaPktIdnj3mVu6Uu7Mw==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/template-vite-typescript/-/template-vite-typescript-7.11.1.tgz",
+ "integrity": "sha512-Us4AHXFb+4z+gXgZImSqMBS63oKnsQWLOhqRg321xiDzu2UcQPlwgWNb4rAEKNVC1e7LXrUNDHuBiTrQkvWXbg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/template-base": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/template-base": "7.11.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -2365,14 +2365,14 @@
}
},
"node_modules/@electron-forge/template-webpack": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/template-webpack/-/template-webpack-7.10.2.tgz",
- "integrity": "sha512-VIUXA+XHM5SLjg7fIpOOmBsgi0LstkjrEz4gUzVL0AaITM7e+BCziIHld1ceXLbQ1FnKtrUGnQ9X/cHYxYvhHg==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/template-webpack/-/template-webpack-7.11.1.tgz",
+ "integrity": "sha512-15lbXxi+er461MPk6sbwAOyjofAHwmQjTvxNCiNpaU2naEwbj3t0SlLq/BMr5HxnVOaMmA7+lKV9afkIom+d4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/template-base": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/template-base": "7.11.1",
"fs-extra": "^10.0.0"
},
"engines": {
@@ -2380,14 +2380,14 @@
}
},
"node_modules/@electron-forge/template-webpack-typescript": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.10.2.tgz",
- "integrity": "sha512-JtrLUAFbxxWJ1kU7b8MNyL5SO9/rY5UeNz1b9hvMvilW8GxyMWUen58dafgdnx3OpKLNZnhOOhgRagNppEzJOA==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/template-webpack-typescript/-/template-webpack-typescript-7.11.1.tgz",
+ "integrity": "sha512-6ExfFnFkHBz8rvRFTFg5HVGTC12uJpbVk4q8DVg0R8rhhxhqiVNh8lF2UPtZ2yT2UtGWjXNVlyP3Y3T6q6E3GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@electron-forge/shared-types": "7.10.2",
- "@electron-forge/template-base": "7.10.2",
+ "@electron-forge/shared-types": "7.11.1",
+ "@electron-forge/template-base": "7.11.1",
"fs-extra": "^10.0.0",
"typescript": "~5.4.5",
"webpack": "^5.69.1"
@@ -2411,9 +2411,9 @@
}
},
"node_modules/@electron-forge/tracer": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/tracer/-/tracer-7.10.2.tgz",
- "integrity": "sha512-jhLLQbttfZViSOYn/3SJc8HML+jNZAytPVJwgGGd3coUiFysWJ2Xald99iqOiouPAhIigBfNPxQb/q/EbcDu4g==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/tracer/-/tracer-7.11.1.tgz",
+ "integrity": "sha512-tiB6cglVQFcSw9N8GRwVwZUeB9u0DOx2Mj7aFXBUsFLUYQapvVGv51tUSy/UAW5lvmubGscYIILuVko+II3+NA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2424,9 +2424,9 @@
}
},
"node_modules/@electron-forge/web-multi-logger": {
- "version": "7.10.2",
- "resolved": "https://registry.npmmirror.com/@electron-forge/web-multi-logger/-/web-multi-logger-7.10.2.tgz",
- "integrity": "sha512-6ltVtkpGSleEGOPd4C+Z+GUKoMsKjTGD1YwcedZM1L07dlpQAgvRU2e/6lRQuOxVPmylpzbotow/h1mRgDBVWQ==",
+ "version": "7.11.1",
+ "resolved": "https://registry.npmmirror.com/@electron-forge/web-multi-logger/-/web-multi-logger-7.11.1.tgz",
+ "integrity": "sha512-NZQyAxaDPHK/9G24sMKB3RknYxz79C+qO+SkIKoGjN0vT44kxAYc0u4rpem9FYPNN3hpSLFVOgn1EWoaKXnl7A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2881,9 +2881,9 @@
"license": "MIT"
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "version": "4.9.1",
+ "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3105,9 +3105,9 @@
}
},
"node_modules/@inquirer/core/node_modules/@types/node": {
- "version": "22.19.3",
- "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.3.tgz",
- "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+ "version": "22.19.7",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.7.tgz",
+ "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3632,18 +3632,18 @@
}
},
"node_modules/@parcel/watcher": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz",
- "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz",
+ "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
- "detect-libc": "^1.0.3",
+ "detect-libc": "^2.0.3",
"is-glob": "^4.0.3",
- "micromatch": "^4.0.5",
- "node-addon-api": "^7.0.0"
+ "node-addon-api": "^7.0.0",
+ "picomatch": "^4.0.3"
},
"engines": {
"node": ">= 10.0.0"
@@ -3653,25 +3653,25 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "@parcel/watcher-android-arm64": "2.5.1",
- "@parcel/watcher-darwin-arm64": "2.5.1",
- "@parcel/watcher-darwin-x64": "2.5.1",
- "@parcel/watcher-freebsd-x64": "2.5.1",
- "@parcel/watcher-linux-arm-glibc": "2.5.1",
- "@parcel/watcher-linux-arm-musl": "2.5.1",
- "@parcel/watcher-linux-arm64-glibc": "2.5.1",
- "@parcel/watcher-linux-arm64-musl": "2.5.1",
- "@parcel/watcher-linux-x64-glibc": "2.5.1",
- "@parcel/watcher-linux-x64-musl": "2.5.1",
- "@parcel/watcher-win32-arm64": "2.5.1",
- "@parcel/watcher-win32-ia32": "2.5.1",
- "@parcel/watcher-win32-x64": "2.5.1"
+ "@parcel/watcher-android-arm64": "2.5.6",
+ "@parcel/watcher-darwin-arm64": "2.5.6",
+ "@parcel/watcher-darwin-x64": "2.5.6",
+ "@parcel/watcher-freebsd-x64": "2.5.6",
+ "@parcel/watcher-linux-arm-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm-musl": "2.5.6",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.6",
+ "@parcel/watcher-linux-arm64-musl": "2.5.6",
+ "@parcel/watcher-linux-x64-glibc": "2.5.6",
+ "@parcel/watcher-linux-x64-musl": "2.5.6",
+ "@parcel/watcher-win32-arm64": "2.5.6",
+ "@parcel/watcher-win32-ia32": "2.5.6",
+ "@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
- "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
+ "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
"cpu": [
"arm64"
],
@@ -3690,9 +3690,9 @@
}
},
"node_modules/@parcel/watcher-darwin-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
- "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
+ "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
"cpu": [
"arm64"
],
@@ -3711,9 +3711,9 @@
}
},
"node_modules/@parcel/watcher-darwin-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
- "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
+ "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
"cpu": [
"x64"
],
@@ -3732,9 +3732,9 @@
}
},
"node_modules/@parcel/watcher-freebsd-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
- "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
+ "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
"cpu": [
"x64"
],
@@ -3753,9 +3753,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
- "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
+ "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
"cpu": [
"arm"
],
@@ -3774,9 +3774,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
- "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
+ "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
"cpu": [
"arm"
],
@@ -3795,9 +3795,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
- "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
+ "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
"cpu": [
"arm64"
],
@@ -3816,9 +3816,9 @@
}
},
"node_modules/@parcel/watcher-linux-arm64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
- "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
+ "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
"cpu": [
"arm64"
],
@@ -3837,9 +3837,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-glibc": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
- "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
+ "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
@@ -3858,9 +3858,9 @@
}
},
"node_modules/@parcel/watcher-linux-x64-musl": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
- "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
+ "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
"cpu": [
"x64"
],
@@ -3879,9 +3879,9 @@
}
},
"node_modules/@parcel/watcher-win32-arm64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
- "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
+ "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
"cpu": [
"arm64"
],
@@ -3900,9 +3900,9 @@
}
},
"node_modules/@parcel/watcher-win32-ia32": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
- "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
+ "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
"cpu": [
"ia32"
],
@@ -3921,9 +3921,9 @@
}
},
"node_modules/@parcel/watcher-win32-x64": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
- "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "version": "2.5.6",
+ "resolved": "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
+ "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
"cpu": [
"x64"
],
@@ -3941,18 +3941,18 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/watcher/node_modules/detect-libc": {
- "version": "1.0.3",
- "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-1.0.3.tgz",
- "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "node_modules/@parcel/watcher/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
"optional": true,
- "bin": {
- "detect-libc": "bin/detect-libc.js"
- },
"engines": {
- "node": ">=0.10"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@pdf-lib/standard-fonts": {
@@ -4061,9 +4061,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@rc-component/async-validator": {
- "version": "5.0.4",
- "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
- "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
+ "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4195,9 +4195,9 @@
}
},
"node_modules/@rc-component/trigger": {
- "version": "2.3.0",
- "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.0.tgz",
- "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.1.tgz",
+ "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4291,16 +4291,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@silentbot1/nat-api/node_modules/human-signals": {
- "version": "4.3.1",
- "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-4.3.1.tgz",
- "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=14.18.0"
- }
- },
"node_modules/@silentbot1/nat-api/node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-3.0.0.tgz",
@@ -4379,19 +4369,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/@silentbot1/nat-api/node_modules/strip-final-newline": {
- "version": "3.0.0",
- "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
- "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz",
@@ -4851,9 +4828,9 @@
}
},
"node_modules/@stdlib/string-base-format-interpolate": {
- "version": "0.2.2",
- "resolved": "https://registry.npmmirror.com/@stdlib/string-base-format-interpolate/-/string-base-format-interpolate-0.2.2.tgz",
- "integrity": "sha512-i9nU9rAB2+o/RR66TS9iQ8x+YzeUDL1SGiAo6GY3hP6Umz5Dx9Qp/v8T69gWVsb4a1YSclz5+YeCWaFgwvPjKA==",
+ "version": "0.2.3",
+ "resolved": "https://registry.npmmirror.com/@stdlib/string-base-format-interpolate/-/string-base-format-interpolate-0.2.3.tgz",
+ "integrity": "sha512-6r8SxRqSuzFaU1Qhvz5XORtYSo/BZ0E0/V0RS1qAX6Z3/Dn+g/qlfYEwQXpSwsch1yIURuNfBc77ychnP7d8XA==",
"dev": true,
"license": "Apache-2.0",
"os": [
@@ -4877,9 +4854,9 @@
}
},
"node_modules/@stdlib/string-base-format-tokenize": {
- "version": "0.2.2",
- "resolved": "https://registry.npmmirror.com/@stdlib/string-base-format-tokenize/-/string-base-format-tokenize-0.2.2.tgz",
- "integrity": "sha512-kXq2015i+LJjqth5dN+hYnvJXBSzRm8w0ABWB5tYAsIuQTpQK+mSo2muM8JBEFEnqUHAwpUsu2qNTK/9o8lsJg==",
+ "version": "0.2.3",
+ "resolved": "https://registry.npmmirror.com/@stdlib/string-base-format-tokenize/-/string-base-format-tokenize-0.2.3.tgz",
+ "integrity": "sha512-Ha8ADT8sRlRClu3NSKbmKZY9V9I8TK80pU/pwTbr1F8j+KiIZx2Fs7M5SMk8n1rm1hwxC3Af5qU5t2EZ0fJISg==",
"dev": true,
"license": "Apache-2.0",
"os": [
@@ -5507,10 +5484,8 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "extraneous": true,
"license": "Apache-2.0",
- "optional": true,
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5613,10 +5588,8 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "extraneous": true,
"license": "Apache-2.0",
- "optional": true,
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5663,17 +5636,17 @@
}
},
"node_modules/@thaunknown/simple-peer": {
- "version": "10.0.12",
- "resolved": "https://registry.npmmirror.com/@thaunknown/simple-peer/-/simple-peer-10.0.12.tgz",
- "integrity": "sha512-sDrkkOdzlJL8+FXQqYcBb2THHQU+Yrar92SjfW4ZLs877/4QA2kFejuA6DVepsoMpoIbXShc7OCXCwYt4AtGdQ==",
+ "version": "10.1.0",
+ "resolved": "https://registry.npmmirror.com/@thaunknown/simple-peer/-/simple-peer-10.1.0.tgz",
+ "integrity": "sha512-xNM49v0rBbjIKrS9XNwXW3FFuGvsPGadFRWbBdLAY87pEJeo7V0dxyX6GBHP8UVlefffRedCLsjYXb6i8W9Ofg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "debug": "^4.3.7",
+ "debug": "^4.4.3",
"err-code": "^3.0.1",
- "streamx": "^2.20.1",
- "uint8-util": "^2.2.5",
- "webrtc-polyfill": "^1.1.10"
+ "streamx": "^2.23.0",
+ "uint8-util": "^2.2.6",
+ "webrtc-polyfill": "^1.2.0"
}
},
"node_modules/@thaunknown/simple-peer/node_modules/err-code": {
@@ -5683,6 +5656,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@thaunknown/simple-peer/node_modules/streamx": {
+ "version": "2.23.0",
+ "resolved": "https://registry.npmmirror.com/streamx/-/streamx-2.23.0.tgz",
+ "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
"node_modules/@thaunknown/simple-websocket": {
"version": "9.1.3",
"resolved": "https://registry.npmmirror.com/@thaunknown/simple-websocket/-/simple-websocket-9.1.3.tgz",
@@ -5908,9 +5893,22 @@
}
},
"node_modules/@types/express-serve-static-core": {
- "version": "4.19.7",
- "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
- "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
+ "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/express/node_modules/@types/express-serve-static-core": {
+ "version": "4.19.8",
+ "resolved": "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
+ "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5949,9 +5947,9 @@
"license": "MIT"
},
"node_modules/@types/http-cache-semantics": {
- "version": "4.0.4",
- "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
- "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
+ "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==",
"dev": true,
"license": "MIT"
},
@@ -5997,9 +5995,9 @@
}
},
"node_modules/@types/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==",
"dev": true,
"license": "MIT"
},
@@ -6030,9 +6028,9 @@
}
},
"node_modules/@types/node": {
- "version": "24.10.4",
- "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.4.tgz",
- "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
+ "version": "24.10.9",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.9.tgz",
+ "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
@@ -6090,9 +6088,9 @@
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.2.7",
- "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.7.tgz",
- "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
+ "version": "19.2.10",
+ "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.10.tgz",
+ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -6133,13 +6131,12 @@
"license": "MIT"
},
"node_modules/@types/send": {
- "version": "0.17.6",
- "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.6.tgz",
- "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/mime": "^1",
"@types/node": "*"
}
},
@@ -6165,6 +6162,17 @@
"@types/send": "<1"
}
},
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmmirror.com/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/simple-peer": {
"version": "9.11.9",
"resolved": "https://registry.npmmirror.com/@types/simple-peer/-/simple-peer-9.11.9.tgz",
@@ -6471,9 +6479,9 @@
}
},
"node_modules/@vscode/sudo-prompt": {
- "version": "9.3.1",
- "resolved": "https://registry.npmmirror.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.1.tgz",
- "integrity": "sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA==",
+ "version": "9.3.2",
+ "resolved": "https://registry.npmmirror.com/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz",
+ "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==",
"dev": true,
"license": "MIT"
},
@@ -6784,7 +6792,6 @@
"version": "0.5.16",
"resolved": "https://registry.npmmirror.com/adm-zip/-/adm-zip-0.5.16.tgz",
"integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0"
@@ -7340,14 +7347,14 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.14",
- "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
- "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==",
+ "version": "0.4.15",
+ "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz",
+ "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.27.7",
- "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-define-polyfill-provider": "^0.6.6",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -7379,13 +7386,13 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.6.5",
- "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz",
- "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==",
+ "version": "0.6.6",
+ "resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz",
+ "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.5"
+ "@babel/helper-define-polyfill-provider": "^0.6.6"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -7434,9 +7441,9 @@
}
},
"node_modules/bare-fs": {
- "version": "4.5.2",
- "resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.5.2.tgz",
- "integrity": "sha512-veTnRzkb6aPHOvSKIOy60KzURfBdUflr5VReI+NSaPL6xf+XLdONQgZgpYvUuZLVQ8dCqxpBAudaOM1+KpAUxw==",
+ "version": "4.5.3",
+ "resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.5.3.tgz",
+ "integrity": "sha512-9+kwVx8QYvt3hPWnmb19tPnh38c6Nihz8Lx3t0g9+4GoIf3/fTgYwM4Z6NxgI+B9elLQA7mLE9PpqcWtOMRDiQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -7459,9 +7466,9 @@
}
},
"node_modules/bare-module-resolve": {
- "version": "1.12.0",
- "resolved": "https://registry.npmmirror.com/bare-module-resolve/-/bare-module-resolve-1.12.0.tgz",
- "integrity": "sha512-JrzrqlC3Tds0iKRwQs8xIIJ+FRieKA9ll0jaqpotDLZtjJPVevzRoeuUYZ5GIo1t1z7/pIRdk85Q3i/2xQLfEQ==",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmmirror.com/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz",
+ "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
@@ -7568,9 +7575,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.9.11",
- "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
- "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
+ "version": "2.9.19",
+ "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
+ "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -7837,9 +7844,9 @@
}
},
"node_modules/block-iterator": {
- "version": "1.1.1",
- "resolved": "https://registry.npmmirror.com/block-iterator/-/block-iterator-1.1.1.tgz",
- "integrity": "sha512-DrjdVWZemVO4iBf4tiOXjUrY5cNesjzy0t7sIiu2rdl8cOCHRxAgKjSJFc3vBZYYMMmshUAxajl8QQh/uxXTKQ==",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/block-iterator/-/block-iterator-1.1.2.tgz",
+ "integrity": "sha512-yAHUP44v2K25xLPdrgVTgwtuQctlullzjczu9CoUZom5AP3g4p1R1+aWHjS1GHG9JtcSUVUnbEPiuXiW5YZ24w==",
"dev": true,
"license": "MIT"
},
@@ -8200,7 +8207,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/bufferutil/-/bufferutil-4.1.0.tgz",
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -8476,9 +8482,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001761",
- "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz",
- "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==",
+ "version": "1.0.30001766",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
+ "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
"dev": true,
"funding": [
{
@@ -9159,96 +9165,39 @@
"webpack": "^5.1.0"
}
},
- "node_modules/copy-webpack-plugin/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "node_modules/core-js-compat": {
+ "version": "3.48.0",
+ "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.48.0.tgz",
+ "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
+ "browserslist": "^4.28.1"
},
"funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
}
},
- "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true,
"license": "MIT"
},
- "node_modules/copy-webpack-plugin/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
- "node_modules/core-js-compat": {
- "version": "3.47.0",
- "resolved": "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.47.0.tgz",
- "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.28.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/core-js"
- }
- },
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cosmiconfig": {
- "version": "7.1.0",
- "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
- "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
},
"engines": {
"node": ">=10"
@@ -9965,6 +9914,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/default-gateway/node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
"node_modules/default-gateway/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz",
@@ -9998,6 +9957,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/default-gateway/node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz",
@@ -10127,9 +10096,9 @@
"license": "MIT"
},
"node_modules/diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/diff/-/diff-4.0.4.tgz",
+ "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -10883,9 +10852,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.267",
- "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
- "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "version": "1.5.283",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
+ "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==",
"dev": true,
"license": "ISC"
},
@@ -10972,9 +10941,9 @@
}
},
"node_modules/electron/node_modules/@types/node": {
- "version": "22.19.3",
- "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.3.tgz",
- "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
+ "version": "22.19.7",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-22.19.7.tgz",
+ "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11571,14 +11540,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.5.4",
- "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz",
- "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==",
+ "version": "5.5.5",
+ "resolved": "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
+ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "prettier-linter-helpers": "^1.0.0",
- "synckit": "^0.11.7"
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -11748,9 +11717,9 @@
}
},
"node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "version": "1.7.0",
+ "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -11814,9 +11783,9 @@
}
},
"node_modules/eventemitter3": {
- "version": "5.0.1",
- "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.1.tgz",
- "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
"dev": true,
"license": "MIT"
},
@@ -11830,6 +11799,16 @@
"node": ">=0.8.x"
}
},
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
"node_modules/evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@@ -12037,11 +12016,9 @@
"version": "5.0.10",
"resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "dev": true,
+ "extraneous": true,
"hasInstallScript": true,
"license": "MIT",
- "optional": true,
- "peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -12335,6 +12312,25 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
+ "node_modules/file-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
@@ -12547,6 +12543,25 @@
}
}
},
+ "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
@@ -13438,9 +13453,9 @@
}
},
"node_modules/html-webpack-plugin": {
- "version": "5.6.5",
- "resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz",
- "integrity": "sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==",
+ "version": "5.6.6",
+ "resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz",
+ "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -13640,13 +13655,13 @@
}
},
"node_modules/human-signals": {
- "version": "2.1.0",
- "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz",
- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmmirror.com/human-signals/-/human-signals-4.3.1.tgz",
+ "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": ">=10.17.0"
+ "node": ">=14.18.0"
}
},
"node_modules/humanize-ms": {
@@ -14491,9 +14506,9 @@
}
},
"node_modules/isomorphic-git": {
- "version": "1.36.1",
- "resolved": "https://registry.npmmirror.com/isomorphic-git/-/isomorphic-git-1.36.1.tgz",
- "integrity": "sha512-fC8SRT8MwoaXDK8G4z5biPEbqf2WyEJUb2MJ2ftSd39/UIlsnoZxLGux+lae0poLZO4AEcx6aUVOh5bV+P8zFA==",
+ "version": "1.36.3",
+ "resolved": "https://registry.npmmirror.com/isomorphic-git/-/isomorphic-git-1.36.3.tgz",
+ "integrity": "sha512-bHF1nQTjL0IfSo13BHDO8oQ6SvYNQduTAdPJdSmrJ5JwZY2fsyjLujEXav5hqPCegSCAnc75ZsBUHqT/NqR7QA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -15267,9 +15282,9 @@
}
},
"node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "version": "4.17.23",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"dev": true,
"license": "MIT"
},
@@ -16045,9 +16060,9 @@
}
},
"node_modules/nan": {
- "version": "2.24.0",
- "resolved": "https://registry.npmmirror.com/nan/-/nan-2.24.0.tgz",
- "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==",
+ "version": "2.25.0",
+ "resolved": "https://registry.npmmirror.com/nan/-/nan-2.25.0.tgz",
+ "integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==",
"dev": true,
"license": "MIT",
"optional": true
@@ -16146,9 +16161,9 @@
}
},
"node_modules/node-abi": {
- "version": "3.85.0",
- "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.85.0.tgz",
- "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==",
+ "version": "3.87.0",
+ "resolved": "https://registry.npmmirror.com/node-abi/-/node-abi-3.87.0.tgz",
+ "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -16184,39 +16199,17 @@
}
},
"node_modules/node-datachannel": {
- "version": "0.12.0",
- "resolved": "https://registry.npmmirror.com/node-datachannel/-/node-datachannel-0.12.0.tgz",
- "integrity": "sha512-pZ9FsVZpHdUKqyWynuCc9IBLkZPJMpDzpNk4YNPCizbIXHYifpYeWqSF35REHGIWi9JMCf11QzapsyQGo/Y4Ig==",
+ "version": "0.32.0",
+ "resolved": "https://registry.npmmirror.com/node-datachannel/-/node-datachannel-0.32.0.tgz",
+ "integrity": "sha512-kohrPL3rOWfiD+oJgc9R7Ibx10U3/yJ+oQ/uB2W2d2B+y4B1bkAMPoONRVS3PTz7AHFqdvoEe5VoRDZ+I6xFEw==",
"dev": true,
"hasInstallScript": true,
"license": "MPL 2.0",
"dependencies": {
- "node-domexception": "^2.0.1",
- "prebuild-install": "^7.0.1"
+ "prebuild-install": "^7.1.3"
},
"engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/node-datachannel/node_modules/node-domexception": {
- "version": "2.0.2",
- "resolved": "https://registry.npmmirror.com/node-domexception/-/node-domexception-2.0.2.tgz",
- "integrity": "sha512-Qf9vHK9c5MGgUXj8SnucCIS4oEPuUstjRaMplLGeZpbWMfNV1rvEcXuwoXfN51dUfD1b4muPHPQtCx/5Dj/QAA==",
- "deprecated": "Use your platform's native DOMException instead",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=16"
+ "node": ">=18.20.0"
}
},
"node_modules/node-domexception": {
@@ -16275,7 +16268,6 @@
"version": "4.8.4",
"resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
- "dev": true,
"license": "MIT",
"optional": true,
"bin": {
@@ -16380,13 +16372,6 @@
"node": ">=10"
}
},
- "node_modules/node-stdlib-browser/node_modules/punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/nopt": {
"version": "6.0.0",
"resolved": "https://registry.npmmirror.com/nopt/-/nopt-6.0.0.tgz",
@@ -17554,9 +17539,9 @@
}
},
"node_modules/prettier-linter-helpers": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
- "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
+ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17730,19 +17715,16 @@
}
},
"node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "license": "MIT"
},
"node_modules/qs": {
- "version": "6.14.0",
- "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz",
- "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "version": "6.14.1",
+ "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -18589,24 +18571,24 @@
}
},
"node_modules/react": {
- "version": "19.2.3",
- "resolved": "https://registry.npmmirror.com/react/-/react-19.2.3.tgz",
- "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmmirror.com/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
+ "version": "19.2.4",
+ "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
- "react": "^19.2.3"
+ "react": "^19.2.4"
}
},
"node_modules/react-is": {
@@ -18617,9 +18599,9 @@
"license": "MIT"
},
"node_modules/react-router": {
- "version": "7.11.0",
- "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.11.0.tgz",
- "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.13.0.tgz",
+ "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -18640,13 +18622,13 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.11.0",
- "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.11.0.tgz",
- "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
+ "version": "7.13.0",
+ "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.13.0.tgz",
+ "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "react-router": "7.11.0"
+ "react-router": "7.13.0"
},
"engines": {
"node": ">=20.0.0"
@@ -19409,9 +19391,9 @@
"license": "MIT"
},
"node_modules/sass": {
- "version": "1.97.1",
- "resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.1.tgz",
- "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
+ "version": "1.97.3",
+ "resolved": "https://registry.npmmirror.com/sass/-/sass-1.97.3.tgz",
+ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -19501,11 +19483,14 @@
}
},
"node_modules/sax": {
- "version": "1.4.3",
- "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz",
- "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
+ "version": "1.4.4",
+ "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
"dev": true,
- "license": "BlueOak-1.0.0"
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
},
"node_modules/scheduler": {
"version": "0.27.0",
@@ -19514,15 +19499,16 @@
"license": "MIT"
},
"node_modules/schema-utils": {
- "version": "3.3.0",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz",
- "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "version": "4.3.3",
+ "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
+ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
@@ -19532,6 +19518,43 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/schema-utils/node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/schema-utils/node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
+ "node_modules/schema-utils/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/scroll-into-view-if-needed": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
@@ -19667,22 +19690,26 @@
}
},
"node_modules/serve-index": {
- "version": "1.9.1",
- "resolved": "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz",
- "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
+ "version": "1.9.2",
+ "resolved": "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.2.tgz",
+ "integrity": "sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "accepts": "~1.3.4",
+ "accepts": "~1.3.8",
"batch": "0.6.1",
"debug": "2.6.9",
"escape-html": "~1.0.3",
- "http-errors": "~1.6.2",
- "mime-types": "~2.1.17",
- "parseurl": "~1.3.2"
+ "http-errors": "~1.8.0",
+ "mime-types": "~2.1.35",
+ "parseurl": "~1.3.3"
},
"engines": {
"node": ">= 0.8.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/serve-index/node_modules/debug": {
@@ -19706,28 +19733,22 @@
}
},
"node_modules/serve-index/node_modules/http-errors": {
- "version": "1.6.3",
- "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz",
- "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"depd": "~1.1.2",
- "inherits": "2.0.3",
- "setprototypeof": "1.1.0",
- "statuses": ">= 1.4.0 < 2"
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.6"
}
},
- "node_modules/serve-index/node_modules/inherits": {
- "version": "2.0.3",
- "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz",
- "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/serve-index/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
@@ -19735,13 +19756,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/serve-index/node_modules/setprototypeof": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz",
- "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
- "dev": true,
- "license": "ISC"
- },
"node_modules/serve-index/node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz",
@@ -20615,13 +20629,16 @@
}
},
"node_modules/strip-final-newline": {
- "version": "2.0.0",
- "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-json-comments": {
@@ -20849,9 +20866,9 @@
}
},
"node_modules/synckit": {
- "version": "0.11.11",
- "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.11.tgz",
- "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
+ "version": "0.11.12",
+ "resolved": "https://registry.npmmirror.com/synckit/-/synckit-0.11.12.tgz",
+ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -20995,9 +21012,9 @@
}
},
"node_modules/terser": {
- "version": "5.44.1",
- "resolved": "https://registry.npmmirror.com/terser/-/terser-5.44.1.tgz",
- "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
+ "version": "5.46.0",
+ "resolved": "https://registry.npmmirror.com/terser/-/terser-5.46.0.tgz",
+ "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -21048,63 +21065,6 @@
}
}
},
- "node_modules/terser-webpack-plugin/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz",
@@ -21702,9 +21662,9 @@
}
},
"node_modules/uint8-util": {
- "version": "2.2.5",
- "resolved": "https://registry.npmmirror.com/uint8-util/-/uint8-util-2.2.5.tgz",
- "integrity": "sha512-/QxVQD7CttWpVUKVPz9znO+3Dd4BdTSnFQ7pv/4drVhC9m4BaL2LFHTkJn6EsYoxT79VDq/2Gg8L0H22PrzyMw==",
+ "version": "2.2.6",
+ "resolved": "https://registry.npmmirror.com/uint8-util/-/uint8-util-2.2.6.tgz",
+ "integrity": "sha512-r+ZjS8CzPhtPF771ROOadUoqC40OVdiMKBI8lTfJQWb4W7+73sMBwMYmai/uvNcmZ7tBJJyZSad03yMWIt3RQg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -21895,6 +21855,16 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/uri-js/node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/url": {
"version": "0.11.4",
"resolved": "https://registry.npmmirror.com/url/-/url-0.11.4.tgz",
@@ -21909,13 +21879,6 @@
"node": ">= 0.4"
}
},
- "node_modules/url/node_modules/punycode": {
- "version": "1.4.1",
- "resolved": "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/username": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/username/-/username-5.1.0.tgz",
@@ -21993,7 +21956,6 @@
"version": "6.0.6",
"resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-6.0.6.tgz",
"integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==",
- "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -22113,9 +22075,9 @@
"license": "MIT"
},
"node_modules/watchpack": {
- "version": "2.5.0",
- "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.0.tgz",
- "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==",
+ "version": "2.5.1",
+ "resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.5.1.tgz",
+ "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -22236,63 +22198,6 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
- "node_modules/webpack-dev-middleware/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/webpack-dev-middleware/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/webpack-dev-server": {
"version": "4.15.2",
"resolved": "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz",
@@ -22353,36 +22258,6 @@
}
}
},
- "node_modules/webpack-dev-server/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/webpack-dev-server/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
"node_modules/webpack-dev-server/node_modules/ipaddr.js": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
@@ -22393,33 +22268,6 @@
"node": ">= 10"
}
},
- "node_modules/webpack-dev-server/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/webpack-dev-server/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/webpack-merge": {
"version": "5.10.0",
"resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.10.0.tgz",
@@ -22445,72 +22293,14 @@
"node": ">=10.13.0"
}
},
- "node_modules/webpack/node_modules/ajv": {
- "version": "8.17.1",
- "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
- "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "fast-uri": "^3.0.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/webpack/node_modules/ajv-keywords": {
- "version": "5.1.0",
- "resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
- "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.3"
- },
- "peerDependencies": {
- "ajv": "^8.8.2"
- }
- },
- "node_modules/webpack/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/webpack/node_modules/schema-utils": {
- "version": "4.3.3",
- "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.3.tgz",
- "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/json-schema": "^7.0.9",
- "ajv": "^8.9.0",
- "ajv-formats": "^2.1.1",
- "ajv-keywords": "^5.1.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/webrtc-polyfill": {
- "version": "1.1.10",
- "resolved": "https://registry.npmmirror.com/webrtc-polyfill/-/webrtc-polyfill-1.1.10.tgz",
- "integrity": "sha512-sOn0bj3/noUdzQX7rvk0jFbBurqWDGGo2ipl+WfgoOe/x3cxbGLk/ZUY+WHCISSlLaIeBumi1X3wxQZnUESExQ==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/webrtc-polyfill/-/webrtc-polyfill-1.2.0.tgz",
+ "integrity": "sha512-epaVJbKzWOY5Wf3k7DoZLNgHP/5IoALBvjvlZQgX+9vFnf9UfCHv+rc+r/vJ7jxQUwH3cIYx9blHfyWWxGbw1g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "node-datachannel": "^v0.12.0",
- "node-domexception": "^1.0.0"
+ "node-datachannel": "^0.32.0"
},
"engines": {
"node": ">=16.0.0"
@@ -22731,9 +22521,9 @@
"optional": true
},
"node_modules/which-typed-array": {
- "version": "1.1.19",
- "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz",
- "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "version": "1.1.20",
+ "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -22840,9 +22630,9 @@
"license": "ISC"
},
"node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "version": "8.19.0",
+ "resolved": "https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz",
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"devOptional": true,
"license": "MIT",
"engines": {
From 7e5569f201d4715982bac8b41230968fc56a835f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=94=9A=E5=8B=83=E9=B9=8F?= <2032806273@qq.com>
Date: Sat, 31 Jan 2026 17:51:44 +0800
Subject: [PATCH 19/22] refactor(launcher-update): use Squirrel for Windows
updates instead of manual ZIP extraction
---
external-resources/dlc/dlc-index.json | 6 +-
src/main/launcher-update/index.ts | 577 ++++++++++++--------------
2 files changed, 259 insertions(+), 324 deletions(-)
diff --git a/external-resources/dlc/dlc-index.json b/external-resources/dlc/dlc-index.json
index 1c4d1d0..463a477 100644
--- a/external-resources/dlc/dlc-index.json
+++ b/external-resources/dlc/dlc-index.json
@@ -32,9 +32,9 @@
"id": "AI_LEARNING_ASSISTANT_LAUNCHER",
"name": "AI学习助手启动器",
"versions": {
- "2.0.3": {
- "magnet": "magnet:?xt=urn:btih:5e685a7cbe4045efc40f7a593dddfdf56ae7c59d&dn=AI-Learning-Assistant-Launcher-win32-x64-2.0.3.zip&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
- "comment": "第一个版本",
+ "2.0.4": {
+ "magnet": "magnet:?xt=urn:btih:ea0ebea633f25e936c55a095f66f15d65089dcec&dn=AILearningAssistantLauncher-2.0.4-full.nupkg&tr=wss%3A%2F%2Ftracker.openwebtorrent.com",
+ "comment": "测试更新使用版本",
"require": {}
}
}
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index 63b70ea..b6fb248 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -14,27 +14,23 @@ import {
import semver from 'semver';
import path from 'path';
import { appPath } from '../exec';
+import { existsSync } from 'fs';
import {
- existsSync,
- mkdirSync,
- readdirSync,
- statSync,
- rmSync,
- writeFileSync,
- appendFileSync,
-} from 'fs';
+ readdir,
+ writeFile,
+ appendFile,
+ readFile,
+ stat,
+ access,
+ rename,
+ constants,
+} from 'fs/promises';
import { spawn } from 'child_process';
-import AdmZip from 'adm-zip';
+import { createHash } from 'crypto';
import packageJson from '../../../package.json';
const currentVersion = packageJson.version;
-// 在 Electron 中使用 original-fs 处理 asar 打包后的文件操作
-// original-fs 是 Electron 内置模块,API 与 Node.js fs 模块相同
-// 使用类型注解确保类型安全
-// eslint-disable-next-line @typescript-eslint/no-var-requires
-const originalFs: typeof import('fs') = require('original-fs');
-
// 更新日志文件路径
const updateLogPath = path.join(appPath, 'launcher-update.log');
@@ -44,7 +40,7 @@ const updateLogPath = path.join(appPath, 'launcher-update.log');
* @param message 日志消息
* @param data 附加数据
*/
-function writeUpdateLog(
+async function writeUpdateLog(
level: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
message: string,
data?: unknown,
@@ -61,7 +57,7 @@ function writeUpdateLog(
logLine += '\n';
try {
- appendFileSync(updateLogPath, logLine, { encoding: 'utf8' });
+ await appendFile(updateLogPath, logLine, { encoding: 'utf8' });
} catch (err) {
console.error('写入更新日志失败:', err);
}
@@ -86,14 +82,14 @@ function writeUpdateLog(
/**
* 初始化日志文件(清空旧日志或添加分隔符)
*/
-function initUpdateLog() {
+async function initUpdateLog() {
const separator = `
${'='.repeat(60)}
[${new Date().toISOString()}] 启动器更新日志开始
${'='.repeat(60)}
`;
try {
- appendFileSync(updateLogPath, separator, { encoding: 'utf8' });
+ await appendFile(updateLogPath, separator, { encoding: 'utf8' });
} catch {
// 忽略错误
}
@@ -112,8 +108,8 @@ export default async function init(ipcMain: IpcMain) {
}
export async function checkLauncherUpdate() {
- initUpdateLog();
- writeUpdateLog('INFO', '[checkLauncherUpdate] 开始检查启动器更新');
+ await initUpdateLog();
+ await writeUpdateLog('INFO', '[checkLauncherUpdate] 开始检查启动器更新');
try {
const latestVersionInfo = getLatestVersion(
@@ -122,7 +118,7 @@ export async function checkLauncherUpdate() {
const latestVersion = latestVersionInfo.version;
const haveNew = semver.lt(currentVersion, latestVersion);
- writeUpdateLog('INFO', '[checkLauncherUpdate] 检查启动器更新完成', {
+ await writeUpdateLog('INFO', '[checkLauncherUpdate] 检查启动器更新完成', {
currentVersion,
latestVersion,
haveNew,
@@ -134,7 +130,7 @@ export async function checkLauncherUpdate() {
haveNew,
};
} catch (error) {
- writeUpdateLog(
+ await writeUpdateLog(
'ERROR',
'[checkLauncherUpdate] 检查启动器更新失败',
error instanceof Error
@@ -150,20 +146,24 @@ export async function checkLauncherUpdate() {
}
export async function downloadLauncherUpdate() {
- writeUpdateLog('INFO', '[downloadLauncherUpdate] 开始下载启动器更新');
+ await writeUpdateLog('INFO', '[downloadLauncherUpdate] 开始下载启动器更新');
try {
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
- writeUpdateLog('DEBUG', '[downloadLauncherUpdate] 获取到最新版本信息', {
- version: latestVersionInfo.version,
- magnet: latestVersionInfo.dlcInfo?.magnet?.substring(0, 50) + '...',
- });
+ await writeUpdateLog(
+ 'DEBUG',
+ '[downloadLauncherUpdate] 获取到最新版本信息',
+ {
+ version: latestVersionInfo.version,
+ magnet: latestVersionInfo.dlcInfo?.magnet?.substring(0, 50) + '...',
+ },
+ );
await startWebtorrent(latestVersionInfo.dlcInfo.magnet);
- writeUpdateLog('DEBUG', '[downloadLauncherUpdate] WebTorrent 已启动');
+ await writeUpdateLog('DEBUG', '[downloadLauncherUpdate] WebTorrent 已启动');
const torrent = await waitTorrentDone(
'AI_LEARNING_ASSISTANT_LAUNCHER',
@@ -171,15 +171,19 @@ export async function downloadLauncherUpdate() {
);
const filePath = path.join(torrent.path, torrent.files[0].name);
- writeUpdateLog('INFO', '[downloadLauncherUpdate] 启动器更新下载完成', {
- path: torrent.path,
- fileName: torrent.files[0].name,
- });
+ await writeUpdateLog(
+ 'INFO',
+ '[downloadLauncherUpdate] 启动器更新下载完成',
+ {
+ path: torrent.path,
+ fileName: torrent.files[0].name,
+ },
+ );
// 检查是否为本地开发环境
const isDev = !app.isPackaged;
if (isDev) {
- writeUpdateLog(
+ await writeUpdateLog(
'WARN',
'[downloadLauncherUpdate] 当前为本地开发环境,启动器更新功能可能无法正常工作',
);
@@ -192,7 +196,7 @@ export async function downloadLauncherUpdate() {
isDev,
};
} catch (error) {
- writeUpdateLog(
+ await writeUpdateLog(
'ERROR',
'[downloadLauncherUpdate] 下载启动器更新失败',
error instanceof Error
@@ -203,26 +207,177 @@ export async function downloadLauncherUpdate() {
}
}
+/**
+ * 获取 Squirrel Update.exe 的路径
+ * Squirrel 安装后,Update.exe 位于应用安装目录的上一级
+ */
+function getSquirrelUpdateExePath(): string | null {
+ const exePath = app.getPath('exe');
+ const exeDir = path.dirname(exePath);
+ // Squirrel 安装结构: /Update.exe, /app-x.x.x/app.exe
+ const updateExePath = path.join(exeDir, '..', 'Update.exe');
+
+ if (existsSync(updateExePath)) {
+ return path.resolve(updateExePath);
+ }
+ return null;
+}
+
+/**
+ * 计算文件的 SHA1 哈希值
+ */
+async function calculateSha1(filePath: string): Promise {
+ const fileBuffer = await readFile(filePath);
+ const hash = createHash('sha1');
+ hash.update(fileBuffer);
+ return hash.digest('hex').toUpperCase();
+}
+
+/**
+ * 生成 Squirrel RELEASES 文件
+ * RELEASES 文件格式: SHA1 filename size
+ */
+async function generateReleasesFile(
+ nupkgPath: string,
+ outputDir: string,
+): Promise {
+ const fileName = path.basename(nupkgPath);
+ const fileStat = await stat(nupkgPath);
+ const fileSize = fileStat.size;
+ const sha1Hash = await calculateSha1(nupkgPath);
+
+ const releasesContent = `${sha1Hash} ${fileName} ${fileSize}`;
+ const releasesPath = path.join(outputDir, 'RELEASES');
+
+ await writeFile(releasesPath, releasesContent, { encoding: 'utf8' });
+ await writeUpdateLog('INFO', '[generateReleasesFile] RELEASES 文件已生成', {
+ path: releasesPath,
+ content: releasesContent,
+ });
+
+ return releasesPath;
+}
+
+/**
+ * 等待文件句柄释放的重试配置
+ */
+const FILE_RELEASE_CONFIG = {
+ MAX_RETRIES: 10,
+ INITIAL_DELAY_MS: 200,
+ MAX_DELAY_MS: 2000,
+};
+
+/**
+ * 检测文件是否可访问(句柄已释放)
+ * 通过尝试 rename 操作来验证文件是否被占用
+ * @param filePath 文件路径
+ * @returns 是否可访问
+ */
+async function isFileAccessible(filePath: string): Promise {
+ try {
+ // 检查文件是否可读
+ await access(filePath, constants.R_OK);
+
+ // 尝试 rename 到自身(Windows 下被占用的文件无法 rename)
+ // 这是检测文件句柄是否释放的可靠方法
+ const tempPath = filePath + '.tmp_check';
+ await rename(filePath, tempPath);
+ await rename(tempPath, filePath);
+
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * 等待文件句柄释放,使用指数退避重试机制
+ * @param filePath 文件路径
+ * @returns 文件是否可访问
+ */
+async function waitForFileRelease(filePath: string): Promise {
+ let delay = FILE_RELEASE_CONFIG.INITIAL_DELAY_MS;
+
+ for (let attempt = 1; attempt <= FILE_RELEASE_CONFIG.MAX_RETRIES; attempt++) {
+ const accessible = await isFileAccessible(filePath);
+
+ if (accessible) {
+ await writeUpdateLog('DEBUG', '[waitForFileRelease] 文件句柄已释放', {
+ filePath,
+ attempt,
+ });
+ return;
+ }
+
+ await writeUpdateLog(
+ 'DEBUG',
+ '[waitForFileRelease] 文件仍被占用,等待重试',
+ {
+ filePath,
+ attempt,
+ nextDelayMs: delay,
+ },
+ );
+
+ await new Promise((resolve) => setTimeout(resolve, delay));
+
+ // 指数退避,但不超过最大延迟
+ delay = Math.min(delay * 2, FILE_RELEASE_CONFIG.MAX_DELAY_MS);
+ }
+
+ throw new Error(
+ `文件句柄释放超时: ${filePath},已重试 ${FILE_RELEASE_CONFIG.MAX_RETRIES} 次`,
+ );
+}
+
export async function installLauncherUpdate() {
- writeUpdateLog('INFO', '[installLauncherUpdate] 开始执行安装更新');
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 开始执行 Squirrel 更新',
+ );
// 如果是开发模式,不执行更新
const isPackaged = app.isPackaged;
if (!isPackaged) {
- writeUpdateLog('WARN', '[installLauncherUpdate] 开发模式下不支持自动更新');
+ await writeUpdateLog(
+ 'WARN',
+ '[installLauncherUpdate] 开发模式下不支持自动更新',
+ );
+ return {
+ success: false,
+ message: '开发模式下不支持自动更新',
+ };
+ }
+
+ // 检查是否为 Squirrel 安装
+ const updateExePath = getSquirrelUpdateExePath();
+ if (!updateExePath) {
+ await writeUpdateLog(
+ 'WARN',
+ '[installLauncherUpdate] 未检测到 Squirrel 安装,无法使用 Squirrel 更新',
+ );
return {
success: false,
- message: '开发模式下不支持自动更新,请手动解压',
+ message: '当前安装方式不支持 Squirrel 自动更新,请手动下载安装包更新',
};
}
+ await writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 检测到 Update.exe:',
+ updateExePath,
+ );
+
try {
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 获取最新版本信息...');
+ await writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 获取最新版本信息...',
+ );
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
const version = latestVersionInfo.version;
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 最新版本:', version);
+ await writeUpdateLog('DEBUG', '[installLauncherUpdate] 最新版本:', version);
const downloadPath = path.join(
appPath,
@@ -231,193 +386,65 @@ export async function installLauncherUpdate() {
'AI_LEARNING_ASSISTANT_LAUNCHER',
version,
);
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 下载路径:', downloadPath);
- writeUpdateLog(
+ await writeUpdateLog(
+ 'DEBUG',
+ '[installLauncherUpdate] 下载路径:',
+ downloadPath,
+ );
+ await writeUpdateLog(
'DEBUG',
'[installLauncherUpdate] 下载路径是否存在:',
existsSync(downloadPath),
);
- const files = readdirSync(downloadPath);
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 下载目录文件列表:', files);
- const zipFile = files.find((f) => f.endsWith('.zip'));
-
- if (!zipFile) {
- writeUpdateLog('ERROR', '[installLauncherUpdate] 未找到下载的更新包');
- throw new Error('未找到下载的更新包');
+ if (!existsSync(downloadPath)) {
+ await writeUpdateLog('ERROR', '[installLauncherUpdate] 下载目录不存在');
+ throw new Error('更新包未下载,请先下载更新包');
}
- // 销毁 WebTorrent 以释放文件句柄,避免后续读取 zip 文件时出现 EBUSY 错误
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...',
- );
- await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
- // 等待文件句柄完全释放
- await new Promise((resolve) => setTimeout(resolve, 1000));
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 文件句柄已释放');
-
- const zipPath = path.join(downloadPath, zipFile);
- writeUpdateLog('DEBUG', '[installLauncherUpdate] zip文件路径:', zipPath);
- writeUpdateLog(
+ const files = await readdir(downloadPath);
+ await writeUpdateLog(
'DEBUG',
- '[installLauncherUpdate] zip文件是否存在:',
- existsSync(zipPath),
+ '[installLauncherUpdate] 下载目录文件列表:',
+ files,
);
- const tempDir = path.join(appPath, 'update-temp');
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录:', tempDir);
- if (existsSync(tempDir)) {
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 清理已存在的临时目录...',
- );
- rmSync(tempDir, { recursive: true, force: true });
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录清理完成');
- }
- mkdirSync(tempDir, { recursive: true });
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录已创建');
-
- // 使用 adm-zip 解压 zip 文件
- writeUpdateLog('INFO', '[installLauncherUpdate] 正在解压文件...');
- writeUpdateLog('DEBUG', '[installLauncherUpdate] ZIP路径:', zipPath);
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 目标路径:', tempDir);
-
- try {
- const zip = new AdmZip(zipPath);
- const zipEntries = zip.getEntries();
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] ZIP包含的文件数量:',
- zipEntries.length,
- );
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] ZIP包含的文件列表:',
- zipEntries.slice(0, 5).map((e) => e.entryName),
- );
-
- // 手动解压每个文件,使用 original-fs 避免 asar 打包问题和 Windows 下的 chmod 问题
- for (const entry of zipEntries) {
- const entryPath = path.join(tempDir, entry.entryName);
- if (entry.isDirectory) {
- // 创建目录
- if (!originalFs.existsSync(entryPath)) {
- originalFs.mkdirSync(entryPath, { recursive: true });
- }
- } else {
- // 确保父目录存在
- const parentDir = path.dirname(entryPath);
- if (!originalFs.existsSync(parentDir)) {
- originalFs.mkdirSync(parentDir, { recursive: true });
- }
- // 写入文件
- const content = entry.getData();
- originalFs.writeFileSync(entryPath, content);
- }
- }
- writeUpdateLog('INFO', '[installLauncherUpdate] 解压完成');
- } catch (extractError) {
- writeUpdateLog(
+ // 查找 .nupkg 文件
+ const nupkgFile = files.find((f) => f.endsWith('.nupkg'));
+ if (!nupkgFile) {
+ await writeUpdateLog(
'ERROR',
- '[installLauncherUpdate] 解压失败',
- extractError instanceof Error
- ? { message: extractError.message, stack: extractError.stack }
- : extractError,
+ '[installLauncherUpdate] 未找到 .nupkg 更新包',
);
- throw extractError;
+ throw new Error('未找到 .nupkg 更新包');
}
- // 查找解压后的目录(通常会有一个子目录)
- const extractedItems = readdirSync(tempDir);
- writeUpdateLog(
+ const nupkgPath = path.join(downloadPath, nupkgFile);
+ await writeUpdateLog(
'DEBUG',
- '[installLauncherUpdate] 解压后的文件数量:',
- extractedItems.length,
+ '[installLauncherUpdate] nupkg 文件路径:',
+ nupkgPath,
);
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 解压后的文件列表:',
- extractedItems,
- );
-
- let sourceDir = tempDir;
- if (extractedItems.length === 1) {
- const subPath = path.join(tempDir, extractedItems[0]);
- const subStat = statSync(subPath);
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 检查子路径:', subPath);
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 是否为目录:',
- subStat.isDirectory(),
- );
- if (subStat.isDirectory()) {
- sourceDir = subPath;
- const subItems = readdirSync(sourceDir);
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 子目录文件列表:',
- subItems,
- );
- }
- }
-
- writeUpdateLog('DEBUG', '[installLauncherUpdate] 解压源目录:', sourceDir);
-
- const findExe = (dir: string): string | null => {
- const items = readdirSync(dir);
- for (const item of items) {
- const fullPath = path.join(dir, item);
- const stat = statSync(fullPath);
- if (stat.isDirectory()) {
- const result = findExe(fullPath);
- if (result) return result;
- } else if (
- item.endsWith('.exe') &&
- item.includes('AI-Learning-Assistant-Launcher')
- ) {
- return fullPath;
- }
- }
- return null;
- };
-
- const newExePath = findExe(sourceDir);
-
- if (!newExePath) {
- writeUpdateLog(
- 'ERROR',
- '[installLauncherUpdate] 未在更新包中找到启动器可执行文件',
- );
- throw new Error('未在更新包中找到启动器可执行文件');
- }
- writeUpdateLog(
+ // 销毁 WebTorrent 以释放文件句柄
+ await writeUpdateLog(
'DEBUG',
- '[installLauncherUpdate] 找到新版本可执行文件:',
- newExePath,
+ '[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...',
);
+ await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
- const currentExePath = app.getPath('exe');
- const currentExeDir = path.dirname(currentExePath);
- const currentExeName = path.basename(currentExePath);
-
- const backupPath = path.join(currentExeDir, `${currentExeName}.old`);
+ // 使用重试机制等待文件句柄释放
+ await waitForFileRelease(nupkgPath);
+ await writeUpdateLog('DEBUG', '[installLauncherUpdate] 文件句柄已释放');
- // 找到新版本可执行文件所在目录的所有文件
- const newExeDir = path.dirname(newExePath);
- writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 新版本文件目录:',
- newExeDir,
+ // 生成 RELEASES 文件(Squirrel 需要此文件来确定更新信息)
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 生成 RELEASES 文件...',
);
- writeUpdateLog('INFO', '[installLauncherUpdate] 更新路径信息', {
- currentExePath,
- currentExeDir,
- backupPath,
- newExeDir,
- });
+ await generateReleasesFile(nupkgPath, downloadPath);
+ // 显示更新确认对话框
const result = await dialog.showMessageBox({
type: 'info',
title: '准备更新',
@@ -429,147 +456,55 @@ export async function installLauncherUpdate() {
});
if (result.response === 0) {
- writeUpdateLog(
+ await writeUpdateLog(
'INFO',
- '[installLauncherUpdate] 用户确认更新,准备执行更新',
+ '[installLauncherUpdate] 用户确认更新,准备执行 Squirrel 更新',
);
- // 创建更新脚本,在应用退出后执行
- const updateScriptPath = path.join(appPath, 'update.bat');
- // bat 脚本也写入日志文件,方便追踪更新过程(Windows BAT 脚本路径不需要转义)
- const batLogPath = updateLogPath;
- const updateScript = `@echo off
-chcp 65001
-echo ===================================
-echo 正在更新启动器...
-echo ===================================
-
-REM 写入日志函数
-set "LOG_FILE=${batLogPath}"
-echo [%date% %time%] [BAT] 更新脚本开始执行 >> "%LOG_FILE%"
-
-REM 等待应用完全退出并释放文件句柄
-echo 等待应用退出...
-echo [%date% %time%] [BAT] 等待应用退出... >> "%LOG_FILE%"
-timeout /t 3 /nobreak >nul
-
-REM 删除旧备份
-if exist "${backupPath}" (
- echo 删除旧备份...
- echo [%date% %time%] [BAT] 删除旧备份: ${backupPath} >> "%LOG_FILE%"
- del /f /q "${backupPath}"
-)
-
-REM 备份当前版本的可执行文件
-if exist "${currentExePath}" (
- echo 备份当前版本...
- echo [%date% %time%] [BAT] 备份当前版本: ${currentExePath} -^> ${backupPath} >> "%LOG_FILE%"
- move /y "${currentExePath}" "${backupPath}"
- if errorlevel 1 (
- echo 备份失败!
- echo [%date% %time%] [BAT] [ERROR] 备份失败! >> "%LOG_FILE%"
- pause
- exit /b 1
- )
- echo [%date% %time%] [BAT] 备份成功 >> "%LOG_FILE%"
-)
-
-REM 使用 robocopy 复制新版本的所有文件到当前目录
-echo 正在复制文件...
-echo [%date% %time%] [BAT] 开始复制文件: ${newExeDir} -^> ${currentExeDir} >> "%LOG_FILE%"
-robocopy "${newExeDir}" "${currentExeDir}" /E /IS /IT /NFL /NDL /NP
-
-REM robocopy 返回值说明:
-REM 0 = 没有文件被复制
-REM 1 = 所有文件复制成功
-REM 2 = 有额外的文件或目录
-REM 3 = 有文件被复制,也有不匹配的文件
-REM 4 = 有不匹配的文件或目录
-REM 5 = 有文件被复制,也有不匹配的文件或目录
-REM 6 = 有额外的文件/目录和不匹配的文件
-REM 7 = 文件被复制,有额外的文件和不匹配的文件
-REM 8+ = 有错误
-
-if %ERRORLEVEL% GEQ 8 (
- echo ===================================
- echo 复制文件失败!错误码: %ERRORLEVEL%
- echo 正在恢复原版本...
- echo ===================================
- echo [%date% %time%] [BAT] [ERROR] 复制文件失败!错误码: %ERRORLEVEL% >> "%LOG_FILE%"
- if exist "${backupPath}" (
- move /y "${backupPath}" "${currentExePath}"
- echo [%date% %time%] [BAT] 已恢复原版本 >> "%LOG_FILE%"
- )
- echo 更新失败,已恢复原版本
- pause
- exit /b 1
-)
-
-echo 文件复制完成,错误码: %ERRORLEVEL%
-echo [%date% %time%] [BAT] 文件复制完成,错误码: %ERRORLEVEL% >> "%LOG_FILE%"
-
-REM 清理临时文件
-echo 清理临时文件...
-echo [%date% %time%] [BAT] 清理临时文件: ${tempDir} >> "%LOG_FILE%"
-if exist "${tempDir}" (
- rmdir /s /q "${tempDir}" 2>nul
-)
-echo [%date% %time%] [BAT] 临时文件清理完成 >> "%LOG_FILE%"
-
-REM 启动新版本
-echo ===================================
-echo 启动新版本...
-echo ===================================
-echo [%date% %time%] [BAT] 启动新版本: ${currentExePath} >> "%LOG_FILE%"
-start "" "${currentExePath}"
-
-REM 等待一下确保应用启动
-timeout /t 2 /nobreak >nul
-echo [%date% %time%] [BAT] 更新脚本执行完成 >> "%LOG_FILE%"
-
-REM 删除更新脚本自身
-(goto) 2>nul & del "%~f0"
-`;
-
- writeFileSync(updateScriptPath, updateScript, { encoding: 'utf8' });
- writeUpdateLog(
+ // 使用 Squirrel 的 Update.exe 执行更新
+ // 参数 --update= 指定包含 RELEASES 和 .nupkg 的目录
+ await writeUpdateLog(
'INFO',
- '[installLauncherUpdate] 更新脚本已创建:',
- updateScriptPath,
+ '[installLauncherUpdate] 调用 Squirrel Update.exe',
+ {
+ updateExePath,
+ updatePath: downloadPath,
+ },
);
- // 使用 detached 模式启动更新脚本
- const updateProcess = spawn('cmd.exe', ['/c', updateScriptPath], {
+ const updateProcess = spawn(updateExePath, ['--update', downloadPath], {
detached: true,
stdio: 'ignore',
- windowsHide: false,
});
updateProcess.unref();
- writeUpdateLog('INFO', '[installLauncherUpdate] 更新脚本已启动');
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] Squirrel 更新进程已启动',
+ );
- // 延迟退出,确保更新脚本已经启动
- // 注意:这里不使用 app.relaunch(),而是让更新脚本完成后手动启动新版本
- setTimeout(() => {
- writeUpdateLog('INFO', '[installLauncherUpdate] 准备退出应用');
- app.exit(0);
- }, 1000);
+ // 延迟退出应用,让 Squirrel 接管更新流程
+ setTimeout(async () => {
+ await writeUpdateLog('INFO', '[installLauncherUpdate] 准备退出应用');
+ // Squirrel 会在更新完成后自动重启应用
+ app.quit();
+ }, 1500);
return {
success: true,
- message: '正在更新启动器...',
+ message: '正在通过 Squirrel 更新启动器...',
};
} else {
- writeUpdateLog('INFO', '[installLauncherUpdate] 用户取消更新');
+ await writeUpdateLog('INFO', '[installLauncherUpdate] 用户取消更新');
return {
success: false,
message: '用户取消更新',
};
}
} catch (error) {
- writeUpdateLog(
+ await writeUpdateLog(
'ERROR',
- '[installLauncherUpdate] 安装启动器更新失败',
+ '[installLauncherUpdate] Squirrel 更新失败',
error instanceof Error
? { message: error.message, stack: error.stack }
: error,
From b79f311777bd7bec604efd0aff5ba007073fcf80 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Mon, 2 Feb 2026 08:46:35 +0800
Subject: [PATCH 20/22] refactor(update): replace Squirrel with custom
ZIP-based updater
---
external-resources/dlc/dlc-index.json | 4 +-
package-lock.json | 15 +-
src/main/launcher-update/index.ts | 284 ++++++++++++++------------
3 files changed, 167 insertions(+), 136 deletions(-)
diff --git a/external-resources/dlc/dlc-index.json b/external-resources/dlc/dlc-index.json
index 463a477..ecc0e7e 100644
--- a/external-resources/dlc/dlc-index.json
+++ b/external-resources/dlc/dlc-index.json
@@ -32,8 +32,8 @@
"id": "AI_LEARNING_ASSISTANT_LAUNCHER",
"name": "AI学习助手启动器",
"versions": {
- "2.0.4": {
- "magnet": "magnet:?xt=urn:btih:ea0ebea633f25e936c55a095f66f15d65089dcec&dn=AILearningAssistantLauncher-2.0.4-full.nupkg&tr=wss%3A%2F%2Ftracker.openwebtorrent.com",
+ "2.0.3": {
+ "magnet": "magnet:?xt=urn:btih:5e685a7cbe4045efc40f7a593dddfdf56ae7c59d&dn=AI-Learning-Assistant-Launcher-win32-x64-2.0.3.zip&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&tr=wss%3A%2F%2Ftracker.btorrent.xyz",
"comment": "测试更新使用版本",
"require": {}
}
diff --git a/package-lock.json b/package-lock.json
index c799fac..e3674cb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5484,8 +5484,10 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "extraneous": true,
+ "dev": true,
"license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5588,8 +5590,10 @@
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "extraneous": true,
+ "dev": true,
"license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8207,6 +8211,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/bufferutil/-/bufferutil-4.1.0.tgz",
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -12016,9 +12021,11 @@
"version": "5.0.10",
"resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
- "extraneous": true,
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
+ "optional": true,
+ "peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -16268,6 +16275,7 @@
"version": "4.8.4",
"resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
+ "dev": true,
"license": "MIT",
"optional": true,
"bin": {
@@ -21956,6 +21964,7 @@
"version": "6.0.6",
"resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-6.0.6.tgz",
"integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==",
+ "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index b6fb248..274d36a 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -14,19 +14,16 @@ import {
import semver from 'semver';
import path from 'path';
import { appPath } from '../exec';
-import { existsSync } from 'fs';
+import { existsSync, mkdirSync } from 'fs';
import {
readdir,
writeFile,
appendFile,
- readFile,
- stat,
access,
rename,
constants,
} from 'fs/promises';
import { spawn } from 'child_process';
-import { createHash } from 'crypto';
import packageJson from '../../../package.json';
const currentVersion = packageJson.version;
@@ -180,7 +177,7 @@ export async function downloadLauncherUpdate() {
},
);
- // 检查是否为本地开发环境
+ // 检查是否为本地开发环境,请打包后测试
const isDev = !app.isPackaged;
if (isDev) {
await writeUpdateLog(
@@ -208,54 +205,100 @@ export async function downloadLauncherUpdate() {
}
/**
- * 获取 Squirrel Update.exe 的路径
- * Squirrel 安装后,Update.exe 位于应用安装目录的上一级
+ * 生成自定义更新批处理脚本
+ * 脚本功能:等待应用退出、备份用户数据、解压更新包、替换文件、恢复数据、启动新版本
*/
-function getSquirrelUpdateExePath(): string | null {
- const exePath = app.getPath('exe');
- const exeDir = path.dirname(exePath);
- // Squirrel 安装结构: /Update.exe, /app-x.x.x/app.exe
- const updateExePath = path.join(exeDir, '..', 'Update.exe');
-
- if (existsSync(updateExePath)) {
- return path.resolve(updateExePath);
+async function generateUpdateScript(
+ zipPath: string,
+ appDir: string,
+ tempDir: string,
+ newVersion: string,
+): Promise {
+ const scriptContent = `@echo off
+chcp 65001 >nul
+setlocal enabledelayedexpansion
+
+set "ZIP_PATH=${zipPath.replace(/\//g, '\\')}"
+set "APP_DIR=${appDir.replace(/\//g, '\\')}"
+set "TEMP_DIR=${tempDir.replace(/\//g, '\\')}"
+set "BACKUP_DIR=${tempDir.replace(/\//g, '\\')}\\external-resources-backup"
+set "EXTRACT_DIR=${tempDir.replace(/\//g, '\\')}\\extracted"
+
+echo [更新] 等待应用退出...
+:waitloop
+tasklist /FI "IMAGENAME eq AI Learning Assistant Launcher.exe" 2>NUL | find /I "AI Learning Assistant Launcher.exe" >NUL
+if not errorlevel 1 (
+ timeout /t 1 /nobreak >nul
+ goto waitloop
+)
+
+echo [更新] 应用已退出,开始更新...
+
+:: 创建临时目录
+if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%"
+if not exist "%EXTRACT_DIR%" mkdir "%EXTRACT_DIR%"
+
+:: 备份 external-resources
+echo [更新] 备份用户数据...
+if exist "%APP_DIR%\\external-resources" (
+ xcopy "%APP_DIR%\\external-resources" "%BACKUP_DIR%\\" /E /I /H /Y /Q
+)
+
+:: 解压 ZIP 文件
+echo [更新] 解压更新包...
+powershell -Command "Expand-Archive -Path '%ZIP_PATH%' -DestinationPath '%EXTRACT_DIR%' -Force"
+
+:: 查找解压后的目录(ZIP 内可能有一层目录)
+for /d %%D in ("%EXTRACT_DIR%\\*") do set "SOURCE_DIR=%%D"
+if not defined SOURCE_DIR set "SOURCE_DIR=%EXTRACT_DIR%"
+
+:: 删除旧文件(保留 external-resources)
+echo [更新] 清理旧版本...
+for /d %%D in ("%APP_DIR%\\*") do (
+ if /I not "%%~nxD"=="external-resources" rd /s /q "%%D" 2>nul
+)
+for %%F in ("%APP_DIR%\\*") do (
+ del /f /q "%%F" 2>nul
+)
+
+:: 复制新文件
+echo [更新] 安装新版本...
+xcopy "%SOURCE_DIR%\\*" "%APP_DIR%\\" /E /I /H /Y /Q
+
+:: 恢复 external-resources(合并)
+echo [更新] 恢复用户数据...
+if exist "%BACKUP_DIR%" (
+ xcopy "%BACKUP_DIR%\\*" "%APP_DIR%\\external-resources\\" /E /I /H /Y /Q
+)
+
+:: 清理临时文件
+echo [更新] 清理临时文件...
+rd /s /q "%TEMP_DIR%" 2>nul
+
+:: 启动新版本
+echo [更新] 启动新版本...
+start "" "%APP_DIR%\\AI Learning Assistant Launcher.exe"
+
+exit
+`;
+
+ // 确保临时目录存在
+ if (!existsSync(tempDir)) {
+ mkdirSync(tempDir, { recursive: true });
}
- return null;
-}
-/**
- * 计算文件的 SHA1 哈希值
- */
-async function calculateSha1(filePath: string): Promise {
- const fileBuffer = await readFile(filePath);
- const hash = createHash('sha1');
- hash.update(fileBuffer);
- return hash.digest('hex').toUpperCase();
-}
+ const scriptPath = path.join(tempDir, 'update.bat');
+ await writeFile(scriptPath, scriptContent, { encoding: 'utf8' });
-/**
- * 生成 Squirrel RELEASES 文件
- * RELEASES 文件格式: SHA1 filename size
- */
-async function generateReleasesFile(
- nupkgPath: string,
- outputDir: string,
-): Promise {
- const fileName = path.basename(nupkgPath);
- const fileStat = await stat(nupkgPath);
- const fileSize = fileStat.size;
- const sha1Hash = await calculateSha1(nupkgPath);
-
- const releasesContent = `${sha1Hash} ${fileName} ${fileSize}`;
- const releasesPath = path.join(outputDir, 'RELEASES');
-
- await writeFile(releasesPath, releasesContent, { encoding: 'utf8' });
- await writeUpdateLog('INFO', '[generateReleasesFile] RELEASES 文件已生成', {
- path: releasesPath,
- content: releasesContent,
+ await writeUpdateLog('INFO', '[generateUpdateScript] 更新脚本已生成', {
+ scriptPath,
+ zipPath,
+ appDir,
+ tempDir,
+ newVersion,
});
- return releasesPath;
+ return scriptPath;
}
/**
@@ -331,10 +374,7 @@ async function waitForFileRelease(filePath: string): Promise {
}
export async function installLauncherUpdate() {
- await writeUpdateLog(
- 'INFO',
- '[installLauncherUpdate] 开始执行 Squirrel 更新',
- );
+ await writeUpdateLog('INFO', '[installLauncherUpdate] 开始执行自定义更新');
// 如果是开发模式,不执行更新
const isPackaged = app.isPackaged;
@@ -349,26 +389,8 @@ export async function installLauncherUpdate() {
};
}
- // 检查是否为 Squirrel 安装
- const updateExePath = getSquirrelUpdateExePath();
- if (!updateExePath) {
- await writeUpdateLog(
- 'WARN',
- '[installLauncherUpdate] 未检测到 Squirrel 安装,无法使用 Squirrel 更新',
- );
- return {
- success: false,
- message: '当前安装方式不支持 Squirrel 自动更新,请手动下载安装包更新',
- };
- }
-
- await writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 检测到 Update.exe:',
- updateExePath,
- );
-
try {
+ // 获取最新版本信息
await writeUpdateLog(
'DEBUG',
'[installLauncherUpdate] 获取最新版本信息...',
@@ -379,6 +401,7 @@ export async function installLauncherUpdate() {
const version = latestVersionInfo.version;
await writeUpdateLog('DEBUG', '[installLauncherUpdate] 最新版本:', version);
+ // 下载目录
const downloadPath = path.join(
appPath,
'external-resources',
@@ -391,17 +414,13 @@ export async function installLauncherUpdate() {
'[installLauncherUpdate] 下载路径:',
downloadPath,
);
- await writeUpdateLog(
- 'DEBUG',
- '[installLauncherUpdate] 下载路径是否存在:',
- existsSync(downloadPath),
- );
if (!existsSync(downloadPath)) {
await writeUpdateLog('ERROR', '[installLauncherUpdate] 下载目录不存在');
throw new Error('更新包未下载,请先下载更新包');
}
+ // 查找 ZIP 文件
const files = await readdir(downloadPath);
await writeUpdateLog(
'DEBUG',
@@ -409,21 +428,20 @@ export async function installLauncherUpdate() {
files,
);
- // 查找 .nupkg 文件
- const nupkgFile = files.find((f) => f.endsWith('.nupkg'));
- if (!nupkgFile) {
+ const zipFile = files.find((f) => f.endsWith('.zip'));
+ if (!zipFile) {
await writeUpdateLog(
'ERROR',
- '[installLauncherUpdate] 未找到 .nupkg 更新包',
+ '[installLauncherUpdate] 未找到 .zip 更新包',
);
- throw new Error('未找到 .nupkg 更新包');
+ throw new Error('未找到 .zip 更新包');
}
- const nupkgPath = path.join(downloadPath, nupkgFile);
+ const zipPath = path.join(downloadPath, zipFile);
await writeUpdateLog(
'DEBUG',
- '[installLauncherUpdate] nupkg 文件路径:',
- nupkgPath,
+ '[installLauncherUpdate] ZIP 文件路径:',
+ zipPath,
);
// 销毁 WebTorrent 以释放文件句柄
@@ -434,15 +452,16 @@ export async function installLauncherUpdate() {
await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
// 使用重试机制等待文件句柄释放
- await waitForFileRelease(nupkgPath);
+ await waitForFileRelease(zipPath);
await writeUpdateLog('DEBUG', '[installLauncherUpdate] 文件句柄已释放');
- // 生成 RELEASES 文件(Squirrel 需要此文件来确定更新信息)
- await writeUpdateLog(
- 'INFO',
- '[installLauncherUpdate] 生成 RELEASES 文件...',
- );
- await generateReleasesFile(nupkgPath, downloadPath);
+ // 获取应用目录和临时目录
+ const exePath = app.getPath('exe');
+ const appDir = path.dirname(exePath);
+ const tempDir = path.join(app.getPath('temp'), 'launcher-update');
+
+ await writeUpdateLog('DEBUG', '[installLauncherUpdate] 应用目录:', appDir);
+ await writeUpdateLog('DEBUG', '[installLauncherUpdate] 临时目录:', tempDir);
// 显示更新确认对话框
const result = await dialog.showMessageBox({
@@ -455,56 +474,59 @@ export async function installLauncherUpdate() {
cancelId: 1,
});
- if (result.response === 0) {
- await writeUpdateLog(
- 'INFO',
- '[installLauncherUpdate] 用户确认更新,准备执行 Squirrel 更新',
- );
-
- // 使用 Squirrel 的 Update.exe 执行更新
- // 参数 --update= 指定包含 RELEASES 和 .nupkg 的目录
- await writeUpdateLog(
- 'INFO',
- '[installLauncherUpdate] 调用 Squirrel Update.exe',
- {
- updateExePath,
- updatePath: downloadPath,
- },
- );
-
- const updateProcess = spawn(updateExePath, ['--update', downloadPath], {
- detached: true,
- stdio: 'ignore',
- });
-
- updateProcess.unref();
- await writeUpdateLog(
- 'INFO',
- '[installLauncherUpdate] Squirrel 更新进程已启动',
- );
-
- // 延迟退出应用,让 Squirrel 接管更新流程
- setTimeout(async () => {
- await writeUpdateLog('INFO', '[installLauncherUpdate] 准备退出应用');
- // Squirrel 会在更新完成后自动重启应用
- app.quit();
- }, 1500);
-
- return {
- success: true,
- message: '正在通过 Squirrel 更新启动器...',
- };
- } else {
+ if (result.response !== 0) {
await writeUpdateLog('INFO', '[installLauncherUpdate] 用户取消更新');
return {
success: false,
message: '用户取消更新',
};
}
+
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 用户确认更新,准备执行自定义更新',
+ );
+
+ // 生成更新脚本
+ const scriptPath = await generateUpdateScript(
+ zipPath,
+ appDir,
+ tempDir,
+ version,
+ );
+
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 启动更新脚本:',
+ scriptPath,
+ );
+
+ // 启动更新脚本
+ const updateProcess = spawn('cmd.exe', ['/c', scriptPath], {
+ detached: true,
+ stdio: 'ignore',
+ windowsHide: true,
+ });
+ updateProcess.unref();
+
+ await writeUpdateLog(
+ 'INFO',
+ '[installLauncherUpdate] 更新脚本已启动,准备退出应用',
+ );
+
+ // 延迟退出应用
+ setTimeout(() => {
+ app.quit();
+ }, 1000);
+
+ return {
+ success: true,
+ message: '正在更新启动器...',
+ };
} catch (error) {
await writeUpdateLog(
'ERROR',
- '[installLauncherUpdate] Squirrel 更新失败',
+ '[installLauncherUpdate] 自定义更新失败',
error instanceof Error
? { message: error.message, stack: error.stack }
: error,
From 17c26015d074e160febf8d5230772ee5fd4aba0c Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Mon, 2 Feb 2026 08:47:24 +0800
Subject: [PATCH 21/22] refactor(update): replace Squirrel with custom
ZIP-based updater
---
forge.config.ts | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/forge.config.ts b/forge.config.ts
index e64f757..8105eaa 100644
--- a/forge.config.ts
+++ b/forge.config.ts
@@ -20,13 +20,7 @@ const config: ForgeConfig = {
},
rebuildConfig: {},
makers: [
- new MakerSquirrel({
- name: 'AILearningAssistantLauncher',
- authors: 'James Zhang',
- description: 'AI Learning Assistant Launcher',
- // 设置图标路径
- setupIcon: path.join(__dirname, 'icons', 'icon.ico'),
- }),
+ // new MakerSquirrel({}),
new MakerZIP({}, ['darwin', 'win32']),
// new MakerRpm({}),
// new MakerDeb({})
From e6f43fc4c47093a7b67e68d01e817bf3c4aa6e11 Mon Sep 17 00:00:00 2001
From: xiaoshen
Date: Mon, 2 Feb 2026 09:26:42 +0800
Subject: [PATCH 22/22] feat(update): implement atomic update with rollback and
safety checks
---
src/main/launcher-update/index.ts | 264 ++++++++++++++++++++++++++----
1 file changed, 231 insertions(+), 33 deletions(-)
diff --git a/src/main/launcher-update/index.ts b/src/main/launcher-update/index.ts
index 274d36a..3423050 100644
--- a/src/main/launcher-update/index.ts
+++ b/src/main/launcher-update/index.ts
@@ -14,13 +14,15 @@ import {
import semver from 'semver';
import path from 'path';
import { appPath } from '../exec';
-import { existsSync, mkdirSync } from 'fs';
+import { existsSync, mkdirSync, rmSync } from 'fs';
import {
readdir,
writeFile,
appendFile,
access,
rename,
+ stat,
+ unlink,
constants,
} from 'fs/promises';
import { spawn } from 'child_process';
@@ -30,6 +32,12 @@ const currentVersion = packageJson.version;
// 更新日志文件路径
const updateLogPath = path.join(appPath, 'launcher-update.log');
+const updateLogBackupPath = path.join(appPath, 'launcher-update.log.old');
+
+// 日志滚动配置
+const LOG_ROTATION_CONFIG = {
+ MAX_SIZE_BYTES: 1 * 1024 * 1024, // 1MB
+};
/**
* 写入更新日志到文件
@@ -77,9 +85,36 @@ async function writeUpdateLog(
}
/**
- * 初始化日志文件(清空旧日志或添加分隔符)
+ * 初始化日志文件(检查大小并执行滚动)
+ * 当日志文件超过 MAX_SIZE_BYTES 时,将旧日志重命名为 .old 文件
*/
async function initUpdateLog() {
+ try {
+ // 检查日志文件是否存在及其大小
+ if (existsSync(updateLogPath)) {
+ const stats = await stat(updateLogPath);
+
+ if (stats.size >= LOG_ROTATION_CONFIG.MAX_SIZE_BYTES) {
+ // 删除旧的备份文件(如果存在)
+ try {
+ await unlink(updateLogBackupPath);
+ } catch {
+ // 备份文件不存在,忽略
+ }
+
+ // 将当前日志重命名为备份
+ await rename(updateLogPath, updateLogBackupPath);
+
+ console.log(
+ `[initUpdateLog] 日志文件已滚动,旧日志大小: ${(stats.size / 1024).toFixed(2)} KB`,
+ );
+ }
+ }
+ } catch (err) {
+ console.error('[initUpdateLog] 日志滚动检查失败:', err);
+ }
+
+ // 添加分隔符
const separator = `
${'='.repeat(60)}
[${new Date().toISOString()}] 启动器更新日志开始
@@ -104,6 +139,31 @@ export default async function init(ipcMain: IpcMain) {
);
}
+/**
+ * 清理更新临时目录
+ * 当已是最新版本时调用,清理之前更新留下的临时文件
+ */
+async function cleanupUpdateTempDir() {
+ const tempDir = path.join(app.getPath('temp'), 'launcher-update');
+
+ try {
+ if (existsSync(tempDir)) {
+ rmSync(tempDir, { recursive: true, force: true });
+ await writeUpdateLog(
+ 'INFO',
+ '[cleanupUpdateTempDir] 已清理更新临时目录',
+ { tempDir },
+ );
+ }
+ } catch (err) {
+ await writeUpdateLog(
+ 'WARN',
+ '[cleanupUpdateTempDir] 清理更新临时目录失败',
+ err instanceof Error ? { message: err.message } : err,
+ );
+ }
+}
+
export async function checkLauncherUpdate() {
await initUpdateLog();
await writeUpdateLog('INFO', '[checkLauncherUpdate] 开始检查启动器更新');
@@ -113,6 +173,26 @@ export async function checkLauncherUpdate() {
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
const latestVersion = latestVersionInfo.version;
+
+ // 版本号规范校验
+ if (!semver.valid(currentVersion)) {
+ await writeUpdateLog(
+ 'ERROR',
+ '[checkLauncherUpdate] 当前版本号格式无效',
+ { currentVersion },
+ );
+ throw new Error(`当前版本号格式无效: ${currentVersion}`);
+ }
+
+ if (!semver.valid(latestVersion)) {
+ await writeUpdateLog(
+ 'ERROR',
+ '[checkLauncherUpdate] 最新版本号格式无效',
+ { latestVersion },
+ );
+ throw new Error(`最新版本号格式无效: ${latestVersion}`);
+ }
+
const haveNew = semver.lt(currentVersion, latestVersion);
await writeUpdateLog('INFO', '[checkLauncherUpdate] 检查启动器更新完成', {
@@ -121,6 +201,11 @@ export async function checkLauncherUpdate() {
haveNew,
});
+ // 如果已是最新版本,清理更新临时目录
+ if (!haveNew) {
+ await cleanupUpdateTempDir();
+ }
+
return {
currentVersion,
latestVersion,
@@ -150,16 +235,23 @@ export async function downloadLauncherUpdate() {
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
+ // 空值检查:确保 dlcInfo 和 magnet 存在
+ if (!latestVersionInfo.dlcInfo?.magnet) {
+ throw new Error('未找到有效的更新包信息(dlcInfo 或 magnet 缺失)');
+ }
+
+ const { magnet } = latestVersionInfo.dlcInfo;
+
await writeUpdateLog(
'DEBUG',
'[downloadLauncherUpdate] 获取到最新版本信息',
{
version: latestVersionInfo.version,
- magnet: latestVersionInfo.dlcInfo?.magnet?.substring(0, 50) + '...',
+ magnet: magnet.substring(0, 50) + '...',
},
);
- await startWebtorrent(latestVersionInfo.dlcInfo.magnet);
+ await startWebtorrent(magnet);
await writeUpdateLog('DEBUG', '[downloadLauncherUpdate] WebTorrent 已启动');
const torrent = await waitTorrentDone(
@@ -206,7 +298,13 @@ export async function downloadLauncherUpdate() {
/**
* 生成自定义更新批处理脚本
- * 脚本功能:等待应用退出、备份用户数据、解压更新包、替换文件、恢复数据、启动新版本
+ * 脚本功能:等待应用退出、解压更新包、校验完整性、备份旧版本、替换文件、恢复数据、启动新版本
+ *
+ * 安全策略:采用"解压 -> 校验 -> 备份 -> 替换"模式
+ * 1. 先在临时目录完整解压并校验
+ * 2. 校验失败则直接启动旧版本,不做任何修改
+ * 3. 校验成功后备份整个旧版本目录
+ * 4. 替换失败时可从备份恢复
*/
async function generateUpdateScript(
zipPath: string,
@@ -222,9 +320,14 @@ set "ZIP_PATH=${zipPath.replace(/\//g, '\\')}"
set "APP_DIR=${appDir.replace(/\//g, '\\')}"
set "TEMP_DIR=${tempDir.replace(/\//g, '\\')}"
set "BACKUP_DIR=${tempDir.replace(/\//g, '\\')}\\external-resources-backup"
+set "APP_BACKUP_DIR=${tempDir.replace(/\//g, '\\')}\\app-backup"
set "EXTRACT_DIR=${tempDir.replace(/\//g, '\\')}\\extracted"
+set "UPDATE_LOG=${tempDir.replace(/\//g, '\\')}\\update-script.log"
-echo [更新] 等待应用退出...
+:: 日志函数
+call :log "========== 更新脚本开始 =========="
+
+call :log "等待应用退出..."
:waitloop
tasklist /FI "IMAGENAME eq AI Learning Assistant Launcher.exe" 2>NUL | find /I "AI Learning Assistant Launcher.exe" >NUL
if not errorlevel 1 (
@@ -232,54 +335,142 @@ if not errorlevel 1 (
goto waitloop
)
-echo [更新] 应用已退出,开始更新...
+call :log "应用已退出,开始更新..."
-:: 创建临时目录
-if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%"
-if not exist "%EXTRACT_DIR%" mkdir "%EXTRACT_DIR%"
+:: ========== 阶段1: 准备和解压 ==========
+call :log "[阶段1] 准备临时目录..."
+if exist "%TEMP_DIR%" rd /s /q "%TEMP_DIR%" 2>nul
+mkdir "%TEMP_DIR%"
+mkdir "%EXTRACT_DIR%"
-:: 备份 external-resources
-echo [更新] 备份用户数据...
-if exist "%APP_DIR%\\external-resources" (
- xcopy "%APP_DIR%\\external-resources" "%BACKUP_DIR%\\" /E /I /H /Y /Q
+call :log "[阶段1] 解压更新包到临时目录..."
+powershell -Command "try { Expand-Archive -Path '%ZIP_PATH%' -DestinationPath '%EXTRACT_DIR%' -Force -ErrorAction Stop; exit 0 } catch { Write-Host $_.Exception.Message; exit 1 }"
+if errorlevel 1 (
+ call :log "[错误] 解压失败!启动旧版本..."
+ goto :launch_old_version
)
-:: 解压 ZIP 文件
-echo [更新] 解压更新包...
-powershell -Command "Expand-Archive -Path '%ZIP_PATH%' -DestinationPath '%EXTRACT_DIR%' -Force"
-
:: 查找解压后的目录(ZIP 内可能有一层目录)
+set "SOURCE_DIR="
for /d %%D in ("%EXTRACT_DIR%\\*") do set "SOURCE_DIR=%%D"
if not defined SOURCE_DIR set "SOURCE_DIR=%EXTRACT_DIR%"
-:: 删除旧文件(保留 external-resources)
-echo [更新] 清理旧版本...
+call :log "[阶段1] 解压完成,源目录: %SOURCE_DIR%"
+
+:: ========== 阶段2: 校验解压完整性 ==========
+call :log "[阶段2] 校验更新包完整性..."
+
+:: 检查关键文件是否存在
+if not exist "%SOURCE_DIR%\\AI Learning Assistant Launcher.exe" (
+ call :log "[错误] 校验失败:主程序文件不存在!启动旧版本..."
+ goto :launch_old_version
+)
+
+if not exist "%SOURCE_DIR%\\resources" (
+ call :log "[错误] 校验失败:resources 目录不存在!启动旧版本..."
+ goto :launch_old_version
+)
+
+call :log "[阶段2] 校验通过,更新包完整"
+
+:: ========== 阶段3: 备份用户数据和旧版本 ==========
+call :log "[阶段3] 备份用户数据 external-resources..."
+if exist "%APP_DIR%\\external-resources" (
+ xcopy "%APP_DIR%\\external-resources" "%BACKUP_DIR%\\" /E /I /H /Y /Q >nul 2>&1
+ if errorlevel 1 (
+ call :log "[警告] 用户数据备份可能不完整"
+ ) else (
+ call :log "[阶段3] 用户数据备份完成"
+ )
+)
+
+call :log "[阶段3] 备份旧版本应用..."
+mkdir "%APP_BACKUP_DIR%" 2>nul
+xcopy "%APP_DIR%\\*" "%APP_BACKUP_DIR%\\" /E /I /H /Y /Q >nul 2>&1
+if errorlevel 1 (
+ call :log "[警告] 旧版本备份可能不完整,但继续更新"
+) else (
+ call :log "[阶段3] 旧版本备份完成"
+)
+
+:: ========== 阶段4: 替换文件 ==========
+call :log "[阶段4] 清理旧版本文件(保留 external-resources)..."
for /d %%D in ("%APP_DIR%\\*") do (
- if /I not "%%~nxD"=="external-resources" rd /s /q "%%D" 2>nul
+ if /I not "%%~nxD"=="external-resources" (
+ rd /s /q "%%D" 2>nul
+ )
)
for %%F in ("%APP_DIR%\\*") do (
del /f /q "%%F" 2>nul
)
-:: 复制新文件
-echo [更新] 安装新版本...
-xcopy "%SOURCE_DIR%\\*" "%APP_DIR%\\" /E /I /H /Y /Q
+call :log "[阶段4] 安装新版本..."
+xcopy "%SOURCE_DIR%\\*" "%APP_DIR%\\" /E /I /H /Y /Q >nul 2>&1
+if errorlevel 1 (
+ call :log "[错误] 复制新文件失败!尝试从备份恢复..."
+ goto :restore_from_backup
+)
+
+:: 验证安装结果
+if not exist "%APP_DIR%\\AI Learning Assistant Launcher.exe" (
+ call :log "[错误] 安装验证失败:主程序不存在!尝试从备份恢复..."
+ goto :restore_from_backup
+)
+
+call :log "[阶段4] 新版本安装完成"
-:: 恢复 external-resources(合并)
-echo [更新] 恢复用户数据...
+:: ========== 阶段5: 恢复用户数据 ==========
+call :log "[阶段5] 恢复用户数据..."
if exist "%BACKUP_DIR%" (
- xcopy "%BACKUP_DIR%\\*" "%APP_DIR%\\external-resources\\" /E /I /H /Y /Q
+ xcopy "%BACKUP_DIR%\\*" "%APP_DIR%\\external-resources\\" /E /I /H /Y /Q >nul 2>&1
+ call :log "[阶段5] 用户数据恢复完成"
)
-:: 清理临时文件
-echo [更新] 清理临时文件...
-rd /s /q "%TEMP_DIR%" 2>nul
+:: ========== 阶段6: 清理和启动 ==========
+call :log "[阶段6] 更新成功!清理临时文件..."
+:: 延迟删除临时目录,避免影响日志
+start /b cmd /c "timeout /t 5 /nobreak >nul & rd /s /q "%TEMP_DIR%" 2>nul"
-:: 启动新版本
-echo [更新] 启动新版本...
+call :log "[阶段6] 启动新版本..."
+call :log "========== 更新脚本结束 =========="
start "" "%APP_DIR%\\AI Learning Assistant Launcher.exe"
+exit
+:: ========== 错误处理: 从备份恢复 ==========
+:restore_from_backup
+call :log "[恢复] 开始从备份恢复旧版本..."
+if exist "%APP_BACKUP_DIR%\\AI Learning Assistant Launcher.exe" (
+ :: 清理可能的残留
+ for /d %%D in ("%APP_DIR%\\*") do rd /s /q "%%D" 2>nul
+ for %%F in ("%APP_DIR%\\*") do del /f /q "%%F" 2>nul
+
+ :: 恢复备份
+ xcopy "%APP_BACKUP_DIR%\\*" "%APP_DIR%\\" /E /I /H /Y /Q >nul 2>&1
+ if exist "%APP_DIR%\\AI Learning Assistant Launcher.exe" (
+ call :log "[恢复] 旧版本恢复成功"
+ goto :launch_old_version
+ )
+)
+call :log "[恢复] 恢复失败,请手动重新安装应用"
+pause
+exit
+
+:: ========== 错误处理: 启动旧版本 ==========
+:launch_old_version
+call :log "[回退] 启动旧版本..."
+if exist "%APP_DIR%\\AI Learning Assistant Launcher.exe" (
+ start "" "%APP_DIR%\\AI Learning Assistant Launcher.exe"
+) else (
+ call :log "[错误] 旧版本也不存在,请手动重新安装"
+ pause
+)
exit
+
+:: ========== 日志函数 ==========
+:log
+echo [%date% %time%] %~1
+echo [%date% %time%] %~1 >> "%UPDATE_LOG%" 2>nul
+exit /b 0
`;
// 确保临时目录存在
@@ -398,6 +589,13 @@ export async function installLauncherUpdate() {
const latestVersionInfo = getLatestVersion(
'AI_LEARNING_ASSISTANT_LAUNCHER',
);
+
+ // 空值检查:确保 dlcInfo 和 magnet 存在
+ if (!latestVersionInfo.dlcInfo?.magnet) {
+ throw new Error('未找到有效的更新包信息(dlcInfo 或 magnet 缺失)');
+ }
+
+ const { magnet } = latestVersionInfo.dlcInfo;
const version = latestVersionInfo.version;
await writeUpdateLog('DEBUG', '[installLauncherUpdate] 最新版本:', version);
@@ -449,7 +647,7 @@ export async function installLauncherUpdate() {
'DEBUG',
'[installLauncherUpdate] 销毁 WebTorrent 释放文件句柄...',
);
- await destroyWebtorrentForInstall(latestVersionInfo.dlcInfo.magnet);
+ await destroyWebtorrentForInstall(magnet);
// 使用重试机制等待文件句柄释放
await waitForFileRelease(zipPath);