feat(plugin): add Hitokoto quote plugin - #2474
Conversation
Walkthrough新增 Hitokoto 一言插件,支持随机与每日引文、首次聊天问候、缓存并发控制、管理面板、多语言界面及测试;同时扩展用户话语事件的跨进程发布、校验、去重与上下文写入链路喵。 ChangesHitokoto 插件功能
用户话语观测事件
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile Summary新增 Hitokoto 一言插件,并完成真实用户话语到 Agent 插件运行时的跨进程桥接喵。
Confidence Score: 5/5当前实现看起来可以安全合并,先前报告的三个缓存代际问题均已修复喵。 当前代码会在设置变化时推进缓存代际、清除内存缓存并隔离旧持久化记录;旧 flight 的 leader 与 waiter 都会在返回前检测代际变化并使用最新设置重试,因此没有剩余的阻塞性失败喵。
|
| Filename | Overview |
|---|---|
| plugin/plugins/hitokoto/init.py | 实现一言获取、设置持久化、代际化每日缓存、跨事件循环 single-flight、每日问候和管理入口;先前报告的旧代际返回路径已完整处理喵。 |
| main_logic/core/turn.py | 将经过裁剪和规范化的真实用户话语同步写入兼容镜像,并以 best-effort 方式异步转发至 Agent 进程喵。 |
| main_logic/agent_event_bus.py | 新增带严格类型、长度和时间戳校验的用户话语跨进程事件发布接口喵。 |
| app/agent_server/api_runtime.py | 验证、去重并将用户话语事件写入 Agent 所拥有的插件上下文存储喵。 |
| plugin/plugins/hitokoto/static/index.html | 新增 Hosted UI,用于状态查看、设置管理、API 测试、缓存清理和即时试用喵。 |
| plugin/tests/unit/plugins/test_hitokoto.py | 覆盖 API 校验、缓存竞争、设置代际、持久化、每日问候、SDK 元数据和 Hosted UI 合同喵。 |
Sequence Diagram
sequenceDiagram
participant U as User
participant M as Main conversation runtime
participant A as Agent server
participant P as Hitokoto plugin
participant H as Hitokoto API
U->>M: Submit text or voice utterance
M->>M: Record compatibility mirror
M-->>A: Publish bounded utterance event
A->>A: Validate and deduplicate event
A->>P: Append plugin memory observation
P->>P: Detect first chat of local day
P->>H: Fetch daily quote when cache misses
H-->>P: Return quote
P->>M: Push quote with respond behavior
M-->>U: Character shares quote naturally
Reviews (4): Last reviewed commit: "fix(hitokoto): retry stale daily flights" | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
plugin/plugins/hitokoto/__init__.py (3)
248-255: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value这里的
if uuid分支永远为真喵~Line 249 已经在
not uuid时提前return None了,所以下面的三元判断和https://hitokoto.cn/兜底根本走不到,属于死代码喵。♻️ 简化建议
- trace_url = ( - f"https://hitokoto.cn?uuid={url_quote(uuid, safe='')}" - if uuid - else "https://hitokoto.cn/" - ) + trace_url = f"https://hitokoto.cn?uuid={url_quote(uuid, safe='')}"🤖 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/hitokoto/__init__.py` around lines 248 - 255, Remove the unreachable conditional fallback in the trace_url assignment after the validation in the surrounding Hitokoto parsing function. Since missing uuid already returns None, always construct the URL using the quoted uuid while preserving the existing URL encoding behavior.
1487-1523: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value内存缓存已经清掉了却返回
Err,状态和结果对不上喵~Line 1496-1498 已经推进了
_cache_generation、清空_memory_daily并置_ignore_persisted_daily = True,之后持久化删除失败才return Err。调用方看到失败,但实际内存缓存确实已清除,面板上会显示"清除失败"却又拿不到旧缓存喵。考虑返回
Ok({"cleared": True, "persisted": 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/hitokoto/__init__.py` around lines 1487 - 1523, Update clear_daily_cache so a failed _store_delete does not return Err after the in-memory cache has already been cleared. Preserve the cleared state and return Ok with cleared=True, persisted=False, and the existing previous_date when persistence fails; ensure callers can distinguish the incomplete persistence outcome and present it as such.
608-684: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value三个 store 包装器几乎一模一样,抽一个小 helper 会更清爽喵~
_store_get/_store_set/_store_delete的 try/except +Err+ 非Ok三段判定逻辑完全重复,只有操作名和失败返回值不同。可以抽成一个_store_call(op_name, key, coro_factory, failure_value)之类的内部方法,未来再加 store 操作时也不用复制粘贴喵。(非阻塞项,你要是嫌麻烦本喵也不逼你啦~)🤖 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/hitokoto/__init__.py` around lines 608 - 684, Extract the duplicated store operation handling from _store_get, _store_set, and _store_delete into a shared internal helper such as _store_call, parameterized by operation name, key, coroutine factory, and failure value. Preserve each wrapper’s existing disabled-store behavior, success value handling for _store_get, and operation-specific failure returns and warning messages.plugin/tests/unit/plugins/test_hitokoto.py (1)
899-921: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win跨线程测试靠固定 delay/timeout,在慢 CI 上有点容易翻车喵~
delay=0.05、barrier.wait(timeout=1)、join(timeout=2)这套组合在负载高的 runner 上可能偶发失败。把 join 超时放宽(比如 10s)能明显降低 flaky 概率,同时不影响正常情况下的执行速度喵。Also applies to: 1092-1115
🤖 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/tests/unit/plugins/test_hitokoto.py` around lines 899 - 921, In test_cross_loop_daily_calls_share_single_flight and the additionally affected cross-thread test, increase the thread join timeout from 2 seconds to approximately 10 seconds while preserving the existing barrier timeout, assertions, and normal execution behavior.
🤖 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 `@plugin/plugins/hitokoto/static/index.html`:
- Around line 713-715: 扩展 SUPPORTED_LOCALES 和 normalizeLocale(),纳入已发布的 es、ja、ko
资源,并单独支持 zh-TW;确保这些 locale 能被规范化并选择对应语言包,而不是回退到 zh-CN,同时保留现有 zh-CN 和 en 的行为。
- Around line 1112-1118: Update the error/finalization flow around the initial
panel-state request so a failed first load does not leave the panel permanently
disabled. In the catch/finally logic near setBusy and initialLoadSucceeded,
clear the busy state after failure while preserving the existing busy behavior
for successful initial loading and subsequent requests.
---
Nitpick comments:
In `@plugin/plugins/hitokoto/__init__.py`:
- Around line 248-255: Remove the unreachable conditional fallback in the
trace_url assignment after the validation in the surrounding Hitokoto parsing
function. Since missing uuid already returns None, always construct the URL
using the quoted uuid while preserving the existing URL encoding behavior.
- Around line 1487-1523: Update clear_daily_cache so a failed _store_delete does
not return Err after the in-memory cache has already been cleared. Preserve the
cleared state and return Ok with cleared=True, persisted=False, and the existing
previous_date when persistence fails; ensure callers can distinguish the
incomplete persistence outcome and present it as such.
- Around line 608-684: Extract the duplicated store operation handling from
_store_get, _store_set, and _store_delete into a shared internal helper such as
_store_call, parameterized by operation name, key, coroutine factory, and
failure value. Preserve each wrapper’s existing disabled-store behavior, success
value handling for _store_get, and operation-specific failure returns and
warning messages.
In `@plugin/tests/unit/plugins/test_hitokoto.py`:
- Around line 899-921: In test_cross_loop_daily_calls_share_single_flight and
the additionally affected cross-thread test, increase the thread join timeout
from 2 seconds to approximately 10 seconds while preserving the existing barrier
timeout, assertions, and normal execution behavior.
🪄 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: e7255bbe-dec1-407f-b31b-94fc5a31864b
📒 Files selected for processing (13)
plugin/plugins/hitokoto/README.mdplugin/plugins/hitokoto/__init__.pyplugin/plugins/hitokoto/i18n/en.jsonplugin/plugins/hitokoto/i18n/es.jsonplugin/plugins/hitokoto/i18n/ja.jsonplugin/plugins/hitokoto/i18n/ko.jsonplugin/plugins/hitokoto/i18n/pt.jsonplugin/plugins/hitokoto/i18n/ru.jsonplugin/plugins/hitokoto/i18n/zh-CN.jsonplugin/plugins/hitokoto/i18n/zh-TW.jsonplugin/plugins/hitokoto/plugin.tomlplugin/plugins/hitokoto/static/index.htmlplugin/tests/unit/plugins/test_hitokoto.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40fb2260c7
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
plugin/tests/unit/plugins/test_hitokoto.py (2)
2261-2296: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff契约断言直接绑死源码字符串,改一个空格就红一片喵。
这一串
in submit_handler断言等于把 JS 实现细节抄了一遍,后续任何无害的重构(换变量名、换行、Prettier 一跑)都会误报。下面test_static_panel_submits_valid_settings_with_exact_payload已经用真实执行覆盖了同样的行为,这里可以只保留少量真正的契约点(例如checkValidity先于runAction),其余交给行为测试就够啦~ 当然,如果是刻意要防回归,保持现状也不是不行喵。🤖 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/tests/unit/plugins/test_hitokoto.py` around lines 2261 - 2296, 简化 test_static_panel_settings_submit_contract,移除对具体变量名、格式、日志及实现字符串的逐项断言,避免无害重构导致测试脆弱;仅保留必要的提交契约断言,例如 event.currentTarget.checkValidity() 在 void runAction 之前执行,并依赖下方真实执行测试覆盖有效载荷行为。
41-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value内联脚本提取方式有点脆弱喵~
rsplit("<script>", maxsplit=1)取的是最后一个 script 块,一旦index.html以后追加尾部脚本(哪怕只是个 analytics 片段),所有run_panel_js场景就会静默地跑错代码块,报错信息还会非常难懂喵。加一条断言把假设钉死会舒服很多的说~🧷 建议加一层保护
html = PANEL_HTML.read_text(encoding="utf-8") + assert html.count("<script>") == 1, "面板只应有一个内联脚本块" inline_script = html.rsplit("<script>", maxsplit=1)[1].split(🤖 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/tests/unit/plugins/test_hitokoto.py` around lines 41 - 55, 在 run_panel_js 中为内联脚本提取增加显式断言,验证 PANEL_HTML 仍符合“最后一个 script 块就是面板脚本”的假设;若 script 块数量或结构发生变化,应立即以清晰错误失败,而不是继续静默执行错误脚本。保留现有脚本提取和测试暴露逻辑。main_logic/core/__init__.py (1)
88-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win注释清单忘记加新名字啦,笨蛋主人喵!
第171-188行的注释专门枚举了所有需要通过
_core_facade.<attr>late-binding 读取(而非普通 rebind)的 patch 目标,比如publish_analyze_request_reliably、publish_voice_transcript_observed_best_effort等。现在新加的publish_user_utterance_observed(本行)也是走同一套机制(turn.py里用_core_facade.publish_user_utterance_observed读取,测试里也确实monkeypatch.setattr(core_facade, "publish_user_utterance_observed", ...)),但没被列进那份清单,容易误导后来者喵。📝 建议补充注释
# get_tts_worker, publish_analyze_request_reliably and -# publish_voice_transcript_observed_best_effort therefore keeps working +# publish_voice_transcript_observed_best_effort, publish_user_utterance_observed +# therefore keeps working # through a different mechanism: the mixins do not from-import those symbols🤖 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 `@main_logic/core/__init__.py` at line 88, 在第171-188行列举 `_core_facade.<attr>` late-binding patch 目标的注释清单中补充 `publish_user_utterance_observed`,并保持其余注释内容和导出代码不变。main_logic/agent_event_bus.py (1)
365-407: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win发布侧和接收侧的校验逻辑重复了两遍,笨蛋主人快去合并一下喵! 两处都对
event_id/timestamp/lanlan_name/content/is_voice做了完全相同的类型检查、长度上限判断与 strip 归一化,共享同一组USER_UTTERANCE_*_MAX_CHARS常量却各写了一份校验代码,以后改规则容易漏掉一处、两侧行为分叉喵。
main_logic/agent_event_bus.py#L365-L407:把这段校验+归一化抽成一个共享纯函数(例如_normalize_and_validate_user_utterance_fields(event_id, timestamp, lanlan_name, content, is_voice) -> Optional[dict]),本函数只负责调用它再转发给publish_session_event。app/agent_server/api_runtime.py#L196-L307:_handle_user_utterance_observed改为 import 并复用main_logic.agent_event_bus里抽出的同一个校验函数,只保留去重与写入 bucket 这部分接收侧特有的逻辑。♻️ 抽取共享校验函数示意
# main_logic/agent_event_bus.py def normalize_user_utterance_fields( *, event_id, timestamp, lanlan_name, content, is_voice, ) -> Optional[dict]: """Shared validation/normalization for the user_utterance_observed contract.""" if type(event_id) is not str: return None normalized_event_id = event_id.strip() if not normalized_event_id or len(normalized_event_id) > USER_UTTERANCE_EVENT_ID_MAX_CHARS: return None if type(timestamp) is not float or not math.isfinite(timestamp) or timestamp <= 0: return None if type(lanlan_name) is not str: return None normalized_lanlan = lanlan_name.strip() or "default" if len(normalized_lanlan) > USER_UTTERANCE_LANLAN_MAX_CHARS: return None if type(content) is not str: return None normalized_content = content.strip() if not normalized_content or len(normalized_content) > USER_UTTERANCE_CONTENT_MAX_CHARS: return None if type(is_voice) is not bool: return None return { "event_id": normalized_event_id, "timestamp": timestamp, "lanlan_name": normalized_lanlan, "content": normalized_content, "is_voice": is_voice, }两处调用方各自消费返回的
dict(或None表示拒绝),行为与现状完全一致,只是消灭了重复代码喵。🤖 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 `@main_logic/agent_event_bus.py` around lines 365 - 407, Extract the duplicated user-utterance validation and normalization into a shared pure function in main_logic/agent_event_bus.py around publish_user_utterance_observed, returning normalized fields or None while preserving all existing checks and defaults. Update publish_user_utterance_observed to call it before publish_session_event, and update app/agent_server/api_runtime.py lines 196-307 in _handle_user_utterance_observed to import and reuse the shared function, retaining only receiver-specific deduplication and bucket-writing logic.
🤖 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/turn.py`:
- Around line 692-709: 将 _core_facade.publish_user_utterance_observed 的协程构造与
fire_task 调度统一纳入同一个 try 块,确保属性访问或调用异常也被 best-effort 处理并记录 debug
日志,同时仅在协程已成功创建且可关闭时执行 close,避免异常向 handle_input_transcript 冒泡;在
tests/test_user_utterance_bus_publish.py 补充 fire_task 抛异常后协程被正确 close
且异常不外传的回归测试。
---
Nitpick comments:
In `@main_logic/agent_event_bus.py`:
- Around line 365-407: Extract the duplicated user-utterance validation and
normalization into a shared pure function in main_logic/agent_event_bus.py
around publish_user_utterance_observed, returning normalized fields or None
while preserving all existing checks and defaults. Update
publish_user_utterance_observed to call it before publish_session_event, and
update app/agent_server/api_runtime.py lines 196-307 in
_handle_user_utterance_observed to import and reuse the shared function,
retaining only receiver-specific deduplication and bucket-writing logic.
In `@main_logic/core/__init__.py`:
- Line 88: 在第171-188行列举 `_core_facade.<attr>` late-binding patch 目标的注释清单中补充
`publish_user_utterance_observed`,并保持其余注释内容和导出代码不变。
In `@plugin/tests/unit/plugins/test_hitokoto.py`:
- Around line 2261-2296: 简化
test_static_panel_settings_submit_contract,移除对具体变量名、格式、日志及实现字符串的逐项断言,避免无害重构导致测试脆弱;仅保留必要的提交契约断言,例如
event.currentTarget.checkValidity() 在 void runAction 之前执行,并依赖下方真实执行测试覆盖有效载荷行为。
- Around line 41-55: 在 run_panel_js 中为内联脚本提取增加显式断言,验证 PANEL_HTML 仍符合“最后一个 script
块就是面板脚本”的假设;若 script 块数量或结构发生变化,应立即以清晰错误失败,而不是继续静默执行错误脚本。保留现有脚本提取和测试暴露逻辑。
🪄 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: f40c4c36-a0ac-4a56-92d1-711e1a05e57e
📒 Files selected for processing (17)
app/agent_server/api_runtime.pymain_logic/agent_event_bus.pymain_logic/core/__init__.pymain_logic/core/turn.pyplugin/plugins/hitokoto/__init__.pyplugin/plugins/hitokoto/i18n/en.jsonplugin/plugins/hitokoto/i18n/es.jsonplugin/plugins/hitokoto/i18n/ja.jsonplugin/plugins/hitokoto/i18n/ko.jsonplugin/plugins/hitokoto/i18n/pt.jsonplugin/plugins/hitokoto/i18n/ru.jsonplugin/plugins/hitokoto/i18n/zh-CN.jsonplugin/plugins/hitokoto/i18n/zh-TW.jsonplugin/plugins/hitokoto/static/index.htmlplugin/tests/unit/plugins/test_hitokoto.pytests/test_user_utterance_bus_publish.pytests/unit/test_agent_runtime_user_utterance_event.py
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
Project-N-E-K-O/N.E.K.O.-PC(manual)
🚧 Files skipped from review as they are similar to previous changes (8)
- plugin/plugins/hitokoto/i18n/en.json
- plugin/plugins/hitokoto/i18n/es.json
- plugin/plugins/hitokoto/i18n/zh-TW.json
- plugin/plugins/hitokoto/i18n/pt.json
- plugin/plugins/hitokoto/i18n/zh-CN.json
- plugin/plugins/hitokoto/i18n/ja.json
- plugin/plugins/hitokoto/static/index.html
- plugin/plugins/hitokoto/init.py
Invalidate settings-sensitive caches across restarts, harden the user utterance relay boundary, and make Hosted UI tests decode Node output as UTF-8 on Windows.\n\nVerified by two independent read-only reviews, focused tests, integration tests, Ruff, package verification, and the PR report gate.
Retry current-generation daily quote work when settings or cache invalidation makes an in-flight leader or waiter stale, without swallowing caller cancellation.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
改动概述 / Summary
新增内置
hitokoto插件:random_quote与daily_quote同时注册为普通插件入口和聊天可见的 LLM toolszh-CN、zh-TW、en、ja、ko、es、pt、ru用户可见行为 / User-facing Behavior
a–l官方分类push_message(..., ai_behavior="respond")把内容交给当前角色,由角色用自己的口吻表达hitokoto_random_quotehitokoto_daily_quote回归报告 / Regression Report
本 PR 修改了
main_logic/core/__init__.py与main_logic/core/turn.py,用于把真实、非空的用户话语以 best-effort 方式发布到插件进程事件总线,从而让每日首次聊天分享在真实宿主链路中生效。回归边界:
不拆分理由 / Why Not Split
不拆分。插件的每日首次聊天能力必须依赖同一 PR 中的真实用户话语桥接;若拆开,插件会在生产聊天链路中无法触发。缓存设置身份、Hosted UI、i18n、测试和核心桥接共同构成一个可安装且可验证的完整功能。
scripts/check_pr_report.py --base upstream/main当前结果:19 个变更文件、4 个计入上限、4 个高风险模块文件,均在仓库门限内。测试 / Testing
自动化检查
uv run ruff check .— 通过uv run pytest plugin/tests/unit/plugins/test_hitokoto.py -q— 147 passeduv run pytest plugin/tests/integration/test_neko_plugin_cli_repo_plugins.py -q— 22 passeduv run pytest plugin/tests -q(macOS,初版全量基线)— 2859 passed, 20 skipped, 9 failedgalgame_*/study_companion*Windows 专用路径;没有 Hitokoto 失败windows-latest,用于覆盖这些 Win32 路径scripts/check_pr_report.py --base upstream/main— 通过(19 files / 4 counted / 4 watched)neko_plugin_cli check— 0 errors;仅有内置插件不适用的 standalone-repository scaffolding warningsneko_plugin_cli build、inspect、verify— 通过,payload_hash_verified=True(payload8e98d640fcfadd2054d59db3733e3e9408fefef886b6af34f32961f56567a390)static/index.html宿主与界面验证
main_server /api/tools中确认两个 remote LLM tools 均已注册/runscreate / poll / export 链路通过隐私与外部服务 / Privacy and External Service
https://v1.hitokoto.cn/,不上传聊天内容,不需要账号或密钥textContent渲染;未使用innerHTML、eval等危险 DOM sink开发说明 / Development Note
实现过程使用了 AI 辅助;贡献者对代码、隐私、安全、许可证和测试结果负责,并完成了 Windows 端实际使用验证。
Summary by CodeRabbit