牛马论坛查看原帖 →

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

以南 35 阅读 8 回复
有问题可以评论我会回答
感谢,还是原生的应用不烧心
最近把自己写的工作 agent Ally agent 的界面重写了一遍,起因是无意中看到有人推荐 MyGo ,加上我对内存和性能一直有点追求,多年来一直在用 wails ,想换个试试,就想拿自己的项目试点,没想到结果意外的不错。
旧版本的技术栈是典型的 Go + Wails + Vue ([b]wails 我用了很多年 最近终于 V3 发正式版了 等了四年,可惜我也老了[/b]),界面用 Naive UI ,样式美观,效果也相当不错。但做久了有几个地方比较麻烦( tarui 应该也是一样):
跨平台界面的细节不一致,构建链比较长,还有前端内存出了问题以后,我这个写后端的人不太容易排查。
折腾了一段时间,现在主要页面已经迁到 MyGo 了,记录一下过程中比较有感触的地方。
先说一下技术路线:Wails 的界面通过系统 WebView 展示网页; MyGo 既支持这种方式,也支持用 Go 写 Native UI ,由框架自己绘制。WebView 也有自己的优势,MyGo 两种都支持,相当于有个兜底,下面会说。
Ally 现在的聊天、设置等常用页面用 Native UI ,插件页面和部分预览仍然按需使用 WebView 。
[size=5]一、UI 跨平台一致性[/size]

这是我这次迁移最重视的一点。
之前用 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,不过人家具体为什么换,我就不瞎猜了。
[size=5]二、内存问题现在能用熟悉的 Go 工具查[/size]

以前 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.pprofheap 主要看还活着的对象,allocs 主要看累计分配。分配量大不一定是泄漏,也可能是某段代码反复创建临时对象,GC 能回收,但会持续消耗 CPU 。
当然,pprof 主要看到的是 Go 堆。系统和 GPU 资源,以及 WebView 子进程的内存,还是得用其他工具看。
[size=5]三、帧处理的坑[/size]

MyGo 的界面更新会重新构建控件树,写 UI 时要特别留意渲染函数里做了什么。
我的 Agent 有命令执行卡片,命令输出可能很长。之前即使卡片折叠着、界面上只展示最后几行,代码仍然会先把整段输出拆分、去空行,再取出末尾几行。
看起来只是一个字符串处理函数,但界面更新时会反复执行。
后来改了两处:
[list=1]
[*]只需要末尾几行时,从字符串尾部向前扫描,不再先拆分整个输出。
[*]需要重复使用的派生结果按原始内容缓存;内容没变就复用。折叠时不构造展开用的行数据,并释放对应缓存。
[/list]一次本地测试里,用两万行命令输出测试折叠卡片的相关处理,[b]单次分配从约 1.9 MB 降到 1.9 KB ,耗时从约 0.72 ms 降到 0.02 ms[/b]。
这是这段处理在该样本下的局部结果,不是整个应用的性能对比。对应修改在这里。
类似的问题还有工具参数 JSON:同一条消息的卡片头、卡片体都可能读取参数,如果每次渲染都 Unmarshal 一遍,长对话里分配会越来越明显。现在把解析结果挂在控件局部状态上,原始参数变化时才重新解析。
我的感受是,性能问题要先分清楚是“[b]每次请求执行一次[/b]”,还是“[b]界面更新时每帧执行一次[/b]”。后者哪怕单次看着不重,乘上帧数和控件数量也很拖累性能。
[size=5]四、构建链变短[/size]

旧项目的构建大致要经过前端依赖、绑定生成、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 那套构建流程了。
[size=5]五、MyGo 组件够用,但 Web 生态还是强很多[/size]

