轻小说 AI 总结启动
11 - 轻小说 AI 总结启动
背景
轻小说查询详情页原本支持登录用户手动编辑 novel_ai_summary.intro。本次新增管理员/编辑权限下的 AI 启动入口:在“AI总结”标题区点击 AI 按钮,输入 wenku8 全本 TXT 下载链接后,由服务器下载 TXT、写入缓存、后台启动 opencode,并写入临时启动提示。
架构
novel-search.php (详情页 AI 按钮 + dialog + EventSource)
│
├─ GET /novel-ai-summary-api.php?action=token
│ └─ Typecho 登录态 + editor 权限 + CSRF token
│
├─ GET /novel-ai-summary-api.php?action=models
│ └─ opencode models → 30 分钟 JSON 缓存 → 默认选中模型
│
└─ GET /novel-ai-summary-api.php?action=start&bookid=&url=&model=&_=
│ SSE 进度:校验 → 下载 → 写缓存 → 检查启动环境 → 启动 CLI → 写提示
│
├─ inc/wenku8-txt.php
│ └─ wenku8 链接解析、TXT 下载、GBK/Big5/UTF-8 转换
│
├─ usr/cache/ai_summary_cache/{小说名}.txt
├─ opencode run -m <model> "/novel-summary <txt> <bookid>"
└─ novel_ai_summary.intro = "yyyy-mm-dd hh:mm:ss 启动AI总结"新增 / 修改文件
| 文件 | 说明 |
|---|---|
novel-ai-summary-api.php | 新增 AI 总结启动 API:token + models + SSE start |
usr/themes/classic-22/inc/wenku8-txt.php | 新增 wenku8 TXT 解析/下载/转码共享模块 |
usr/themes/classic-22/novel-search.php | AI 总结标题区新增 AI 按钮和启动弹窗 |
epub-convert-api.php | 改用 inc/wenku8-txt.php,移除重复 TXT 下载逻辑 |
关键实现说明
权限与安全
- AI 启动入口仅对
editor及以上权限显示;后端再次硬校验,未登录返回 401,权限不足返回 403。 - 前端先请求
action=token获取 TypechoWidget\Securitytoken;action=start必须携带_参数并通过hash_equals()校验。 - 前端和后端都只接受
https://dl.wenku8.com/down.php?...&id=<bookid>全本链接;分卷packtxt.php不允许用于 AI 总结。 - 后端强制校验链接中的
id与当前详情页传入的bookid一致,避免误启动其他小说。 - 服务器命令使用
escapeshellarg()分别转义 bin、model、prompt、log path;用户输入不会直接拼入 shell。
模型选择
- 弹窗打开时请求
GET /novel-ai-summary-api.php?action=models,仅editor及以上权限可访问。 - 后端复用
AI_SUMMARY_OPENCODE_BIN和AI_SUMMARY_OPENCODE_CWD执行opencode models,并将可用模型列表缓存到AI_SUMMARY_CACHE_DIR/.opencode-models.json。 - 模型缓存 TTL 固定为 1800 秒;30 分钟内重复打开弹窗优先读取缓存,避免频繁执行 CLI。
- 接口返回
models、default_model、selected_model;若AI_SUMMARY_OPENCODE_MODEL存在于模型列表中,则selected_model为配置模型,否则为第一项。 - 前端加载模型期间禁用模型下拉框和启动按钮;加载失败或列表为空时显示错误并保持启动按钮禁用。
下载与缓存
- 缓存目录通过
AI_SUMMARY_CACHE_DIR配置,建议指向{TYPECHO_ROOT}/usr/cache/ai_summary_cache/;未配置时后端有默认值。 - 文件名来自
novels.title,会移除/ \ : * ? " < > |和控制字符,并限制长度;为空时降级为bookid-<id>.txt。 - TXT 下载会校验实际读取字节数;随后先写
.tmp.<random>,成功后rename()到正式文件,避免 opencode 读取半文件。 - TXT 下载上限复用共享模块默认 30 MB。
opencode 启动
AI_SUMMARY_OPENCODE_MODEL作为默认模型配置:弹窗默认选中它;旧调用未传model时也会回退使用它。- 可选配置:
AI_SUMMARY_OPENCODE_BIN(默认opencode)、AI_SUMMARY_OPENCODE_CWD、AI_SUMMARY_TASK_LOCK_TTL。 - 后端在 TXT 写入缓存后再检查
novel_ai_summary表、opencode 配置和可执行文件,再启动后台命令,通过& echo $!获取后台 pid;接口不等待总结完成。 action=start接受可选model参数;前端传入用户选择的模型,后端校验为provider/model形态后用于opencode run -m。- 启动 prompt 会同时传入 TXT 绝对路径和
bookid,供novel-summaryskill 最终写回novel_ai_summary。 - 成功发起 CLI 后才写入临时 intro:
Y-m-d H:i:s 启动AI总结。
并发控制
- 同一本书启动时会写
.ai-summary-<bookid>.lock。 - 默认 10 分钟内再次启动同一本书会被拒绝,避免重复任务互相覆盖。
- 如果启动失败,lock 会立即删除;启动成功后 lock 保留到 TTL 过期。
配置示例
以下配置写入 config.inc.php,不要写入 Git:
define('AI_SUMMARY_OPENCODE_MODEL', 'your-model-name');
define('AI_SUMMARY_CACHE_DIR', __TYPECHO_ROOT_DIR__ . '/usr/cache/ai_summary_cache/');
define('AI_SUMMARY_OPENCODE_BIN', 'opencode');
// 可选:define('AI_SUMMARY_OPENCODE_CWD', __TYPECHO_ROOT_DIR__);
// 可选:define('AI_SUMMARY_TASK_LOCK_TTL', 600);服务器需确保 Web 进程对缓存目录有写权限,并能执行 opencode。
注意事项
novel_ai_summary表仍按 09 号文档维护,站点代码不自动建表。- opencode 生成最终总结后会自行写库;本站只负责启动任务和写入启动提示。
- 如果前端显示“模型列表获取失败”或“模型列表为空”,需检查 Web 进程是否能执行
opencode models,以及AI_SUMMARY_OPENCODE_CWD下的 opencode 配置是否完整。 - 如果错误提示以“TXT 已缓存”开头,说明下载与落盘已成功,需检查提示中的后续配置、表结构或执行权限问题。