我用 WPF 写了个 DeepSeek 桌面 AI 编程助手——从重构到发布全记录

一句话:一个 63MB 单文件的 Windows AI 编程助手,双击即用,深度榨干 DeepSeek V4 Pro 的 KV Cache。

📦 GitHub · Gitee · 下载 exe


目录


一、为什么要从 TypeScript 切到 WPF?

📜 第一版的教训

第一版用 TypeScript + Node.js 写的 CLI。理想很丰满:

Ink + React 全屏 TUI → 漂亮的终端界面 → 丝滑的 AI 对话

现实很骨感:

问题 详情
🐛 Windows 终端渲染 Ink 在 Windows Terminal 下布局错位,中文 IME 跟 readline 冲突
🐛 打包分发 pkg 已死、sea 不稳定,没法让用户"双击即用"
🐛 代码渲染 终端里做语法高亮和 diff 预览是天方夜谭

修修补补了两天,最后搞了个 Ink 渲染输出 + readline 处理输入 的混合架构——能跑,但丑。

✅ 为什么选 WPF + WebView2?

WebView2 是 Windows 上的 Electron,但它不需要打包浏览器。

Windws 10/11 自带 WebView2 Runtime,你可以用 marked.js + highlight.js + KaTeX 做流式 Markdown 渲染,效果碾压任何终端方案:

  • 🎨 代码语法高亮(30+ 语言)
  • 📐 LaTeX 数学公式
  • 📊 Diff 绿红对比
  • 🚀 流式实时渲染

最终技术选型:

┌──────────────────────────────────┐
│         WPF (.NET 10)            │  ← 原生 Windows,GPU 加速
│  ┌────────────────────────────┐  │
│  │    WebView2 渲染引擎        │  │  ← marked.js + highlight.js + KaTeX
│  │    流式 Markdown 实时渲染   │  │     全部离线,不依赖外网
│  └────────────────────────────┘  │
│  ┌────────────────────────────┐  │
│  │    EventBus 事件总线        │  │  ← 22 个事件类型解耦 UI ↔ 业务
│  │    ServiceLocator DI       │  │
│  └────────────────────────────┘  │
│  ┌────────────────────────────┐  │
│  │    15 个内置工具 + MCP 扩展 │  │  ← 文件/Git/Shell/Web 抓取
│  │    12 个 Slash 命令         │  │
│  └────────────────────────────┘  │
└──────────────────────────────────┘

二、DeepSeek V4 Pro 适配——KV Cache 才是大招

这是本文的核心。理解了这一节,你一个月能省几百块 API 费用。

2.1 两个关键数字

deepseek-v4-pro deepseek-v4-flash
上下文窗口 1M tokens 1M tokens
最大输出 384K 384K
缓存命中(输入) $0.003625 / 1M $0.0028 / 1M
缓存未命中(输入) $0.435 / 1M $0.14 / 1M
缓存命中折扣 🔥 120 倍 50 倍

一个 900K prompt 的费用对比

缓存未命中 缓存命中
V4 Pro $0.39 / 次 $0.003 / 次
V4 Flash $0.13 / 次 $0.0025 / 次

💡 命中的话,聊一整天可能就几分钱。

2.2 怎么才能命中?

DeepSeek 的 KV Cache 用前缀匹配。缓存单元在"请求边界"产生——每条 system 消息末尾、每条 user 消息末尾、每次模型输出末尾。

看一个例子:

请求 1:┌─────────────────────────────┐
        │ system: "你是助手"          │ ← 缓存锚点
        │ user:   "北京是哪个首都?"    │ ← 此处产生缓存前缀单元
        └─────────────────────────────┘

请求 2:┌─────────────────────────────┐
        │ system: "你是助手"          │ ← ✅ 前缀匹配!
        │ user:   "北京是哪个首都?"    │
        │ assistant: "北京是中国的首都" │
        │ user:   "上海呢?"           │
        └─────────────────────────────┘
        → system + user1 命中缓存,只有 assistant + user2 需要计算

核心规律

+ ✅ system 消息是缓存锚点 —— 绝对不能乱改
+ ✅ 多轮对话天然利于缓存 —— 每次追加,前缀不变
- ❌ 在消息列表中间插入/修改 → 后面的全废

2.3 我们最初的做法——好心办坏事 ❌

对话太长时需要压缩。最初的 SmartCompress 策略是:AI 生成摘要,插入一条 system 消息

旧:[system, user1, assistant1, user2, assistant2, user3, assistant3]
                                ↓ 超 900K → SmartCompress
