# EPUB 审校器长期维护手册 ## 目标与边界 本工具服务于中日双语 EPUB 的网页审校:长篇连续阅读、逐段修改中文译文、标记翻译问题、导出反馈与修订 EPUB。它也提供可选的书架入口 AI 翻译模块,用于对已入书架的日语 EPUB 发起 API 辅助首译,并生成中日双语或纯中文 EPUB。翻译质量规则仍由项目根目录的 `AGENTS.md`、`rules/translation_rules.md`、当前系列的 `series-style.md` 与术语表共同约束。 AI 翻译模块是可选辅助首译入口,不替代长期项目里的候选词确认、复审、QA 与正式交付流程。用户在网页中留下的编辑、问题备注、长期规则建议,需要由 Codex 后续读取反馈文件后再沉淀到系列风格或候选术语中。网页术语表编辑是用户显式操作正式术语表的入口,保存时会直接写入配置路径指向的 JSON 文件。 ## 数据模型与会话目录 `--review-root` 是唯一需要备份的运行数据目录。典型结构: ```text epub_review_sessions/ app_state.json gpt_config.json library.json series_configs/ .json translation_jobs/ / job.json translations.json translated_outputs/ *_AI翻译_*.epub / source.epub extracted/ review_state/ epub_entries.json rows.json session.json state.json feedback/ translation_feedback.jsonl feedback_for_codex.md exports/ ``` 兼容要求: - `rows.json` 是初始解析结果,不应因用户编辑而重写。 - `state.json.edits` 保存用户修改与标记,字段缺省时必须能回退到原始译文。 - `state.json.reading_position` 保存当前阅读位置,字段包括 `mode`、`scope`、`chapter_id`、`part_id`、`image_item_id`、`row_id`、`scroll_top`、`offset`、`updated_at`;缺省时必须回退到首页/首章。 - `session.json` 与 `app_state.json` 只保存会话定位信息。 - `gpt_config.json` 只属于本地运行环境,不写入 EPUB、不写入导出文件、不通过 API 回传密钥。 - `gpt_config.json` 可保存本地提示词设置与术语表路径,但仍不得写入 EPUB 或导出文件。 - `library.json` 是由 EPUB 元数据和会话状态生成的书架索引,可重建;字段包括书籍、系列、作者、出版社、语言、封面和更新时间。 - `series_configs/.json` 保存同系列共享的 `glossary_path`、`translation_prompt`、`format_prompt`、`character_prompt`,不得保存 API Key、Base URL 或模型密钥。 - `translation_jobs//job.json` 保存整书 AI 翻译任务状态、进度、输出路径和日志;不得保存 API Key。 - `translated_outputs/` 保存 AI 翻译生成的 EPUB;生成后应创建独立 session,使 `/api/sessions` 和书架自然可见。 - 项目根目录的 `打开EPUB审校器.cmd` 是用户一键入口,委托 `tools/epub_review_editor/open_editor.ps1` 创建 `.venv`、安静检测 Flask 依赖、缺失时自动安装依赖、复用同版本已运行服务或启动 `server.py --daemon` 并打开浏览器;该入口不得硬编码用户私有路径。 ## 目录与插图 结构接口 `/api/structure` 应保持向后兼容: - text chapter: `kind: "text"`、`parts`、`row_count`、`touched_count`、`marked_count` - image item: 优先作为 text chapter 的 `items` 子项返回,字段为 `kind: "image"`、`images`、`parts: []`、`row_count: 0`;没有所属文字章时才保留顶层 image chapter - 统计字段同时保留总数和细分:`chapter_count`、`text_chapter_count`、`image_chapter_count`、`top_level_image_chapter_count`、`image_count`、`asset_count`。其中 `image_chapter_count` 表示目录里的图片条目数,不一定是顶层章节数 - part 标题默认使用 `part0000` 形式,真实标题可保留在 `source_title` - 插图通过 `/api/asset/` 读取,必须继续使用 `extracted_path()` 防止路径穿越;只把 raster 图片纳入 `/api/structure` 与 `/api/asset` 白名单,不能任意读取解包目录文件 章节来源优先级: 1. EPUB 内部目录页的中文条目 2. `nav.xhtml` / `toc.xhtml` 3. 正文 `` 标题 4. 文件名 fallback 不要把正文首段或第一句对白作为章节标题 fallback。宁可显示 `part0000`,也不要让目录被正文内容污染。 ## 阅读与侧栏交互 验收口径: - 顶栏左上角必须保留“书架”入口;书架是独立页面,列出 `--review-root` 下历史打开过的审校书籍/会话。 - 点击书架条目必须通过会话打开流程进入对应审校页面,并恢复该书上次阅读位置;从审校页进入书架或切换书籍前必须保存当前阅读位置并沿用未保存编辑保护。 - 没有历史书籍时,书架显示空状态并提供“打开新的 EPUB”入口;开始页/书架页顶栏保持正常显示,不套用审校页沉浸隐藏。 - 书架应按 EPUB 元数据建立分类:全部书籍、同系列、作者、出版社、语言;同系列分类使用稳定 `series_id`,显示书籍数量。 - 书架主区域以封面网格为主,不用横向信息卡替代封面;无封面时使用稳定 fallback,不出现破图或布局跳动。 - 鼠标悬停或键盘 focus 到封面时,封面变暗并显示书名、系列/作者/卷数、审校段数、最近更新时间,以及“校对”“翻译”“删除”按钮;点击“翻译”或“删除”不得触发“校对”。 - 书架删除默认是软删除:只在 session manifest/state 中写入 `hidden` / `deleted_at`,从 `/api/sessions` 与 `/api/library` 隐藏,不删除原始 EPUB、导出 EPUB 或反馈文件;删除 active session 时必须清空 `active_session_id`。 - 选中同系列分类后,必须能打开“系列设置”,设置该系列共享的术语表路径、翻译内容提示词、格式提示词和角色口吻提示词。 - 初始进入审校时,左侧侧栏隐藏,正文占主要空间。 - 顶部“目录”只打开目录,“检索”只打开搜索/段落列表,不合并成一个常驻面板。 - 目录可展开/收起章节,插图作为所属章节的次级目录条目可点击阅读,文字 part 可点击定位。 - 点击目录章节、part 或插图条目后,侧栏不得自动收起;用户仍可点“隐藏”或按 `Escape` 关闭。 - 检索必须保留“当前范围 / 全书全局”切换;当前范围随当前章节或 part 更新,全局检索可跨章节返回并跳转。 - 阅读模式不显示每段编号、快速编辑、快速标记按钮。 - 阅读区提供默认、护眼、夜间背景切换,并允许调整字号与行距;设置可保存在浏览器本地,不要求写入 EPUB 或 session。 - 审校页顶部标题栏与阅读标题栏默认收起,鼠标移到页面上方或键盘焦点进入标题栏时再显示;开始页保持正常显示;展开后不得遮住侧栏“隐藏”或审校工具“关闭”等面板操作。 - 鼠标停留在顶栏或阅读标题栏时,沉浸顶栏不得因自动计时过早收起;前端脚本和样式发布时应带版本参数或等效缓存失效机制,避免浏览器继续执行旧入口代码。 - 阅读区“上一节 / 下一节”必须按 spine/目录内的小章节顺序移动,覆盖文字 part 与插图条目,不得只跳顶层大章节。 - 点击正文段落打开右侧精修与重翻面板。 - 右侧面板打开或关闭导致正文宽度变化时,应恢复用户原来正在看的段落位置。 - 刷新页面、重启服务或从已有会话重新打开 EPUB 时,应恢复到上次阅读的章节/part/插图、段落和滚动位置。 - 移动窄屏下,侧栏和右侧面板应以抽屉/底部面板方式显示,不遮死主要操作。 ## 编辑、反馈与导出 保存本段时必须: - sanitize 中文 HTML,但允许 `ruby/rb/rt/rp/mark/span` 等必要内联标签。 - 记录 `before_cn_html`、`after_cn_html`、问题类型、严重程度、标签、备注、长期规则建议。 - 更新 `feedback/translation_feedback.jsonl`。 生成反馈时必须: - 写入 `feedback_for_codex.md`。 - 只汇总已编辑、已标记或有备注的段落。 - 保留日文原文、修改前、修改后,便于后续翻译规则复盘。 导出 EPUB 前必须先写回已保存的译文修改,插图文件保持原样。 ## GPT 重翻 GPT 能力是可选增强,不应影响离线审校。 安全与配置: - 支持 OpenAI 兼容的 `base_url`、`model`、`api_key`。 - 支持单独设置 `translation_prompt`、`format_prompt`、`character_prompt`;缺省时必须回退到内置预设。 - API Key 优先读取 `gpt_config.json`,也支持环境变量 `OPENAI_API_KEY`。 - `/api/gpt/config` 不得返回 API Key。 - `/api/gpt/config` 可以返回提示词与预设,但不得返回密钥。 - `/api/glossary` 读取和保存配置路径指向的 `mingcibiao.json`;默认路径应由当前打开 EPUB 对应的系列目录或用户保存的配置决定,不应在通用维护口径中硬编码某个具体系列。 - 未保存配置时,可在项目根目录下唯一的一级系列目录 `mingcibiao.json` 中自动推断默认术语表;若无法唯一推断,则回退到项目根目录 `mingcibiao.json` 并允许用户在网页中显式设置。 - 术语表保存应保持 JSON 对象结构:key 为源语或匹配词,value 为译名,可继续使用 `#` 作为备注分隔。 - 重翻时必须实时读取术语表,按目标段落日文纯文本、日文 HTML 与 ruby/rt 命中相关条目,不得只在提示词里泛泛要求“遵守术语表”。 - 未配置 Key 时,重翻接口应返回可读错误,不影响编辑保存。 - 重翻请求不得临时覆盖 `base_url` 或 `model` 后继续复用已保存密钥;要更换端点或模型必须先保存配置,避免把密钥发往未确认端点。 单段重翻流程: 1. 用户点击阅读段落,打开右侧面板。 2. 用户可填写额外重翻要求。 3. `/api/row//retranslate` 使用提示词、目标语句、实时命中的术语表条目、同文件前后一段少量上下文、角色口吻提示生成候选。 4. 候选只显示在面板里,不自动覆盖。 5. 用户点击“应用重翻”后写入编辑框,再点击保存才进入审校记录。 6. 候选事件写入 jsonl,便于后续根据精修反馈优化提示词;当前版本不自动改写提示词。 术语实时命中实现口径: - 每次 `/api/row//retranslate` 请求都必须读取当前配置的 glossary 文件,不使用服务启动时的缓存作为唯一来源。 - 单段重翻的术语命中范围只包括目标段落日文纯文本和日文 HTML 中的可见文本/ruby/rb/rt 信息;当前中文译文不能反向触发新术语,避免把旧误译或幻觉带入 prompt。 - 传给 GPT 的目标日文 HTML 只保留必要内联标签和 ruby 结构,不携带 class/style/data-* 等属性,避免属性值污染术语命中或 prompt。 - 前后一段上下文只用于语气、指代和连贯性参考,不参与单段重翻的术语命中;单段重翻的 `glossary_matches` 只由目标段落自身触发。 - 正式术语表 value 如包含 `#`,`#` 前是指定译名,`#` 后只是备注,不写入正文。 - 未命中术语时,重翻 prompt 不得塞入无关术语兜底规则;例如只有原文出现 `ファルシオン` 或 `Falchion` 时,才可命中“大砍刀”。 - 读取旧版保存提示词时,若只剩“优先服从给定名词表”这类泛泛规则,应回退到当前内置预设,避免用户继续沿用已淘汰的全局术语设计。 - 任何术语特例都不得写入全局 prompt。Falchion / ファルシオン / 大砍刀 只能由 `glossary_matches` 动态生成;未命中时,重翻 messages 中不得出现这些字符串。 - 命中结果必须随候选事件写入 jsonl,字段至少包含 `glossary_path` 与 `glossary_matches`;未命中时记录空数组,便于排查 prompt。 Prompt 第一性原理: - prompt 必须只携带目标段落、少量上下文、命中术语、系列风格摘要和输出格式;单段重翻不携带完整术语表。 - prompt 不得只泛泛要求遵守术语表,必须列出本段命中的具体术语;未命中的术语不得进入提示。 - prompt 必须要求只输出修订后的中文 HTML,不输出说明。 - prompt 必须要求不合并、不拆分段落,不删除段落编号或必要标签。 - prompt 必须要求保留有意义 ruby 为真实 `......`。 Prompt 硬规则: - 输出简体中文轻小说文风。 - 意义优先,不贴日语语序硬译。 - 不合并、不拆分段落。 - 有意义 ruby 必须保留为真实 `......`,不要改成括号。 - 标点统一为简中出版格式:对话引号用「」,省略号用……,破折号用——,问号用?,感叹号用!,问叹连用用?!/!?。 ## 整书 AI 翻译 整书 AI 翻译从书架页进入,只处理已经上传或打开过、因此已有 session 的 EPUB。它必须作为后台任务运行,前端只负责创建任务、轮询进度、展示日志和打开输出 session。 后端接口口径: - `GET /api/translation/defaults` 返回当前公开 GPT 配置、默认提示词、默认术语表路径、默认范围和输出模式,不返回 API Key。 - `GET /api/session//translation-source` 只读取指定 session,统计可翻译日文正文块和样例,不切换 active session;必须同时返回诊断统计,说明审校行数、可翻译正文块、图片、目录、空白块不是同一口径。 - `GET /api/library` 与 `GET /api/sessions` 返回按 EPUB 元数据生成的书架索引、系列列表和封面 URL。 - `GET/POST /api/series//config` 读写同系列翻译配置;空字段表示未设置,不能自动写入全局默认 prompt。 - `POST /api/translation/start` 创建后台任务并快速返回 `job_id`;请求不得临时覆盖 `base_url` 或 `model` 后复用已保存密钥,要更换端点或模型必须先保存配置。 - `GET /api/translation/jobs/` 返回任务状态、进度、日志、输出 EPUB 和输出 session id,不返回 API Key。 翻译输入与提示词: - 日语源 EPUB 需要新的源段落抽取器,不复用只识别双语灰字的 `build_rows()`。 - 翻译源抽取器至少覆盖 `

