2026-09-23·by Sijie Wang#lucerna#software#architecture

vocabulary-loop

词汇循环(点击 → 查询 → 保存 → 划线 → 复习)

父节点:key-designs

这是产品的核心循环。每一步都能在代码里找到对应的实现。

1. 点击一个单词 → 三层查询usecases/lookupWord.tsuseLookup.ts)。

  • L1 IndexedDB 中的 DictCache(即时、离线可用);
  • L2 mainbackend 的全局词典(DictionaryApi/api/v1/dictionary/entries)——所有用户共享的缓存;
  • L3 用户自己配置的 LLMapi/llmLookup.tsllmLookupBasic,温度 0.1)——给出 word/lemma/词性/释义/名词信息。命中 L3 后会以 fire-and-forget 的方式回写到 L1 和 L2,于是下一个点同一个词的读者就不用再付这个代价。

2. 动词补全(惰性、异步)——如果词性是动词,llmLookupVerbTable 会填出完整的变位表,services/verbForms.ts 收集每一个变位形式加分词,一并追加进 L2 词典。这样以后查询任何一个变位形式都会命中缓存,而不用再问 LLM。

3. 保存usecases/markWordFromLookup.ts)——构建一条 VocabularyEntry:名词的词元带上性别前缀(如 der Kopf),点击位置被定位到一个全书绝对坐标 rawStart(通过双坐标映射——raw-display-cursor),并且抓取段落级的 context(段落 → 句子 → 页面子串,依次降级)供制作抽认卡使用。

4. 到处划线——打开书籍时,loadBookVocabulary 先用缓存绘制再做校验,并运行全形式迁移vocabFormsMigration):按 bookId::lemma 把条目分组,保留最早的那条(以保住 SRS 历史),解析出完整的形式集合(先问 LLM,词典兜底),把 word 重写成 lemma,删除重复项——这个过程是幂等的,并有并发保护。之后由 all-forms-underline 把每一个形式都画上下划线。

5. 复习——SRS 由服务端掌管。 客户端只把一个评级('dont_know' | 'fuzzy' | 'know')发送到 /reviews;真正的 SM-2 算法跑在 Go 里mainbackend/internal/domain/review/srs.go——难度系数增减 −0.2/−0.15/+0.1,下限钳位在 1.3,dont_know → 1 天后复习,know 则 1→6→×难度系数,评级在服务端校验)。把算法放在服务端,意味着 web 端和桌面端的复习计划始终一致,也不会被过期的客户端篡改。

这个循环正是"精读"能成立的原因:阅读产生分级的词汇,而保存下来的词汇又反馈回阅读界面,变成随处可见的下划线。

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →