DevLog @ 2026.01.01
新年快乐!这里是 @LemonNekoGH,AIRI 的维护者之一,新年的第一篇 DevLog 是我的了,(按下 B 键选择了大笑表情)啊哈哈哈哈哈哈!

好的我们来到正题。
AIRI Pocket
在两天前,我们引入了 Capacitor 来为 AIRI 构建移动端应用(#845),我们称之为 AIRI Pocket。
目前我们把 iOS 的部分点亮了,同时为它加入了通知能力,也就是说,如果她想,她可以主动通过通知来提醒你去陪她了。
别太在意那个 Capacitor 默认图标,之后会换的。
在视频中,我把 AIRI 从后台列表移除了,不久后 AIRI 就弹出了一条通知,这种后台通知在 PWA 中很难做到,在 iOS 原生应用上就轻而易举了。
诶,这么顺利吗,没有遇到什么问题吗?
不安全上下文导致的功能限制
显然是遇到了问题的,首当其冲的就是我们的 VAD 部分,由于 VAD 依赖了 AudioWorkletNode,这个类只有在安全上下文(Secure Context)中才能使用,而 Capacitor 的 iOS 应用在开发时需要热重载,它会直接访问你的开发环境暴露的端口,因此浏览器认为这是一个不安全的上下文,不会提供 AudioWorkletNode 类,导致 VAD 无法工作。
虽然在生产环境,打包之后,它会是安全的,但是我们需要在开发的时候测试它,所以这个问题必须解决。
在 AI 和搜索引擎的指引下,我找到了 vite-plugin-mkcert 这个插件,它可以帮助我们生成一个自签名证书,并且安装到系统中,从而让浏览器认为这是一个安全的上下文。
那这样做,解决了吗?依然没有,因为虽然证书被安装到本地的系统上了,但是没有安装到 iOS 上,所以 WKWebview 不信任这个证书,然而,如果每次 IP 变动都要重新安装证书,那也太麻烦了。
那要不直接修改原生代码,在开发阶段信任所有的证书?还真可以:
import UIKit
import Capacitor
import WebKit
class DevBridgeViewController: CAPBridgeViewController {
#if DEBUG
override func viewDidLoad() {
super.viewDidLoad()
bridge?.webView?.navigationDelegate = self
}
#endif
}
#if DEBUG
extension DevBridgeViewController: WKNavigationDelegate {
func webView(
_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
#endif注意这个 #if DEBUG 宏,这是为了在开发阶段启用,在生产环境会被优化掉,否则在生产环境会允许所有的证书,这显然是不安全的。
在 FlowChat 中实验的记忆层
先给大家看一下柠猫在 FlowChat 中实验的记忆层的效果:
在视频中,我要求 LLM 记住我的名字,它生成完回复之后,在设置界面就看到它记住了,即使开了新的对话,它也能想得起来。
这是怎么做到的呢?目前的实现相当简易:
- 创建一张记忆表。
- 给 LLM 提供一个工具函数,在它认为需要记住什么的时候,以陈述句来概括要记住的内容,然后调用这个工具函数。
- 在每次请求生成新的回复时,把所有的记忆拼接在系统提示词中。
如何动态拼接提示词呢?我使用了 @velin-dev/vue 包,它允许我们用 Vue 来写提示词,所有 Vue 拥有的能力它都有。
prompt.velin.md
<script setup lang="ts">
const props = defineProps<{
memory: string[]
}>()
</script>
<!-- Other content -->
## Your memories
<ul>
<li v-for="memory in props.memory">{{ memory }}</li>
</ul>
<!-- Other content -->像上面这样的代码,它也是支持写 markdown 的。
不知道你有没有注意到一点,就是我在介绍步骤时,说「把所有的记忆拼接在提示词中」,当记忆越来越多,这个提示词就会变得越来越长,怎么优化呢?不知道,那也许就是下一篇 DevLog 的内容了。
结束
好的,今年第一篇 DevLog 就被我 水 写好了,希望你看得开心。
我们下一篇 DevLog 再见。