• 请不要在回答技术问题时复制粘贴 AI 生成的内容
bronyakaka
V2EX  ›  程序员

我把自己的 Agent 从 Wails 迁到了 MyGo 原生 GUI,全程踩坑和实践分享

  •  
  •   bronyakaka · 6h 8m ago · 507 views

    最近把自己写的工作 agent Ally agent 的界面重写了一遍,起因是无意中看到有人推荐 MyGo ,加上我对内存和性能一直有点追求,多年来一直在用 wails ,想换个试试,就想拿自己的项目试点,没想到结果意外的不错。

    旧版本的技术栈是典型的 Go + Wails + Vue (wails 我用了很多年 最近终于 V3 发正式版了 等了四年,可惜我也老了),界面用 Naive UI ,样式美观,效果也相当不错。但做久了有几个地方比较麻烦( tarui 应该也是一样):

    跨平台界面的细节不一致,构建链比较长,还有前端内存出了问题以后,我这个写后端的人不太容易排查。

    折腾了一段时间,现在主要页面已经迁到 MyGo 了,记录一下过程中比较有感触的地方。

    先说一下技术路线:Wails 的界面通过系统 WebView 展示网页; MyGo 既支持这种方式,也支持用 Go 写 Native UI ,由框架自己绘制。WebView 也有自己的优势,MyGo 两种都支持,相当于有个兜底,下面会说。

    Ally 现在的聊天、设置等常用页面用 Native UI ,插件页面和部分预览仍然按需使用 WebView 。

    一、UI 跨平台一致性

    这是我这次迁移最重视的一点。

    之前用 Wails 做界面,Windows 上调好的页面,到了 macOS 上经常还要再看一遍。字体、行高、滚动条、输入框这些地方,偶尔就会有一点不一样。

    比如我画的表格,不知道为啥线条在 macOS 上看着发虚,Windows 上就正常。单看问题都不大,但调整完一个平台还得顾着另一个平台,挺烦的。

    Wails 使用系统 WebView ,Windows 和 macOS 背后的浏览器引擎不一样,前端样式最终要经过各自的渲染实现。

    MyGo 的 Native UI 由框架自己负责布局和绘制。迁移后,我在 Windows 上调好的主体界面,拿到 macOS 上看到的效果接近得多,需要针对平台单独修的地方也少了。

    当然,自绘也不等于所有像素都完全一致。MyGo 的文字排版仍然使用各平台的文字系统:Windows 是 DirectWrite ,macOS 是 Core Text ,Linux 是 Pango 。字体回退、DPI 、输入法这些还是要分别检查。但至少不用再同时处理两套 WebView 引擎带来的样式差异了。

    以前总觉得 Electron 重,碰到这些问题后倒是能理解为什么有些项目愿意用它:自带 Chromium ,渲染环境相对好统一。OpenCode 桌面端后来也从 Tauri 换到了 Electron,不过人家具体为什么换,我就不瞎猜了。

    二、内存问题现在能用熟悉的 Go 工具查

    以前 Wails 版本的 Go 后端一样可以用 pprof,但界面在 WebView 里,Go 堆、JavaScript 堆、DOM 、浏览器渲染进程是几块不同的东西。

    前端也有 DevTools ,可以拍 JS heap snapshot 、查 DOM 引用。但我平时主要写 Go ,遇到界面内存持续上涨,分析起来没有 Go 那么顺手。

    现在主要界面和业务都在 Go 进程里,我给 Ally 加了一个按需导出诊断的入口:在设置里点“导出内存诊断”,会导出 heap.pprof、allocs.pprof、goroutine.pprof 等文件。

    比如在空闲时导出一次,复现内存增长后再导出一次,就可以对比:

    go tool pprof -http=127.0.0.1:0 \
      -base=before/heap.pprof after/heap.pprof
    

    heap 主要看还活着的对象,allocs 主要看累计分配。分配量大不一定是泄漏,也可能是某段代码反复创建临时对象,GC 能回收,但会持续消耗 CPU 。

    当然,pprof 主要看到的是 Go 堆。系统和 GPU 资源,以及 WebView 子进程的内存,还是得用其他工具看。

    三、帧处理的坑

    MyGo 的界面更新会重新构建控件树,写 UI 时要特别留意渲染函数里做了什么。

    我的 Agent 有命令执行卡片,命令输出可能很长。之前即使卡片折叠着、界面上只展示最后几行,代码仍然会先把整段输出拆分、去空行,再取出末尾几行。

    看起来只是一个字符串处理函数,但界面更新时会反复执行。

    后来改了两处:

    1. 只需要末尾几行时,从字符串尾部向前扫描,不再先拆分整个输出。
    2. 需要重复使用的派生结果按原始内容缓存;内容没变就复用。折叠时不构造展开用的行数据,并释放对应缓存。

    一次本地测试里,用两万行命令输出测试折叠卡片的相关处理,单次分配从约 1.9 MB 降到 1.9 KB ,耗时从约 0.72 ms 降到 0.02 ms。

    这是这段处理在该样本下的局部结果,不是整个应用的性能对比。对应修改在这里。

    类似的问题还有工具参数 JSON:同一条消息的卡片头、卡片体都可能读取参数,如果每次渲染都 Unmarshal 一遍,长对话里分配会越来越明显。现在把解析结果挂在控件局部状态上,原始参数变化时才重新解析。

    我的感受是,性能问题要先分清楚是“每次请求执行一次”,还是“界面更新时每帧执行一次”。后者哪怕单次看着不重,乘上帧数和控件数量也很拖累性能。

    四、构建链变短

    旧项目的构建大致要经过前端依赖、绑定生成、Vite 构建,再编译 Go 。MyGo Native UI 的主要页面直接用 Go 写,主界面去掉了前端打包这一步,我自己的项目构建起来明显快了不少。

    MyGo 不依赖 CGO ,交叉编译也省事。目前 Ally 的构建脚本可以在 macOS 上处理 Windows 、macOS 、Linux 的 x64 和 ARM64 六个目标,另外生成 macOS Universal 包。

    例如编译一个 Windows x64 二进制,核心就是:

    CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o Ally.exe .
    

    实际发布还要处理资源、压缩包、签名等事情;能交叉编译出来,也不代表已经在目标系统完成运行验证。Linux 的图形环境和使用 WebView 时需要的系统库,也仍然要考虑。

    这里补充一下:我刚看了 Wails 3.1 的发布说明,它也加入了无 CGO 构建能力。目前 macOS 和 Linux 的 purego 后端还是实验性质,CGO 仍是默认选项。

    所以“无 CGO”以后不一定是两者之间的主要区别。对我这个项目来说,更明显的差别是主界面不用再经过 Vue 那套构建流程了。

    五、MyGo 组件够用,但 Web 生态还是强很多

    迁移前我担心原生 GUI 要从头写一堆控件,实际比想象中好一些。

    MyGo 已经有常见的表单、列表、树、弹窗,也有代码编辑器、Diff 、Markdown 等组件。Ally 的文件浏览和代码编辑用到了它的编辑器,聊天内容也用到了 Markdown 相关能力。

    但如果和成熟的 Web 组件库比,差距还是很明显。Naive UI 这类库积累了很长时间,一个表单、选择器或者弹窗拿来就能用,样式、焦点、键盘操作、动画都比较完整。

    好在 MyGo 的定制能力不错,但要做出自己满意的效果,颜色、间距、状态和交互细节还是得花时间。我之前不太想碰这类原生 UI ,一个原因就是觉得手写样式太累。

    现在有 AI 帮忙,这件事容易太多了。先用框架已有组件和 Base/Parts API ,让 AI 根据目标效果调整样式,再自己检查实际交互。

    我的经验是,尽量复用组件本身的焦点、键盘导航、浮层和滚动行为,别为了改外观把这些重新实现一遍,非常容易踩坑。

    六、WebView 的优势还在,可以混合使用

    WebView 最大的好处之一就是生态。

    视频播放、复杂图表、各种文件预览,Web 上已经有大量现成方案。很多东西如果坚持全部原生重写,维护成本很高。

    MyGo 可以在 Native UI 窗口里嵌入 WebView ,所以我的做法是:常驻界面用原生 UI ,需要 Web 生态的地方再创建 WebView 。

    Ally 目前的 HTML 和 Mermaid 预览就是这样处理的。预览可见时按需创建,数量做限制,离开后释放。否则一个长对话里出现很多图表时,每个预览都长期挂着 WebView ,前面省下来的资源很容易又花回去。

    迁移后日常使用的内存占用和响应都有比较明显的改善。不过我还没有整理同一机器、相同会话和操作条件下的完整对照数据,暂时不拿具体数字比较两个框架。

    七、插件化是 Go 里比较麻烦的东西,需要谨慎考虑

    这是迁移前有点低估、做起来以后感受很深的一点。

    看看 DeepSeek Harness 的 Web Client 架构,它有插件加载、服务、事件和 UI Slots ,插件可以在已有界面里贡献功能。

    Web 技术和 JavaScript 的动态加载能力,让这类扩展比较容易分发和迭代。当然,WebView 本身不会自动提供插件体系,生命周期、扩展点和权限边界都得应用自己设计。

    纯 Go 桌面应用要做同样的事就麻烦得多。Go 标准库的 plugin 不支持 Windows ,加载后也不能卸载,对编译工具链和依赖版本还有要求,不适合直接拿来做一个跨 Windows 、macOS 、Linux 的热插拔 GUI 插件市场。

    最后 Ally 还是用了混合方案:主程序是原生 UI ,插件页面按需跑在 WebView 里。插件可以打成 zip 安装,在侧栏增加页面,通过宿主提供的接口访问存储、HTTP 和工作区等能力;重新导入后可以更新,不用重启整个应用。

    目前这更接近“可插拔页面”,和 DSH 那种能扩展更多核心能力的插件体系不太一样。WebView 也有自己的生命周期和资源成本,要做成完整生态,扩展点、兼容性和权限模型还有不少事要解决。

    代码

    大概就这些,迁移完之后我自己还是比较满意的,欢迎大家一起尝试原生界面

    我的项目代码在 Ally 仓库: https://github.com/Bronya0/ally-agent 原来的 Wails 版本还在 main 分支,MyGo 版本在 mygo 分支,感兴趣或想学习 MyGo 的可以对比着看,欢迎点个 Star 。

    6 replies  •  2026-11-03 01:05:36 +08:00
    bronyakaka
    bronyakaka
    OP
    6h 2m ago
    1
    有问题可以评论我会回答
    lolo1
    lolo1
    5h 59m ago via Android
    2
    感谢,还是原生的应用不烧心
    bronyakaka
    bronyakaka
    OP
    5h 54m ago
    3
    @lolo1 确实😁 现在我看着电脑里这一个 electron 应用就头疼,还没开始干活一两个 G 内存没了
    E1n
    E1n
    5h 51m ago via Android
    4
    大数据?今天 vibe 一个 Wails 客户端应用:)
    humbass
    humbass
    3h 58m ago
    5
    这种项目到后面肯定是不可持续的,只是一时爽。
    WorldDominator
    WorldDominator
    2h 59m ago
    6
    It's mygo!
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   1267 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 20:05 · PVG 04:05 · LAX 12:05 · JFK 15:05
    ♥ Do have faith in what you're doing.