Skip to content

实现猫娘伴学笔记本与伴学交互增强#1582

Closed
MomiJiSan wants to merge 6 commits into
Project-N-E-K-O:mainfrom
MomiJiSan:feat/study-companion-notebook
Closed

实现猫娘伴学笔记本与伴学交互增强#1582
MomiJiSan wants to merge 6 commits into
Project-N-E-K-O:mainfrom
MomiJiSan:feat/study-companion-notebook

Conversation

@MomiJiSan
Copy link
Copy Markdown
Contributor

@MomiJiSan MomiJiSan commented May 31, 2026

概述

实现猫娘伴学笔记本能力,并补齐伴学插件相关的命令、语音桥接与前端交互支撑。

主要变更

  • 新增笔记本/笔记数据模型、SQLite schema、FTS5 搜索与 CRUD 存储层
  • 新增 study_note_* 插件入口,支持笔记创建、更新、查询、跨模块搜索
  • 新增笔记本前端界面,包括笔记列表、编辑器、搜索与卡片组件
  • 新增 LLM 笔记扩写与总结能力
  • 补充多语言文案、KaTeX 静态资源与学习面板入口集成
  • 增加 Neko command、语音转写桥接、事件契约相关支撑
  • 补充笔记本、语音桥接、命令回路、插件调度等单元/集成测试

验证

  • ruff check 通过
  • py_compile 通过
  • npm run check-hosted-tsx 通过
  • pytest plugin/tests/unit/plugins/test_study_notebook.py:8 passed
  • 伴学相关扩展测试集:137 passed

风险说明

GitNexus detect_changes 已在提交前执行,staged 变更风险等级为 medium,未发现 HIGH/CRITICAL 阻塞项。

Summary by CodeRabbit

发布说明

  • 新功能

    • 添加学习笔记系统:支持创建、编辑、删除和搜索笔记;支持按主题、标签和笔记本组织内容
    • 添加语音转写桥接:支持通过语音命令控制响应(取消或预填上下文)
    • 添加 AI 辅助笔记功能:支持笔记智能扩写和内容自动总结
    • 添加数学公式渲染:回复中支持 LaTeX 数学表达式显示
  • UI 增强

    • 新增笔记编辑器、笔记本面板和全文搜索界面
    • 支持快捷键保存笔记和文本格式化(加粗、斜体、链接)
    • 增强键盘导航和焦点管理
  • 文档

    • 补充完整的多语言本地化文案(10+ 语言)
    • 新增入门引导文档

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

Caution

Review failed

Failed to post review comments

Walkthrough

本PR引入完整的语音转写转发桥接、插件笔记CRUD系统、前端编辑与KaTeX数学渲染的大规模功能整合喵。从Agent语音处理、事件总线等待机制、到笔记存储全文搜索、Tutor LLM扩展、最后到React前端UI与数学公式渲染,构成端到端的学习伴侣音声+笔记体验喵~

Changes

语音转写桥接链路

层级 / 文件 概述
Agent语音转写处理与派发
app/agent_server.py
新增语音转写事件入口,校验transcript非空、agent与插件启用状态、启动生命周期、触发自定义事件、仲裁结果后上报voice_bridge_result主事件喵~
主服务器语音结果通知
app/main_server.py
新增voice_bridge_result事件分支,从payload提取event_id与result,调用notify_voice_bridge_result回调喵~
事件总线语音请求等待
main_logic/agent_event_bus.py
新增模块级waiter映射与resolving集合、notify线程安全解析、publish可靠发送与超时二次等待机制喵~
会话语音桥接决策
main_logic/core.py
在handle_input_transcript新增桥接调用,若返回cancel_response则立即return中止后续处理;会话快照校验防止跨轮误操作喵~

插件分发与语音仲裁

层级 / 文件 概述
插件分发基础设施
plugin/server/application/plugins/dispatch_service.py
新增trigger_custom_event_subscribers批量分发,处理器匹配、参数深拷贝隔离、并发执行与结果聚合喵~
语音结果仲裁逻辑
plugin/plugins/study_companion/voice_contracts.py
事件常量、动作常量、结果归一化、优先级解析、多候选rank排序与最终仲裁函数喵~
语音过滤规则引擎
plugin/plugins/study_companion/voice_filter.py
命名窗口管理、问句意图识别、OCR重叠判定、主题推断、上下文构建、安全取消响应喵~
插件语音事件处理
plugin/plugins/study_companion/__init__.py
handle_voice_transcript自定义事件处理器,会话分组、过滤判定、上下文生成、返回动作喵~

笔记系统后端

层级 / 文件 概述
数据库表设计
plugin/plugins/study_companion/store_schema.py
notebooks与notes表、FTS5全文索引、INSERT/DELETE/UPDATE触发器维护索引一致性喵~
笔记存储业务逻辑
plugin/plugins/study_companion/store_notebook.py
NotebookStore完整CRUD、content清洗与派生字段、两阶段检索(FTS+LIKE回退)、跨资源搜索喵~
数据模型定义
plugin/plugins/study_companion/models.py
NotebookMeta/NoteItem/NoteSearchResult、recent_notes状态字段喵~
笔记导出与知识图统计
plugin/plugins/study_companion/doc_exporter.py, entry_knowledge_entries.py, entry_export_support.py
note_ids导出参数、知识图note_count统计、导出入口扩展喵~

笔记插件接口与Tutor LLM

层级 / 文件 概述
笔记条目处理器
plugin/plugins/study_companion/entry_notebook.py
新增_NotebookEntriesMixin with 笔记本CRUD、笔记CRUD、搜索、AI扩写/生成、高亮选中动作喵~
Tutor LLM笔记操作
plugin/plugins/study_companion/tutor_llm_agent_notebook.py
expand_note与summarize_to_note的LLM operation、模型分组覆盖、降级处理、多语言分区标题喵~
Tutor Agent模型分层
plugin/plugins/study_companion/tutor_llm_agent.py
model_group_override参数、call_type_group计算、vision/tutor/summary分组选择逻辑喵~
插件笔记能力集成
plugin/plugins/study_companion/__init__.py
_NotebookEntriesMixin继承、笔记存储初始化、recent_notes状态、Agent方法校验喵~

