Skip to content

讨论:为 librime 异步 / 可扩展插件提供前端侧的扩展挂载点 #1124

@wyjrichhh

Description

@wyjrichhh

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 源码:

  1. 插件日志难以查看——librime 有静态链接 glog 的历史限制(插件想写日志只能自己初始化 glog;此时它需要知道"主程序日志目录"在哪,但 squirrel 默认放在 $TMPDIR 且不暴露给插件。
  2. 想给 AI 候选加个特征色——只能改 squirrel 源码或写 lua 脚本绕,没有 theme 层 API。
  3. 异步推理完成后想刷新候选栏——librime 已有 notification_handler + Context property 通道,但 squirrel 默认不响应任何 property 通知。

现状分析(基于 upstream/master @ 151a43c

A. 日志路径与可发现性

  • sources/Main.swift#L21

    static let logDir = FileManager.default.temporaryDirectory.appending(
        component: "rime.squirrel", directoryHint: .isDirectory)

    落在 $TMPDIR/rime.squirrel/

  • 已知历史问题:

    • rime/squirrel#356open since 2019,无回复):用户报告 TMPDIR 下日志符号链接隔天失效,find 全盘也找不到。
    • rime/squirrel#303:用户找不到 $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 文本内容,统一套色。
  • 结果:translator / filter 想给某类候选打视觉标记,只能改 squirrel 源码或在文本里加符号。我们的 ai_predict_filter 给候选写了 comment="AI",希望让它显示为另一种颜色——目前没有干净的做法。

C. 异步候选就绪后的前端刷新

  • sources/SquirrelApplicationDelegate.swift#L238-271notificationHandler 当前只处理 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. 日志路径迁移 + 通过环境变量暴露给插件

思路

  1. Main.swift 里的 logDir$TMPDIR/rime.squirrel/ 改为 ~/Library/Logs/Squirrel/(macOS 标准日志位置,Console.app 可见)。
  2. 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 自带)?
  • #356 是否可以在该改动合并后一并关闭?

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 下覆盖。

待解疑问

  • comment 文本精确匹配是否够灵活?还是应该按 candidate type / source 之类更结构化的维度?(按 type 需要 librime 暴露 API,按 comment 文本完全在前端解决)
  • yaml 字段名 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 候选)
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 主仓任何改动。

待解疑问

  • C1 vs C2 维护团队倾向哪个?或有第三方案?
  • 多个 async 插件同帧通知时是否需要合并刷新避免抖动?squirrel 当前如何节流?
  • 需要 find_session 之类的 session 校验来避免错误刷新已切走的窗口(我们 fork 里已加,可参考 e1778a7refreshUIFromAsyncUpdate(for:) 方法)。
  • 这个机制是否值得推到其他 Rime 前端(weasel / ibus-rime / fcitx-rime)形成跨前端约定?如是,是否需要 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 等),相关条目:

issue 与本 issue 关系
rime/squirrel#356 直接相关(A),open since 2019
rime/squirrel#303 直接相关(A),closed
rime/librime#1025 直接相关(C),用户层面对 predict 异步刷新的需求
rime/librime#1004 间接相关(C),ML 后端方向的系统讨论
rime/librime#897 邻近话题(上下文调频,非异步)

未发现完全重复的提案;如有遗漏请指出,我们会合并讨论。


最后,我们是第一次以项目维护方身份参与开源协作,如果上述提案在术语 / 流程 / 表达 / 与上游协作方式上有任何不妥,恳请直接指出。十分感谢 librime 与 squirrel 团队多年来的工作。

— [筠荐 / 蚌壳开发团队]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions