方便调试做的客户端在这里插入图片描述

用纯 C# WinForms 写一个本地大模型管理器:开发全过程

缘起

本地跑着三个 llama-server 实例——MXFP4(8080 端口)、Qwen Q4(8081)、Gemma-4(8082),每次开机要手动开三个命令行窗口,参数又长又容易敲错。需要一个原生 Windows 图形界面来管理它们。

需求很明确:

  • 零外部依赖,不要 WebView2 / Electron / Python
  • .NET Framework 内置组件就够了
  • 深色主题,中文界面,启动快
  • 托盘运行,后台健康检测

第一阶段:单实例 Launcher(Llama Server)

最初只管理一个实例。用 CSharpCodeProvider 从 PowerShell 脚本内联编译 C# 代码,输出一个 32KB 的 EXE。

参数面板的布局折腾

最折腾的是参数面板布局。一开始用 tiled 2 列布局,用户嫌太挤;调大间距又说空间利用率低;换成垂直单列,但用 parent.Width 算坐标——构造时 parent 还没显示,Width=0,所有控件都不可见。

最终方案:不用绝对定位,全部 Dock = DockStyle.Top 堆叠,组框自动撑满宽度。高度按 36 + 控件数 * 38 动态计算。

C# 版本兼容坑

CSharpCodeProvider 默认使用系统自带的 C# 编译器(Windows 7 上是 C# 5,Windows 10 上是 C# 6/7),不支持局部函数。写了 static 局部函数编译不过,全部改成类级 private 方法。

模型切换

从硬编码路径改成下拉选择框(ComboBox),两个预设:MXFP4 和 Uncensored Q4_K_M。切换时自动更新模型路径、mmproj、别名和副标题。

第二阶段:多实例管理(Llama Manager)

管理 3 个实例,每个实例一个标签页。左侧边栏放操作按钮(启动/重启/浏览器/参数),右侧主区域显示日志输出。

架构设计

每个实例用 ServerSlot 类描述:

Title, Label, Port, ModelDir, ModelFile, MmprojFile, Args
Process, LogBox, StatusLabel, IsRunning

启动流程:Process.Start → 订阅 OutputDataReceived / ErrorDataReceivedBeginOutputReadLine → 端口健康检测。

经典 Bug:事件订阅顺序

花了最多时间排查的一个 bug——日志框一直是空的。检查发现事件订阅在 BeginOutputReadLine() 之后

// ❌ 这样收不到任何输出
proc.BeginOutputReadLine();
proc.OutputDataReceived += OnData;

// ✅ 必须先订阅再开始读取
proc.OutputDataReceived += OnData;
proc.BeginOutputReadLine();

--log-disable 参数也导致服务端不输出任何日志,去掉后恢复正常。

参数调节对话框

每个实例的启动参数很长(20+ 个 flag),用户不想手写。写了个 ParamForm 对话框,用正则解析 Args 字符串,把 --ngl / --ctx-size / --temp 等参数分布到 GPU/CPU、Context/Cache、Sampling、Misc 四个分组,每个参数有对应的 NumericUpDown / ComboBox / CheckBox。修改后保存回 slot 的 Args 字符串,下次启动生效。

配置文件

每个实例参数存为 slot 的字符串字段,用 Json 序列化保存/加载。

第三阶段:UI 打磨——从极暗到磨砂玻璃

深色主题 1.0

纯黑底 #0F0F12 + 白字。简洁但太「硬」。

磨砂玻璃效果

SetWindowCompositionAttribute 调起 Windows 10/11 的 Acrylic Blur(亚克力模糊),配合各 Panel 半透明底色(alpha 150~200)让桌面背景透过来。

踩了个坑:RichTextBox 不支持半透明 BackColor,设了直接抛 ArgumentException,进程启动就崩。只能在日志框保留完全不透明底色。

性能问题

Acrylic 模糊拖拽窗口时严重卡顿。换用 ACCENT_ENABLE_BLURBEHIND(经典 Aero 玻璃模糊),效果接近但流畅很多。

标题栏变白问题

DWM 模糊效果有时让窗口标题栏变成白色,与深色主题不搭。用 DwmSetWindowAttribute(handle, 20, 1) 开启深色标题栏解决。

对比度调整

初期按钮用高饱和度的绿/蓝/红,在玻璃背景上太跳脱。统一改为低饱和度暗灰底 + 浅灰文字(RGB(50,50,55) + RGB(200,200,200)),用文字颜色区分功能——结果是用户说连文字颜色也不想要,全部统一成浅灰。

最终交付物

文件 大小 说明
Llama Manager.exe 33 KB 多实例管理器,管理 3 个实例
Llama Server.exe 32 KB 单实例启动器
编译脚本 ~500 行 C# 内嵌 用 PowerShell CSharpCodeProvider 编译

编译方式:powershell -File Build-LlamaManager.ps1,一次生成完整 EXE,无需 Visual Studio。

技术要点总结

  1. 内联编译CSharpCodeProvider + PowerShell 脚本,无需 VS 即可发布 WinForms 程序
  2. C# 版本兼容:避免 C# 7+ 语法(局部函数、模式匹配等),确保系统编译器可用
  3. WinForms 透明限制:RichTextBox/TextBox 不支持 alpha BackColor,Panel 支持有限
  4. DWM 窗口效果SetWindowCompositionAttribute 实现模糊/亚克力,DwmSetWindowAttribute 控制标题栏
  5. Process 异步读取:必须先订阅事件再调用 BeginOutputReadLine
  6. 布局策略:构造时不可用绝对定位(Width=0),用 Dock 代替

一些感想

33KB 的 EXE 完成三个大模型实例的完整管理——启动、停止、参数调节、日志监控、托盘运行、健康检测。没有 Electron 的几百 MB 运行时,没有 WebView2 的依赖,只有一个 exe 和一个记事本能改的编译脚本。

有时候,最合适的工具就是 Windows 自带的那些东西。

更多推荐