前端笔记UI与数学渲染

层级 / 文件 概述
KaTeX数学渲染管线
plugin/plugins/study_companion/static/math-parser.js, katex-render.js, katex.min.css, index.html, main.js
math-parser分段拆分、katex-render渲染、样式字体加载、脚本加载顺序、study_panel MathReply集成喵~
笔记卡片组件
plugin/plugins/study_companion/surfaces/note_card.tsx
NoteCard标题/元信息/片段、话题/标签chip、选中态与回调喵~
笔记编辑器表面
plugin/plugins/study_companion/surfaces/note_editor.tsx
受控输入、AI扩写、Markdown快捷键(S/B/I/K)、Preview预览、自动落盘喵~
笔记搜索表面
plugin/plugins/study_companion/surfaces/note_search.tsx
Debounce查询、多tab结果、NoteCard与通用结果行渲染喵~
笔记本面板表面
plugin/plugins/study_companion/surfaces/notebook_panel.tsx
三栏布局、笔记本列表创建、笔记搜索过滤、详情删除喵~
学习面板KaTeX集成
plugin/plugins/study_companion/surfaces/study_panel.tsx
MathReply组件、Katex加载同步、Escape中止请求、回复数学渲染喵~

本地化与文档

层级 / 文件 概述
多语言UI文案
plugin/plugins/study_companion/i18n/*.json
七语言(en/es/ja/ko/pt/ru/zh-CN/zh-TW) study labels、notebook/note条目、UI surface与状态提示喵~
入门指南与配置
plugin/plugins/study_companion/onboarding.md, plugin.toml
新增onboarding文档、三个notebook UI指南条目、文档权限配置喵~

全系统单元测试

层级 / 文件 概述
语音桥接测试
tests/unit/test_agent_server_voice_bridge.py, test_core_game_route_memory_contract.py, test_voice_bridge_event_bus.py
Agent分支、主服务通知、事件总线waiter、会话决策与取消路径喵~
语音过滤与仲裁测试
plugin/tests/unit/plugins/test_study_companion_voice_filter.py, voice_contracts.py, voice_bridge.py, voice_scenarios.py
过滤规则、仲裁逻辑、插件端处理、50条场景接受度喵~
笔记系统测试
plugin/tests/unit/plugins/test_study_notebook.py
CRUD、FTS降级、并发写入、LLM操作、导出集成喵~
插件分发与阶段校验
plugin/tests/unit/server/test_plugin_dispatch_service.py, test_study_companion_phase9_ux.py
批量分发隔离、数学资产检验、i18n一致性喵~

Poem

语音桥接接笔记,
KaTeX公式闪闪闪,
Tutor扩写有智能,
伴学体验再升级喵✨

🎯 4 (Complex) | ⏱️ ~75 minutes

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 034f9994d4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +192 to +196
notebook_id=notebook_id or None,
title=title,
content=content,
topic_ids=topic_ids or [],
tags=tags or [],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve fields when updating an existing note

When study_note_upsert is called with an existing note_id but only the fields the caller wants to change, the omitted fields are coerced to None, "", or [] here and then written back by NotebookStore.upsert_note, which unfiles the note and clears its content/topics/tags. This can happen through the public plugin entry because all of those fields are optional/defaulted in the schema, so a metadata-only update can silently destroy the existing note body and links.

Useful? React with 👍 / 👎.

Comment on lines +598 to +600
records = self.store.list_qa_records(limit=max(1, int(limit or 20)))
for item in records:
if str(item.get("session_id") or "") != session_key:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Query QA records by the requested session

For session note generation, this fetches only the latest global limit QA records and filters them in Python, so generating notes for any session that is not represented in the most recent 20 records loses all of that session's Q/A context. The source text should be selected by session_id in SQL before applying the limit; otherwise older sessions produce summaries from only the session metadata/summary.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (7)
pyproject.toml (1)

86-86: ⚡ Quick win

建议为 jieba 依赖添加版本约束喵~

新增的 jieba 依赖没有版本约束,可能导致不同环境安装不同版本,影响构建可复现性喵。建议添加版本约束,例如 jieba~=0.42.1 或其他稳定版本喵。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` at line 86, The dependency entry "jieba" has no version
constraint which can break reproducible builds; update the pyproject.toml
dependency list by replacing the bare "jieba" entry with a pinned or constrained
spec (for example "jieba~=0.42.1" or a suitable >=/<= range) and then regenerate
your lockfile (e.g., poetry lock / pip-compile) so the lockfile reflects the
chosen version; target the "jieba" dependency string in the pyproject.toml when
making this change.
plugin/plugins/study_companion/surfaces/notebook_panel.tsx (1)

115-123: ⚡ Quick win

搜索没做防抖,每敲一个键都会打两次后端喵~

query 直接进了 useEffect 依赖,用户每输入一个字符就会触发一次 refresh(),而它又用 Promise.all 同时打 study_notebook_liststudy_note_list。在搜索框上连打字时会瞬间堆一串请求,既浪费后端又可能出现旧响应覆盖新状态的乱序问题,笨蛋作者是不是忘了喵~

建议把 query 防抖后再驱动请求(SDK 已暴露 useDebouncedState/useDebounce),selectedNotebookId 切换可保持即时。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/surfaces/notebook_panel.tsx` around lines 115
- 123, The effect currently depends directly on query so every keystroke
triggers refresh() (which calls study_notebook_list and study_note_list) causing
duplicate/overlapping requests and possible race conditions; debounce the query
before using it in the effect by introducing the SDK hook (useDebouncedState or
useDebounce) to produce debouncedQuery and keep selectedNotebookId immediate,
then change the dependency in the useEffect from query to debouncedQuery and
call refresh(controller.signal, selectedNotebookId, debouncedQuery); ensure the
AbortController logic remains to cancel stale requests and preserve
setStatus(errorMessage(error)) behavior.
plugin/plugins/study_companion/tutor_llm_agent_notebook.py (1)

169-174: ⚡ Quick win

降级文案硬编码中文,建议使用 i18n 机制喵~

_fallback_expand_note 返回的降级提示文案是硬编码的中文:"暂时无法连接模型扩写。建议补充定义、例子、易错点和一个自测问题。"

虽然这只是降级场景,但是如果用户配置的语言不是中文的话,看到这段中文提示可能会感到困惑喵。

建议参考其他地方的做法(比如 tutor_llm_agent.py 里的 _localize_reply 方法),使用 study_i18n_t 来实现多语言支持喵~

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/tutor_llm_agent_notebook.py` around lines 169
- 174, The fallback message in _fallback_expand_note is hardcoded in Chinese;
replace it with a localized string via the existing i18n helper (use the same
pattern as tutor_llm_agent.py::_localize_reply), e.g. call study_i18n_t with an
appropriate key (create a new key like "fallback.expand_note_unavailable") and
substitute the localized text for the current user locale instead of the Chinese
literal; update the function _fallback_expand_note to build the message using
study_i18n_t(...) and ensure the new i18n key is added to the translation files.
plugin/plugins/study_companion/voice_filter.py (3)

412-421: ⚡ Quick win

安全取消响应的异常处理可能隐藏真实错误喵!

safe_cancel_response 在 Line 418 调用 _is_safe_cancel_exception(exc) 来判断异常是否可以安全忽略。但是:

  1. Line 447-454 的 _is_safe_cancel_exception 依赖异常类型名称字符串匹配(如 "ResourceNotFound"),这个很脆弱
  2. Line 450-454 还检查异常消息文本是否包含 "not found"、"already"、"response" 等关键词,这种启发式规则可能误判

如果实际的取消失败是因为其他严重错误(如网络故障、权限问题),这些异常可能被意外吞掉,导致调用方误以为取消成功了喵。

建议:

  • 在异常被判定为"安全可忽略"时,至少记录一条 warning 日志
  • 或者缩小"安全"异常的范围,只匹配确定的异常类型

不过考虑到这个函数已经返回 False 来表示取消未成功,调用方应该能正确处理,所以风险可控喵~

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/voice_filter.py` around lines 412 - 421,
safe_cancel_response currently swallows certain exceptions based on fuzzy string
checks in _is_safe_cancel_exception, which can hide real failures; update
safe_cancel_response to, when _is_safe_cancel_exception(exc) returns True, emit
a warning (via the module logger or provided logger) that includes the exception
type and message before returning False, and then refactor
_is_safe_cancel_exception to narrow its criteria to explicit exception classes
or well-defined attributes (instead of loose string matching) so only known safe
exceptions are treated as ignorable; refer to the functions safe_cancel_response
and _is_safe_cancel_exception and ensure the logged warning includes
exc.__class__.__name__ and str(exc).

254-262: 💤 Low value

名称匹配的 ASCII 判断可能遗漏混合字符情况喵~

Line 254 用 name.isascii() and name.isalnum() 来判断是否应用单词边界正则匹配,但是:

  1. 如果名字包含连字符或下划线(如 "Neko-chan"),isalnum() 会返回 False,导致回退到普通的 find(),可能会误匹配
  2. 对于类似 "YuiChan" 这种驼峰命名,虽然能正确用正则,但如果 OCR 文本中出现 "YuiChannel",正则的 \b 边界可能无法正确分隔

不过考虑到这是语音触发场景,实际名字列表应该都比较简单,这个问题影响不大喵。只是提醒一下有这个边界情况喵~

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/voice_filter.py` around lines 254 - 262, The
ASCII+isalnum() gatecan reject valid names with hyphens/underscores or camelCase
and causes fallback to plain find(), so change the matching to always use a
boundary-aware regex that defines boundaries as start/end or non-alphanumeric
characters instead of relying on isalnum(); e.g., build a pattern using
lookarounds around re.escape(needle) like (?<![A-Za-z0-9])needle(?![A-Za-z0-9])
(use re.ASCII) and apply it to lower_text when computing pos for name/needle so
best_name/best_pos logic remains correct; ensure fallback still uses
lower_text.find() only if regex fails to match.

357-362: 💤 Low value

学科推断顺序可能导致误判喵~

学科检测的顺序是:chemistry → physics → math (Lines 357-362)。但是:

  1. 化学公式 H2O 等也可能出现在物理题目中(如热学、流体力学)
  2. 物理关键词"速度"、"加速度"也可能出现在数学的运动学应用题中

虽然这个优先级对大多数情况是合理的,但在边界 case 可能会把物理题误判为化学、数学题误判为物理喵。

建议考虑:

  • 使用关键词权重而不是简单的优先级
  • 或者在文档中说明这个判断是"best effort",存在误判可能

不过对于当前的"学习自述"场景,这个简单规则应该够用了喵~

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/voice_filter.py` around lines 357 - 362, The
current subject-detection block that checks chemistry_hits/chemistry_formula_re,
physics_hits, and math_hits/math_expression_re in strict priority causes
misclassification in edge cases; update the logic to compute weighted scores
instead of short-circuiting by priority: count matches for
chemistry_hits/chemistry_formula_re, physics_hits, and
math_hits/math_expression_re, apply simple weights (e.g., chemistry_weight,
physics_weight, math_weight), and choose the subject with the highest weighted
score (fall back to a "best-effort" label if scores tie/are low). Locate and
replace the priority checks around chemistry_hits, chemistry_formula_re,
physics_hits, math_hits, and math_expression_re with this scoring approach, or
alternatively add a clear "best effort" comment/docstring in the same function
if you prefer not to change logic.
plugin/plugins/study_companion/entry_neko_commands.py (1)

536-572: 💤 Low value

_handle_neko_change_mode 中重新抛出异常可能导致意外行为喵~

在 Line 546 和 572,推送错误消息后重新 raise 异常,但因为调用方 _run_interruptible(Line 290-294)会捕获所有异常(包括非 CancelledError),这个 raise 实际上会被静默吞掉喵。这不是 bug,但代码意图不太明显,建议添加注释说明为什么需要 raise 喵~

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/entry_neko_commands.py` around lines 536 -
572, The code in _handle_neko_change_mode catches errors from study_set_mode,
sends a user-facing error via _push_neko_command_message and then re-raises the
exception, but callers like _run_interruptible swallow all exceptions; add a
brief clarifying comment above each raise in _handle_neko_change_mode explaining
the intent (e.g., that we re-raise to ensure upstream task/state
handling/metrics/termination logic sees the failure even though
_run_interruptible will catch it, or to distinguish cancellation vs. other
errors), reference the specific raises after the except Exception as exc block
and after the non-Ok result handling, and if the intent is to surface only
cancellations consider replacing generic raise with re-raising CancelledError
explicitly; update comments accordingly to make the behavior explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@main_logic/core.py`:
- Around line 1998-2011: The current handling of
session_snapshot.cancel_response returns the "cancel_response" action even when
cancel_response is not callable or when it raises, which causes the user's
transcription to be dropped; change the logic so that you only return action
after a successful cancellation: check session_snapshot.cancel_response is
callable, call await cancel_response() inside a try block and only return action
if _session_changed() stays false and the await completes without exception (log
success via logger.debug as now); if it's not callable or an exception occurs
(or _session_changed() becomes true), do not return action—return an empty
string to allow normal message processing to continue; keep references to
_session_changed(), session_snapshot.cancel_response, await cancel_response(),
and the existing logger.debug/error paths when adjusting the control flow.

In `@plugin/plugins/study_companion/i18n/es.json`:
- Around line 199-251: The listed notebook/note entries in es.json are still
English; update the values for keys like
entries.notebook_create.name/description, entries.note_* keys, and all ui.* keys
(e.g., ui.surface.notebook_panel, ui.status.searching, ui.button.save,
ui.notebook.empty, ui.notebook.search_placeholder, ui.notebook.tab_* etc.) with
correct Spanish translations, preserving the exact JSON keys, any placeholders,
punctuation, and capitalization; prioritize user-facing UI strings
(ui.surface.*, ui.status.*, ui.button.*, ui.notebook.*) while translating
descriptions for entries.* so the file remains valid JSON and keys/placeholder
tokens are unchanged.

In `@plugin/plugins/study_companion/i18n/ko.json`:
- Around line 199-251: The JSON keys (e.g., entries.notebook_create.*,
entries.note_upsert.*, ui.surface.notebook_panel, ui.button.save, ui.notebook.*
etc.) currently contain English values; replace each English string with the
correct Korean translation for those keys, preserving the existing key names and
JSON structure/encoding (UTF-8), and ensure punctuation and capitalization are
appropriate for Korean; after updating, run a JSON linter to confirm valid
syntax and spot-check the UI entries (notebook_create, note_get,
note_ai_generate, ui.surface.*, ui.button.*, ui.notebook.*) to verify
translations read naturally in context.

In `@plugin/plugins/study_companion/i18n/pt.json`:
- Around line 199-251: The pt.json locale contains many untranslated strings
(keys like "entries.notebook_create.*", "entries.note_*",
"ui.surface.notebook_panel", "ui.button.save", "ui.notebook.*", etc.); update
those keys with proper Portuguese translations to replace the English values,
ensuring grammar and context match (e.g., "Create Study Notebook" → appropriate
PT-BR/pt-PT phrasing) and keep the JSON formatting and key names unchanged so
the app loads the locale correctly; verify all listed keys in the diff are
translated and run a quick lint/parse to ensure valid JSON.

In `@plugin/plugins/study_companion/i18n/ru.json`:
- Around line 199-251: The Russian i18n file has most values still in English;
translate the remaining English strings into Russian for all affected keys (e.g.
entries.notebook_create.name/description, entries.notebook_list.*,
entries.note_upsert.*, entries.note_get.*, entries.note_delete.*,
entries.note_list.*, entries.note_search_all.*, entries.note_ai_expand.*,
entries.note_ai_generate.*, entries.note_highlight_action.*,
ui.surface.notebook_panel, ui.surface.note_editor, ui.surface.note_search,
ui.button.save, ui.button.search and the entire ui.notebook.* block) so the
value strings are proper Russian; update each JSON value (not the keys) with
accurate Russian text and preserve punctuation/placeholder tokens exactly as in
the original values.

In `@plugin/plugins/study_companion/surfaces/note_editor.tsx`:
- Around line 161-182: The effect unconditionally upserts on unmount whenever
draft.title or draft.content are non-empty, causing unnecessary writes; fix by
capturing a baseline snapshot when loading the note (e.g., set
savedSnapshot.current or a savedBaseline variable inside loadNote / after
loadNote resolves) and on unmount compare latestDraft.current to that baseline
(title, content, notebookId, topic/tag lists) and only call
callPlugin('study_note_upsert', ...) if any field differs; also ensure saveNote
updates savedSnapshot.current after a successful save so subsequent unmount
comparisons see the latest saved state.

In `@plugin/plugins/study_companion/surfaces/study_panel.tsx`:
- Line 55: The math parsing code (CURRENCY_START_PATTERN, hasEscapedDelimiter,
findMathDelimiter, splitMathText and related logic) is duplicated—extract these
into a single TypeScript module (e.g., math-parser.ts) that exports the helpers
and the CURRENCY_START_PATTERN, then replace the implementations in
study_panel.tsx (functions: hasEscapedDelimiter, findMathDelimiter,
splitMathText and the regex constant) with imports from that module;
alternatively, if you prefer global reuse, expose those same helpers on
window.__studyCompanionMath and import/use them in study_panel.tsx instead of
reimplementing so both katex-render.js and study_panel.tsx share the single
source of truth.

In `@plugin/plugins/study_companion/tutor_llm_agent_notebook.py`:
- Around line 162-165: The _ensure_note_summary_structure function currently
hardcodes Chinese headings ("## 要点", "### 细节"); update it to use the same
i18n/localization source or language setting used by the agent (e.g., pull title
strings from the existing localization/config or the same helper used by the
system prompt and _fallback_summary_note) so inserted headings match the user's
language; replace the literal strings in _ensure_note_summary_structure with
localized variables (e.g., summary_heading, details_heading) and ensure
_fallback_summary_note uses the same localized values so both generation and
fallback are consistent across languages.
- Around line 92-94: The system prompt currently hardcodes Chinese Markdown
headings ("# 标题", "## 要点", "### 细节"); update the prompt generation to use
self._config.language to select localized heading strings (e.g., via a small
mapping or helper like get_localized_headings(language)) and substitute those
localized headings into the system prompt used in the agent setup, and also
update _ensure_note_summary_structure to validate against the same localized
headings (or derive its expected structure from the same helper) so both prompt
and validation stay consistent across languages.

In `@plugin/tests/unit/plugins/test_study_companion_neko_commands.py`:
- Around line 67-71: get_own_effective_config() is returning the wrong
attribute; change it to return the prepared self._effective_config instead of
self._config so tests use the intended effective config. Locate the getter
method named get_own_effective_config (and the similar getter occurrences around
the other block mentioned) and update their return expressions to reference
self._effective_config; ensure any tests relying on startup/command branches now
receive the in-test configuration rather than the production one.

---

Nitpick comments:
In `@plugin/plugins/study_companion/entry_neko_commands.py`:
- Around line 536-572: The code in _handle_neko_change_mode catches errors from
study_set_mode, sends a user-facing error via _push_neko_command_message and
then re-raises the exception, but callers like _run_interruptible swallow all
exceptions; add a brief clarifying comment above each raise in
_handle_neko_change_mode explaining the intent (e.g., that we re-raise to ensure
upstream task/state handling/metrics/termination logic sees the failure even
though _run_interruptible will catch it, or to distinguish cancellation vs.
other errors), reference the specific raises after the except Exception as exc
block and after the non-Ok result handling, and if the intent is to surface only
cancellations consider replacing generic raise with re-raising CancelledError
explicitly; update comments accordingly to make the behavior explicit.

In `@plugin/plugins/study_companion/surfaces/notebook_panel.tsx`:
- Around line 115-123: The effect currently depends directly on query so every
keystroke triggers refresh() (which calls study_notebook_list and
study_note_list) causing duplicate/overlapping requests and possible race
conditions; debounce the query before using it in the effect by introducing the
SDK hook (useDebouncedState or useDebounce) to produce debouncedQuery and keep
selectedNotebookId immediate, then change the dependency in the useEffect from
query to debouncedQuery and call refresh(controller.signal, selectedNotebookId,
debouncedQuery); ensure the AbortController logic remains to cancel stale
requests and preserve setStatus(errorMessage(error)) behavior.

In `@plugin/plugins/study_companion/tutor_llm_agent_notebook.py`:
- Around line 169-174: The fallback message in _fallback_expand_note is
hardcoded in Chinese; replace it with a localized string via the existing i18n
helper (use the same pattern as tutor_llm_agent.py::_localize_reply), e.g. call
study_i18n_t with an appropriate key (create a new key like
"fallback.expand_note_unavailable") and substitute the localized text for the
current user locale instead of the Chinese literal; update the function
_fallback_expand_note to build the message using study_i18n_t(...) and ensure
the new i18n key is added to the translation files.

In `@plugin/plugins/study_companion/voice_filter.py`:
- Around line 412-421: safe_cancel_response currently swallows certain
exceptions based on fuzzy string checks in _is_safe_cancel_exception, which can
hide real failures; update safe_cancel_response to, when
_is_safe_cancel_exception(exc) returns True, emit a warning (via the module
logger or provided logger) that includes the exception type and message before
returning False, and then refactor _is_safe_cancel_exception to narrow its
criteria to explicit exception classes or well-defined attributes (instead of
loose string matching) so only known safe exceptions are treated as ignorable;
refer to the functions safe_cancel_response and _is_safe_cancel_exception and
ensure the logged warning includes exc.__class__.__name__ and str(exc).
- Around line 254-262: The ASCII+isalnum() gatecan reject valid names with
hyphens/underscores or camelCase and causes fallback to plain find(), so change
the matching to always use a boundary-aware regex that defines boundaries as
start/end or non-alphanumeric characters instead of relying on isalnum(); e.g.,
build a pattern using lookarounds around re.escape(needle) like
(?<![A-Za-z0-9])needle(?![A-Za-z0-9]) (use re.ASCII) and apply it to lower_text
when computing pos for name/needle so best_name/best_pos logic remains correct;
ensure fallback still uses lower_text.find() only if regex fails to match.
- Around line 357-362: The current subject-detection block that checks
chemistry_hits/chemistry_formula_re, physics_hits, and
math_hits/math_expression_re in strict priority causes misclassification in edge
cases; update the logic to compute weighted scores instead of short-circuiting
by priority: count matches for chemistry_hits/chemistry_formula_re,
physics_hits, and math_hits/math_expression_re, apply simple weights (e.g.,
chemistry_weight, physics_weight, math_weight), and choose the subject with the
highest weighted score (fall back to a "best-effort" label if scores tie/are
low). Locate and replace the priority checks around chemistry_hits,
chemistry_formula_re, physics_hits, math_hits, and math_expression_re with this
scoring approach, or alternatively add a clear "best effort" comment/docstring
in the same function if you prefer not to change logic.

In `@pyproject.toml`:
- Line 86: The dependency entry "jieba" has no version constraint which can
break reproducible builds; update the pyproject.toml dependency list by
replacing the bare "jieba" entry with a pinned or constrained spec (for example
"jieba~=0.42.1" or a suitable >=/<= range) and then regenerate your lockfile
(e.g., poetry lock / pip-compile) so the lockfile reflects the chosen version;
target the "jieba" dependency string in the pyproject.toml when making this
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c893057-665d-453d-934f-afcecc682ff5

📥 Commits

Reviewing files that changed from the base of the PR and between f064460 and 034f999.

⛔ Files ignored due to path filters (62)
  • plugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.ttf is excluded by !**/*.ttf
  • plugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.woff is excluded by !**/*.woff
  • plugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.woff2 is excluded by !**/*.woff2
  • plugin/plugins/study_companion/static/katex.min.js is excluded by !**/*.min.js
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (63)
  • app/agent_server.py
  • app/main_server.py
  • frontend/plugin-manager/scripts/check-hosted-tsx.mjs
  • main_logic/agent_event_bus.py
  • main_logic/core.py
  • plugin/plugins/study_companion/__init__.py
  • plugin/plugins/study_companion/constants.py
  • plugin/plugins/study_companion/doc_exporter.py
  • plugin/plugins/study_companion/entry_common.py
  • plugin/plugins/study_companion/entry_export_support.py
  • plugin/plugins/study_companion/entry_knowledge_entries.py
  • plugin/plugins/study_companion/entry_memory_review_entries.py
  • plugin/plugins/study_companion/entry_neko_commands.py
  • plugin/plugins/study_companion/entry_notebook.py
  • plugin/plugins/study_companion/entry_pomodoro_entries.py
  • plugin/plugins/study_companion/entry_status_entries.py
  • plugin/plugins/study_companion/entry_tutor_explain_entries.py
  • plugin/plugins/study_companion/entry_tutor_question_entries.py
  • plugin/plugins/study_companion/i18n/en.json
  • plugin/plugins/study_companion/i18n/es.json
  • plugin/plugins/study_companion/i18n/ja.json
  • plugin/plugins/study_companion/i18n/ko.json
  • plugin/plugins/study_companion/i18n/pt.json
  • plugin/plugins/study_companion/i18n/ru.json
  • plugin/plugins/study_companion/i18n/zh-CN.json
  • plugin/plugins/study_companion/i18n/zh-TW.json
  • plugin/plugins/study_companion/models.py
  • plugin/plugins/study_companion/onboarding.md
  • plugin/plugins/study_companion/plugin.toml
  • plugin/plugins/study_companion/service.py
  • plugin/plugins/study_companion/static/index.html
  • plugin/plugins/study_companion/static/katex-render.js
  • plugin/plugins/study_companion/static/katex.min.css
  • plugin/plugins/study_companion/static/main.js
  • plugin/plugins/study_companion/static/style.css
  • plugin/plugins/study_companion/store_notebook.py
  • plugin/plugins/study_companion/store_schema.py
  • plugin/plugins/study_companion/surfaces/note_card.tsx
  • plugin/plugins/study_companion/surfaces/note_editor.tsx
  • plugin/plugins/study_companion/surfaces/note_search.tsx
  • plugin/plugins/study_companion/surfaces/notebook_panel.tsx
  • plugin/plugins/study_companion/surfaces/study_panel.tsx
  • plugin/plugins/study_companion/tutor_llm_agent.py
  • plugin/plugins/study_companion/tutor_llm_agent_notebook.py
  • plugin/plugins/study_companion/voice_filter.py
  • plugin/server/application/plugins/dispatch_service.py
  • plugin/server/application/plugins/event_contracts.py
  • plugin/server/application/plugins/voice_transcript_bridge.py
  • plugin/tests/integration/test_study_companion_neko_command_roundtrip.py
  • plugin/tests/unit/plugins/test_study_companion.py
  • plugin/tests/unit/plugins/test_study_companion_neko_commands.py
  • plugin/tests/unit/plugins/test_study_companion_phase9_ux.py
  • plugin/tests/unit/plugins/test_study_companion_voice_bridge.py
  • plugin/tests/unit/plugins/test_study_companion_voice_filter.py
  • plugin/tests/unit/plugins/test_study_companion_voice_scenarios.py
  • plugin/tests/unit/plugins/test_study_notebook.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
  • plugin/tests/unit/server/test_plugin_event_contracts.py
  • plugin/tests/unit/server/test_plugin_voice_transcript_bridge.py
  • pyproject.toml
  • tests/unit/test_agent_server_voice_bridge.py
  • tests/unit/test_core_game_route_memory_contract.py
  • tests/unit/test_voice_bridge_event_bus.py

Comment thread main_logic/core.py Outdated
Comment thread plugin/plugins/study_companion/i18n/es.json Outdated
Comment thread plugin/plugins/study_companion/i18n/ko.json Outdated
Comment thread plugin/plugins/study_companion/i18n/pt.json Outdated
Comment thread plugin/plugins/study_companion/i18n/ru.json Outdated
Comment thread plugin/plugins/study_companion/surfaces/note_editor.tsx
Comment thread plugin/plugins/study_companion/surfaces/study_panel.tsx Outdated
Comment thread plugin/plugins/study_companion/tutor_llm_agent_notebook.py
Comment thread plugin/plugins/study_companion/tutor_llm_agent_notebook.py Outdated
Comment thread plugin/tests/unit/plugins/test_study_companion_neko_commands.py
@MomiJiSan MomiJiSan marked this pull request as draft May 31, 2026 13:08
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/plugins/study_companion/tutor_llm_agent_notebook.py (1)

198-203: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

_fallback_expand_note 的提示文案还是硬编码中文喵!

摘要那条路径(_fallback_summary_note / _ensure_note_summary_structure)已经按 self._config.language 本地化了,但扩写降级这里的 > [!ai] callout 内容仍然写死了中文喵。当用户语言是 en/ja/ko 时,模型连不上就会冒出一句中文,跟整体本地化对不上喵~

由于 expand_note 目前没把语言传进来,可以让 _fallback_expand_note 接收语言(或一份多语言文案映射)再选对应文案喵。

🌐 一种本地化思路
-def _fallback_expand_note(original: str) -> str:
+_EXPAND_FALLBACK_NOTICE = {
+    "zh": "暂时无法连接模型扩写。建议补充定义、例子、易错点和一个自测问题。",
+    "en": "Could not reach the model to expand. Consider adding a definition, an example, a common pitfall, and one self-check question.",
+}
+
+
+def _fallback_expand_note(original: str, *, language: str = "") -> str:
+    primary = str(language or "").strip().lower().replace("_", "-").split("-", 1)[0]
+    notice = _EXPAND_FALLBACK_NOTICE.get(primary, _EXPAND_FALLBACK_NOTICE["en"])
     return (
         f"{original.strip()}\n\n"
         "> [!ai]\n"
-        "> 暂时无法连接模型扩写。建议补充定义、例子、易错点和一个自测问题。"
+        f"> {notice}"
     ).strip()

调用处(Line 84)记得带上语言喵:

-        markdown = _fallback_expand_note(original)
+        markdown = _fallback_expand_note(original, language=self._config.language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/plugins/study_companion/tutor_llm_agent_notebook.py` around lines 198
- 203, The fallback expand message in _fallback_expand_note is hardcoded in
Chinese; change _fallback_expand_note to accept a language (or a
localized-messages dict) and return the callout text selected by language
(mirroring how _fallback_summary_note/_ensure_note_summary_structure use
self._config.language); update the caller of expand_note to pass
self._config.language (or the mapping) so the fallback message matches user
locale; ensure the function signature and any references to
_fallback_expand_note and expand_note are updated accordingly and include a
sensible default (e.g., English) when the language is not found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@plugin/plugins/study_companion/tutor_llm_agent_notebook.py`:
- Around line 198-203: The fallback expand message in _fallback_expand_note is
hardcoded in Chinese; change _fallback_expand_note to accept a language (or a
localized-messages dict) and return the callout text selected by language
(mirroring how _fallback_summary_note/_ensure_note_summary_structure use
self._config.language); update the caller of expand_note to pass
self._config.language (or the mapping) so the fallback message matches user
locale; ensure the function signature and any references to
_fallback_expand_note and expand_note are updated accordingly and include a
sensible default (e.g., English) when the language is not found.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4268716e-f70d-400a-9995-c38ea069f1ca

📥 Commits

Reviewing files that changed from the base of the PR and between 034f999 and 9226840.

📒 Files selected for processing (17)
  • main_logic/core.py
  • plugin/plugins/study_companion/entry_notebook.py
  • plugin/plugins/study_companion/i18n/es.json
  • plugin/plugins/study_companion/i18n/ko.json
  • plugin/plugins/study_companion/i18n/pt.json
  • plugin/plugins/study_companion/i18n/ru.json
  • plugin/plugins/study_companion/static/index.html
  • plugin/plugins/study_companion/static/katex-render.js
  • plugin/plugins/study_companion/static/math-parser.js
  • plugin/plugins/study_companion/store_notebook.py
  • plugin/plugins/study_companion/surfaces/note_editor.tsx
  • plugin/plugins/study_companion/surfaces/study_panel.tsx
  • plugin/plugins/study_companion/tutor_llm_agent_notebook.py
  • plugin/tests/unit/plugins/test_study_companion_neko_commands.py
  • plugin/tests/unit/plugins/test_study_companion_phase9_ux.py
  • plugin/tests/unit/plugins/test_study_notebook.py
  • tests/unit/test_core_game_route_memory_contract.py
✅ Files skipped from review due to trivial changes (1)
  • plugin/plugins/study_companion/i18n/ru.json
🚧 Files skipped from review as they are similar to previous changes (11)
  • plugin/plugins/study_companion/i18n/pt.json
  • plugin/plugins/study_companion/i18n/es.json
  • plugin/tests/unit/plugins/test_study_companion_phase9_ux.py
  • tests/unit/test_core_game_route_memory_contract.py
  • plugin/plugins/study_companion/static/index.html
  • plugin/plugins/study_companion/surfaces/study_panel.tsx
  • main_logic/core.py
  • plugin/plugins/study_companion/entry_notebook.py
  • plugin/plugins/study_companion/surfaces/note_editor.tsx
  • plugin/plugins/study_companion/store_notebook.py
  • plugin/tests/unit/plugins/test_study_companion_neko_commands.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/agent_server.py`:
- Around line 1679-1706: 函数 _voice_bridge_action_from_dispatch_results
不应直接返回第一个成功结果以避免并发返回顺序覆盖优先级;请改为遍历 dispatch_results 收集所有有效的成功结果(从每个 item 的
result.action、source_plugin/plugin_id、source_event_id 等字段构建候选 payload 列表,同时统计
failure_count),然后将该候选列表交给统一的 voice-contract 仲裁合并逻辑(例如新增或调用
voice_contract_arbitrate 或类似函数)来选择/合并最终动作,再返回仲裁后的 payload;保留原来的 failure_count
处理并在无可仲裁结果时调用 _voice_bridge_noop("no_handler_result", failures=failure_count)。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e2010fc-ed12-4b8a-b6e3-de53219a6e45

📥 Commits

Reviewing files that changed from the base of the PR and between 9226840 and 077b5d6.

📒 Files selected for processing (7)
  • app/agent_server.py
  • plugin/plugins/study_companion/__init__.py
  • plugin/plugins/study_companion/voice_contracts.py
  • plugin/server/application/plugins/dispatch_service.py
  • plugin/tests/unit/plugins/test_study_companion_voice_contracts.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
  • tests/unit/test_agent_server_voice_bridge.py
💤 Files with no reviewable changes (2)
  • plugin/server/application/plugins/dispatch_service.py
  • plugin/tests/unit/server/test_plugin_dispatch_service.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/test_agent_server_voice_bridge.py
  • plugin/plugins/study_companion/init.py

Comment thread app/agent_server.py Outdated
@MomiJiSan MomiJiSan marked this pull request as ready for review May 31, 2026 14:53
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dc5c5137f0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

snippet = ?,
topic_ids = ?,
tags = ?,
word_count = ?,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update AI-generated flag on note edits

When study_note_upsert is used to save AI-generated content over an existing note, the entry passes is_ai_generated into NotebookStore.upsert_note, but the existing-note UPDATE never writes that column. In that scenario the note keeps its previous manual/AI marker even though the caller explicitly supplied the new metadata, so lists/exports that rely on is_ai_generated report stale provenance.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a55704908

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +152 to +156
const payload = await callPlugin<{ content?: string }>('study_note_ai_expand', {
note_id: noteId,
content,
topic_context: topics,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait long enough for AI note expansion

This surface calls study_note_ai_expand, whose entry timeout is 75s, through the shared callPlugin helper that stops polling after 30s. When the model legitimately takes more than 30 seconds, the editor reports Plugin call timed out even though the backend run is still allowed to finish, so users lose the AI-expanded note result from the UI. Please give this call a longer polling budget or add a timeout override to callPlugin for long-running AI entries.

Useful? React with 👍 / 👎.

@wehos
Copy link
Copy Markdown
Contributor

wehos commented Jun 1, 2026

合并遥遥无期x2了

@MomiJiSan MomiJiSan force-pushed the feat/study-companion-notebook branch from 8a55704 to c4f0e1a Compare June 1, 2026 09:44
@MomiJiSan MomiJiSan force-pushed the feat/study-companion-notebook branch from c4f0e1a to 496ade1 Compare June 1, 2026 09:47
@MomiJiSan
Copy link
Copy Markdown
Contributor Author

这个 PR 做了什么
这个 PR 给 study_companion 插件新增了完整的伴学笔记本能力,并增强了伴学语音交互。

主要包括:

新增伴学笔记本:支持创建、更新、搜索、导出学习笔记,并能从当前学习会话内容生成结构化笔记。
新增 notebook 相关插件 entry 和 NEKO command,让猫娘可以把讲解、问答、复习内容沉淀成笔记。
新增笔记 UI:note card、note editor、note search、notebook panel,并补了 KaTeX/数学表达式渲染。
新增语音 transcript 接管链路:宿主收到实时语音转写后,发 voice_transcript custom event 给插件;study_companion 自己做过滤、判断和动作生成。
收敛边界:宿主只负责发事件和接收 {action},cancel_response / prime_context 等动作定义、仲裁、过滤逻辑都放回 plugin/plugins/study_companion。
修复 review comment:包括 partial upsert 不再覆盖未传字段、session source 先按 session 过滤再 limit、cancel 失败不再误报成功、i18n、prompt 本地化、auto-save 脏检查、数学 parser 去重、测试 mock 修正等。
补充大量单测,覆盖 notebook store/entry、NEKO commands、voice filter、voice bridge、event bus、plugin dispatch、UI math parser 等。
为什么要这样做
核心目标是把“学习内容沉淀”和“伴学语音判断”收敛到插件内部,而不是让宿主承载伴学业务语义。

宿主仍然需要一个很小的 hook,因为实时语音 transcript 和 realtime session 的 cancel_response / prime_context 执行动作在宿主侧。但宿主不再判断“这句话是不是自言自语、是不是要打断、要注入什么上下文”,这些都由 study_companion 插件决定。这样边界更合理:

插件负责业务:学习场景、过滤规则、动作定义、笔记生成。
宿主负责通道:转发 transcript、等待插件结果、执行通用 session action。
后续插件逻辑可以继续演进,不需要继续污染宿主层。
review 里指出的数据一致性、重复逻辑和本地化问题也一起收掉,PR 更容易合并。

@MomiJiSan
Copy link
Copy Markdown
Contributor Author

🔀 此 PR 已按 reviewer 要求拆分为两个 PR

原 PR 包含的变更已拆分为:

PR 内容 文件数
#1609 Neko 本体:voice bridge + plugin dispatch 基础设施 10
#1610 study_companion 插件:notebook + voice filter + UI + KaTeX 109

审查顺序:请先审查 #1609(基础设施),再审查 #1610(插件本体)。两个 PR 按此顺序合并。

本 PR 将关闭,后续审查请在上述两个 PR 中进行。

@MomiJiSan
Copy link
Copy Markdown
Contributor Author

🔀 此 PR 已按 reviewer 要求拆分为两个 PR(第二次拆分,无文件重叠)

PR 内容 文件数 零重叠验证
#1612 Neko 本体:voice bridge + plugin dispatch 基础设施 10
#1613 study_companion 插件:notebook + voice filter + KaTeX + i18n 109

两个 PR 的文件零重叠,均可独立审查。审查顺序:先 #1612(基础设施),后 #1613(插件本体)。

MomiJiSan added a commit to MomiJiSan/N.E.K.O that referenced this pull request Jun 4, 2026
Implement the complete study_companion plugin capabilities:

Notebook System:
- Note data model, SQLite schema, FTS5 search, CRUD store
  (store_notebook.py, store_schema.py)
- Plugin entries: note create/update/query/cross-module search
  (entry_notebook.py)
- Frontend UI: note_card, note_editor, note_search, notebook_panel
- AI note expansion and summarization (tutor_llm_agent_notebook.py)
- KaTeX math rendering with full font set and CSS/JS
- jieba Chinese text segmentation for search (pyproject.toml dep)

Voice Interaction:
- Voice transcript filter with subject detection and keyword matching
  (voice_filter.py)
- Voice contracts for cancel_response / prime_context actions
  (voice_contracts.py)
- Integration with neko voice bridge infrastructure

Localization:
- 10+ language i18n support for notebook, voice, UI entries

This is Part 2 of the original PR Project-N-E-K-O#1582 split.
Depends on Part 1 (voice bridge + plugin dispatch infrastructure).
MomiJiSan added a commit to MomiJiSan/N.E.K.O that referenced this pull request Jun 4, 2026
Implement the complete study_companion plugin capabilities:

Notebook System:
- Note data model, SQLite schema, FTS5 search, CRUD store
  (store_notebook.py, store_schema.py)
- Plugin entries: note create/update/query/cross-module search
  (entry_notebook.py)
- Frontend UI: note_card, note_editor, note_search, notebook_panel
- AI note expansion and summarization (tutor_llm_agent_notebook.py)
- KaTeX math rendering with full font set and CSS/JS
- jieba Chinese text segmentation for search (pyproject.toml dep)

Voice Interaction:
- Voice transcript filter with subject detection and keyword matching
  (voice_filter.py)
- Voice contracts for cancel_response / prime_context actions
  (voice_contracts.py)
- Integration with neko voice bridge infrastructure

Localization:
- 10+ language i18n support for notebook, voice, UI entries

This is Part 2 of the original PR Project-N-E-K-O#1582 split.
Depends on Part 1 (voice bridge + plugin dispatch infrastructure).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants