From deba852624b76b68344d8b7e4a391243c50bb29d Mon Sep 17 00:00:00 2001 From: Reasonix Date: Fri, 14 Aug 2026 15:30:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=AF=E5=8A=A8=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E3=80=81=E5=BC=80=E6=9C=BA=E8=87=AA=E5=90=AF?= =?UTF-8?q?=E9=80=89=E9=A1=B9=E3=80=81=E5=BA=94=E7=94=A8=E5=86=85=E5=8D=87?= =?UTF-8?q?=E7=BA=A7=E5=BC=95=E5=AF=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 启动等待时显示进度条+百分比+后端最新输出(等待上限 240s) - 托盘菜单新增"开机自启服务"勾选项(注册表 Run + vbs 隐藏启动器) - 启动后后台检查 DSH 最新版本,有新版弹窗引导升级(npm 后台完成,自动重启服务) - StartDshService 禁用 DSH_TELEMETRY_DISABLED 遥测(可能小幅加速) - 新增 start-dsh-service.vbs,更新 README --- README.md | 19 ++- app/dsh-app.exe | Bin 53248 -> 59904 bytes app/src/dsh-app.cs | 245 ++++++++++++++++++++++++++++++++++++-- app/start-dsh-service.vbs | 4 + 4 files changed, 250 insertions(+), 18 deletions(-) create mode 100644 app/start-dsh-service.vbs diff --git a/README.md b/README.md index e64e52e..2c3ec0e 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,14 @@ - **独立窗口**:WebView2 渲染 `http://127.0.0.1:3080`,无浏览器地址栏/标签栏 - **自动拉起后端**:打开应用时若 dsh 服务未运行,自动静默启动(node 进程,无 CMD 窗口) +- **启动进度提示**:等待后端就绪时显示进度条 + 百分比 + 后端最新输出(首次冷启动较慢) - **系统托盘**: - 点窗口 `X` → 直接最小化到托盘(后端保持运行) - 托盘双击 / 右键「打开」→ 恢复窗口 - - 托盘右键「退出」→ 完全退出(**同时关闭后端服务**) + - 右键「**开机自启服务**」→ 勾选后开机自动后台启动后端(打开应用秒开) + - 右键「退出」→ 完全退出(**同时关闭后端服务**) +- **应用内升级引导**:启动时后台检查 DSH 最新版本,有新版本弹窗引导升级 + (npm 后台完成,无需打开浏览器/终端;升级后自动重启服务) - **DPI 感知**:PerMonitorV2,高分屏(250% 缩放)下界面清晰不模糊 - **DeepSeek 官方图标**:窗口、任务栏、快捷方式均为官方鲸鱼 logo @@ -24,6 +28,7 @@ DeepSeekHarness\ ├── .gitignore 排除运行时数据 └── app\ ├── dsh-app.exe 主程序(C# / WinForms / WebView2) + ├── start-dsh-service.vbs 开机自启用的隐藏启动器(wscript 调用) ├── Microsoft.Web.WebView2.Core.dll WebView2 托管组件 1.0.3719.77 ├── Microsoft.Web.WebView2.WinForms.dll 同上 ├── WebView2Loader.dll 原生加载器(x64) @@ -31,6 +36,7 @@ DeepSeekHarness\ │ ├── dsh-app.cs 全部源码 │ └── app.manifest DPI manifest(PerMonitorV2) ├── web.log dsh 服务日志(运行时生成) + ├── upgrade.log 升级日志(运行时生成) └── dsh-app.exe.WebView2\ WebView2 用户数据(运行时生成) ``` @@ -46,16 +52,16 @@ DeepSeekHarness\ ## 使用 1. 双击桌面(或 `C:\Users\VanuSystems-Mu\DeskBox\AI\`)的 **DeepSeek Harness** 快捷方式 -2. 首次打开等待几秒(后端自动启动),之后秒开 -3. 用完点 `X` 即最小化到托盘;想彻底退出用托盘右键 → **退出** +2. 首次打开需等待后端冷启动(**2~3 分钟**,窗口有进度提示);之后秒开 +3. 用完点 `X` 即最小化到托盘;彻底退出用托盘右键 → **退出** -> 后端服务日志:`app\web.log`;启动失败排查:`app\start-error.log`(如存在)。 +> **关于启动速度**:慢的根源是 dsh 自身(启动需加载 194 个插件包)。 +> 推荐在托盘菜单勾选「**开机自启服务**」——开机后台预热,之后任何时间打开应用都是秒开。 ## 从源码构建 ```sh cd app\src -# 需要 dsh.ico 与 app.manifest 位于 src 上级(或按需调整路径) csc /target:winexe /nologo \ /win32manifest:app.manifest \ /win32icon:..\..\dsh.ico \ @@ -74,7 +80,8 @@ csc 位于 `C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe`。 |---|---| | 界面模糊 | 确认 exe 已带 manifest(PerMonitorV2);重新构建 | | 弹 CMD 窗口 | 旧版本用 `cmd /c` 启动后端所致;当前版本直接启动 `node.exe`,无窗口 | -| 打开后一直"正在启动服务" | 查看 `web.log`;确认 `dsh` 已全局安装、端口 3080 未被占用 | +| 打开后一直"正在启动服务" | dsh 冷启动需 2~3 分钟,进度条会显示;若超 4 分钟查看 `web.log` | +| 升级失败 | 查看 `app\upgrade.log`;确认 npm 镜像源可用 | | 托盘图标消失 | 进程异常退出;重新双击快捷方式即可(会自动拉起后端) | ## 许可 diff --git a/app/dsh-app.exe b/app/dsh-app.exe index 5d28e7866219dde3b77d6d9305355d96c77a24e2..f611ab8b92bb700827168072c741db143f9ef0b0 100644 GIT binary patch delta 12124 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 ShowMainWindow()); menu.Items.Add(new ToolStripSeparator()); + var autostart = new ToolStripMenuItem("开机自启服务") + { + CheckOnClick = true, + Checked = IsAutostartEnabled(), + }; + autostart.Click += (s, e) => SetAutostart(autostart.Checked); + menu.Items.Add(autostart); + menu.Items.Add(new ToolStripSeparator()); menu.Items.Add("退出", null, (s, e) => ExitApp()); trayIcon.ContextMenuStrip = menu; @@ -68,6 +88,16 @@ static class Program mainForm.Hide(); }; + // 启动进度界面:阶段文字 + 进度条 + var panel = new TableLayoutPanel + { + Dock = DockStyle.Fill, + RowCount = 2, + ColumnCount = 1, + }; + panel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); + panel.RowStyles.Add(new RowStyle(SizeType.Absolute, 36)); + var status = new Label { Dock = DockStyle.Fill, @@ -75,28 +105,51 @@ static class Program Font = new Font("Microsoft YaHei UI", 14), Text = "正在启动 DeepSeek Harness 服务…", }; - mainForm.Controls.Add(status); + var bar = new ProgressBar + { + Dock = DockStyle.Fill, + Minimum = 0, + Maximum = WaitSteps, + Value = 0, + Style = ProgressBarStyle.Continuous, + Margin = new Padding(120, 4, 120, 16), + }; + panel.Controls.Add(status, 0, 0); + panel.Controls.Add(bar, 0, 1); + mainForm.Controls.Add(panel); var web = new WebView2 { Dock = DockStyle.Fill }; mainForm.Shown += async (s, e) => { - // 等待服务就绪(最多 ~30 秒) + // 等待服务就绪(最多 ~240 秒,首次冷启动可能较慢),期间显示进度 bool ready = false; - for (int i = 0; i < 60; i++) + for (int i = 0; i < WaitSteps; i++) { await Task.Delay(500); + int pct = Math.Min(99, i * 100 / WaitSteps); + string line; + lock (outputLock) { line = lastOutput; } + bar.Value = Math.Min(bar.Maximum, i); + status.Text = "正在启动 DeepSeek Harness 服务… " + pct + "%\n" + + (string.IsNullOrEmpty(line) ? "正在初始化后端…" : line); if (IsPortOpen(Port)) { ready = true; break; } } if (!ready) { + bar.Value = bar.Maximum; status.Text = "无法连接 DeepSeek Harness 服务(端口 " + Port + ")。\n" + - "请查看日志:" + Path.Combine(AppDir(), "web.log"); + "请查看日志:" + Path.Combine(AppDir(), "web.log") + "\n" + + "或稍后从托盘重新打开窗口重试。"; return; } + bar.Value = bar.Maximum; + status.Text = "服务就绪,正在加载界面…"; + await Task.Delay(300); + mainForm.SuspendLayout(); - mainForm.Controls.Remove(status); + mainForm.Controls.Remove(panel); mainForm.Controls.Add(web); mainForm.ResumeLayout(); @@ -109,6 +162,7 @@ static class Program cp.UserDataFolder = userData; web.CreationProperties = cp; await web.EnsureCoreWebView2Async(); + webView = web; web.Source = new Uri(WebUrl); } catch (Exception ex) @@ -118,6 +172,9 @@ static class Program } }; + // 后台检查 DSH 版本更新(有新版才提示,不阻塞启动) + CheckUpdateAsync(); + Application.Run(mainForm); } @@ -146,13 +203,12 @@ static class Program string logFile = Path.Combine(appDir, "web.log"); string npmDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); - string nodeExe = @"C:\Program Files\nodejs\node.exe"; string binJs = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); try { - if (!File.Exists(nodeExe) || !File.Exists(binJs)) return; + if (!File.Exists(NodeExe) || !File.Exists(binJs)) return; - var psi = new ProcessStartInfo(nodeExe) + var psi = new ProcessStartInfo(NodeExe) { UseShellExecute = false, CreateNoWindow = true, @@ -170,17 +226,28 @@ static class Program path = npmDir + ";" + path; psi.EnvironmentVariables["PATH"] = path; + // 禁用 dsh 启动遥测(可能小幅加速冷启动) + psi.EnvironmentVariables["DSH_TELEMETRY_DISABLED"] = "1"; + serviceProcess = Process.Start(psi); startedByUs = true; - // 输出写日志文件(防管道缓冲死锁) + // 输出写日志文件并记录最新行(供进度提示展示) serviceProcess.OutputDataReceived += (s, e) => { - if (e.Data != null) { try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } } + if (e.Data != null) + { + lock (outputLock) { lastOutput = e.Data; } + try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } + } }; serviceProcess.ErrorDataReceived += (s, e) => { - if (e.Data != null) { try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } } + if (e.Data != null) + { + lock (outputLock) { lastOutput = e.Data; } + try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } + } }; serviceProcess.BeginOutputReadLine(); serviceProcess.BeginErrorReadLine(); @@ -219,4 +286,158 @@ static class Program } Application.Exit(); } + + // ---- 开机自启 ---- + + static bool IsAutostartEnabled() + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(RunKeyPath)) + { + return key != null && key.GetValue(RunValueName) != null; + } + } + catch { return false; } + } + + static void SetAutostart(bool enable) + { + try + { + using (var key = Registry.CurrentUser.CreateSubKey(RunKeyPath)) + { + if (enable) + { + string vbs = Path.Combine(AppDir(), "start-dsh-service.vbs"); + key.SetValue(RunValueName, "wscript.exe \"" + vbs + "\""); + } + else + { + key.DeleteValue(RunValueName, false); + } + } + } + catch { } + } + + // ---- DSH 版本检查与升级引导(应用内完成,无需浏览器/终端)---- + + static void CheckUpdateAsync() + { + Task.Run(() => + { + try + { + Thread.Sleep(4000); // 等窗口稳定后再检查 + string local = GetLocalVersion(); + string latest = GetLatestVersion(); + if (string.IsNullOrEmpty(latest) || latest == local) return; + string msg = "发现 DSH 新版本 " + latest + "(当前 " + local + ")。\n" + + "是否现在升级?升级在后台完成,无需打开浏览器或终端。"; + mainForm.BeginInvoke((Action)(() => + { + var r = MessageBox.Show(mainForm, msg, "DeepSeek Harness 更新", + MessageBoxButtons.YesNo, MessageBoxIcon.Information); + if (r != DialogResult.Yes) return; + bool ok = RunNpmUpgrade(); + if (ok) + { + // 重启服务以加载新版本 + try { if (serviceProcess != null && !serviceProcess.HasExited) serviceProcess.Kill(); } catch { } + Thread.Sleep(1500); + startedByUs = false; + StartDshService(); + MessageBox.Show(mainForm, + "升级完成(" + local + " → " + latest + "),服务已重启。\n界面将自动刷新。", + "DeepSeek Harness", MessageBoxButtons.OK, MessageBoxIcon.Information); + try { if (webView != null) webView.Reload(); } catch { } + } + else + { + MessageBox.Show(mainForm, + "升级失败,请查看日志:" + Path.Combine(AppDir(), "upgrade.log"), + "DeepSeek Harness", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + })); + } + catch { } + }); + } + + static string GetLocalVersion() + { + try + { + string pkg = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "npm", "node_modules", "@deepseek-ai", "dsh", "package.json"); + if (File.Exists(pkg)) + { + var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); + var m = doc.Match(File.ReadAllText(pkg)); + if (m.Success) return m.Groups[1].Value; + } + } + catch { } + return ""; + } + + static string GetLatestVersion() + { + try + { + string stdout = ""; + var psi = new ProcessStartInfo(NodeExe) + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + Arguments = "\"" + NpmCli + "\" view @deepseek-ai/dsh version", + }; + using (var p = Process.Start(psi)) + { + p.ErrorDataReceived += (s, e) => { }; + p.BeginErrorReadLine(); + stdout = p.StandardOutput.ReadToEnd(); + p.WaitForExit(60000); + } + return stdout.Trim(); + } + catch { return ""; } + } + + static bool RunNpmUpgrade() + { + string logFile = Path.Combine(AppDir(), "upgrade.log"); + try + { + var psi = new ProcessStartInfo(NodeExe) + { + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardOutput = true, + RedirectStandardError = true, + Arguments = "\"" + NpmCli + "\" install -g @deepseek-ai/dsh@latest", + }; + using (var p = Process.Start(psi)) + { + p.OutputDataReceived += (s, e) => + { + if (e.Data != null) { try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } } + }; + p.ErrorDataReceived += (s, e) => + { + if (e.Data != null) { try { File.AppendAllText(logFile, e.Data + "\r\n"); } catch { } } + }; + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + p.WaitForExit(180000); + return p.ExitCode == 0; + } + } + catch { return false; } + } } diff --git a/app/start-dsh-service.vbs b/app/start-dsh-service.vbs new file mode 100644 index 0000000..d765672 --- /dev/null +++ b/app/start-dsh-service.vbs @@ -0,0 +1,4 @@ +' Start dsh (DeepSeek Harness) service in background, no window. +' Used by "autostart" tray option (registry Run entry via wscript). +Set ws = CreateObject("WScript.Shell") +ws.Run "cmd /c """"C:\Program Files\nodejs\node.exe"" ""C:\Users\VanuSystems-Mu\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js"" web > ""C:\Users\VanuSystems-Mu\AppData\Local\DeepSeekHarness\app\web.log"" 2>&1""", 0, False