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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions