实现猫娘伴学笔记本与伴学交互增强#1582
Conversation
|
Caution Review failedFailed to post review comments Walkthrough本PR引入完整的语音转写转发桥接、插件笔记CRUD系统、前端编辑与KaTeX数学渲染的大规模功能整合喵。从Agent语音处理、事件总线等待机制、到笔记存储全文搜索、Tutor LLM扩展、最后到React前端UI与数学公式渲染,构成端到端的学习伴侣音声+笔记体验喵~ Changes语音转写桥接链路
插件分发与语音仲裁
笔记系统后端
笔记插件接口与Tutor LLM
前端笔记UI与数学渲染
本地化与文档
全系统单元测试
Poem
🎯 4 (Complex) | ⏱️ ~75 minutes |
There was a problem hiding this comment.
💡 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".
| notebook_id=notebook_id or None, | ||
| title=title, | ||
| content=content, | ||
| topic_ids=topic_ids or [], | ||
| tags=tags or [], |
There was a problem hiding this comment.
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 👍 / 👎.
| 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: |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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_list和study_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)来判断异常是否可以安全忽略。但是:
- Line 447-454 的
_is_safe_cancel_exception依赖异常类型名称字符串匹配(如 "ResourceNotFound"),这个很脆弱- 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()来判断是否应用单词边界正则匹配,但是:
- 如果名字包含连字符或下划线(如 "Neko-chan"),
isalnum()会返回False,导致回退到普通的find(),可能会误匹配- 对于类似 "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)。但是:
- 化学公式
H2O等也可能出现在物理题目中(如热学、流体力学)- 物理关键词"速度"、"加速度"也可能出现在数学的运动学应用题中
虽然这个优先级对大多数情况是合理的,但在边界 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
⛔ Files ignored due to path filters (62)
plugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_AMS-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Bold.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Caligraphic-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Bold.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Fraktur-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Main-Bold.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Main-BoldItalic.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Main-Italic.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Main-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Math-BoldItalic.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Math-Italic.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Bold.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Italic.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_SansSerif-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Script-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Size1-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Size2-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Size3-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Size4-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.ttfis excluded by!**/*.ttfplugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.woffis excluded by!**/*.woffplugin/plugins/study_companion/static/fonts/KaTeX_Typewriter-Regular.woff2is excluded by!**/*.woff2plugin/plugins/study_companion/static/katex.min.jsis excluded by!**/*.min.jsuv.lockis excluded by!**/*.lock
📒 Files selected for processing (63)
app/agent_server.pyapp/main_server.pyfrontend/plugin-manager/scripts/check-hosted-tsx.mjsmain_logic/agent_event_bus.pymain_logic/core.pyplugin/plugins/study_companion/__init__.pyplugin/plugins/study_companion/constants.pyplugin/plugins/study_companion/doc_exporter.pyplugin/plugins/study_companion/entry_common.pyplugin/plugins/study_companion/entry_export_support.pyplugin/plugins/study_companion/entry_knowledge_entries.pyplugin/plugins/study_companion/entry_memory_review_entries.pyplugin/plugins/study_companion/entry_neko_commands.pyplugin/plugins/study_companion/entry_notebook.pyplugin/plugins/study_companion/entry_pomodoro_entries.pyplugin/plugins/study_companion/entry_status_entries.pyplugin/plugins/study_companion/entry_tutor_explain_entries.pyplugin/plugins/study_companion/entry_tutor_question_entries.pyplugin/plugins/study_companion/i18n/en.jsonplugin/plugins/study_companion/i18n/es.jsonplugin/plugins/study_companion/i18n/ja.jsonplugin/plugins/study_companion/i18n/ko.jsonplugin/plugins/study_companion/i18n/pt.jsonplugin/plugins/study_companion/i18n/ru.jsonplugin/plugins/study_companion/i18n/zh-CN.jsonplugin/plugins/study_companion/i18n/zh-TW.jsonplugin/plugins/study_companion/models.pyplugin/plugins/study_companion/onboarding.mdplugin/plugins/study_companion/plugin.tomlplugin/plugins/study_companion/service.pyplugin/plugins/study_companion/static/index.htmlplugin/plugins/study_companion/static/katex-render.jsplugin/plugins/study_companion/static/katex.min.cssplugin/plugins/study_companion/static/main.jsplugin/plugins/study_companion/static/style.cssplugin/plugins/study_companion/store_notebook.pyplugin/plugins/study_companion/store_schema.pyplugin/plugins/study_companion/surfaces/note_card.tsxplugin/plugins/study_companion/surfaces/note_editor.tsxplugin/plugins/study_companion/surfaces/note_search.tsxplugin/plugins/study_companion/surfaces/notebook_panel.tsxplugin/plugins/study_companion/surfaces/study_panel.tsxplugin/plugins/study_companion/tutor_llm_agent.pyplugin/plugins/study_companion/tutor_llm_agent_notebook.pyplugin/plugins/study_companion/voice_filter.pyplugin/server/application/plugins/dispatch_service.pyplugin/server/application/plugins/event_contracts.pyplugin/server/application/plugins/voice_transcript_bridge.pyplugin/tests/integration/test_study_companion_neko_command_roundtrip.pyplugin/tests/unit/plugins/test_study_companion.pyplugin/tests/unit/plugins/test_study_companion_neko_commands.pyplugin/tests/unit/plugins/test_study_companion_phase9_ux.pyplugin/tests/unit/plugins/test_study_companion_voice_bridge.pyplugin/tests/unit/plugins/test_study_companion_voice_filter.pyplugin/tests/unit/plugins/test_study_companion_voice_scenarios.pyplugin/tests/unit/plugins/test_study_notebook.pyplugin/tests/unit/server/test_plugin_dispatch_service.pyplugin/tests/unit/server/test_plugin_event_contracts.pyplugin/tests/unit/server/test_plugin_voice_transcript_bridge.pypyproject.tomltests/unit/test_agent_server_voice_bridge.pytests/unit/test_core_game_route_memory_contract.pytests/unit/test_voice_bridge_event_bus.py
There was a problem hiding this comment.
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
📒 Files selected for processing (17)
main_logic/core.pyplugin/plugins/study_companion/entry_notebook.pyplugin/plugins/study_companion/i18n/es.jsonplugin/plugins/study_companion/i18n/ko.jsonplugin/plugins/study_companion/i18n/pt.jsonplugin/plugins/study_companion/i18n/ru.jsonplugin/plugins/study_companion/static/index.htmlplugin/plugins/study_companion/static/katex-render.jsplugin/plugins/study_companion/static/math-parser.jsplugin/plugins/study_companion/store_notebook.pyplugin/plugins/study_companion/surfaces/note_editor.tsxplugin/plugins/study_companion/surfaces/study_panel.tsxplugin/plugins/study_companion/tutor_llm_agent_notebook.pyplugin/tests/unit/plugins/test_study_companion_neko_commands.pyplugin/tests/unit/plugins/test_study_companion_phase9_ux.pyplugin/tests/unit/plugins/test_study_notebook.pytests/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
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
app/agent_server.pyplugin/plugins/study_companion/__init__.pyplugin/plugins/study_companion/voice_contracts.pyplugin/server/application/plugins/dispatch_service.pyplugin/tests/unit/plugins/test_study_companion_voice_contracts.pyplugin/tests/unit/server/test_plugin_dispatch_service.pytests/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
There was a problem hiding this comment.
💡 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 = ?, |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
💡 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".
| const payload = await callPlugin<{ content?: string }>('study_note_ai_expand', { | ||
| note_id: noteId, | ||
| content, | ||
| topic_context: topics, | ||
| }); |
There was a problem hiding this comment.
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 👍 / 👎.
|
合并遥遥无期x2了 |
8a55704 to
c4f0e1a
Compare
c4f0e1a to
496ade1
Compare
|
这个 PR 做了什么 主要包括: 新增伴学笔记本:支持创建、更新、搜索、导出学习笔记,并能从当前学习会话内容生成结构化笔记。 宿主仍然需要一个很小的 hook,因为实时语音 transcript 和 realtime session 的 cancel_response / prime_context 执行动作在宿主侧。但宿主不再判断“这句话是不是自言自语、是不是要打断、要注入什么上下文”,这些都由 study_companion 插件决定。这样边界更合理: 插件负责业务:学习场景、过滤规则、动作定义、笔记生成。 |
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).
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).
概述
实现猫娘伴学笔记本能力,并补齐伴学插件相关的命令、语音桥接与前端交互支撑。
主要变更
study_note_*插件入口,支持笔记创建、更新、查询、跨模块搜索验证
ruff check通过py_compile通过npm run check-hosted-tsx通过pytest plugin/tests/unit/plugins/test_study_notebook.py:8 passed风险说明
GitNexus
detect_changes已在提交前执行,staged 变更风险等级为 medium,未发现 HIGH/CRITICAL 阻塞项。Summary by CodeRabbit
发布说明
新功能
UI 增强
文档