新:[system, 📛 system-summary, user2, assistant2, user3, assistant3]
              ↑ 插入了新 system 消息!
              ↑ 前缀变了 → 下次请求 100% cache miss!

🔴 每压缩一次,缓存全废。费用暴涨 120 倍。

2.4 缓存友好改造 ✅

核心原则:永远不动 system 消息,摘要作为 user 消息注入。

// ❌ 旧方案:插入 system 消息 → 破坏缓存前缀
result.Add(ChatMessage.CreateSystem(
    $"## 历史对话摘要\n\n{summary}"));

// ✅ 新方案:作为 user 消息注入 → system 前缀不变 → 缓存持续命中
result.Add(ChatMessage.CreateUser(
    $"<conversation_history_summary>\n" +
    $"以下是对之前 {count} 轮对话的摘要:\n\n{summary}\n" +
    $"</conversation_history_summary>\n\n" +
    $"请基于以上摘要和接下来的对话继续工作。"));

改造前后对比:

❌ 旧:[system, system-summary, round3, round4]
        → 前缀变化,cache miss

✅ 新:[system, user-summary, round3, round4]
        → system 前缀不变,cache hit!费用 ×0.01
              ↑ 摘要作为 user 消息

2.5 三级上下文策略

有了缓存友好基础,再加分层管理,渐进式而非一刀切

层级 Token 范围 策略 动作
🟢 正常 0 - 500K 不做处理,让缓存自然累积
🟡 轻度 500K - 800K TruncateToolResults 截断 >5KB 的工具结果
🟠 中度 800K - 950K SmartCompress AI 摘要旧轮次(缓存友好)
🔴 重度 950K+ SlidingWindow 暴力裁剪最早轮次兜底
// App.xaml.cs 中的策略注册
var orchestrator = new ContextStrategyOrchestrator(options)
    .AddStrategy(new TruncateToolResultsStrategy())  // 500K+
    .AddStrategy(new SmartCompressStrategy())         // 800K+
    .AddStrategy(new SlidingWindowStrategy())         // 950K+
    .AddStrategy(new FileInjectionStrategy());        // 始终

三、其他技术优化

3.1 Token 估算——中英文分离

📐 官方文档:1 英文字符 ≈ 0.3 token,1 中文字符 ≈ 0.6 token。

之前一刀切 text.Length / 2.5,中英文混合场景误差 ±30%

// ✅ 按字符类型分段估算
public static int EstimateTokenCountSync(string text)
{
    int asciiChars = 0, cjkChars = 0;
    foreach (var c in text)
    {
        if (IsCJK(c))  // 汉字/平假名/片假名/韩文
            cjkChars++;
        else
            asciiChars++;
    }
    return (int)Math.Ceiling(asciiChars * 0.3 + cjkChars * 0.6);
}

3.2 上下文裁剪性能——O(n²) → O(n)

原始的 TrimContextIfNeeded 每删除一轮就重新估算全部消息

// ❌ O(n²):每次循环 O(n),循环 n 次
while (rounds.Count > minRounds)
{
    tokenCount = EstimateAll(messages);  // ← 重复计算!
    if (tokenCount <= max) break;
    RemoveOldestRound();
}

改为预计算每轮 token,删除时减法

// ✅ O(n):预计算一次,后续 O(1) 减法
int[] roundTokens = rounds.Select(r => r.Sum(m => Estimate(m.Content))).ToArray();
int currentTotal = roundTokens.Sum();

while (currentTotal > max && rounds.Count > minRounds)
{
    currentTotal -= roundTokens[0];  // ← O(1)!
    RemoveOldestRound();
}

100+ 轮对话裁剪速度提升 10-100 倍

3.3 WebView2 渲染节流

⚡ SSE 流式输出每几毫秒一个 chunk。如果不节流,每秒 100+ 次 full re-render。

❌ 优化前:chunk→render chunk→render chunk→render...
           每个 chunk 触发:marked.js + highlight.js + KaTeX + innerHTML
           长文本直接卡死

✅ 优化后:chunk chunk chunk... → 50ms攒一批 → render
           每秒 ~20 次渲染,始终流畅

C# 侧(50ms Stopwatch 节流):

private void AppendStreamText(string text)
{
    _aiStreamBuffer.Append(text);
    // 50ms 内跳过渲染,只攒文本
    if (_renderThrottle.ElapsedMilliseconds < 50 && !_iterationFirstContent)
        return;
    _renderThrottle.Restart();
    _ = _chatRenderer.UpdateAiContent(_aiStreamBuffer.ToString());
}

JS 侧(requestAnimationFrame 防抖):

let renderPending = false;

