事情要从我女儿发来的一个压缩包说起。她说这是自己做的一个小应用,叫“文谈”,可以跟鲁迅、李白这些文学大师聊天。她觉得挺有意思的,但有两个地方用起来不太顺手:一是每次都要配置API Key,对普通人来说门槛太高;二是在对话框里打完字,按回车键发不出去,非得用鼠标点那个发送按钮,交互体验打了折扣。于是她让我帮忙看看,能不能改造改造。
一、文谈原版是个什么样的应用
打开压缩包一看,这是一个用 Flutter 做的 Web 应用。Flutter 是 Google 推出的跨平台 UI 框架,一次编写可以同时在手机、网页、电脑上运行。这个“文谈” App做得相当精致——左侧是一列文学家卡片,鲁迅、李白、杜甫、苏轼、白居易、王维、李清照、曹雪芹,还有莎士比亚和托尔斯泰,每位都有专属的头像配色和年代标签。点选一位大师,右边就进入对话界面,界面古色古香,字体的选择也很讲究。发送消息后,它调用DeepSeek大模型,根据每位文学家的性格和文风生成回复——跟鲁迅聊天他会一针见血,跟李白聊天他飘逸洒脱,跟苏轼聊天他豁达幽默。
技术上看,原版有这些特点:一是渲染引擎用了CanvasKit,画质好但需要从Google CDN加载,在国内经常加载不出来;二是数据存储用了SQLite,聊天记录和配置都保存在浏览器本地;三是API调用直连DeepSeek的服务器,每次对话都要带上用户的API Key。整个应用打完包大约50MB,其中大部分是Flutter的运行环境和字体资源。
一句话概括:这是一个构思巧妙、UI精美的文学聊天应用,但在国内网络环境和普通用户场景下,可访问性和易用性存在明显短板。
二、用了哪些工具和方法来改造
这次改造的核心思路是:在不动原版界面和交互逻辑的前提下,解决三个实际问题——国内能打开、不配API Key也能用、回车键能发送。这个过程中用了两种AI工具配合:
一是 Claude Code。这是 Anthropic 推出的命令行 AI 编程助手,可以在终端里直接操作文件、执行命令、分析代码。我让它去读那个 41MB 的编译后文件,找 API Key 的检查逻辑在哪里,找 CanvasKit 的加载路径在哪里。它能够在几秒钟内扫完十万行代码,定位到关键函数,然后提出修改方案。
二是 DeepSeek 本身的 API 能力。但这里有一个问题——既然要“不用 Key”,那就不能走 DeepSeek 的付费通道。最后选了一个叫 Pollinations AI 的免费服务,它不需要注册、不需要 Key,支持 OpenAI 兼容的接口格式,直接调用就行。虽然回复质量跟大厂模型比稍逊一些,但对于日常聊天完全够用。
三、具体改了什么
第一,CanvasKit 本地化。原版的 flutter_bootstrap.js 里,CanvasKit 的加载地址写死了 Google CDN:https://www.gstatic.com/flutter-canvaskit。国内访问这个域名基本是超时的,所以 App 打开就是白屏。改法很简单:在加载配置里加一行 canvasKitBaseUrl: “canvaskit/”,让它直接读本地目录里的 canvaskit 文件,绕过了网络问题。
第二,API 代理层。在 index.html 里注入了一段 JavaScript,拦截了所有发往 api.deepseek.com 的请求,把它们转到本地服务器上的 /api 路径。本地服务器再转发给Pollinations的免费API,拿到回复后原封不动地返回给App。这样一来,App以为自己一直在跟DeepSeek对话,其实背后跑的是免费的服务。同时,在服务器端还加了禁用缓存的头信息,避免浏览器缓存旧版本导致改了代码看不到效果的情况反复出现。
第三,一键启动。写了一个server.py,把静态文件托管和API代理合在一起,端口8080。配合一个启动.bat批处理,双击就可以启动服务器并自动打开浏览器。不需要装任何额外的东西,系统自带的Python 3就能跑。
第四,回车键问题。这个比较复杂。Flutter在多行文本输入框里,默认把Enter键的行为设定为“换行”而不是“发送”,这是框架层面的设计。编译后的代码是一个41MB的JavaScript文件,直接改字节很容易把文件搞坏导致白屏。试了好几个方案,最后选择了一个JavaScript层面的折中方案:在页面上监听键盘事件,检测到Enter键按下时,模拟鼠标点击发送按钮的位置。这个方法不完美,但至少比什么都不做强。要彻底解决,需要拿到Flutter的Dart源代码,把文本框改成单行模式并设置textInputAction为send,然后重新编译。
四、过程中踩了哪些坑
回过头看,最耗时间的有三个坑。
第一个坑,浏览器缓存。Flutter的Service Worker会把所有资源缓存到浏览器里,改了代码刷新页面看不到效果。加上服务器端的Cache-Control头设为no-cache才解决。即使这样,有时候还是得用无痕窗口或者换个新端口才能看到最新的效果。
第二个坑,字节级补丁。main.dart.js是Dart编译后的产物,所有变量名都被缩短成单字母,逻辑被压缩成一行。我尝试在里面直接修改Enter键的行为——把TextInputAction.newline换成TextInputAction.send。虽然能做到字节长度完全一致不破坏文件结构,但改完后整个Flutter应用启动就白屏,说明某些被替换掉的函数调用产生了意料之外的副作用。这是一个典型的编译后代码不可维护的案例。
第三个坑,SQLite 注入。原版 App 用了一个叫 Drift 的库来管理 SQLite 数据库,API Key 存在里面。我想通过 Python 生成一个预配好 Key 的数据库文件,然后用 Base64 编码嵌入到 index.html,在 Flutter 启动前注入到浏览器的 localStorage。数据库生成成功了,注入脚本也生效了,但Flutter对数据库格式有严格的校验,自建的数据库文件跟它预期的略有差异,会导致App无法正常初始化。最后放弃了这条路。
五、最终效果和遗留问题
经过一番折腾,现在的 文谈 是这样的:双击启动.bat,浏览器自动打开,文学家列表和对话界面完整保留,选一位大师就能开始聊天,不用配API Key,背后跑的是免费AI。在国内网络环境下能正常加载,不会因为Google CDN被墙而白屏。
但有两个遗憾:一是回车键的问题只解决了部分场景。在英文输入时,按回车有时可以触发发送;但在中文输入法激活时,回车键首先被输入法拦截用于确认拼音,不会直接发送消息。目前的替代方案是用鼠标点击发送按钮。二是API Key的注入方案受限于Flutter数据库的格式校验,最终没能实现完全无感的自动配置,用户首次使用仍然需要在设置页面随意输入一个字符并保存。这些都不是AI不想做,而是受限于编译后代码的可修改性,确实存在天花板。
六、一些感受
做完这件事,我有三点比较深的体会。
第一,现在的AI编程工具已经能处理相当复杂的工程任务——扫十万行代码、定位关键函数、精准替换二进制片段、生成服务端代理逻辑——这些放在一两年前,没有半天人工排查是搞不定的。但同时也有明显的天花板:遇到编译后的黑盒产物,AI也只能在外围想办法;有些改动,知道要改哪里,但没有源码就是改不了。
第二,好的产品设计应该考虑零配置。原版 文谈 其实是一个很有灵气的作品,文学家的人设、对话的语调、界面的气质,都看得出来用了心思。但一个需要去平台注册、复制粘贴密钥、还得懂API是什么的流程,对于大多数只想打开就用的人来说,就是一道翻不过去的墙。这恰恰是AI可以帮忙填补的缝隙。
第三,父女之间的这次合作方式挺有意思。女儿负责创意和产品——做出来一个跟大师对话的有趣应用;爸爸用AI工具做工程化——让它能真正跑在普通人的电脑上。虽然过程有点折腾,来回了好多次,但最终一个本来在自己电脑上都打不开的应用,变成了一个解压即用、双击启动、零配置就能聊天的工具,这种让好东西被更多人用到的满足感,是写代码本身比不了的。
如果你也有类似的兴趣——想把一个半成品打磨到真正好用——现在的AI工具确实是很好的帮手。它不一定一次就做对,但它不怕反复修改,也不会因为踩了三个坑就撂挑子。关键是你要把需求想清楚,把验收标准说清楚,剩下的就是耐心和迭代。
夜雨聆风