` 与正文 ``;日文上下文中的纯汉字/标点短句应进入翻译队列,纯图片、空白和目录页不进入。 - 默认范围必须是前 N 段试译,当前默认 20 段;全书翻译必须由用户显式选择。 - 每段请求只携带目标段落日文 HTML、前后一段少量上下文、该段实时命中的术语、翻译提示词、格式提示词与角色口吻提示词。 - 不得把完整术语表塞进 prompt;未命中的术语不得进入 prompt。 - 若同系列配置存在,整书翻译优先使用同系列的术语表路径和提示词;API Key、Base URL、模型仍使用全局 API 配置。 - 输出只接受当前段中文 HTML 片段,随后进行中文标点规整、HTML sanitize 与术语 ruby 括号形式修复。 输出与书架: - `bilingual` 模式在原日文 `

` 后插入中文 `

`,并把原日文段落标为灰色且标记为 source,方便进入现有双语审校器逐段审校。 - 如果输入已经是中日双语 EPUB,翻译源抽取必须识别显式灰色/source 源段与下一段中文译文的 pair;`bilingual` 模式应替换已有中文层,不得追加第二个中文层;`translated` 模式应输出新的中文层并移除对应日文源层。 - 如果历史 session 已经出现“日文源层 + 中文层 + 旧中文灰色源层 + 新中文层”的四层坏结构,进入翻译源统计、整书翻译或 rows 重建前应自动清理为“日文源层 + 第一个中文层”,避免旧中文继续被当作日文源文。 - `translated` 模式用中文替换原日文段落;该输出会加入书架,但由于没有日中成对段落,可能没有逐段双语审校行。 - 图片、样式、OPF、spine 和非正文文件应原样保留。 - 翻译失败的单段必须写入可见占位和任务日志,不能让整本输出静默缺段。 - 输出 EPUB 创建 session 时不应自动切换用户当前 active session;只有用户点击“打开输出审校”才切换。 任务安全与恢复: - 后台 job 不能在网络调用期间持有全局锁;只在写状态文件时短暂加锁或原子写入。 - `job.json` 和中间 `translations.json` 必须原子写入,避免前端轮询读到半截 JSON。 - 日志、状态、EPUB 元数据和反馈文件不得包含 API Key。 - 任务失败时应将状态置为 `failed` 并写清楚错误,不能让前端永久停在 running。 - 后续如加入暂停/取消/恢复,必须保持现有 `job.json` 字段向后兼容。 ## 版本规则 版本号使用 `version = "0.1.0"` 格式: | 层级 | 触发条件 | | --- | --- | | PATCH | 修复 bug 或兼容性小修 | | MINOR | 新增向后兼容能力、API 字段或可选配置 | | MAJOR | 破坏兼容的 API、配置或行为变更 | 发版时必须同步: - `version.py` - `static/index.html` 顶部版本徽标 fallback - `README.md` 当前版本与版本说明 - 必要时同步 `MAINTENANCE.md` 中的维护口径 版本文档同步边界: - 审校器自身行为、API、配置、前端交互变化,同步本文件。 - 会影响整个翻译项目的规则变化,同步项目根目录 `AGENTS.md` 与 `rules/translation_rules.md`,不要只写在审校器维护手册中。 - 对外最小包内容、启动方式或发布说明变化,同步 README。 ## 回归清单 每轮改动至少检查: - `python -m py_compile tools/epub_review_editor/server.py tools/epub_review_editor/version.py` - `node --check tools/epub_review_editor/static/app.js` - `powershell -NoProfile -ExecutionPolicy Bypass -File tools/epub_review_editor/open_editor.ps1` 能启动服务并打印 URL - 从项目根目录执行 `打开EPUB审校器.cmd` 能启动服务;失败时窗口停留并显示错误;成功时浏览器打开书架/上传页 - 重复执行一键入口时优先复用同版本已运行服务;如需新起服务,端口探测不得允许多个服务同时监听同一端口 - `git diff --check` - `/api/version` - `/api/session` - `/api/structure` - `/api/gpt/config` - 首个插图 `/api/asset/...` 返回 200 且浏览器可显示 - 非白名单或非 raster asset 不能被 `/api/asset/...` 读取 - 目录按钮、检索按钮、隐藏侧栏、点击段落打开右侧面板 - 阅读段落下方没有编号和“快速编辑/快速标记” - 未配置 API Key 时重翻返回清晰错误 - 配置保存后 `/api/gpt/config` 不包含密钥字段 - `/api/glossary` 能读取术语表;新增、修改、删除条目后保存,再读取内容一致 - 重翻候选事件包含 `glossary_path` 与 `glossary_matches` - 无关段落的重翻 messages 不得包含 `大砍刀`、`Falchion` 或 `ファルシオン`;含 `ファルシオン` 或 ruby `Falchion` 的段落必须命中“大砍刀” - 顶栏复杂操作收敛在“审校工具”入口;未选择段落时仍可配置 AI、术语表并执行反馈与交付,段落编辑/重翻按钮禁用 - `/api/reading-position` 能保存/读取位置;刷新或重启后回到上次阅读处 - 阅读外观默认/护眼/夜间、字号、行距切换即时生效,刷新后仍保留;夜间模式下正文、原文、按钮和侧栏文本可读 - 审校页顶部栏和阅读标题栏默认不遮挡正文,鼠标移到页面上方后可操作;开始页顶栏不隐藏;目录侧栏和审校工具打开时,其顶部按钮仍可直接点击 - 鼠标停留在顶栏按钮区域时,书架/打开 EPUB/审校工具按钮能连续点击,不会在点击前自动收起;浏览器重新加载后应使用当前版本 app.js/style.css - “上一节 / 下一节”在相邻 part 和章节内插图之间移动,不再按顶层大章节跳转 - 顶栏左上角“书架”能打开独立书架页;书架列出历史会话,空状态可进入上传页,点击书籍可进入对应审校页并恢复阅读位置 - 书架左侧分类可按全部书籍、同系列、作者、出版社、语言筛选;右侧封面网格在桌面端悬停变暗显示信息和“校对/翻译”,移动端不依赖 hover 才能操作 - `GET /api/sessions` 和 `GET /api/library` 返回 `library`、`series`、书籍元数据、`series_id`、`cover_url`,并写入可重建的 `library.json` - `GET/POST /api/series//config` 能读写同系列术语表和提示词;保存后同系列其他书进入翻译页会自动套用 - 书架卡片“翻译”按钮能进入独立翻译页,且不会触发卡片的“进入审校” - `/api/translation/defaults` 不返回 API Key,提示词和术语表路径可读取 - `/api/session//translation-source` 能统计日语 EPUB 可翻译正文块和诊断字段;非日语或无正文时返回清晰空状态;审校行数、源正文块数、试译 limit 不得在 UI 上混为一个“段落数” - 翻译页默认只显示源书名和“可翻译正文块 N 个”,不保留冗余段落预览、诊断 note 或大批统计 chip 的可见入口;诊断字段可保留在 API 中供排查 - 未配置 API Key 时,`/api/translation/start` 返回清晰错误,不影响阅读、编辑、导出和单段重翻 - 启动整书翻译任务后,`/api/translation/jobs/` 可看到 pending/running/completed/failed、总段数、已完成数、当前文件和日志 - 默认翻译范围是前 20 段试译;全书翻译必须由用户显式切换并确认 - 整书翻译每段只注入该段源文/ruby 命中的术语,不注入无关术语或全表 - 整书翻译完成后,输出 EPUB 写入 `translated_outputs/` 并自动出现在书架;中日双语输出打开后 `row_count > 0` - 已双语 EPUB 再次执行整书翻译时,输出不得出现“旧中文译文 + 新中文译文”重复层;审校行 `jp_text` 不得变成中文旧译文 - 纯中文输出可以加入书架,且文档说明其不适合逐段双语审校 - 离开翻译页、返回书架或打开输出审校时,前端进度轮询会停止 - 书架卡片“删除”按钮能软删除会话、刷新书架、清空 active session(如删除当前书),且不会删除原始 EPUB 文件