迁移前我担心原生 GUI 要从头写一堆控件,实际比想象中好一些。
MyGo 已经有常见的表单、列表、树、弹窗,也有代码编辑器、Diff 、Markdown 等组件。Ally 的文件浏览和代码编辑用到了它的编辑器,聊天内容也用到了 Markdown 相关能力。
但如果和成熟的 Web 组件库比,差距还是很明显。Naive UI 这类库积累了很长时间,一个表单、选择器或者弹窗拿来就能用,样式、焦点、键盘操作、动画都比较完整。
好在 MyGo 的定制能力不错,但要做出自己满意的效果,颜色、间距、状态和交互细节还是得花时间。我之前不太想碰这类原生 UI ,一个原因就是觉得手写样式太累。
现在有 AI 帮忙,这件事容易太多了。先用框架已有组件和 Base/Parts API ,让 AI 根据目标效果调整样式,再自己检查实际交互。
我的经验是,尽量复用组件本身的焦点、键盘导航、浮层和滚动行为,别为了改外观把这些重新实现一遍,非常容易踩坑。
[size=5]六、WebView 的优势还在,可以混合使用[/size]

WebView 最大的好处之一就是生态。
视频播放、复杂图表、各种文件预览,Web 上已经有大量现成方案。很多东西如果坚持全部原生重写,维护成本很高。
MyGo 可以在 Native UI 窗口里嵌入 WebView ,所以我的做法是:[b]常驻界面用原生 UI ,需要 Web 生态的地方再创建 WebView 。[/b]
Ally 目前的 HTML 和 Mermaid 预览就是这样处理的。预览可见时按需创建,数量做限制,离开后释放。否则一个长对话里出现很多图表时,每个预览都长期挂着 WebView ,前面省下来的资源很容易又花回去。
迁移后日常使用的内存占用和响应都有比较明显的改善。不过我还没有整理同一机器、相同会话和操作条件下的完整对照数据,暂时不拿具体数字比较两个框架。
[size=5]七、插件化是 Go 里比较麻烦的东西,需要谨慎考虑[/size]

这是迁移前有点低估、做起来以后感受很深的一点。
看看 DeepSeek Harness 的 Web Client 架构,它有插件加载、服务、事件和 UI Slots ,插件可以在已有界面里贡献功能。
Web 技术和 JavaScript 的动态加载能力,让这类扩展比较容易分发和迭代。当然,WebView 本身不会自动提供插件体系,生命周期、扩展点和权限边界都得应用自己设计。
纯 Go 桌面应用要做同样的事就麻烦得多。Go 标准库的 plugin [b]不支持 Windows ,加载后也不能卸载[/b],对编译工具链和依赖版本还有要求,不适合直接拿来做一个跨 Windows 、macOS 、Linux 的热插拔 GUI 插件市场。
最后 Ally 还是用了混合方案:主程序是原生 UI ,插件页面按需跑在 WebView 里。插件可以打成 zip 安装,在侧栏增加页面,通过宿主提供的接口访问存储、HTTP 和工作区等能力;重新导入后可以更新,不用重启整个应用。
目前这更接近“可插拔页面”,和 DSH 那种能扩展更多核心能力的插件体系不太一样。WebView 也有自己的生命周期和资源成本,要做成完整生态,扩展点、兼容性和权限模型还有不少事要解决。
[size=5]代码[/size]

大概就这些,迁移完之后我自己还是比较满意的,欢迎大家一起尝试原生界面
我的项目代码在 Ally 仓库: https://github.com/Bronya0/ally-agent 原来的 Wails 版本还在 main 分支,MyGo 版本在 mygo 分支,感兴趣或想学习 MyGo 的可以对比着看,欢迎点个 Star 。


⚠️内容来自互联网,如有侵权请点击反馈!侵权投诉
@lolo1 确实😁 现在我看着电脑里这一个 electron 应用就头疼,还没开始干活一两个 G 内存没了
大数据?今天 vibe 一个 Wails 客户端应用:)
用 Go 直接写 Native UI 这块我挺好奇的,聊天、设置这种页面控件够用吗?我之前也折腾过非 WebView 方案,最头疼的是布局和列表滚动这些细节,你迁过去之后有没有碰到类似卡点
[这只是一个小尾巴]
这种项目到后面肯定是不可持续的,只是一时爽。
It's mygo!
现在都有不错的 go 原生 gui 框架了?两年多前找啊找用了 wails+svelte ,webview 说是用起来写 ui 方便,但是我的用户机器老,其实还是问题很麻烦的,但是没有比较好的原生 gui (也可能当时没找到),后面想将 web 的改成 app 也放弃 wails 改用 avalonia 了不知道 mygo 如何

阅读全文与全部回复 →