init: DeepSeek Harness WebView2 桌面客户端

- dsh-app.cs: C#/WinForms/WebView2 主程序(托盘常驻、自动拉起后端、DPI 感知)
- app.manifest: PerMonitorV2 DPI 声明
- dsh.ico / logo-512.png: DeepSeek 官方鲸鱼图标
- README.md: 使用与构建文档
This commit is contained in:
Reasonix committed 2026-08-14 15:11:41 +08:00
commit f8c101d487
10 files changed
+330

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="DeepSeekHarness.dsh-app" type="win32"/>
<description>DeepSeek Harness</description>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
</assembly>
+222
View File
@@ -0,0 +1,222 @@
// DeepSeek Harness — WebView2 独立桌面客户端(托盘常驻版)
// 编译: csc /target:winexe /nologo /win32manifest:app.manifest /win32icon:dsh.ico
// /out:dsh-app.exe /r:Microsoft.Web.WebView2.Core.dll /r:Microsoft.Web.WebView2.WinForms.dll dsh-app.cs
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
static class Program
{
private const int Port = 3080;
private static readonly string WebUrl = "http://127.0.0.1:3080";
private static Process serviceProcess; // 本应用拉起的 dsh 服务进程
private static bool startedByUs; // 服务是否由本应用启动
private static NotifyIcon trayIcon;
private static Form mainForm;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 确保 dsh 服务在运行(若端口未监听则后台拉起)
if (!IsPortOpen(Port))
{
StartDshService();
}
mainForm = new Form
{
Text = "DeepSeek Harness",
ClientSize = new Size(1440, 900),
StartPosition = FormStartPosition.CenterScreen,
MinimumSize = new Size(800, 600),
};
// 窗口标题栏/任务栏使用 DeepSeek 图标
try
{
mainForm.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
}
catch { }
// 系统托盘常驻图标
trayIcon = new NotifyIcon();
trayIcon.Icon = mainForm.Icon;
trayIcon.Text = "DeepSeek Harness";
trayIcon.Visible = true;
trayIcon.DoubleClick += (s, e) => ShowMainWindow();
var menu = new ContextMenuStrip();
menu.Items.Add("打开 DeepSeek Harness", null, (s, e) => ShowMainWindow());
menu.Items.Add(new ToolStripSeparator());
menu.Items.Add("退出", null, (s, e) => ExitApp());
trayIcon.ContextMenuStrip = menu;
// 点 X:直接最小化到系统托盘继续后台运行;完全退出请用托盘右键菜单
mainForm.FormClosing += (s, e) =>
{
if (e.CloseReason != CloseReason.UserClosing) return;
e.Cancel = true;
mainForm.Hide();
};
var status = new Label
{
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleCenter,
Font = new Font("Microsoft YaHei UI", 14),
Text = "正在启动 DeepSeek Harness 服务…",
};
mainForm.Controls.Add(status);
var web = new WebView2 { Dock = DockStyle.Fill };
mainForm.Shown += async (s, e) =>
{
// 等待服务就绪(最多 ~30 秒)
bool ready = false;
for (int i = 0; i < 60; i++)
{
await Task.Delay(500);
if (IsPortOpen(Port)) { ready = true; break; }
}
if (!ready)
{
status.Text = "无法连接 DeepSeek Harness 服务(端口 " + Port + ")。\n" +
"请查看日志:" + Path.Combine(AppDir(), "web.log");
return;
}
mainForm.SuspendLayout();
mainForm.Controls.Remove(status);
mainForm.Controls.Add(web);
mainForm.ResumeLayout();
try
{
string userData = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"DeepSeekHarness", "EBWebView");
var cp = new CoreWebView2CreationProperties();
cp.UserDataFolder = userData;
web.CreationProperties = cp;
await web.EnsureCoreWebView2Async();
web.Source = new Uri(WebUrl);
}
catch (Exception ex)
{
MessageBox.Show("WebView2 初始化失败:\n" + ex.Message, "DeepSeek Harness",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
};
Application.Run(mainForm);
}
static string AppDir()
{
return Path.GetDirectoryName(Application.ExecutablePath);
}
static bool IsPortOpen(int port)
{
try
{
using (var client = new TcpClient())
{
var task = client.ConnectAsync("127.0.0.1", port);
return task.Wait(500) && client.Connected;
}
}
catch { return false; }
}
// 直接启动 node.exe 运行 dsh 入口(不经 cmd/批处理,避免弹出 CMD 窗口)
static void StartDshService()
{
string appDir = AppDir();
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;
var psi = new ProcessStartInfo(nodeExe)
{
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
psi.Arguments = "\"" + binJs + "\" web";
// 确保 node 与 npm 命令可用(dsh 插件可能用到)
string path = psi.EnvironmentVariables["PATH"] ?? "";
if (path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0)
path = @"C:\Program Files\nodejs;" + path;
if (path.IndexOf("npm", StringComparison.OrdinalIgnoreCase) < 0)
path = npmDir + ";" + path;
psi.EnvironmentVariables["PATH"] = path;
serviceProcess = Process.Start(psi);
startedByUs = true;
// 输出写日志文件(防管道缓冲死锁)
serviceProcess.OutputDataReceived += (s, e) =>
{
if (e.Data != null) { 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 { } }
};
serviceProcess.BeginOutputReadLine();
serviceProcess.BeginErrorReadLine();
}
catch (Exception ex)
{
try { File.WriteAllText(Path.Combine(AppDir(), "start-error.log"), ex.ToString()); } catch { }
}
}
static void ShowMainWindow()
{
if (mainForm == null) return;
mainForm.Show();
if (mainForm.WindowState == FormWindowState.Minimized)
mainForm.WindowState = FormWindowState.Normal;
mainForm.Activate();
}
// 完全退出:关闭后端服务 + 托盘 + 应用
static void ExitApp()
{
try
{
if (startedByUs && serviceProcess != null && !serviceProcess.HasExited)
{
serviceProcess.Kill();
}
}
catch { }
if (trayIcon != null)
{
trayIcon.Visible = false;
trayIcon.Dispose();
trayIcon = null;
}
Application.Exit();
}
}