licyk commited on
Commit
08bb3ef
·
verified ·
1 Parent(s): f2d2451

Upload sd_portable_downloader.bat

Browse files
Files changed (1) hide show
  1. sd_portable_downloader.bat +1774 -0
sd_portable_downloader.bat ADDED
@@ -0,0 +1,1774 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ @setlocal DisableDelayedExpansion
3
+ set "__BatFilePath_wjZHNFMz__=%~f0"
4
+ set "__BatFilePathP_ESJYJL3u__=%__BatFilePath_wjZHNFMz__:'=''%"
5
+ set "__WorkPath_XvGO8XXY__=%~dp0"
6
+ set "__POWERSHELL_CODE_ARG_Tbs9uXUY__=%*"
7
+ if "%__WorkPath_XvGO8XXY__:~-1%"=="\" set "__WorkPath_XvGO8XXY__=%__WorkPath_XvGO8XXY__:~0,-1%"
8
+ set "BAT_SCRIPT_ROOT=%__WorkPath_XvGO8XXY__%"
9
+ set "__DefaultPowerShellExecutable_exj9gQWc__="
10
+ if "%__DefaultPowerShellExecutable_exj9gQWc__%" == "" (
11
+ where pwsh >nul 2>&1
12
+ if not errorlevel 1 (
13
+ set "__PowerShellExecutable_yc3YvlUU__=pwsh"
14
+ goto :ExecCode
15
+ )
16
+ where powershell >nul 2>&1
17
+ if not errorlevel 1 (
18
+ set "__PowerShellExecutable_yc3YvlUU__=powershell"
19
+ goto :ExecCode
20
+ )
21
+ ) else (
22
+ set "__PowerShellExecutable_yc3YvlUU__=%__DefaultPowerShellExecutable_exj9gQWc__%"
23
+ )
24
+ :ExecCode
25
+ if "%__PowerShellExecutable_yc3YvlUU__%" == "" (
26
+ echo PowerShell Executable Not Found
27
+ pause
28
+ set "_psh_exit_code_=1"
29
+ goto :ExitCode
30
+ )
31
+ cmd /c " "%__PowerShellExecutable_yc3YvlUU__%" -ExecutionPolicy Bypass -nop -c ""$f = [System.IO.File]::ReadAllText('%__BatFilePathP_ESJYJL3u__%') -split ':__PowerShellCodeExec_9cErTct9__\:.*'; try { . ([scriptblock]::Create($f[1])) -BatchPath '%__BatFilePathP_ESJYJL3u__%' } catch { $_; exit 1 } " "
32
+ set "_psh_exit_code_=%ERRORLEVEL%"
33
+ goto :ExitCode
34
+
35
+ :__PowerShellCodeExec_9cErTct9__:
36
+ param (
37
+ [string]$BatchPath,
38
+ [Parameter(ValueFromRemainingArguments=$true)]$ExtraArgs
39
+ )
40
+
41
+ function Get-ExtraArgs-From-Env {
42
+ if ([string]::IsNullOrEmpty($Env:__POWERSHELL_CODE_ARG_Tbs9uXUY__)) {
43
+ return @()
44
+ }
45
+ $launch_args = $Env:__POWERSHELL_CODE_ARG_Tbs9uXUY__
46
+ $arguments = [regex]::Matches($launch_args, '("[^"]*"|''[^'']*''|\S+)') | ForEach-Object {
47
+ $_.Value -replace '^["'']|["'']$', ''
48
+ }
49
+ return $arguments
50
+ }
51
+
52
+ function Get-ExtraArgs {
53
+ param (
54
+ [string[]]$ExcludeArgs = @("-CommandWithArgs")
55
+ )
56
+ $extra_args = New-Object System.Collections.ArrayList
57
+
58
+ foreach ($a in $ExtraArgs) {
59
+ if (!($a -in $ExcludeArgs)) {
60
+ $extra_args.Add($a) | Out-Null
61
+ }
62
+ }
63
+
64
+ if ($extra_args.Count -eq 0) {
65
+ $extra_args = Get-ExtraArgs-From-Env
66
+ }
67
+
68
+ $params = $extra_args.ForEach{
69
+ if ($_ -match '\s|"') { "'{0}'" -f ($_ -replace "'", "''") }
70
+ else { $_ }
71
+ } -join ' '
72
+
73
+ return $params
74
+ }
75
+
76
+ function Get-PowerShell-Code {
77
+ param (
78
+ [string]$ScriptPath,
79
+ [string]$Prefix = "__PowerShellCode_pxwOLOkD__"
80
+ )
81
+ $f = [System.IO.File]::ReadAllText($ScriptPath) -split ":${Prefix}\:.*"
82
+ return $([scriptblock]::Create($f[1]))
83
+ }
84
+
85
+ function New-RandomName {
86
+ param(
87
+ [ValidateRange(4, 32)]
88
+ [int]$Length = 32
89
+ )
90
+
91
+ ([guid]::NewGuid().ToString("N")).Substring(0, $Length)
92
+ }
93
+
94
+ function New-TemporaryDirectory {
95
+ param(
96
+ [ValidateRange(4, 32)]
97
+ [int]$NameLength = 32
98
+ )
99
+
100
+ do {
101
+ $name = New-RandomName -Length $NameLength
102
+ $path = Join-Path ([IO.Path]::GetTempPath()) $name
103
+ } until (-not (Test-Path $path))
104
+
105
+ New-Item -ItemType Directory -Path $path | Out-Null
106
+
107
+ $obj = [pscustomobject]@{ Path = $path }
108
+ $obj | Add-Member ScriptMethod Dispose {
109
+ if (Test-Path $this.Path) {
110
+ Remove-Item $this.Path -Recurse -Force -ErrorAction SilentlyContinue
111
+ }
112
+ }
113
+ return $obj
114
+ }
115
+
116
+ function New-TemporaryFile {
117
+ param(
118
+ [string]$Extension = ".tmp",
119
+ [switch]$Open,
120
+ [ValidateRange(4, 32)]
121
+ [int]$NameLength = 32
122
+ )
123
+
124
+ do {
125
+ $name = (New-RandomName -Length $NameLength) + $Extension
126
+ $path = Join-Path ([IO.Path]::GetTempPath()) $name
127
+ } until (-not (Test-Path $path))
128
+
129
+ if ($Open) {
130
+ $fs = [IO.File]::Open(
131
+ $path,
132
+ [IO.FileMode]::CreateNew,
133
+ [IO.FileAccess]::ReadWrite,
134
+ [IO.FileShare]::None
135
+ )
136
+ }
137
+ else {
138
+ New-Item -ItemType File -Path $path | Out-Null
139
+ $fs = $null
140
+ }
141
+
142
+ $obj = [pscustomobject]@{
143
+ Path = $path
144
+ Stream = $fs
145
+ }
146
+
147
+ $obj | Add-Member ScriptMethod Dispose {
148
+ if ($this.Stream) { $this.Stream.Dispose() }
149
+ if (Test-Path $this.Path) {
150
+ Remove-Item $this.Path -Force -ErrorAction SilentlyContinue
151
+ }
152
+ }
153
+
154
+ return $obj
155
+ }
156
+
157
+ function Main {
158
+ $tmp = New-TemporaryDirectory -NameLength 8
159
+ $temp_script_name = "$(New-RandomName -NameLength 8).ps1"
160
+ $temp_script_path = (Join-Path $tmp.Path $temp_script_name)
161
+ $psh_script_encoding = if ($PSVersionTable.PSVersion.Major -le 5) { "UTF8" } else { "utf8BOM" }
162
+ try {
163
+ $psh_code = Get-PowerShell-Code -ScriptPath $BatchPath -Prefix "__PowerShellCode_pxwOLOkD__"
164
+ Set-Content -Value $psh_code -Path $temp_script_path -Encoding $psh_script_encoding -Force
165
+ $extra_args = Get-ExtraArgs
166
+ $Env:__BatFilePath_wjZHNFMz__ = $null
167
+ $Env:__BatFilePathP_ESJYJL3u__ = $null
168
+ $Env:__WorkPath_XvGO8XXY__ = $null
169
+ $Env:__POWERSHELL_CODE_ARG_Tbs9uXUY__ = $null
170
+ $Env:__DefaultPowerShellExecutable_exj9gQWc__ = $null
171
+ $Env:__PowerShellExecutable_yc3YvlUU__ = $null
172
+ Invoke-Expression "& `"$temp_script_path`" $extra_args"
173
+ }
174
+ finally {
175
+ $tmp.Dispose()
176
+ }
177
+ }
178
+
179
+ Main
180
+ :__PowerShellCodeExec_9cErTct9__:
181
+
182
+ :__PowerShellCode_pxwOLOkD__:
183
+ <#
184
+ .SYNOPSIS
185
+ AI整合包可视化下载管理工具
186
+
187
+ .DESCRIPTION
188
+ 这是一个功能完整的WPF GUI应用,用于下载和管理各类AI模型应用的可发行包。
189
+
190
+ 主要功能:
191
+ - 支持多源下载:HuggingFace、ModelScope
192
+ - 版本选择:稳定版(Stable)、夜间构建版(Nightly)
193
+ - 智能队列管理:支持任务排队、自动调度、中断控制
194
+ - 自动解压:下载完成后可自动提取压缩包
195
+ - 磁盘监控:实时显示目标路径的磁盘空间占用
196
+ - 系统主题适配:自动检测Windows深色/浅色模式
197
+ - 代理配置:自动读取系统代理设置
198
+ - 断点续传:使用Aria2核心支持多线程下载
199
+
200
+ .PARAMETER ScriptRootPath
201
+ 脚本执行的根路径。如果未指定,将使用当前工作目录。
202
+ 类型: [string]
203
+ 示例: "C:\Downloads"
204
+
205
+ .NOTES
206
+ 环境要求:
207
+ - Windows 7 (SP1) 或更高版本
208
+ - PowerShell 5.0 或更高版本
209
+ - 需要 .NET Framework 4.5+ (WPF支持)
210
+ - 可选:系统已安装Aria2和7-Zip工具
211
+
212
+ 外部依赖:
213
+ - Aria2c.exe: 高性能下载引擎,支持多线程和断点续传
214
+ 下载源: https://gitee.com/licyk/sd-webui-all-in-one/raw/master/aria2/windows/amd64/aria2c.exe
215
+ - 7za.exe: 轻量级解压工具,支持7z/zip等多种格式
216
+ 下载源: https://modelscope.cn/models/licyks/sd-webui-all-in-one/resolve/master/7za/windows/amd64/7za.exe
217
+ - portable_list.json: 云端资源配置文件
218
+ 源地址: https://licyk.github.io/resources/portable_list.json
219
+
220
+ 主要类和函数:
221
+ - Get-Aria2-Executable: 获取或下载Aria2可执行文件
222
+ - Get-7za-Executable: 获取或下载7-Zip可执行文件
223
+ - Invoke-DownloadTask: 执行单个下载任务
224
+ - Start-ExtractionTask: 执行解压任务
225
+ - Invoke-Refresh: 从云端刷新可用资源列表
226
+ - Set-Proxy: 配置系统代理
227
+ - Find-Visual-Element: WPF可视树搜索辅助函数
228
+
229
+ .EXAMPLE
230
+ # 标准使用方式
231
+ .\sd_portable_downloader.ps1
232
+
233
+ .EXAMPLE
234
+ # 指定下载保存路径
235
+ .\sd_portable_downloader.ps1 -ScriptRootPath "E:\AIModels"
236
+
237
+ .EXAMPLE
238
+ # 在批处理中调用(cmd.exe)
239
+ powershell -ExecutionPolicy Bypass -File ".\sd_portable_downloader.ps1" -ScriptRootPath "C:\Download"
240
+
241
+ .INPUTS
242
+ [string] ScriptRootPath - 可选的脚本根路径参数
243
+
244
+ .OUTPUTS
245
+ 无直接输出。返回GUI窗口交互式界面。
246
+ 下载的文件保存到用户指定的目录。
247
+
248
+ .LINK
249
+ 项目仓库: https://github.com/licyk/sd-webui-all-in-one
250
+ 讨论区: https://github.com/licyk/sd-webui-all-in-one/discussions/1
251
+
252
+ .AUTHOR
253
+ licyk
254
+
255
+ .VERSION
256
+ 1.0.2
257
+
258
+ .HISTORY
259
+
260
+ #>
261
+ param (
262
+ [string]$ScriptRootPath
263
+ )
264
+ $script:SD_PORTABLE_DOWNLOADER_VERSION = 105
265
+ Add-Type -AssemblyName PresentationFramework, System.Windows.Forms, System.Drawing
266
+
267
+ # 注入 Win32 API 用于实现毛玻璃效果
268
+ Add-Type -TypeDefinition @"
269
+ using System;
270
+ using System.Runtime.InteropServices;
271
+
272
+ [StructLayout(LayoutKind.Sequential)]
273
+ public struct AccentPolicy {
274
+ public int AccentState;
275
+ public int AccentFlags;
276
+ public int GradientColor;
277
+ public int AnimationId;
278
+ }
279
+
280
+ [StructLayout(LayoutKind.Sequential)]
281
+ public struct WindowCompositionAttributeData {
282
+ public int Attribute;
283
+ public IntPtr Data;
284
+ public int SizeOfData;
285
+ }
286
+
287
+ public class BlurHelper {
288
+ [DllImport("user32.dll")]
289
+ public static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
290
+
291
+ [DllImport("dwmapi.dll")]
292
+ public static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
293
+
294
+ public static void SetBlurState(IntPtr hwnd, int state) {
295
+ var accent = new AccentPolicy();
296
+ accent.AccentState = state; // 0: Disabled, 3: Blur
297
+
298
+ var accentStructSize = Marshal.SizeOf(accent);
299
+ var accentPtr = Marshal.AllocHGlobal(accentStructSize);
300
+ Marshal.StructureToPtr(accent, accentPtr, false);
301
+
302
+ var data = new WindowCompositionAttributeData();
303
+ data.Attribute = 19; // WCA_ACCENT_POLICY
304
+ data.SizeOfData = accentStructSize;
305
+ data.Data = accentPtr;
306
+
307
+ SetWindowCompositionAttribute(hwnd, ref data);
308
+ Marshal.FreeHGlobal(accentPtr);
309
+ }
310
+
311
+ public static void EnableBlur(IntPtr hwnd) {
312
+ SetBlurState(hwnd, 3);
313
+ }
314
+
315
+ public static void SetRounding(IntPtr hwnd, bool enabled) {
316
+ // DWMWA_WINDOW_CORNER_PREFERENCE = 33
317
+ // DWMWCP_ROUND = 2, DWMWCP_DONOTROUND = 1
318
+ int preference = enabled ? 2 : 1;
319
+ DwmSetWindowAttribute(hwnd, 33, ref preference, sizeof(int));
320
+ }
321
+
322
+ public static void SetDarkMode(IntPtr hwnd, bool enabled) {
323
+ // DWMWA_USE_IMMERSIVE_DARK_MODE = 20
324
+ int preference = enabled ? 1 : 0;
325
+ DwmSetWindowAttribute(hwnd, 20, ref preference, sizeof(int));
326
+ }
327
+ }
328
+ "@ -PassThru | Out-Null
329
+
330
+ function Show-Update-Popup {
331
+ param([string]$LatestVersion, [string]$CurrentVersion, [bool]$IsDarkMode)
332
+
333
+ $github = "https://github.com/licyk/sd-webui-all-in-one/releases/download/portable/sd_portable_downloader.bat"
334
+ $gitee = "https://gitee.com/licyk/sd-webui-all-in-one/releases/download/portable/sd_portable_downloader.bat"
335
+
336
+ # 根据主题定义颜色
337
+ $theme = if ($IsDarkMode) {
338
+ @{ BG = "#2D2D2D"; TextMain = "#FFFFFF"; TextSec = "#AAAAAA"; Border = "#444444"; BtnGray = "#4A4A4A"; BtnGrayText = "#FFFFFF" }
339
+ } else {
340
+ @{ BG = "#FFFFFF"; TextMain = "#333333"; TextSec = "Gray"; Border = "#0078D4"; BtnGray = "#E1E1E1"; BtnGrayText = "#333333" }
341
+ }
342
+
343
+ $rs = [runspacefactory]::CreateRunspace()
344
+ $rs.ApartmentState = "STA"
345
+ $rs.Open()
346
+ $ps = [powershell]::Create().AddScript({
347
+ param($v, $cv, $gh, $gt, $t, $dark)
348
+ Add-Type -AssemblyName PresentationFramework
349
+
350
+ [xml]$xaml = @"
351
+ <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
352
+ Title="更新提示" Height="200" Width="420" WindowStartupLocation="CenterScreen"
353
+ WindowStyle="None" AllowsTransparency="True" Background="Transparent" Topmost="True">
354
+ <Border CornerRadius="12" Background="$($t.BG)" BorderBrush="$($t.Border)" BorderThickness="1.5">
355
+ <Border.Effect>
356
+ <DropShadowEffect BlurRadius="15" ShadowDepth="0" Opacity="0.4"/>
357
+ </Border.Effect>
358
+ <StackPanel Margin="25">
359
+ <TextBlock Text="发现新版本!" FontSize="20" FontWeight="Bold" Foreground="#0078D4" Margin="0,0,0,8"/>
360
+ <TextBlock Text="最新版本: $v (当前版本: $cv)" FontSize="12" Foreground="$($t.TextSec)" Margin="0,0,0,15"/>
361
+ <TextBlock Text="建议下载最新的 .bat 启动器以获得更好的体验。" FontSize="13" Foreground="$($t.TextMain)" Margin="0,0,0,20" TextWrapping="Wrap"/>
362
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
363
+ <Button Name="GithubBtn" Content="GitHub 下载" Margin="0,0,10,0" Cursor="Hand" Background="#0078D4" Foreground="White" Padding="12,6" BorderThickness="0">
364
+ <Button.Resources><Style TargetType="Border"><Setter Property="CornerRadius" Value="6"/></Style></Button.Resources>
365
+ </Button>
366
+ <Button Name="GiteeBtn" Content="Gitee 下载" Margin="0,0,10,0" Cursor="Hand" Background="#0078D4" Foreground="White" Padding="12,6" BorderThickness="0">
367
+ <Button.Resources><Style TargetType="Border"><Setter Property="CornerRadius" Value="6"/></Style></Button.Resources>
368
+ </Button>
369
+ <Button Name="CloseBtn" Content="稍后" Cursor="Hand" Background="$($t.BtnGray)" Foreground="$($t.BtnGrayText)" Padding="15,6" BorderThickness="0">
370
+ <Button.Resources><Style TargetType="Border"><Setter Property="CornerRadius" Value="6"/></Style></Button.Resources>
371
+ </Button>
372
+ </StackPanel>
373
+ </StackPanel>
374
+ </Border>
375
+ </Window>
376
+ "@
377
+ $reader = New-Object System.Xml.XmlNodeReader $xaml
378
+ $win = [Windows.Markup.XamlReader]::Load($reader)
379
+
380
+ # 处理 Win32 句柄以设置沉浸式深色模式标题栏(虽然是无边框,但有助于系统集成)
381
+ if ($dark) {
382
+ try {
383
+ (New-Object System.Windows.Interop.WindowInteropHelper($win)).EnsureHandle()
384
+ # 此处由于是子 Runspace,不方便调用主进程定义的 BlurHelper,仅通过 XAML 颜色适配
385
+ } catch {}
386
+ }
387
+
388
+ $win.FindName("GithubBtn").Add_Click({ Start-Process $gh; $win.Close() }.GetNewClosure())
389
+ $win.FindName("GiteeBtn").Add_Click({ Start-Process $gt; $win.Close() }.GetNewClosure())
390
+ $win.FindName("CloseBtn").Add_Click({ $win.Close() }.GetNewClosure())
391
+ $win.Add_MouseLeftButtonDown({ $win.DragMove() }.GetNewClosure())
392
+
393
+ $win.ShowDialog() | Out-Null
394
+ }).AddArgument($LatestVersion).AddArgument($CurrentVersion).AddArgument($github).AddArgument($gitee).AddArgument($theme).AddArgument($IsDarkMode)
395
+ $ps.Runspace = $rs
396
+ $ps.BeginInvoke()
397
+ }
398
+
399
+ function Show-Async-MsgBox {
400
+ param([string]$Message, [string]$Title = "提示", [string]$Icon = "Information")
401
+
402
+ $rs = [runspacefactory]::CreateRunspace()
403
+ $rs.Open()
404
+ $ps = [powershell]::Create().AddScript({
405
+ param($msg, $title, $icon)
406
+ Add-Type -AssemblyName PresentationFramework
407
+ [System.Windows.MessageBox]::Show($msg, $title, "OK", $icon)
408
+ }).AddArgument($Message).AddArgument($Title).AddArgument($Icon)
409
+ $ps.Runspace = $rs
410
+ $ps.BeginInvoke()
411
+ }
412
+
413
+ function Invoke-Update-Async {
414
+ param([bool]$IsDarkMode)
415
+ Write-Host "[更新] 正在后台检查新版本..." -ForegroundColor Cyan
416
+ $urls = @(
417
+ "https://github.com/licyk/sd-webui-all-in-one/raw/main/.github/sd_portable_downloader.ps1",
418
+ "https://gitee.com/licyk/sd-webui-all-in-one/raw/main/.github/sd_portable_downloader.ps1"
419
+ )
420
+
421
+ if ($null -eq $Global:DownloadRunspacePool) {
422
+ $Global:DownloadRunspacePool = [runspacefactory]::CreateRunspacePool(1, 2)
423
+ $Global:DownloadRunspacePool.Open()
424
+ }
425
+
426
+ $ps = [powershell]::Create().AddScript({
427
+ param($urls, $currentVersion)
428
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
429
+ $errors = @()
430
+ foreach ($url in $urls) {
431
+ try {
432
+ $response = Invoke-WebRequest -UseBasicParsing -Uri $url -TimeoutSec 10 -ErrorAction Stop
433
+ if ($response.Content -match '\$script:SD_PORTABLE_DOWNLOADER_VERSION\s*=\s*(\d+)') {
434
+ $latestVersion = [int]$matches[1]
435
+ if ($latestVersion -gt $currentVersion) {
436
+ return @{ Success = $true; LatestVersion = $latestVersion }
437
+ } else {
438
+ return @{ Success = $false; UpToDate = $true }
439
+ }
440
+ } else {
441
+ $errors += "无法在源中解析到版本号: $url"
442
+ }
443
+ } catch {
444
+ $errors += "访问源失败 ($url): $($_.Exception.Message)"
445
+ }
446
+ }
447
+ return @{ Success = $false; Error = ($errors -join "`n") }
448
+ }).AddArgument($urls).AddArgument($script:SD_PORTABLE_DOWNLOADER_VERSION)
449
+
450
+ $ps.RunspacePool = $Global:DownloadRunspacePool
451
+ $asyncResult = $ps.BeginInvoke()
452
+ $currentVer = $script:SD_PORTABLE_DOWNLOADER_VERSION
453
+ # 显式捕获函数引用,以便 PS5.1 的闭包能够识别
454
+ $ShowPopup = Get-Command Show-Update-Popup
455
+
456
+ $monitorTimer = New-Object System.Windows.Threading.DispatcherTimer
457
+ $monitorTimer.Interval = [TimeSpan]::FromSeconds(1)
458
+ $monitorTimer.Add_Tick({
459
+ if ($asyncResult.IsCompleted) {
460
+ $monitorTimer.Stop()
461
+ try {
462
+ $result = $ps.EndInvoke($asyncResult)
463
+ if ($result.Success) {
464
+ Write-Host "[更新] 发现新版本: $($result.LatestVersion) (当前: $currentVer)" -ForegroundColor Green
465
+ # 使用 & 符号调用捕获的函数变量
466
+ & $ShowPopup -LatestVersion $result.LatestVersion -CurrentVersion $currentVer -IsDarkMode $IsDarkMode
467
+ } elseif ($result.UpToDate) {
468
+ Write-Host "[更新] 当前已是最新版本 ($currentVer)" -ForegroundColor Gray
469
+ } elseif ($result.Error) {
470
+ Write-Host "[更新] 检查更新失败: $($result.Error)" -ForegroundColor Red
471
+ } else {
472
+ Write-Host "[更新] 检查更新任务完成,但未发现有效版本信息。" -ForegroundColor Yellow
473
+ }
474
+ } catch {
475
+ Write-Host "[更新] 检查更新时发生异常: $($_.Exception.Message)" -ForegroundColor Red
476
+ } finally {
477
+ $ps.Dispose()
478
+ }
479
+ }
480
+ }.GetNewClosure())
481
+ $monitorTimer.Start()
482
+ }
483
+
484
+ function Open-Url {
485
+ param([string]$Url)
486
+ try { Start-Process $Url } catch { Write-Warning "无法打开链接: $Url" }
487
+ }
488
+
489
+ function Invoke-WebRequest-Async {
490
+ param([string]$Uri, [string]$OutFile)
491
+
492
+ # 检查当前线程是否有关联的 Dispatcher (UI 线程)
493
+ $dispatcher = [System.Windows.Threading.Dispatcher]::FromThread([System.Threading.Thread]::CurrentThread)
494
+ if ($null -eq $dispatcher) {
495
+ # 非 UI 线程,直接同步执行
496
+ Invoke-WebRequest -UseBasicParsing -Uri $Uri -OutFile $OutFile
497
+ } else {
498
+ # UI 线程,使用后台运行空间下载,并保持 UI 响应
499
+ $rs = [runspacefactory]::CreateRunspace()
500
+ $rs.Open()
501
+ $ps = [powershell]::Create().AddScript({
502
+ param($u, $o)
503
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
504
+ Invoke-WebRequest -UseBasicParsing -Uri $u -OutFile $o
505
+ }).AddArgument($Uri).AddArgument($OutFile)
506
+ $ps.Runspace = $rs
507
+ $async = $ps.BeginInvoke()
508
+ while (-not $async.IsCompleted) {
509
+ # 允许 UI 处理事件循环
510
+ $dispatcher.Invoke([Action]{}, [System.Windows.Threading.DispatcherPriority]::Background)
511
+ Start-Sleep -Milliseconds 100
512
+ }
513
+ try {
514
+ $ps.EndInvoke($async)
515
+ } finally {
516
+ $ps.Dispose()
517
+ $rs.Close()
518
+ }
519
+ }
520
+ }
521
+
522
+ function Get-Aria2-Executable {
523
+ $binUrl = "https://www.modelscope.cn/models/licyks/sd-webui-all-in-one/resolve/master/aria2/windows/amd64/aria2c.exe"
524
+ $binPath = Join-Path ([IO.Path]::GetTempPath()) "aria2c.exe"
525
+ $localBinPath = Get-Command aria2c -ErrorAction SilentlyContinue
526
+
527
+ if ($null -ne $localBinPath) {
528
+ Write-Host "[环境] 发现系统安装的 Aria2: $($localBinPath.Source)" -ForegroundColor Gray
529
+ return $localBinPath.Source
530
+ }
531
+ if (Test-Path $binPath) {
532
+ try {
533
+ & "$binPath" --version > $null
534
+ if ($?) {
535
+ Write-Host "[环境] 使用缓存的 Aria2 核心: $binPath" -ForegroundColor Gray
536
+ return $binPath
537
+ }
538
+ } catch { }
539
+ }
540
+
541
+ Write-Host "[环境] 正在下发 Aria2 核心组件..." -ForegroundColor Yellow
542
+ Invoke-WebRequest-Async -Uri $binUrl -OutFile $binPath
543
+ return $binPath
544
+ }
545
+
546
+ function Get-7za-Executable {
547
+ $binUrl = "https://modelscope.cn/models/licyks/sd-webui-all-in-one/resolve/master/7za/windows/amd64/7za.exe"
548
+ $binPath = Join-Path ([IO.Path]::GetTempPath()) "7za.exe"
549
+ $localBinPath = Get-Command 7za -ErrorAction SilentlyContinue
550
+ $localBinPath7z = Get-Command 7z -ErrorAction SilentlyContinue
551
+
552
+ if ($null -ne $localBinPath) {
553
+ Write-Host "[环境] 发现系统安装的 7za: $($localBinPath.Source)" -ForegroundColor Gray
554
+ return $localBinPath.Source
555
+ }
556
+ if ($null -ne $localBinPath7z) {
557
+ Write-Host "[环境] 发现系统安装的 7z: $($localBinPath7z.Source)" -ForegroundColor Gray
558
+ return $localBinPath7z.Source
559
+ }
560
+ if (Test-Path $binPath) {
561
+ try {
562
+ & "$binPath" > $null
563
+ if ($?) {
564
+ Write-Host "[环境] 使用缓存的 7-Zip 组件: $binPath" -ForegroundColor Gray
565
+ return $binPath
566
+ }
567
+ } catch { }
568
+ }
569
+
570
+ Write-Host "[环境] 正在下发 7-Zip 解压组件..." -ForegroundColor Yellow
571
+ Invoke-WebRequest-Async -Uri $binUrl -OutFile $binPath
572
+ return $binPath
573
+ }
574
+
575
+ function Invoke-DownloadTask {
576
+ param(
577
+ [Parameter(Mandatory)] [string]$Url,
578
+ [Parameter(Mandatory)] [string]$OutDir,
579
+ [Parameter(Mandatory)] [string]$SaveName,
580
+ [Parameter(Mandatory)] [Object]$State,
581
+ [Parameter(Mandatory)] [Object]$QueueTask
582
+ )
583
+
584
+ $bin = Get-Aria2-Executable
585
+ $launch_args = "-c -x 16 -s 64 -k 1M --file-allocation=none --summary-interval=0 --console-log-level=error -d `"$($OutDir.TrimEnd('\', '/'))`" `"$Url`" -o `"$SaveName`""
586
+
587
+ try {
588
+ Write-Host "[任务] 正在启动 Aria2 下载引擎..." -ForegroundColor Blue
589
+ Write-Host "[任务] 目标: $SaveName" -ForegroundColor Gray
590
+
591
+ $process = Start-Process -FilePath $bin -ArgumentList $launch_args -PassThru -NoNewWindow
592
+ $process.EnableRaisingEvents = $true
593
+
594
+ $taskInfo = [PSCustomObject]@{
595
+ Id = "Task-" + [guid]::NewGuid().Guid.Substring(0, 8)
596
+ Process = $process;
597
+ SaveName = $SaveName;
598
+ OutDir = $OutDir;
599
+ QueueTask = $QueueTask
600
+ }
601
+
602
+ $State.CurrentTask = $taskInfo
603
+ $State.IsCancelled = $false
604
+ Write-Host "[任务] 引擎启动成功 [PID: $($process.Id)]" -ForegroundColor Green
605
+
606
+ Show-Async-MsgBox -Message "任务已开始下载:`n$SaveName`n`n保存路径:`n$OutDir" -Title "下载启动"
607
+ return $taskInfo.Id
608
+ }
609
+ catch {
610
+ Write-Host "[任务] 启动下载任务失败: $($_.Exception.Message)" -ForegroundColor Red
611
+ $QueueTask.Status = "失败"
612
+ Show-Async-MsgBox -Message "无法启动下载任务:`n$($_.Exception.Message)`n`n报告问题与寻求帮助请前往: `nhttps://github.com/licyk/sd-webui-all-in-one/issues" -Title "启动失败" -Icon "Error"
613
+ return $null
614
+ }
615
+ }
616
+
617
+ function Start-ExtractionTask {
618
+ param([string]$FilePath, [string]$ExtractDir, [Object]$State, [Object]$QueueTask)
619
+
620
+ $bin = Get-7za-Executable
621
+ Write-Host "[后处理] 正在启动解压任务: $FilePath ..." -ForegroundColor Cyan
622
+
623
+ try {
624
+ $process = Start-Process -FilePath $bin -ArgumentList "x `"$FilePath`" -o`"$($ExtractDir.TrimEnd('\', '/'))`" -y" -PassThru -NoNewWindow
625
+ $process.EnableRaisingEvents = $true
626
+
627
+ $State.CurrentExtractionTask = [PSCustomObject]@{
628
+ Process = $process;
629
+ FilePath = $FilePath;
630
+ ExtractDir = $ExtractDir;
631
+ QueueTask = $QueueTask
632
+ }
633
+ Write-Host "[后处理] 解压引擎启动成功 [PID: $($process.Id)]" -ForegroundColor Green
634
+ return $process
635
+ }
636
+ catch {
637
+ Write-Host "[后处理] 启动解压失败: $($_.Exception.Message)" -ForegroundColor Red
638
+ $QueueTask.Status = "失败"
639
+ Show-Async-MsgBox -Message "无法启动解压任务:`n$($_.Exception.Message)`n`n报告问题与寻求帮助请前往: `nhttps://github.com/licyk/sd-webui-all-in-one/issues" -Title "解压失败" -Icon "Error"
640
+ return $null
641
+ }
642
+ }
643
+
644
+ function Find-Visual-Element {
645
+ param([System.Windows.DependencyObject]$Parent, [string]$Name)
646
+ if ($null -eq $Parent) { return $null }
647
+ for ($i = 0; $i -lt [System.Windows.Media.VisualTreeHelper]::GetChildrenCount($Parent); $i++) {
648
+ $child = [System.Windows.Media.VisualTreeHelper]::GetChild($Parent, $i)
649
+ if ($child -is [System.Windows.FrameworkElement] -and $child.Name -eq $Name) { return $child }
650
+ $result = Find-Visual-Element -Parent $child -Name $Name
651
+ if ($null -ne $result) { return $result }
652
+ }
653
+ return $null
654
+ }
655
+
656
+ # 将同步逻辑封装为自包含的脚本块(PS5.1 兼容性最佳方案)
657
+ $script:SyncDataGridLogic = {
658
+ param($UI, $State)
659
+ if ($null -eq $State.Metadata) {
660
+ Write-Host "[数据] 无法同步:元数据为空" -ForegroundColor Red
661
+ return
662
+ }
663
+
664
+ $versionType = if ($UI.StableRadio.IsChecked) { "stable" } else { "nightly" }
665
+ $sourceName = if ($UI.HFRadio.IsChecked) { "huggingface" } else { "modelscope" }
666
+
667
+ Write-Host "[数据] 正在切换视图: $sourceName -> $versionType" -ForegroundColor Magenta
668
+ $gridSource = @()
669
+ $sourceNode = $State.Metadata."$sourceName"
670
+ if ($null -ne $sourceNode) {
671
+ $vNode = $sourceNode."$versionType"
672
+ if ($null -ne $vNode) {
673
+ foreach ($comp in $vNode.PSObject.Properties) {
674
+ $versions = @()
675
+ if ($null -ne $comp.Value -and $comp.Value -is [System.Array]) {
676
+ foreach ($vEntry in $comp.Value) {
677
+ if ($vEntry.Count -ge 2) {
678
+ $versions += [PSCustomObject]@{ Name = $vEntry[0]; Url = $vEntry[1] }
679
+ }
680
+ }
681
+ }
682
+ if ($versions.Count -gt 0) {
683
+ $gridSource += [PSCustomObject]@{
684
+ Type = $comp.Name
685
+ Versions = $versions
686
+ SelectedVersion = $versions[0]
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ Write-Host "[数据] 已加载 $($gridSource.Count) 个资源条目" -ForegroundColor Gray
693
+ $UI.MainGrid.ItemsSource = $null
694
+ $UI.MainGrid.ItemsSource = $gridSource
695
+ }
696
+
697
+ function Invoke-Refresh {
698
+ param($UI, $State)
699
+
700
+ # 显式引用脚本块,确保它在当前函数的局部作用域内,以便闭包捕获
701
+ $SyncLogic = $script:SyncDataGridLogic
702
+
703
+ # 显示加载状态
704
+ $UI.LoadingOverlay.Visibility = "Visible"
705
+ $UI.MainGrid.Visibility = "Collapsed"
706
+ $UI.RefreshBtn.IsEnabled = $false
707
+ $UI.UpdateTimeText.Text = "状态: 正在同步云端数据..."
708
+ Write-Host "[UI] 用户触发同步请求,正在发起异步任务..." -ForegroundColor Cyan
709
+
710
+ # 确保全局 RunspacePool 存在
711
+ if ($null -eq $Global:DownloadRunspacePool) {
712
+ $Global:DownloadRunspacePool = [runspacefactory]::CreateRunspacePool(1, 2)
713
+ $Global:DownloadRunspacePool.Open()
714
+ }
715
+
716
+ $ps = [powershell]::Create().AddScript({
717
+ param([string[]]$Uris)
718
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
719
+ $lastError = ""
720
+ foreach ($Uri in $Uris) {
721
+ try {
722
+ $response = Invoke-WebRequest -UseBasicParsing -Uri $Uri -TimeoutSec 15 -ErrorAction Stop
723
+ $json = $response.Content | ConvertFrom-Json
724
+ if ($null -ne $json) { return $json }
725
+ } catch {
726
+ $lastError = $_.Exception.Message
727
+ }
728
+ }
729
+ return "ERROR: " + $lastError
730
+ }).AddArgument(@(
731
+ "https://licyk.github.io/resources/portable_list.json",
732
+ "https://github.com/licyk/resources/raw/gh-pages/portable_list.json",
733
+ "https://gitlab.com/licyk/resources/-/raw/gh-pages/portable_list.json",
734
+ "https://gitee.com/licyk/resources/raw/gh-pages/portable_list.json"
735
+ ))
736
+
737
+ $ps.RunspacePool = $Global:DownloadRunspacePool
738
+ $asyncResult = $ps.BeginInvoke()
739
+
740
+ $monitorTimer = New-Object System.Windows.Threading.DispatcherTimer
741
+ $monitorTimer.Interval = [TimeSpan]::FromMilliseconds(200)
742
+
743
+ $tickAction = {
744
+ if ($asyncResult.IsCompleted) {
745
+ $monitorTimer.Stop()
746
+ Write-Host "[UI] 云端数据已到达 [OK]" -ForegroundColor Green
747
+ try {
748
+ $result = $ps.EndInvoke($asyncResult)
749
+
750
+ if ($null -ne $result -and $null -ne $result.update_time) {
751
+ Write-Host "[数据] 解析成功,更新时间: $($result.update_time)" -ForegroundColor Gray
752
+ $State.Metadata = $result
753
+ $UI.UpdateTimeText.Text = "更新时间: $($result.update_time)"
754
+ # 调用捕获到的脚本块
755
+ & $SyncLogic -UI $UI -State $State
756
+ $UI.UpdateTimeText.Foreground = [System.Windows.Media.Brushes]::Gray
757
+ } elseif ($result -is [string] -and $result.StartsWith("ERROR:")) {
758
+ Write-Host "[数据] 同步异常: $result" -ForegroundColor Red
759
+ $UI.UpdateTimeText.Text = "同步失败: " + $result.Substring(6)
760
+ $UI.UpdateTimeText.Foreground = [System.Windows.Media.Brushes]::Red
761
+ } else {
762
+ Write-Host "[数据] 获取到的数据格式不匹配" -ForegroundColor Red
763
+ $UI.UpdateTimeText.Text = "同步失败: 获取到的数据格式不正确"
764
+ $UI.UpdateTimeText.Foreground = [System.Windows.Media.Brushes]::Red
765
+ }
766
+ } catch {
767
+ Write-Host "[数据] 内部解析错误: $($_.Exception.Message)" -ForegroundColor Red
768
+ $UI.UpdateTimeText.Text = "内部错误: $($_.Exception.Message)"
769
+ $UI.UpdateTimeText.Foreground = [System.Windows.Media.Brushes]::Red
770
+ } finally {
771
+ $ps.Dispose()
772
+ $UI.LoadingOverlay.Visibility = "Collapsed"
773
+ $UI.MainGrid.Visibility = "Visible"
774
+ $UI.RefreshBtn.IsEnabled = $true
775
+ }
776
+ }
777
+ }.GetNewClosure()
778
+
779
+ $monitorTimer.Add_Tick($tickAction)
780
+ $monitorTimer.Start()
781
+ }
782
+
783
+ function Invoke-DownloadAction {
784
+ param($UI, $State, $Button)
785
+ $savePath = $UI.PathInput.Text
786
+ if (-not (Test-Path $savePath -PathType Container)) {
787
+ Write-Host "[UI] 拦截下载请求:保存路径无效 [$savePath]" -ForegroundColor Red
788
+ Show-Async-MsgBox -Message "下载路径无效。" -Title "错误" -Icon "Error"
789
+ return
790
+ }
791
+
792
+ $rowData = $Button.DataContext
793
+ $selected = $rowData.SelectedVersion
794
+ Write-Host "[UI] 用户点击添加任务: $($selected.Name)" -ForegroundColor Cyan
795
+
796
+ $newTask = [PSCustomObject]@{
797
+ Name = $selected.Name
798
+ Url = $selected.Url
799
+ OutDir = $savePath
800
+ AutoExtract = $UI.AutoExtract.IsChecked
801
+ AutoDelete = $UI.AutoDelete.IsChecked
802
+ Status = "等待中"
803
+ Progress = "0%"
804
+ }
805
+
806
+ $State.TaskQueue += $newTask
807
+ & $script:UpdateQueueUI -UI $UI -State $State
808
+ Show-Async-MsgBox -Message "任务已加入队列:`n$($selected.Name)`n`n队列任务将按顺序自动执行。" -Title "任务已添加"
809
+ }
810
+
811
+ function Set-Proxy {
812
+ $Env:NO_PROXY = "localhost,127.0.0.1,::1"
813
+
814
+ $internet_setting = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
815
+ if ($internet_setting.ProxyEnable -ne 1) {
816
+ Write-Host "[环境] 未从系统中检测到代理,跳过设置代理" -ForegroundColor Blue
817
+ return
818
+ }
819
+ $proxy_addr = $($internet_setting.ProxyServer)
820
+ # 提取代理地址
821
+ if (($proxy_addr -match "http=(.*?);") -or ($proxy_addr -match "https=(.*?);")) {
822
+ $proxy_value = $matches[1]
823
+ # 去除 http / https 前缀
824
+ $proxy_value = $proxy_value.ToString().Replace("http://", "").Replace("https://", "")
825
+ $proxy_value = "http://${proxy_value}"
826
+ } elseif ($proxy_addr -match "socks=(.*)") {
827
+ $proxy_value = $matches[1]
828
+ # 去除 socks 前缀
829
+ $proxy_value = $proxy_value.ToString().Replace("http://", "").Replace("https://", "")
830
+ $proxy_value = "socks://${proxy_value}"
831
+ } else {
832
+ $proxy_value = "http://${proxy_addr}"
833
+ }
834
+ $Env:HTTP_PROXY = $proxy_value
835
+ $Env:HTTPS_PROXY = $proxy_value
836
+ Write-Host "[环境] 检测到系统设置了代理,已读取系统中的代理配置并设置代理,代理地址: $proxy_value" -ForegroundColor Green
837
+ }
838
+
839
+ function Start-App {
840
+ Write-Host "[APP] 初始化中..." -ForegroundColor Yellow
841
+ Set-Proxy
842
+ # 实时检测 Windows 深色模式
843
+ $isDarkMode = $false
844
+ try {
845
+ $reg = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" -ErrorAction SilentlyContinue
846
+ if ($null -ne $reg -and $reg.AppsUseLightTheme -eq 0) { $isDarkMode = $true }
847
+ } catch {}
848
+
849
+ # 主题颜色定义
850
+ $colors = if ($isDarkMode) {
851
+ Write-Host "[环境] 应用深色主题 [Dark Mode]" -ForegroundColor Gray
852
+ @{
853
+ WinBG1="#CC1E1E1E"; WinBG2="#CC121212"; PanelBG="#44000000"; TextMain="#FFFFFF"; TextSec="#AAAAAA"; Border="#44FFFFFF"; InputBG="#333333"; BtnNormal="#4A4A4A"; BtnHover="#5A5A5A"; ItemHover="#33FFFFFF"; HeaderBG="#11FFFFFF";
854
+ ScrollBG="#11FFFFFF"; ScrollThumb="#66FFFFFF"
855
+ }
856
+ } else {
857
+ Write-Host "[环境] ���用浅色主题 [Light Mode]" -ForegroundColor Gray
858
+ @{
859
+ WinBG1="#CCF9FAFB"; WinBG2="#CCF3F4F6"; PanelBG="#44FFFFFF"; TextMain="#323130"; TextSec="#666666"; Border="#88C1C1C1"; InputBG="#FFFFFF"; BtnNormal="#FFFFFF"; BtnHover="#F9F9F9"; ItemHover="#F2F7FF"; HeaderBG="#F9FAFB";
860
+ ScrollBG="#05000000"; ScrollThumb="#AAAAAA"
861
+ }
862
+ }
863
+
864
+ [xml]$xaml = @"
865
+ <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
866
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
867
+ Title="AI 整合包下载器" Height="700" Width="900"
868
+ MinHeight="500" MinWidth="700"
869
+ WindowStartupLocation="CenterScreen" WindowStyle="None" AllowsTransparency="True"
870
+ Background="Transparent" ResizeMode="CanResizeWithGrip">
871
+ <Window.Resources>
872
+ <!-- 基础色彩注入 -->
873
+ <SolidColorBrush x:Key="PrimaryBrush" Color="#0078D4"/>
874
+ <SolidColorBrush x:Key="TextMainBrush" Color="$($colors.TextMain)"/>
875
+ <SolidColorBrush x:Key="TextSecBrush" Color="$($colors.TextSec)"/>
876
+ <SolidColorBrush x:Key="BorderBrush" Color="$($colors.Border)"/>
877
+ <SolidColorBrush x:Key="InputBGBrush" Color="$($colors.InputBG)"/>
878
+ <SolidColorBrush x:Key="BtnNormalBrush" Color="$($colors.BtnNormal)"/>
879
+ <SolidColorBrush x:Key="PanelBGBrush" Color="$($colors.PanelBG)"/>
880
+
881
+ <!-- 现代滚动条样式 -->
882
+ <Style TargetType="ScrollBar">
883
+ <Setter Property="Background" Value="$($colors.ScrollBG)"/>
884
+ <Setter Property="Width" Value="8"/>
885
+ <Setter Property="Template">
886
+ <Setter.Value>
887
+ <ControlTemplate TargetType="ScrollBar">
888
+ <Grid Name="Bg" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
889
+ <Track Name="PART_Track" IsDirectionReversed="true" IsEnabled="{TemplateBinding IsEnabled}">
890
+ <Track.Thumb>
891
+ <Thumb>
892
+ <Thumb.Template>
893
+ <ControlTemplate TargetType="Thumb">
894
+ <Border Background="$($colors.ScrollThumb)" CornerRadius="4" Margin="1,0"/>
895
+ </ControlTemplate>
896
+ </Thumb.Template>
897
+ </Thumb>
898
+ </Track.Thumb>
899
+ </Track>
900
+ </Grid>
901
+ </ControlTemplate>
902
+ </Setter.Value>
903
+ </Setter>
904
+ </Style>
905
+
906
+ <!-- 按钮样式 -->
907
+ <Style TargetType="Button">
908
+ <Setter Property="Background" Value="{DynamicResource BtnNormalBrush}"/>
909
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
910
+ <Setter Property="BorderThickness" Value="1"/>
911
+ <Setter Property="BorderBrush" Value="{DynamicResource BorderBrush}"/>
912
+ <Setter Property="Padding" Value="12,6"/>
913
+ <Setter Property="FontSize" Value="13"/>
914
+ <Setter Property="Template">
915
+ <Setter.Value>
916
+ <ControlTemplate TargetType="Button">
917
+ <Border Name="border" Background="{TemplateBinding Background}"
918
+ BorderBrush="{TemplateBinding BorderBrush}"
919
+ BorderThickness="{TemplateBinding BorderThickness}"
920
+ CornerRadius="6" SnapsToDevicePixels="True">
921
+ <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}">
922
+ <ContentPresenter.Resources>
923
+ <Style TargetType="TextBlock">
924
+ <Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
925
+ </Style>
926
+ </ContentPresenter.Resources>
927
+ </ContentPresenter>
928
+ </Border>
929
+ <ControlTemplate.Triggers>
930
+ <Trigger Property="IsMouseOver" Value="True">
931
+ <Setter TargetName="border" Property="Background" Value="$($colors.BtnHover)"/>
932
+ </Trigger>
933
+ <Trigger Property="IsPressed" Value="True">
934
+ <Setter TargetName="border" Property="RenderTransform">
935
+ <Setter.Value><ScaleTransform ScaleX="0.98" ScaleY="0.98"/></Setter.Value>
936
+ </Setter>
937
+ </Trigger>
938
+ <Trigger Property="IsEnabled" Value="False">
939
+ <Setter Property="Opacity" Value="0.5"/>
940
+ </Trigger>
941
+ </ControlTemplate.Triggers>
942
+ </ControlTemplate>
943
+ </Setter.Value>
944
+ </Setter>
945
+ </Style>
946
+
947
+ <!-- 主按钮样式 (蓝色) -->
948
+ <Style x:Key="PrimaryButton" TargetType="Button">
949
+ <Setter Property="Background" Value="#0078D4"/>
950
+ <Setter Property="Foreground" Value="White"/>
951
+ <Setter Property="BorderThickness" Value="0"/>
952
+ <Setter Property="Padding" Value="12,6"/>
953
+ <Setter Property="FontSize" Value="13"/>
954
+ <Setter Property="Template">
955
+ <Setter.Value>
956
+ <ControlTemplate TargetType="Button">
957
+ <Border Name="border" Background="{TemplateBinding Background}" CornerRadius="6" SnapsToDevicePixels="True">
958
+ <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/>
959
+ </Border>
960
+ <ControlTemplate.Triggers>
961
+ <Trigger Property="IsMouseOver" Value="True">
962
+ <Setter TargetName="border" Property="Background" Value="#2B88D8"/>
963
+ </Trigger>
964
+ <Trigger Property="IsPressed" Value="True">
965
+ <Setter TargetName="border" Property="Background" Value="#005A9E"/>
966
+ <Setter TargetName="border" Property="RenderTransform">
967
+ <Setter.Value><ScaleTransform ScaleX="0.97" ScaleY="0.97"/></Setter.Value>
968
+ </Setter>
969
+ </Trigger>
970
+ <Trigger Property="IsEnabled" Value="False">
971
+ <Setter Property="Opacity" Value="0.5"/>
972
+ </Trigger>
973
+ </ControlTemplate.Triggers>
974
+ </ControlTemplate>
975
+ </Setter.Value>
976
+ </Setter>
977
+ </Style>
978
+
979
+ <!-- 输入框样式 -->
980
+ <Style TargetType="TextBox">
981
+ <Setter Property="Padding" Value="8,5"/>
982
+ <Setter Property="VerticalContentAlignment" Value="Center"/>
983
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
984
+ <Setter Property="Template">
985
+ <Setter.Value>
986
+ <ControlTemplate TargetType="TextBox">
987
+ <Border Name="border" Background="{DynamicResource InputBGBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="4">
988
+ <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
989
+ </Border>
990
+ <ControlTemplate.Triggers>
991
+ <Trigger Property="IsFocused" Value="True">
992
+ <Setter TargetName="border" Property="BorderBrush" Value="#0078D4"/>
993
+ <Setter TargetName="border" Property="BorderThickness" Value="2"/>
994
+ </Trigger>
995
+ </ControlTemplate.Triggers>
996
+ </ControlTemplate>
997
+ </Setter.Value>
998
+ </Setter>
999
+ </Style>
1000
+
1001
+ <!-- 下拉框样式 -->
1002
+ <Style TargetType="ComboBox">
1003
+ <Setter Property="Padding" Value="8,4"/>
1004
+ <Setter Property="MinHeight" Value="28"/>
1005
+ <Setter Property="VerticalContentAlignment" Value="Center"/>
1006
+ <Setter Property="Background" Value="{DynamicResource InputBGBrush}"/>
1007
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
1008
+ <Setter Property="Template">
1009
+ <Setter.Value>
1010
+ <ControlTemplate TargetType="ComboBox">
1011
+ <Grid>
1012
+ <ToggleButton Name="ToggleButton" Background="{TemplateBinding Background}"
1013
+ BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1"
1014
+ IsChecked="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
1015
+ Focusable="False" ClickMode="Press">
1016
+ <ToggleButton.Style>
1017
+ <Style TargetType="ToggleButton">
1018
+ <Setter Property="Template">
1019
+ <Setter.Value>
1020
+ <ControlTemplate TargetType="ToggleButton">
1021
+ <Border Name="border" Background="{TemplateBinding Background}"
1022
+ BorderBrush="{TemplateBinding BorderBrush}"
1023
+ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="6">
1024
+ <Path Name="Arrow" Fill="{DynamicResource TextSecBrush}" HorizontalAlignment="Right" VerticalAlignment="Center"
1025
+ Margin="0,0,8,0" Data="M 0 0 L 4 4 L 8 0 Z"/>
1026
+ </Border>
1027
+ <ControlTemplate.Triggers>
1028
+ <Trigger Property="IsMouseOver" Value="True">
1029
+ <Setter TargetName="border" Property="Background" Value="$($colors.BtnHover)"/>
1030
+ </Trigger>
1031
+ <Trigger Property="IsChecked" Value="True">
1032
+ <Setter TargetName="Arrow" Property="RenderTransform">
1033
+ <Setter.Value><RotateTransform Angle="180" CenterX="4" CenterY="2"/></Setter.Value>
1034
+ </Setter>
1035
+ </Trigger>
1036
+ </ControlTemplate.Triggers>
1037
+ </ControlTemplate>
1038
+ </Setter.Value>
1039
+ </Setter>
1040
+ </Style>
1041
+ </ToggleButton.Style>
1042
+ </ToggleButton>
1043
+ <ContentPresenter Name="ContentSite" IsHitTestVisible="False"
1044
+ Content="{TemplateBinding SelectionBoxItem}"
1045
+ ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
1046
+ ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
1047
+ Margin="{TemplateBinding Padding}" VerticalAlignment="Center"/>
1048
+ <Popup Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
1049
+ <Grid Name="DropDown" SnapsToDevicePixels="True" MinWidth="{TemplateBinding ActualWidth}" MaxHeight="{TemplateBinding MaxDropDownHeight}">
1050
+ <Border Name="DropDownBorder" Background="{DynamicResource InputBGBrush}" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" CornerRadius="6" Margin="0,2,0,5">
1051
+ <Border.Effect><DropShadowEffect BlurRadius="5" ShadowDepth="2" Opacity="0.15"/></Border.Effect>
1052
+ <ScrollViewer Margin="2" SnapsToDevicePixels="True">
1053
+ <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained"/>
1054
+ </ScrollViewer>
1055
+ </Border>
1056
+ </Grid>
1057
+ </Popup>
1058
+ </Grid>
1059
+ <ControlTemplate.Triggers>
1060
+ <Trigger Property="IsMouseOver" Value="True">
1061
+ <Setter Property="BorderBrush" Value="#A1A1A1"/>
1062
+ </Trigger>
1063
+ <Trigger Property="IsFocused" Value="True">
1064
+ <Setter Property="BorderBrush" Value="#0078D4"/>
1065
+ </Trigger>
1066
+ </ControlTemplate.Triggers>
1067
+ </ControlTemplate>
1068
+ </Setter.Value>
1069
+ </Setter>
1070
+ </Style>
1071
+
1072
+ <!-- 下拉列表项样式 -->
1073
+ <Style TargetType="ComboBoxItem">
1074
+ <Setter Property="Padding" Value="10,12"/>
1075
+ <Setter Property="MinHeight" Value="34"/>
1076
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
1077
+ <Setter Property="SnapsToDevicePixels" Value="True"/>
1078
+ <Setter Property="Template">
1079
+ <Setter.Value>
1080
+ <ControlTemplate TargetType="ComboBoxItem">
1081
+ <Border Name="Border" Background="Transparent" CornerRadius="4" Margin="2,1">
1082
+ <ContentPresenter VerticalAlignment="Center" Margin="{TemplateBinding Padding}"/>
1083
+ </Border>
1084
+ <ControlTemplate.Triggers>
1085
+ <Trigger Property="IsMouseOver" Value="True"><Setter TargetName="Border" Property="Background" Value="$($colors.ItemHover)"/></Trigger>
1086
+ <Trigger Property="IsSelected" Value="True"><Setter TargetName="Border" Property="Background" Value="#0078D4"/><Setter Property="TextElement.Foreground" Value="White"/></Trigger>
1087
+ </ControlTemplate.Triggers>
1088
+ </ControlTemplate>
1089
+ </Setter.Value>
1090
+ </Setter>
1091
+ </Style>
1092
+
1093
+ <!-- 单选按钮药丸样式 -->
1094
+ <Style TargetType="RadioButton">
1095
+ <Setter Property="FontSize" Value="12"/>
1096
+ <Setter Property="Height" Value="26"/>
1097
+ <Setter Property="Cursor" Value="Hand"/>
1098
+ <Setter Property="Foreground" Value="{DynamicResource TextSecBrush}"/>
1099
+ <Setter Property="Template">
1100
+ <Setter.Value>
1101
+ <ControlTemplate TargetType="RadioButton">
1102
+ <Border Name="border" Background="{DynamicResource BtnNormalBrush}" BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" CornerRadius="13" Padding="12,2" Margin="0,0,5,0">
1103
+ <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
1104
+ </Border>
1105
+ <ControlTemplate.Triggers>
1106
+ <Trigger Property="IsChecked" Value="True">
1107
+ <Setter TargetName="border" Property="Background" Value="#0078D4"/>
1108
+ <Setter TargetName="border" Property="BorderBrush" Value="#0078D4"/>
1109
+ <Setter Property="Foreground" Value="White"/>
1110
+ <Setter Property="FontWeight" Value="SemiBold"/>
1111
+ </Trigger>
1112
+ <MultiTrigger>
1113
+ <MultiTrigger.Conditions>
1114
+ <Condition Property="IsMouseOver" Value="True"/>
1115
+ <Condition Property="IsChecked" Value="False"/>
1116
+ </MultiTrigger.Conditions>
1117
+ <Setter TargetName="border" Property="Background" Value="$($colors.BtnHover)"/>
1118
+ </MultiTrigger>
1119
+ </ControlTemplate.Triggers>
1120
+ </ControlTemplate>
1121
+ </Setter.Value>
1122
+ </Setter>
1123
+ </Style>
1124
+
1125
+ <!-- 开关样式 (之前定义的) -->
1126
+ <Style x:Key="ToggleSwitchStyle" TargetType="CheckBox">
1127
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
1128
+ <Setter Property="Template">
1129
+ <Setter.Value>
1130
+ <ControlTemplate TargetType="CheckBox">
1131
+ <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
1132
+ <Grid Name="SwitchRoot" Width="42" Height="22" Cursor="Hand">
1133
+ <Border Name="Track" Background="{DynamicResource BorderBrush}" CornerRadius="11" BorderThickness="0"/>
1134
+ <Border Name="Thumb" Background="White" Width="18" Height="18" CornerRadius="9" HorizontalAlignment="Left" Margin="2,0,0,0">
1135
+ <Border.Effect><DropShadowEffect ShadowDepth="1" BlurRadius="3" Opacity="0.2"/></Border.Effect>
1136
+ </Border>
1137
+ </Grid>
1138
+ <ContentPresenter Name="Content" Margin="10,0,0,0" VerticalAlignment="Center"/>
1139
+ </StackPanel>
1140
+ <ControlTemplate.Triggers>
1141
+ <Trigger Property="IsChecked" Value="True">
1142
+ <Trigger.EnterActions><BeginStoryboard><Storyboard>
1143
+ <ColorAnimation Storyboard.TargetName="Track" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="#0078D4" Duration="0:0:0.2"/>
1144
+ <ThicknessAnimation Storyboard.TargetName="Thumb" Storyboard.TargetProperty="Margin" To="22,0,0,0" Duration="0:0:0.2"/>
1145
+ </Storyboard></BeginStoryboard></Trigger.EnterActions>
1146
+ <Trigger.ExitActions><BeginStoryboard><Storyboard>
1147
+ <ColorAnimation Storyboard.TargetName="Track" Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)" To="$($colors.Border)" Duration="0:0:0.2"/>
1148
+ <ThicknessAnimation Storyboard.TargetName="Thumb" Storyboard.TargetProperty="Margin" To="2,0,0,0" Duration="0:0:0.2"/>
1149
+ </Storyboard></BeginStoryboard></Trigger.ExitActions>
1150
+ </Trigger>
1151
+ </ControlTemplate.Triggers>
1152
+ </ControlTemplate>
1153
+ </Setter.Value>
1154
+ </Setter>
1155
+ </Style>
1156
+ </Window.Resources>
1157
+
1158
+ <!-- 主容器 -->
1159
+ <Border x:Name="MainBorder" CornerRadius="12" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" ClipToBounds="True">
1160
+ <Border.Background>
1161
+ <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
1162
+ <GradientStop Color="$($colors.WinBG1)" Offset="0.0"/>
1163
+ <GradientStop Color="$($colors.WinBG2)" Offset="1.0"/>
1164
+ </LinearGradientBrush>
1165
+ </Border.Background>
1166
+
1167
+ <Grid>
1168
+ <Grid.RowDefinitions><RowDefinition Height="32"/><RowDefinition Height="*"/></Grid.RowDefinitions>
1169
+ <Grid Name="TitleBar" Grid.Row="0" Background="Transparent">
1170
+ <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="12,0,0,0">
1171
+ <!-- 现代化的下载图标 (圆形+箭头) -->
1172
+ <Path Data="M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M12,17L7,12H10V8H14V12H17L12,17Z"
1173
+ Fill="{DynamicResource PrimaryBrush}" Width="18" Height="18" Stretch="Uniform" Margin="0,0,8,0"/>
1174
+ <TextBlock Text="AI 整合包下载器" FontSize="12" Foreground="{DynamicResource TextSecBrush}" IsHitTestVisible="False"/>
1175
+ </StackPanel>
1176
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
1177
+ <!-- 最小化 -->
1178
+ <Button Name="MinBtn" Content="—" Width="45" Height="32" Background="Transparent" BorderThickness="0" Foreground="{DynamicResource TextSecBrush}">
1179
+ <Button.Style><Style TargetType="Button"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Button"><Border Name="b" Background="{TemplateBinding Background}"><ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/></Border><ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter TargetName="b" Property="Background" Value="$($colors.BtnHover)"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></Button.Style>
1180
+ </Button>
1181
+ <!-- 最大化/还原 -->
1182
+ <Button Name="MaxBtn" Content="⬜" Width="45" Height="32" Background="Transparent" BorderThickness="0" Foreground="{DynamicResource TextSecBrush}">
1183
+ <Button.Style><Style TargetType="Button"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Button"><Border Name="b" Background="{TemplateBinding Background}"><ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,2,0,0"/></Border><ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter TargetName="b" Property="Background" Value="$($colors.BtnHover)"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></Button.Style>
1184
+ </Button>
1185
+ <!-- 关闭 -->
1186
+ <Button Name="CloseBtn" Content="✕" Width="45" Height="32" Background="Transparent" BorderThickness="0" Foreground="{DynamicResource TextSecBrush}">
1187
+ <Button.Style><Style TargetType="Button"><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="Button"><Border Name="b" Background="{TemplateBinding Background}"><ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/></Border><ControlTemplate.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter TargetName="b" Property="Background" Value="#E81123"/><Setter Property="Foreground" Value="White"/></Trigger></ControlTemplate.Triggers></ControlTemplate></Setter.Value></Setter></Style></Button.Style>
1188
+ </Button>
1189
+ </StackPanel>
1190
+ </Grid>
1191
+
1192
+ <Grid Grid.Row="1" Margin="20,5,20,20">
1193
+ <Grid.RowDefinitions>
1194
+ <RowDefinition Height="Auto"/>
1195
+ <RowDefinition Height="*"/>
1196
+ <RowDefinition Height="Auto"/>
1197
+ </Grid.RowDefinitions>
1198
+
1199
+ <!-- 顶部信息 -->
1200
+ <Grid Grid.Row="0" Margin="0,0,0,15">
1201
+ <Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
1202
+ <StackPanel Grid.Column="0">
1203
+ <TextBlock Name="UpdateTime" Text="状态: 正在初始化..." FontSize="11" Foreground="{DynamicResource TextSecBrush}"/>
1204
+ <StackPanel Orientation="Horizontal" Margin="0,12">
1205
+ <TextBlock Text="版本:" VerticalAlignment="Center" Foreground="{DynamicResource TextMainBrush}" Margin="0,0,10,0"/>
1206
+ <RadioButton Name="Stable" Content="Stable" GroupName="V" VerticalAlignment="Center"/>
1207
+ <RadioButton Name="Nightly" Content="Nightly" IsChecked="True" GroupName="V" Margin="12,0" VerticalAlignment="Center"/>
1208
+ <Border Width="1" Height="14" Background="{DynamicResource BorderBrush}" Margin="10,0,20,0"/>
1209
+ <TextBlock Text="下载源:" VerticalAlignment="Center" Foreground="{DynamicResource TextMainBrush}" Margin="0,0,10,0"/>
1210
+ <RadioButton Name="HF" Content="HuggingFace" GroupName="S" VerticalAlignment="Center"/>
1211
+ <RadioButton Name="MS" Content="ModelScope" IsChecked="True" GroupName="S" Margin="12,0" VerticalAlignment="Center"/>
1212
+ </StackPanel>
1213
+ </StackPanel>
1214
+ <StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Top">
1215
+ <Button Name="ProjBtn" Content="项目主页" Margin="0,0,8,0" Padding="10,4" Background="{DynamicResource BtnNormalBrush}"/>
1216
+ <Button Name="DocBtn" Content="使用说明" Margin="0,0,8,0" Padding="10,4" Background="{DynamicResource BtnNormalBrush}"/>
1217
+ <Button Name="LauncherBtn" Content="启动器下载" Margin="0,0,8,0" Padding="10,4" Background="{DynamicResource BtnNormalBrush}"/>
1218
+ <Button Name="IssueBtn" Content="问题反馈" Padding="10,4" Background="{DynamicResource BtnNormalBrush}"/>
1219
+ </StackPanel>
1220
+ </Grid>
1221
+
1222
+ <!-- 数据与队列容器 -->
1223
+ <Grid Name="DataQueueGrid" Grid.Row="1">
1224
+ <Grid.RowDefinitions>
1225
+ <RowDefinition Height="*"/>
1226
+ <RowDefinition Height="Auto"/>
1227
+ <RowDefinition Height="Auto"/>
1228
+ </Grid.RowDefinitions>
1229
+
1230
+ <!-- 数据表格 -->
1231
+ <Border Grid.Row="0" Background="{DynamicResource PanelBGBrush}" CornerRadius="8" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}">
1232
+ <Grid>
1233
+ <DataGrid Name="MainGrid" AutoGenerateColumns="False" IsReadOnly="True"
1234
+ HeadersVisibility="Column" GridLinesVisibility="None" Background="Transparent"
1235
+ BorderThickness="0" RowHeight="42" Visibility="Collapsed"
1236
+ SelectionMode="Single" SelectionUnit="FullRow">
1237
+ <DataGrid.Resources>
1238
+ <Style TargetType="DataGridCell">
1239
+ <Setter Property="Background" Value="Transparent"/>
1240
+ <Setter Property="BorderThickness" Value="0"/>
1241
+ <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
1242
+ <Setter Property="VerticalContentAlignment" Value="Center"/>
1243
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
1244
+ <Setter Property="Template">
1245
+ <Setter.Value>
1246
+ <ControlTemplate TargetType="DataGridCell">
1247
+ <Border Background="{TemplateBinding Background}" BorderThickness="0" SnapsToDevicePixels="True">
1248
+ <ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0"/>
1249
+ </Border>
1250
+ </ControlTemplate>
1251
+ </Setter.Value>
1252
+ </Setter>
1253
+ <Style.Triggers>
1254
+ <Trigger Property="IsSelected" Value="True"><Setter Property="Background" Value="Transparent"/><Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/></Trigger>
1255
+ </Style.Triggers>
1256
+ </Style>
1257
+ <Style TargetType="DataGridColumnHeader">
1258
+ <Setter Property="Background" Value="{DynamicResource PanelBGBrush}"/>
1259
+ <Setter Property="Foreground" Value="{DynamicResource TextSecBrush}"/>
1260
+ <Setter Property="Padding" Value="10,8"/>
1261
+ <Setter Property="FontSize" Value="12"/>
1262
+ <Setter Property="FontWeight" Value="SemiBold"/>
1263
+ <Setter Property="BorderThickness" Value="0,0,0,1"/>
1264
+ <Setter Property="BorderBrush" Value="{DynamicResource BorderBrush}"/>
1265
+ </Style>
1266
+ <Style TargetType="DataGridRow">
1267
+ <Setter Property="Background" Value="Transparent"/>
1268
+ <Setter Property="Focusable" Value="False"/>
1269
+ <Style.Triggers>
1270
+ <Trigger Property="IsMouseOver" Value="True"><Setter Property="Background" Value="$($colors.ItemHover)"/></Trigger>
1271
+ <Trigger Property="IsSelected" Value="True"><Setter Property="Background" Value="Transparent"/></Trigger>
1272
+ </Style.Triggers>
1273
+ </Style>
1274
+ </DataGrid.Resources>
1275
+ <DataGrid.Columns>
1276
+ <DataGridTextColumn Header="资源类型" Binding="{Binding Type}" Width="250"/>
1277
+ <DataGridTemplateColumn Header="版本选择" Width="*">
1278
+ <DataGridTemplateColumn.CellTemplate>
1279
+ <DataTemplate>
1280
+ <ComboBox Name="VersionSelector" ItemsSource="{Binding Versions}"
1281
+ DisplayMemberPath="Name" SelectedItem="{Binding SelectedVersion, UpdateSourceTrigger=PropertyChanged}"
1282
+ Margin="5,4" VerticalContentAlignment="Center"/>
1283
+ </DataTemplate>
1284
+ </DataGridTemplateColumn.CellTemplate>
1285
+ </DataGridTemplateColumn>
1286
+ <DataGridTemplateColumn Header="操作" Width="100">
1287
+ <DataGridTemplateColumn.CellTemplate>
1288
+ <DataTemplate>
1289
+ <Button Name="RowDL" Content="下载" Style="{StaticResource PrimaryButton}" Height="28" Padding="15,2"/>
1290
+ </DataTemplate>
1291
+ </DataGridTemplateColumn.CellTemplate>
1292
+ </DataGridTemplateColumn>
1293
+ </DataGrid.Columns>
1294
+ </DataGrid>
1295
+
1296
+ <!-- 加载遮罩 (放在此处确保只覆盖主列表) -->
1297
+ <StackPanel Name="LoadingOverlay" VerticalAlignment="Center" HorizontalAlignment="Center">
1298
+ <ProgressBar IsIndeterminate="True" Width="220" Height="4" Background="{DynamicResource BorderBrush}" Foreground="#0078D4" BorderThickness="0"/>
1299
+ <TextBlock Text="正在同步云端元数据..." Margin="0,15,0,0" HorizontalAlignment="Center" Foreground="{DynamicResource TextSecBrush}" FontSize="13"/>
1300
+ </StackPanel>
1301
+ </Grid>
1302
+ </Border>
1303
+
1304
+ <!-- 分割标题 -->
1305
+ <Grid Name="QueueHeader" Grid.Row="1" Margin="0,15,0,8" Opacity="1">
1306
+ <TextBlock Text="任务队列" Foreground="{DynamicResource TextSecBrush}" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center"/>
1307
+ <TextBlock Name="StatText" Text="队列统计: 总计 0 | 运行中 0 | 已完成 0" HorizontalAlignment="Right" FontSize="11" Foreground="{DynamicResource TextSecBrush}" VerticalAlignment="Center"/>
1308
+ </Grid>
1309
+
1310
+ <!-- 任务队列表格 -->
1311
+ <Border Name="QueueBorder" Grid.Row="2" Height="140" Background="{DynamicResource PanelBGBrush}" CornerRadius="8" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Opacity="1">
1312
+ <DataGrid Name="QueueGrid" AutoGenerateColumns="False" IsReadOnly="True"
1313
+ HeadersVisibility="Column" GridLinesVisibility="None" Background="Transparent"
1314
+ BorderThickness="0" RowHeight="36"
1315
+ SelectionMode="Single" SelectionUnit="FullRow">
1316
+ <DataGrid.Resources>
1317
+ <Style TargetType="DataGridCell">
1318
+ <Setter Property="Background" Value="Transparent"/>
1319
+ <Setter Property="BorderThickness" Value="0"/>
1320
+ <Setter Property="VerticalContentAlignment" Value="Center"/>
1321
+ <Setter Property="Foreground" Value="{DynamicResource TextMainBrush}"/>
1322
+ <Setter Property="Template">
1323
+ <Setter.Value>
1324
+ <ControlTemplate TargetType="DataGridCell">
1325
+ <Border Background="{TemplateBinding Background}" BorderThickness="0" SnapsToDevicePixels="True">
1326
+ <ContentPresenter VerticalAlignment="Center" Margin="10,0,0,0"/>
1327
+ </Border>
1328
+ </ControlTemplate>
1329
+ </Setter.Value>
1330
+ </Setter>
1331
+ </Style>
1332
+ <Style TargetType="DataGridColumnHeader">
1333
+ <Setter Property="Background" Value="{DynamicResource PanelBGBrush}"/>
1334
+ <Setter Property="Foreground" Value="{DynamicResource TextSecBrush}"/>
1335
+ <Setter Property="Padding" Value="10,6"/>
1336
+ <Setter Property="FontSize" Value="11"/>
1337
+ <Setter Property="FontWeight" Value="SemiBold"/>
1338
+ <Setter Property="BorderThickness" Value="0,0,0,1"/>
1339
+ <Setter Property="BorderBrush" Value="{DynamicResource BorderBrush}"/>
1340
+ </Style>
1341
+ <Style TargetType="DataGridRow">
1342
+ <Setter Property="Background" Value="Transparent"/>
1343
+ <Style.Triggers>
1344
+ <Trigger Property="IsMouseOver" Value="True"><Setter Property="Background" Value="$($colors.ItemHover)"/></Trigger>
1345
+ </Style.Triggers>
1346
+ </Style>
1347
+ </DataGrid.Resources>
1348
+ <DataGrid.Columns>
1349
+ <DataGridTextColumn Header="任务名称" Binding="{Binding Name}" Width="2*"/>
1350
+ <DataGridTextColumn Header="状态" Binding="{Binding Status}" Width="100">
1351
+ <DataGridTextColumn.ElementStyle>
1352
+ <Style TargetType="TextBlock">
1353
+ <Style.Triggers>
1354
+ <Trigger Property="Text" Value="下载中"><Setter Property="Foreground" Value="#0078D4"/><Setter Property="FontWeight" Value="Bold"/></Trigger>
1355
+ <Trigger Property="Text" Value="解压中"><Setter Property="Foreground" Value="#038387"/><Setter Property="FontWeight" Value="Bold"/></Trigger>
1356
+ <Trigger Property="Text" Value="已完成"><Setter Property="Foreground" Value="#107C10"/></Trigger>
1357
+ <Trigger Property="Text" Value="失败"><Setter Property="Foreground" Value="#E81123"/></Trigger>
1358
+ <Trigger Property="Text" Value="已取消"><Setter Property="Foreground" Value="Gray"/></Trigger>
1359
+ </Style.Triggers>
1360
+ </Style>
1361
+ </DataGridTextColumn.ElementStyle>
1362
+ </DataGridTextColumn>
1363
+ <DataGridTextColumn Header="进度" Binding="{Binding Progress}" Width="80"/>
1364
+ <DataGridTemplateColumn Header="操作" Width="80">
1365
+ <DataGridTemplateColumn.CellTemplate>
1366
+ <DataTemplate>
1367
+ <Button Name="KillTask" Content="终止" Height="22" Padding="8,0" FontSize="11"
1368
+ Background="#FFF1F0" Foreground="#E81123" BorderBrush="#FFCCC7">
1369
+ <Button.Style>
1370
+ <Style TargetType="Button">
1371
+ <Style.Triggers>
1372
+ <DataTrigger Binding="{Binding Status}" Value="已完成"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
1373
+ <DataTrigger Binding="{Binding Status}" Value="失败"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
1374
+ <DataTrigger Binding="{Binding Status}" Value="已取消"><Setter Property="IsEnabled" Value="False"/></DataTrigger>
1375
+ </Style.Triggers>
1376
+ </Style>
1377
+ </Button.Style>
1378
+ </Button>
1379
+ </DataTemplate>
1380
+ </DataGridTemplateColumn.CellTemplate>
1381
+ </DataGridTemplateColumn>
1382
+ </DataGrid.Columns>
1383
+ </DataGrid>
1384
+ </Border>
1385
+ </Grid>
1386
+
1387
+ <!-- 底部控制 -->
1388
+ <StackPanel Grid.Row="2" Margin="0,20,0,0">
1389
+ <Grid Margin="0,0,0,8">
1390
+ <Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
1391
+ <TextBox Name="PathInput" Grid.Column="0" Margin="0,0,10,0"/>
1392
+ <Button Name="BrowseBtn" Grid.Column="1" Content="更改保存路径" Background="{DynamicResource BtnNormalBrush}"/>
1393
+ </Grid>
1394
+
1395
+ <!-- 磁盘空间显示 -->
1396
+ <StackPanel Margin="0,0,0,8">
1397
+ <Grid Margin="2,0">
1398
+ <TextBlock Name="DiskLabel" Text="磁盘空间: 计算中..." FontSize="11" Foreground="{DynamicResource TextSecBrush}"/>
1399
+ <TextBlock Name="DiskPercent" Text="0%" HorizontalAlignment="Right" FontSize="11" Foreground="{DynamicResource TextSecBrush}"/>
1400
+ </Grid>
1401
+ <ProgressBar Name="DiskBar" Height="4" Margin="0,5,0,0" Background="{DynamicResource BorderBrush}" Foreground="#0078D4" BorderThickness="0"/>
1402
+ </StackPanel>
1403
+
1404
+ <!-- 底部队列统计 (隐藏队列时显示) -->
1405
+ <TextBlock Name="StatTextBottom" Text="队列统计: 总计 0 | 运行中 0 | 已完成 0" FontSize="11" Foreground="{DynamicResource TextSecBrush}" Margin="2,0,0,10" Visibility="Collapsed"/>
1406
+
1407
+ <DockPanel>
1408
+ <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
1409
+ <CheckBox Name="AutoExtract" Content="下载完成后自动解压到当前目录" IsChecked="True" Style="{StaticResource ToggleSwitchStyle}" Margin="0,0,20,0"/>
1410
+ <CheckBox Name="AutoDelete" Content="解压成功后删除压缩包" IsChecked="True" Style="{StaticResource ToggleSwitchStyle}" />
1411
+ </StackPanel>
1412
+ <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
1413
+ <Button Name="RefreshBtn" Content="刷新同步" Style="{StaticResource PrimaryButton}" Width="100" Height="34" Margin="0,0,10,0"/>
1414
+ <Button Name="ToggleQueueBtn" Content="隐藏/展开队列" Width="120" Height="34" Style="{StaticResource PrimaryButton}"/>
1415
+ </StackPanel>
1416
+ </DockPanel>
1417
+ </StackPanel>
1418
+ </Grid>
1419
+ </Grid>
1420
+ </Border>
1421
+ </Window>
1422
+ "@
1423
+ $reader = New-Object System.Xml.XmlNodeReader $xaml
1424
+ $window = [Windows.Markup.XamlReader]::Load($reader)
1425
+
1426
+ $UI = [PSCustomObject]@{
1427
+ Window = $window; MainGrid = $window.FindName("MainGrid"); QueueGrid = $window.FindName("QueueGrid"); UpdateTimeText = $window.FindName("UpdateTime"); PathInput = $window.FindName("PathInput")
1428
+ StableRadio = $window.FindName("Stable"); NightlyRadio = $window.FindName("Nightly"); HFRadio = $window.FindName("HF"); MSRadio = $window.FindName("MS")
1429
+ RefreshBtn = $window.FindName("RefreshBtn"); ToggleQueueBtn = $window.FindName("ToggleQueueBtn"); BrowseBtn = $window.FindName("BrowseBtn")
1430
+ AutoExtract = $window.FindName("AutoExtract"); AutoDelete = $window.FindName("AutoDelete")
1431
+ ProjBtn = $window.FindName("ProjBtn"); DocBtn = $window.FindName("DocBtn"); LauncherBtn = $window.FindName("LauncherBtn"); IssueBtn = $window.FindName("IssueBtn");
1432
+ LoadingOverlay = $window.FindName("LoadingOverlay")
1433
+ TitleBar = $window.FindName("TitleBar"); CloseBtn = $window.FindName("CloseBtn")
1434
+ MinBtn = $window.FindName("MinBtn"); MaxBtn = $window.FindName("MaxBtn")
1435
+ DiskLabel = $window.FindName("DiskLabel"); DiskBar = $window.FindName("DiskBar"); DiskPercent = $window.FindName("DiskPercent")
1436
+ QueueHeader = $window.FindName("QueueHeader"); QueueBorder = $window.FindName("QueueBorder"); StatText = $window.FindName("StatText"); StatTextBottom = $window.FindName("StatTextBottom")
1437
+ DataQueueGrid = $window.FindName("DataQueueGrid")
1438
+ MainBorder = $window.FindName("MainBorder")
1439
+ }
1440
+
1441
+ # 统计更新逻辑
1442
+ $script:UpdateStatistics = {
1443
+ param($UI, $State)
1444
+
1445
+ $queue = $State.TaskQueue
1446
+ if ($null -eq $queue) {
1447
+ $total = 0
1448
+ $running = 0
1449
+ $completed = 0
1450
+ } else {
1451
+ $total = $queue.Count
1452
+ $running = (@($queue | Where-Object { $_.Status -eq "下载中" -or $_.Status -eq "解压中" })).Count
1453
+ $completed = (@($queue | Where-Object { $_.Status -eq "已完成" })).Count
1454
+ }
1455
+
1456
+ $text = "队列统计: 总计 $total | 运行中 $running | 已完成 $completed"
1457
+ $UI.Window.Dispatcher.Invoke([Action]{
1458
+ $UI.StatText.Text = $text
1459
+ $UI.StatTextBottom.Text = $text
1460
+ })
1461
+ }
1462
+
1463
+ # 队列刷新逻辑
1464
+ $script:UpdateQueueUI = {
1465
+ param($UI, $State)
1466
+ $UI.Window.Dispatcher.Invoke([Action]{
1467
+ $UI.QueueGrid.ItemsSource = $null
1468
+ $UI.QueueGrid.ItemsSource = $State.TaskQueue
1469
+ })
1470
+ & $script:UpdateStatistics -UI $UI -State $State
1471
+ }
1472
+
1473
+ # 磁盘空间更新逻辑
1474
+ $script:UpdateDiskInfo = {
1475
+ param($UI)
1476
+ try {
1477
+ $path = $UI.PathInput.Text
1478
+ if (Test-Path $path) {
1479
+ # 路径合法:恢复正常颜色
1480
+ $UI.PathInput.BorderBrush = [System.Windows.Media.Brushes]::DodgerBlue
1481
+ $UI.PathInput.Background = [System.Windows.Media.Brushes]::White
1482
+
1483
+ $drive = Get-PSDrive -PSProvider FileSystem | Where-Object { $path.StartsWith($_.Root) } | Select-Object -First 1
1484
+ if ($null -ne $drive) {
1485
+ $total = $drive.Used + $drive.Free
1486
+ $usedPercent = ($drive.Used / $total) * 100
1487
+ $freeGB = [Math]::Round($drive.Free / 1GB, 2)
1488
+ $totalGB = [Math]::Round($total / 1GB, 2)
1489
+
1490
+ $UI.DiskLabel.Text = "可用空间: $freeGB GB / 总量: $totalGB GB"
1491
+ $UI.DiskBar.Value = $usedPercent
1492
+ $UI.DiskPercent.Text = "$([Math]::Round($usedPercent, 0))%"
1493
+
1494
+ if ($freeGB -lt 10) {
1495
+ $UI.DiskBar.Foreground = [System.Windows.Media.Brushes]::Red
1496
+ Write-Host "[状态] 磁盘空间严重不足: $freeGB GB" -ForegroundColor Red
1497
+ }
1498
+ else { $UI.DiskBar.Foreground = [System.Windows.Media.Brushes]::DodgerBlue }
1499
+ }
1500
+ } else {
1501
+ # 路径非法:变红提示
1502
+ Write-Host "[状态] 当前路径无效: $path" -ForegroundColor Red
1503
+ $UI.PathInput.BorderBrush = [System.Windows.Media.Red]
1504
+ $UI.PathInput.Background = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.Color]::FromArgb(30, 255, 0, 0)) # 浅红背景
1505
+ $UI.DiskLabel.Text = "磁盘空间: 路径无效"
1506
+ $UI.DiskBar.Value = 0
1507
+ $UI.DiskPercent.Text = "ERR"
1508
+ $UI.DiskBar.Foreground = [System.Windows.Media.Brushes]::Gray
1509
+ }
1510
+ } catch {}
1511
+ }
1512
+
1513
+ # 处理无边框窗口逻辑 (手动模拟最大化以解决毛玻璃遮挡任务栏)
1514
+ $script:RestoreBounds = $null
1515
+
1516
+ $UI.TitleBar.Add_MouseLeftButtonDown({
1517
+ if ($null -ne $script:RestoreBounds) {
1518
+ # 如果在最大化状态拖动,先还原
1519
+ $UI.MaxBtn.RaiseEvent((New-Object System.Windows.RoutedEventArgs([System.Windows.Controls.Button]::ClickEvent)))
1520
+ }
1521
+ $window.DragMove()
1522
+ })
1523
+ $UI.TitleBar.Add_MouseLeftButtonDown({ if ($_.ClickCount -eq 2) { $UI.MaxBtn.RaiseEvent((New-Object System.Windows.RoutedEventArgs([System.Windows.Controls.Button]::ClickEvent))) } })
1524
+
1525
+ $UI.MinBtn.Add_Click({ $window.WindowState = "Minimized" })
1526
+
1527
+ $UI.MaxBtn.Add_Click({
1528
+ $handle = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle
1529
+ if ($null -ne $script:RestoreBounds) {
1530
+ # 还原逻辑
1531
+ $window.Left = $script:RestoreBounds.Left
1532
+ $window.Top = $script:RestoreBounds.Top
1533
+ $window.Width = $script:RestoreBounds.Width
1534
+ $window.Height = $script:RestoreBounds.Height
1535
+ $script:RestoreBounds = $null
1536
+
1537
+ $UI.MaxBtn.Content = "⬜"
1538
+ if ($null -ne $UI.MainBorder) { $UI.MainBorder.CornerRadius = 12 }
1539
+ # 开启系统圆角裁切以解决毛玻璃穿透
1540
+ [BlurHelper]::SetRounding($handle, $true)
1541
+ } else {
1542
+ # 手动模拟最大化逻辑 (不使用 WindowState = Maximized)
1543
+ $script:RestoreBounds = [PSCustomObject]@{
1544
+ Left = $window.Left; Top = $window.Top; Width = $window.Width; Height = $window.Height
1545
+ }
1546
+
1547
+ # 获取当前屏幕工作区 (Win32 Pixels)
1548
+ $screen = [System.Windows.Forms.Screen]::FromHandle($handle)
1549
+ $workingArea = $screen.WorkingArea
1550
+
1551
+ # 考虑 DPI 缩放转换
1552
+ $source = [System.Windows.PresentationSource]::FromVisual($window)
1553
+ if ($null -ne $source) {
1554
+ $matrix = $source.CompositionTarget.TransformToDevice
1555
+ $window.Left = $workingArea.X / $matrix.M11
1556
+ $window.Top = $workingArea.Y / $matrix.M22
1557
+ $window.Width = $workingArea.Width / $matrix.M11
1558
+ $window.Height = $workingArea.Height / $matrix.M22
1559
+ }
1560
+
1561
+ $UI.MaxBtn.Content = "❐"
1562
+ if ($null -ne $UI.MainBorder) { $UI.MainBorder.CornerRadius = 0 }
1563
+ # 关闭系统圆角裁切
1564
+ [BlurHelper]::SetRounding($handle, $false)
1565
+ }
1566
+ })
1567
+ $UI.CloseBtn.Add_Click({ $window.Close() })
1568
+
1569
+ $State = [PSCustomObject]@{ Metadata = $null; TaskQueue = @(); CurrentTask = $null; CurrentExtractionTask = $null; IsCancelled = $false }
1570
+
1571
+ $UI.RefreshBtn.Add_Click({ Invoke-Refresh -UI $UI -State $State })
1572
+ $onStateChanged = { & $script:SyncDataGridLogic -UI $UI -State $State }
1573
+ $UI.StableRadio.Add_Checked($onStateChanged); $UI.NightlyRadio.Add_Checked($onStateChanged); $UI.HFRadio.Add_Checked($onStateChanged); $UI.MSRadio.Add_Checked($onStateChanged)
1574
+
1575
+ # 实时验证路径
1576
+ $UI.PathInput.Add_TextChanged({ & $script:UpdateDiskInfo -UI $UI })
1577
+
1578
+ $UI.BrowseBtn.Add_Click({
1579
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog;
1580
+ if ($dialog.ShowDialog() -eq "OK") {
1581
+ $UI.PathInput.Text = $dialog.SelectedPath
1582
+ }
1583
+ })
1584
+ $UI.MainGrid.AddHandler([System.Windows.Controls.Button]::ClickEvent, [System.Windows.RoutedEventHandler]{ param($s, $e) if ($e.OriginalSource.Name -eq "RowDL") { Invoke-DownloadAction -UI $UI -State $State -Button $e.OriginalSource } })
1585
+
1586
+ $UI.ProjBtn.Add_Click({ Open-Url -Url "https://github.com/licyk/sd-webui-all-in-one" })
1587
+ $UI.DocBtn.Add_Click({ Open-Url -Url "https://licyk.github.io/sd-webui-all-in-one/portable/portable" })
1588
+ $UI.LauncherBtn.Add_Click({ Open-Url -Url "https://licyk.github.io/sd-webui-all-in-one/tools/launcher-gui" })
1589
+ $UI.IssueBtn.Add_Click({ Open-Url -Url "https://github.com/licyk/sd-webui-all-in-one/issues" })
1590
+
1591
+ # 队列显隐切换 (增加过渡动画)
1592
+ $UI.ToggleQueueBtn.Add_Click({
1593
+ $duration = [TimeSpan]::FromMilliseconds(300)
1594
+ $targetHeight = 140 # 设定的展开高度
1595
+
1596
+ if ($UI.QueueBorder.Visibility -eq "Visible" -and $UI.QueueBorder.Height -gt 0) {
1597
+ # 收起动画
1598
+ $animHeight = New-Object System.Windows.Media.Animation.DoubleAnimation(0, $duration)
1599
+ $animOpacity = New-Object System.Windows.Media.Animation.DoubleAnimation(0, $duration)
1600
+
1601
+ $animHeight.add_Completed({
1602
+ $UI.QueueBorder.Visibility = "Collapsed"
1603
+ $UI.QueueHeader.Visibility = "Collapsed"
1604
+ $UI.StatTextBottom.Visibility = "Visible"
1605
+ })
1606
+
1607
+ $UI.QueueBorder.BeginAnimation([System.Windows.Controls.Border]::HeightProperty, $animHeight)
1608
+ $UI.QueueBorder.BeginAnimation([System.Windows.Controls.Border]::OpacityProperty, $animOpacity)
1609
+ $UI.QueueHeader.BeginAnimation([System.Windows.Controls.Grid]::OpacityProperty, $animOpacity)
1610
+ } else {
1611
+ # 展开动画
1612
+ $UI.QueueBorder.Visibility = "Visible"
1613
+ $UI.QueueHeader.Visibility = "Visible"
1614
+ $UI.StatTextBottom.Visibility = "Collapsed"
1615
+
1616
+ $animHeight = New-Object System.Windows.Media.Animation.DoubleAnimation($targetHeight, $duration)
1617
+ $animOpacity = New-Object System.Windows.Media.Animation.DoubleAnimation(1, $duration)
1618
+
1619
+ $UI.QueueBorder.BeginAnimation([System.Windows.Controls.Border]::HeightProperty, $animHeight)
1620
+ $UI.QueueBorder.BeginAnimation([System.Windows.Controls.Border]::OpacityProperty, $animOpacity)
1621
+ $UI.QueueHeader.BeginAnimation([System.Windows.Controls.Grid]::OpacityProperty, $animOpacity)
1622
+ }
1623
+ })
1624
+
1625
+ # 个体任务终止逻辑
1626
+ $UI.QueueGrid.AddHandler([System.Windows.Controls.Button]::ClickEvent, [System.Windows.RoutedEventHandler]{
1627
+ param($s, $e)
1628
+ if ($e.OriginalSource.Name -eq "KillTask") {
1629
+ $task = $e.OriginalSource.DataContext
1630
+ Write-Host "[UI] 用户请求终止任务: $($task.Name)" -ForegroundColor Yellow
1631
+
1632
+ # 如果是当前运行的下载任务
1633
+ if ($null -ne $State.CurrentTask -and $State.CurrentTask.QueueTask -eq $task) {
1634
+ $task.Status = "已取消"
1635
+ try { Stop-Process -Id $State.CurrentTask.Process.Id -Force -ErrorAction SilentlyContinue } catch {}
1636
+ }
1637
+ # 如果是当前运行的解压任务
1638
+ elseif ($null -ne $State.CurrentExtractionTask -and $State.CurrentExtractionTask.QueueTask -eq $task) {
1639
+ $task.Status = "已取消"
1640
+ try { Stop-Process -Id $State.CurrentExtractionTask.Process.Id -Force -ErrorAction SilentlyContinue } catch {}
1641
+ }
1642
+ # 等待中任务
1643
+ else {
1644
+ $task.Status = "已取消"
1645
+ }
1646
+ & $script:UpdateQueueUI -UI $UI -State $State
1647
+ }
1648
+ })
1649
+
1650
+ # 定时器:主调度器与磁盘空间刷新
1651
+ $script:tickCounter = 0
1652
+ $timer = New-Object System.Windows.Threading.DispatcherTimer
1653
+ $timer.Interval = [TimeSpan]::FromMilliseconds(100)
1654
+ $timer.Add_Tick({
1655
+ # 1. 磁盘空间刷新 (1s)
1656
+ $script:tickCounter++
1657
+ if ($script:tickCounter -ge 10) {
1658
+ $script:tickCounter = 0
1659
+ & $script:UpdateDiskInfo -UI $UI
1660
+ }
1661
+
1662
+ # 2. 任务调度逻辑
1663
+ $isAnyBusy = ($null -ne $State.CurrentTask -or $null -ne $State.CurrentExtractionTask)
1664
+ if (-not $isAnyBusy) {
1665
+ # ��找队列中第一个等待中的任务
1666
+ $nextTask = $State.TaskQueue | Where-Object { $_.Status -eq "等待中" } | Select-Object -First 1
1667
+ if ($null -ne $nextTask) {
1668
+ $nextTask.Status = "下载中"
1669
+ & $script:UpdateQueueUI -UI $UI -State $State
1670
+ Invoke-DownloadTask -Url $nextTask.Url -OutDir $nextTask.OutDir -SaveName $nextTask.Name -State $State -QueueTask $nextTask | Out-Null
1671
+ }
1672
+ }
1673
+
1674
+ # 3. 下载进程监控
1675
+ if ($null -ne $State.CurrentTask) {
1676
+ $task = $State.CurrentTask
1677
+ if ($task.Process.HasExited) {
1678
+ $exitCode = $task.Process.ExitCode
1679
+ Write-Host "[任务] 下载进程退出 [代码: $exitCode]" -ForegroundColor Blue
1680
+
1681
+ if ($task.QueueTask.Status -ne "已取消") {
1682
+ if ($exitCode -eq 0) {
1683
+ if ($task.QueueTask.AutoExtract) {
1684
+ $task.QueueTask.Progress = "50%"
1685
+ $task.QueueTask.Status = "解压中"
1686
+ $fullPath = Join-Path $task.OutDir $task.SaveName
1687
+ Write-Host "[后处理] 下载完成,开始解压: $fullPath" -ForegroundColor Cyan
1688
+ Show-Async-MsgBox -Message "下载已完成,正在后台启动解压...`n`n源文件:`n$fullPath`n`n解压目录:`n$($task.OutDir)" -Title "下载完成"
1689
+ Start-ExtractionTask -FilePath $fullPath -ExtractDir $task.OutDir -State $State -QueueTask $task.QueueTask | Out-Null
1690
+ } else {
1691
+ $task.QueueTask.Progress = "100%"
1692
+ $task.QueueTask.Status = "已完成"
1693
+ Show-Async-MsgBox -Message "任务已全部完成!`n`n保存文件:`n$($task.SaveName)`n`n保存路径:`n$($task.OutDir)" -Title "任务完成"
1694
+ }
1695
+ } else {
1696
+ $task.QueueTask.Status = "失败"
1697
+ Show-Async-MsgBox -Message "任务下载失败,请检查控制台日志。`n退出码: ${exitCode}`n`n报告问题与寻求帮助请前往: `nhttps://github.com/licyk/sd-webui-all-in-one/issues" -Title "下载失败" -Icon "Error"
1698
+ }
1699
+ }
1700
+ $State.CurrentTask = $null
1701
+ & $script:UpdateQueueUI -UI $UI -State $State
1702
+ }
1703
+ }
1704
+
1705
+ # 4. 解压进程监控
1706
+ if ($null -ne $State.CurrentExtractionTask) {
1707
+ $task = $State.CurrentExtractionTask
1708
+ if ($task.Process.HasExited) {
1709
+ $exitCode = $task.Process.ExitCode
1710
+ Write-Host "[后处理] 解压进程退出 [代码: $exitCode]" -ForegroundColor Blue
1711
+
1712
+ if ($task.QueueTask.Status -ne "已取消") {
1713
+ if ($exitCode -eq 0) {
1714
+ $task.QueueTask.Status = "已完成"
1715
+ $task.QueueTask.Progress = "100%"
1716
+ if ($task.QueueTask.AutoDelete) {
1717
+ try {
1718
+ Remove-Item $task.FilePath -Force
1719
+ Write-Host "[后处理] 已删除压缩包: $($task.FilePath)" -ForegroundColor Gray
1720
+ } catch {
1721
+ Write-Host "[后处理] 删除压缩包失败: $($_.Exception.Message)" -ForegroundColor Red
1722
+ }
1723
+ }
1724
+ Show-Async-MsgBox -Message "解压已成功完成!`n`n源文件:`n$($task.FilePath)`n`n解压至:`n$($task.ExtractDir)" -Title "解压成功"
1725
+ } else {
1726
+ $task.QueueTask.Status = "失败"
1727
+ Show-Async-MsgBox -Message "解压过程中出现错误,请检查控制台日志。`n退出码: ${exitCode}`n`n报告问题与寻求帮助请前往: `nhttps://github.com/licyk/sd-webui-all-in-one/issues" -Title "解压失败" -Icon "Error"
1728
+ }
1729
+ }
1730
+ $State.CurrentExtractionTask = $null
1731
+ & $script:UpdateQueueUI -UI $UI -State $State
1732
+ }
1733
+ }
1734
+ })
1735
+ $timer.Start()
1736
+
1737
+ $UI.PathInput.Text = if ($Env:BAT_SCRIPT_ROOT) {
1738
+ $Env:BAT_SCRIPT_ROOT
1739
+ } elseif ($script:ScriptRootPath) {
1740
+ $script:ScriptRootPath
1741
+ } elseif ($env:SCRIPT_ROOT_PATH) {
1742
+ $env:SCRIPT_ROOT_PATH
1743
+ } else {
1744
+ (Get-Location).Path
1745
+ }
1746
+ $window.Add_Loaded({
1747
+ # 获取窗口句柄
1748
+ $handle = (New-Object System.Windows.Interop.WindowInteropHelper($window)).Handle
1749
+
1750
+ # 开启毛玻璃效果
1751
+ [BlurHelper]::EnableBlur($handle)
1752
+
1753
+ # 应用系统圆角偏好 (解决毛玻璃穿透圆角问题)
1754
+ [BlurHelper]::SetRounding($handle, $true)
1755
+
1756
+ # 设置 DWM 沉浸式深色模式
1757
+ [BlurHelper]::SetDarkMode($handle, $isDarkMode)
1758
+
1759
+ & $script:UpdateDiskInfo -UI $UI
1760
+ Invoke-Refresh -UI $UI -State $State
1761
+ Invoke-Update-Async -IsDarkMode $isDarkMode
1762
+ })
1763
+ $window.ShowDialog() | Out-Null
1764
+ $timer.Stop()
1765
+ Write-Host "[APP] 退出应用" -ForegroundColor Yellow
1766
+ }
1767
+
1768
+ Start-App
1769
+
1770
+ :__PowerShellCode_pxwOLOkD__:
1771
+
1772
+
1773
+ :ExitCode
1774
+ exit /b %_psh_exit_code_%