function updateAiContent(text) {
    if (renderPending) return;  // ← 跳过重复请求
    renderPending = true;
    requestAnimationFrame(() => {
        renderPending = false;
        aiBlock.innerHTML = marked.parse(text);  // 每帧最多一次 DOM 更新
    });
}

3.4 HttpClient 连接池

var handler = new SocketsHttpHandler
{
    PooledConnectionLifetime   = TimeSpan.FromMinutes(5),  // 连接复用
    EnableMultipleHttp2Connections = true,                 // HTTP/2 多路复用
    KeepAlivePingDelay         = TimeSpan.FromSeconds(30), // 心跳保活
    MaxConnectionsPerServer    = 4                         // 并发上限
};

连续 API 调用复用已有 TCP 连接,省去每次 TLS 握手,延迟 ↓30%


四、项目结构一览

DeepSeekCode/
├── App.xaml.cs                    # 入口,DI 注册
├── MainWindow.xaml                # 主界面(侧边栏 + WebView2 + 输入栏)
├── MainWindow.Conversation.cs     # 流式对话核心 + 工具管线
│
├── Models/                        # 数据模型(AppConfig/ChatMessage/ToolDefinition...)
│
├── Services/
│   ├── DeepSeekClient.cs          # API 客户端(SSE + Thinking + FIM)
│   ├── ConversationManager.cs     # 对话管理 + 上下文裁剪 O(n)
│   ├── ContextStrategy.cs         # 三级上下文策略(缓存友好)
│   ├── EventBus.cs                # 22 个事件类型解耦
│   ├── PlanModeService.cs         # Plan 模式(5 阶段工作流)
│   ├── SubagentRunner.cs          # 子代理引擎(并行 + Flash 模型)
│   └── ...
│
├── Tools/                         # 15 个内置工具
│   ├── FileTools.cs               # read/edit/write/glob/grep
│   ├── GitTools.cs                # diff/log/commit
│   ├── ShellTool.cs               # pwsh 执行 + 危险命令拦截
│   ├── TaskTool.cs                # 子代理分派
│   └── ...
│
├── Commands/                      # Slash 命令系统(12 内置 + 自定义)
├── MCP/                           # JSON-RPC 2.0 over stdio
├── UI/                            # ChatRenderer + StatusViewModel
└── Resources/js/                  # 离线 JS 库(marked/highlight/KaTeX)

15 个内置工具

工具 功能
read_file / edit_file / write_file 文件读写编辑
glob / grep 文件搜索
shell PowerShell 执行
webfetch 网页抓取(正文提取)
read_skill 按需加载技能
git_diff / git_log / git_commit Git 集成
todo_write 任务列表管理
task 子代理分派(explore/general 双模式)
enter_plan_mode / exit_plan_mode Plan 模式切换

12 个 Slash 命令/help /clear /model /save /load /config /compact /settings /workspace /skills /mcp /plan


五、快速开始

📥 下载(推荐)

平台 链接
GitHub Releases · yuyu-s2c/DeepSeekCode
Gitee Releases · yu9929/deep-seek-code

下载 DeepSeekCode.exe(~63MB),双击运行。系统要求:Windows 10/11(自带 WebView2 Runtime)。

🔨 从源码构建

# 需要 .NET 10 SDK
git clone https://github.com/yuyu-s2c/DeepSeekCode.git
cd DeepSeekCode
dotnet build

# 打包为单文件 exe(无需运行时)
dotnet publish -p:PublishSingleFile=true -c Release -o ./publish

🚀 开始使用

  1. 点击齿轮图标 → 填入 DeepSeek API Key
  2. 选择模型:deepseek-v4-pro(强力推理)或 deepseek-v4-flash(快速响应)
  3. 输入问题,开始对话

六、写在最后

💡 适配好 API 的特性,远比堆功能重要。

DeepSeek V4 Pro 的 KV Cache 如果不理解、不利用,同样的代码量,输入费用可能是别人的百倍。反过来,理解了原理之后改几行代码就能大幅优化——这就是"懂底层"和"只会调 API"的区别。

另一个感悟:桌面应用不等于复杂。 WPF + WebView2 的架构让 Windows 原生能力和 Web 渲染生态完美互补,比纯 Electron 方案轻量得多。一个 63MB 的单文件 exe,双击即用,不需要装任何东西——这种体验是 Web 应用永远给不了的。

项目完全开源,MIT 协议。欢迎 ⭐ star,欢迎提 issue 和 pr,一起把 Windows 上的 AI 编程体验做得更好。


个人开发爱好项目,市面上已有许多优秀产品,这个小工具只是多一种选择。有问题还请大佬们轻喷 🙏

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