TL;DR
我们最近基于 librime 开发了一个神经网络候选预测插件 librime-ai-predict ,在与 squirrel 集成时遇到 3 处前端侧的扩展点缺失 ——它们独立、互不依赖、且与 ai_predict 解耦 ,理论上任何"异步候选 / 可扩展配色 / 日志可发现性"相关的 librime 模块都会撞到。我们在 fork feat/ai-inference 分支里写了 3 个临时方案验证可行(每个改动 ≤ 40 行),但希望先与维护者对齐方向再决定 PR 形态。
本 issue 不直接 PR ,目的是收集维护者意见。3 项改动可独立讨论、独立合并。
Non-goals(明确不在范围内)
❌ 不 修改 librime C API;不影响其他前端(weasel / ibus-rime / fcitx-rime)。
❌ 不 要求 squirrel 内置任何 AI / 模型代码;不引入新的依赖。
❌ 不 与 librime-ai-predict 绑定;3 项改动均独立可用、对其它 librime 模块开放。
❌ 不 讨论模型 / 推理本身的合理性。
背景
librime-ai-predict 是一个 librime 插件,用 CTranslate2 加载 int8 量化的 seq2seq 模型,做"基于上下文的整句联想"。它完全以 librime 插件形式存在 ,未修改 librime 一行代码——这部分非常顺利。
但接到 squirrel 时,下面这 3 处前端能力的缺失让我们不得不动 squirrel 源码:
插件日志难以查看 ——librime 有静态链接 glog 的历史限制(插件想写日志只能自己初始化 glog;此时它需要知道"主程序日志目录"在哪,但 squirrel 默认放在 $TMPDIR 且不暴露给插件。
想给 AI 候选加个特征色 ——只能改 squirrel 源码或写 lua 脚本绕,没有 theme 层 API。
异步推理完成后想刷新候选栏 ——librime 已有 notification_handler + Context property 通道,但 squirrel 默认不响应任何 property 通知。
现状分析(基于 upstream/master @ 151a43c)
A. 日志路径与可发现性
B. 候选 comment 配色
C. 异步候选就绪后的前端刷新
sources/SquirrelApplicationDelegate.swift#L238-271 :notificationHandler 当前只处理 messageType == "deploy" / "schema" / "option" 这几类系统消息,对 messageType == "property" 完全不响应 。
librime 侧:插件可以调 ctx->set_property(key, value),librime 会自动以 messageType="property", value="key=value" 回调到前端——这个通道是 librime 已有能力,没有任何前端在用。
实际用户痛点:rime/librime#1025 用户问"用 librime-predict 时如何在 backspace 后继续联想"——predict 类插件想在异步事件后让前端重新拉一次候选,目前没有标准路径 。
长期方向:rime/librime#1004 (label enhancement)已系统讨论"在 lua 里调 onnx 做联想"。这类异步 / ML 类后端如果要落地,前端必须有标准的"被通知后刷新"入口。
我们临时方案的原码(点击展开)
3 个改动各自的 commit:
完整 fork 分支:https://github.com/wyjrichhh/squirrel/tree/feat/ai-inference
普遍性论证
抛开 librime-ai-predict 这个具体项目,下面这些场景只要存在 ,就会撞到与我们相同的 3 堵墙:
假设场景
撞 A
撞 B
撞 C
任何用 lua / 第三方 dylib 写的插件,想自带日志
✅
云输入插件(拉远端候选)
✅(标记云端结果)
✅(HTTP 返回是异步)
其他 ML 后端(onnx / tflite / llama.cpp)
✅
✅
✅
自定义 translator 想标记"某类候选来源"(通假字 / 罕用字 / OCR 等)
✅
任何依赖 set_property 与前端协作的插件
✅
换言之,A 是 librime 插件生态的通用诉求 ,B 是 theme 层缺失的通用维度 ,C 是 librime 已有 API 但前端未对接 的通用扩展点 。
设计提案
三个改动彼此独立,可独立评估 / 合并 / 拒绝。每段含:思路 / 兼容性 / 用户视角配置 / 待解疑问 。
A. 日志路径迁移 + 通过环境变量暴露给插件
思路
把 Main.swift 里的 logDir 从 $TMPDIR/rime.squirrel/ 改为 ~/Library/Logs/Squirrel/(macOS 标准日志位置,Console.app 可见)。
在 SquirrelApplicationDelegate.setupRime() 里 setenv("RIME_LOG_DIR", logDir.path, 1),让插件 dylib 在自己 InitGoogleLogging 时能读取并复用同一目录。
兼容性
路径变更会破坏现有用户依赖 $TMPDIR/rime.squirrel/ 路径的脚本 ——这是本提案最敏感处。
缓解方案备选:保留 $TMPDIR 路径作为 symlink 指向新位置;或加一项 ~/Library/Rime/squirrel.custom.yaml 配置 app_options/log_dir 让用户选。
用户视角配置
无需配置;插件作者参考一个新约定 RIME_LOG_DIR。
待解疑问
B. 主题层支持按候选 comment 文本自定义颜色
思路
新增 yaml 字段 style/comment_color_map,键为 comment 字符串、值为颜色:
style :
comment_color_map :
AI : 0xff8c5a # ai_predict_filter 写的 comment="AI"
云 : 0x5fa8d3 # 假想云输入插件
渲染时若 candidate.comment 命中 map,则用对应颜色覆盖默认 commentTextColor;未命中走原路径。
兼容性
完全 opt-in:未配置 = 行为与现在一字不差。
现有任何 theme 文件无需修改。
用户视角配置
如上 yaml 片段,3 行起步。可在每个 color scheme 下覆盖。
待解疑问
C. 让前端响应 librime 的 property 通知,刷新候选栏(核心讨论点 )
问题
librime 插件可以 ctx->set_property("xxx/yyy", "zzz"),librime 会自动以 notification_handler(type="property", value="xxx/yyy=zzz") 通知前端。但 squirrel 当前的 notificationHandler 完全忽略 property 通知 。
期望流程:
sequenceDiagram
participant U as 用户
participant S as Squirrel<br/>(前端)
participant R as librime<br/>(引擎)
participant P as Plugin<br/>(如 ai_predict)
U->>S: 按键
S->>R: process_key
R->>P: Translator.Query
P->>P: 起后台线程异步推理
P-->>R: 立即返回兜底候选
R-->>S: 同步返回候选列表
S->>U: 渲染候选栏(暂无 AI 候选)
Note over P: 后台推理完成
P->>R: ctx->set_property("ai_predict/ready", "1")
R->>S: notification_handler(<br/>type="property",<br/>value="ai_predict/ready=1")
S--xS: ❌ 当前: 无处理<br/>✅ 期望: 触发 rimeUpdate()
S->>U: 刷新候选栏(含 AI 候选)
Loading
两个候选方案
方案 C1(约定式,最轻) :约定一个 namespace 后缀,譬如 */refresh_ui。插件写 set_property("foo/refresh_ui", "1");squirrel 在 notificationHandler 里识别后缀就调 rimeUpdate()。
优点:零配置;插件作者与前端互不需要知道对方。
缺点:把"刷新候选"语义绑死在魔法后缀上;难以表达"只刷新某些 segment"等更细粒度需求。
方案 C2(声明式,可扩展) :在 schema yaml 里写白名单:
frontend :
refresh_on_properties : [ai_predict/ready, cloud_input/ready]
squirrel 读这个列表,匹配即刷新。
优点:显式、可控、未来可扩展为 refresh_on_properties: { ai_predict/ready: { mode: full } }。
缺点:用户多一项配置;插件作者要在 README 提示用户加白名单。
我们 fork 里硬编码了 ai_predict/ready= 作为概念验证(e1778a7 ),这是临时方案,不应作为最终形态 。
兼容性
完全新增能力,不破坏任何现有行为。
C1 / C2 都不需要 librime 主仓任何改动。
待解疑问
我们能做的
如果方向获得认可,我们承诺承担拆 PR 与后续维护 。
我们倾向先 PR B (最不争议)→ 再 PR A (涉及默认行为变更,需 review 兼容策略)→ 最后 PR C (设计敲定后实施)。
每个 PR 控制在 ~40 行代码量级,便于 review。
已有讨论与背景
我们检索过 rime/squirrel 与 rime/librime 的现有 issue(关键词:property notification / comment color / TMPDIR / log_dir / predict / set_property / async 等),相关条目:
未发现完全重复的提案;如有遗漏请指出,我们会合并讨论。
最后,我们是第一次以项目维护方身份参与开源协作 ,如果上述提案在术语 / 流程 / 表达 / 与上游协作方式上有任何不妥,恳请直接指出。十分感谢 librime 与 squirrel 团队多年来的工作。
— [筠荐 / 蚌壳开发团队]
TL;DR
我们最近基于 librime 开发了一个神经网络候选预测插件 librime-ai-predict,在与 squirrel 集成时遇到 3 处前端侧的扩展点缺失——它们独立、互不依赖、且与 ai_predict 解耦,理论上任何"异步候选 / 可扩展配色 / 日志可发现性"相关的 librime 模块都会撞到。我们在 fork
feat/ai-inference分支里写了 3 个临时方案验证可行(每个改动 ≤ 40 行),但希望先与维护者对齐方向再决定 PR 形态。本 issue 不直接 PR,目的是收集维护者意见。3 项改动可独立讨论、独立合并。
Non-goals(明确不在范围内)
背景
librime-ai-predict 是一个 librime 插件,用 CTranslate2 加载 int8 量化的 seq2seq 模型,做"基于上下文的整句联想"。它完全以 librime 插件形式存在,未修改 librime 一行代码——这部分非常顺利。
但接到 squirrel 时,下面这 3 处前端能力的缺失让我们不得不动 squirrel 源码:
$TMPDIR且不暴露给插件。notification_handler+ Context property 通道,但 squirrel 默认不响应任何 property 通知。现状分析(基于
upstream/master @ 151a43c)A. 日志路径与可发现性
sources/Main.swift#L21:落在
$TMPDIR/rime.squirrel/。已知历史问题:
find全盘也找不到。$TMPDIR/rime.squirrel.INFO。librime 侧关联:#983 详细分析"插件静态链接 glog → 各自独立实例 → 输出路径未初始化",#984 提议改动态链接被关闭未合并。意味着 librime 主仓短期不会从核心层解决;前端只要把日志目录约定暴露出来,插件就能各自初始化自家 glog 写到同一目录。
B. 候选 comment 配色
sources/SquirrelTheme.swift#L48与 #L244-245:当前 theme 只有commentTextColor一个色值,应用于所有 comment。sources/SquirrelPanel.swift#L246:渲染时无视 comment 文本内容,统一套色。ai_predict_filter给候选写了comment="AI",希望让它显示为另一种颜色——目前没有干净的做法。C. 异步候选就绪后的前端刷新
sources/SquirrelApplicationDelegate.swift#L238-271:notificationHandler当前只处理messageType == "deploy" / "schema" / "option"这几类系统消息,对messageType == "property"完全不响应。librime 侧:插件可以调
ctx->set_property(key, value),librime 会自动以messageType="property", value="key=value"回调到前端——这个通道是 librime 已有能力,没有任何前端在用。实际用户痛点:rime/librime#1025 用户问"用 librime-predict 时如何在 backspace 后继续联想"——predict 类插件想在异步事件后让前端重新拉一次候选,目前没有标准路径。
长期方向:rime/librime#1004(label
enhancement)已系统讨论"在 lua 里调 onnx 做联想"。这类异步 / ML 类后端如果要落地,前端必须有标准的"被通知后刷新"入口。我们临时方案的原码(点击展开)
3 个改动各自的 commit:
2e8a2b8+e1778a7的前半50e6e5be1778a7完整 fork 分支:https://github.com/wyjrichhh/squirrel/tree/feat/ai-inference
普遍性论证
抛开 librime-ai-predict 这个具体项目,下面这些场景只要存在,就会撞到与我们相同的 3 堵墙:
set_property与前端协作的插件换言之,A 是 librime 插件生态的通用诉求,B 是 theme 层缺失的通用维度,C 是 librime 已有 API 但前端未对接的通用扩展点。
设计提案
A. 日志路径迁移 + 通过环境变量暴露给插件
思路
Main.swift里的logDir从$TMPDIR/rime.squirrel/改为~/Library/Logs/Squirrel/(macOS 标准日志位置,Console.app 可见)。SquirrelApplicationDelegate.setupRime()里setenv("RIME_LOG_DIR", logDir.path, 1),让插件 dylib 在自己InitGoogleLogging时能读取并复用同一目录。兼容性
$TMPDIR/rime.squirrel/路径的脚本——这是本提案最敏感处。$TMPDIR路径作为 symlink 指向新位置;或加一项~/Library/Rime/squirrel.custom.yaml配置app_options/log_dir让用户选。用户视角配置
无需配置;插件作者参考一个新约定
RIME_LOG_DIR。待解疑问
~/Library/Logs/Squirrel/是否符合 squirrel 长期方向?是否需要 opt-out 旧路径?RIME_LOG_DIR这个环境变量名能否定为 librime 生态的事实约定(让 weasel / ibus-rime / fcitx-rime 也跟)?还是更倾向GOOGLE_LOG_DIR(glog 自带)?B. 主题层支持按候选 comment 文本自定义颜色
思路
新增 yaml 字段
style/comment_color_map,键为 comment 字符串、值为颜色:渲染时若 candidate.comment 命中 map,则用对应颜色覆盖默认
commentTextColor;未命中走原路径。兼容性
用户视角配置
如上 yaml 片段,3 行起步。可在每个 color scheme 下覆盖。
待解疑问
comment_color_map是否符合 squirrel 命名风格?C. 让前端响应 librime 的 property 通知,刷新候选栏(核心讨论点)
问题
librime 插件可以
ctx->set_property("xxx/yyy", "zzz"),librime 会自动以notification_handler(type="property", value="xxx/yyy=zzz")通知前端。但 squirrel 当前的notificationHandler完全忽略 property 通知。期望流程:
sequenceDiagram participant U as 用户 participant S as Squirrel<br/>(前端) participant R as librime<br/>(引擎) participant P as Plugin<br/>(如 ai_predict) U->>S: 按键 S->>R: process_key R->>P: Translator.Query P->>P: 起后台线程异步推理 P-->>R: 立即返回兜底候选 R-->>S: 同步返回候选列表 S->>U: 渲染候选栏(暂无 AI 候选) Note over P: 后台推理完成 P->>R: ctx->set_property("ai_predict/ready", "1") R->>S: notification_handler(<br/>type="property",<br/>value="ai_predict/ready=1") S--xS: ❌ 当前: 无处理<br/>✅ 期望: 触发 rimeUpdate() S->>U: 刷新候选栏(含 AI 候选)两个候选方案
方案 C1(约定式,最轻):约定一个 namespace 后缀,譬如
*/refresh_ui。插件写set_property("foo/refresh_ui", "1");squirrel 在notificationHandler里识别后缀就调rimeUpdate()。方案 C2(声明式,可扩展):在 schema yaml 里写白名单:
squirrel 读这个列表,匹配即刷新。
refresh_on_properties: { ai_predict/ready: { mode: full } }。我们 fork 里硬编码了
ai_predict/ready=作为概念验证(e1778a7),这是临时方案,不应作为最终形态。兼容性
待解疑问
find_session之类的 session 校验来避免错误刷新已切走的窗口(我们 fork 里已加,可参考e1778a7的refreshUIFromAsyncUpdate(for:)方法)。我们能做的
已有讨论与背景
我们检索过 rime/squirrel 与 rime/librime 的现有 issue(关键词:
property notification/comment color/TMPDIR/log_dir/predict/set_property/async等),相关条目:未发现完全重复的提案;如有遗漏请指出,我们会合并讨论。
最后,我们是第一次以项目维护方身份参与开源协作,如果上述提案在术语 / 流程 / 表达 / 与上游协作方式上有任何不妥,恳请直接指出。十分感谢 librime 与 squirrel 团队多年来的工作。
— [筠荐 / 蚌壳开发团队]