本页只讨论核心业务流程怎样改变数据。数据由谁拥有见领域模型与数据归属,完整字段、默认值与索引见数据库表字段参考。
参与者与事务边界
Controller 创建内存任务并立即返回 task_id,然后在后台按步骤调用 Agent 与 data_server。data_server 用 PostgreSQL 连接,外键由数据库强制;user_manager 的 SQLite 连接启用 WAL 和 PRAGMA foreign_keys=ON。两边 SQL 都使用参数绑定。
一次文章处理会跨越多个 HTTP 请求和多个 PostgreSQL 事务,不存在覆盖整篇文章的全局事务。批量创建句子和 apply-edit 各自在一个连接中提交;翻译、讲解、结果、音频、总结和文章状态则由各自的写方法单独提交。因此处理中间状态可能包含文章行、部分结果或已经写入但尚未关联的音频对象。
文字上传
POST /api/articles/upload 返回任务 ID 后,run_upload 依次执行:
- 创建文章。
POST /v1/data/articles 写入 raw_content、output_language、explanation_level 等文章数据,并设置 status=processing、progress=0。缺省等级为 N3。
- 切句并批量落盘。 Agent 返回有序字符串数组;
POST /v1/data/articles/{id}/sentences 用 INSERT OR REPLACE 写入全部句子,初始为 analysis_status=pending。
- 翻译与讲解。 只把
strip() 后非空的句子分批送给 Agent。普通路径先逐句写 translation,再在启用讲解时逐句写 explanation;写讲解同时把 explain_status 设为 explained。
- 生成音频。 启用 TTS 时,非空句子的音频写入或替换
media_objects:local 模式使用 data BLOB,cos 模式使用内容寻址 storage_key 且 data=NULL。随后把 audio_object_id 与 audio_voice 回写到句子。
- 生成总结。 Agent 尽力生成
summary,通过独立接口更新文章。总结失败不会让整篇处理失败。
- 结束处理。 文章更新为
status=completed、progress=100;user_manager 另行记录上传与 LLM 调用用量。
**当前实现限制:**普通上传的分析结果虽然同时包含 translation、explanation、status 和 furigana,但该路径为了分阶段展示进度,分别调用“只更新翻译”和“只更新讲解”的接口。它不会同步更新 analysis_status 或 furigana;其中讲解写入只会更新 explain_status。视频上传和整篇重解析复用同一普通分析函数,也有相同行为。完整结果写入路径才会更新 analysis_status 与结果中的 furigana,按需假名补全路径则只更新 furigana。
当 explain_mode=split 时,流程在批量保存句子后直接把文章标记为 completed,不执行翻译、讲解、TTS 或总结。completed 因而表示所选模式已结束,不表示每条句子都分析成功。
视频上传
视频分为文件接收和内容处理两步:
POST /api/videos/file 校验扩展名与大小,把上传流转发给 data_server FileStore,写入 tmp/u{uid}/transcription/... 并返回 file_handle。该文件不创建 media_objects 行。
POST /api/videos/upload 用 input_handle 创建内存任务。data_server 校验 handle 所有者并返回 10 分钟签名 URL;Agent 只从白名单 COS HTTPS 主机流式下载,禁止重定向并限制大小。
- Agent 在自己的临时文件上转写并清理下载;Controller 尽力润色文本,失败时使用原始转写。
- 润色后文本成为文章的
raw_content,随后执行与文字上传相同的创建、切句、普通分析、TTS、总结和完成步骤。
- 成功时,
keep_audio=true 会把临时对象转入用户 sources/ 并把 handle 写入 articles.metadata.source_media;false 则删除。任务失败或取消也会清理临时对象。
上传源文件由 FileStore 管理,句子/情景音频由 media_objects + ObjectStore 管理;两者都可落 COS,但数据模型和生命周期不同。
空白布局句
Agent 的切句结果可以包含空字符串或纯空白项,用于还原段落、访谈换行或歌词间隔。它们会保留在 sentences 并参与索引排序,但不会送去翻译、讲解或 TTS,也不计入内容句总数。其分析字段可继续保持默认值。
当 genre=song 时,Agent 只按原文中的换行符切分:每个非空歌词行都是一个完整内容句,行内空格、Tab 与句末标点都不会触发二次分句。换行符和连续空行作为布局项原样保留,因此重新拼接句子序列仍可精确还原清理 Markdown 后的输入。
编辑后的最小重算
普通文章编辑由 run_edit 处理:
- 重新切分新的
raw_content,并根据请求单独更新标题、作者或介绍。
apply-edit 在一个 PostgreSQL 事务内更新 articles.raw_content,删除旧句子行,再按 diff 结果重建句子序列。
- 未变化句子被映射到新索引,保留翻译、讲解、假名、分析状态、音频引用、voice、
tts_error、笔记和 metadata。
- 修改或新增句子创建为
pending 行;删除句子不再插回。Controller 只对返回的非空 to_process_indices 重新调用 Agent。
- 这些变更句子通过完整结果接口写入,在一次句子结果提交中更新
translation、explanation、analysis_status、furigana 和可选的 explain_status。
- 有重算内容时,最后把文章标记为
completed/100;没有待重算句子时直接结束任务。
整篇重解析
重解析从数据库读取既有 raw_content、intro、模型、output_language 和 explanation_level,重新切句并调用 apply-edit。与最小重算不同,它随后把全部非空句子送入普通分析路径,因此会重新写翻译和讲解;其状态与假名限制与普通上传相同。explain_mode=split 时只更新句子边界并完成任务。
按需假名补全
POST /api/articles/{id}/generate-furigana 是非破坏性的补全流程:
- 读取文章与全部非空句子,沿用文章的模型、
output_language 和 explanation_level;Prompt 预览与单句重新生成也使用同一等级,自定义 Prompt 仍优先。
- 调用 Agent 时关闭语法讲解,只取每句的
furigana。
PUT .../sentences/{idx}/furigana 只覆盖 furigana JSON 文本,保留已有翻译、讲解和状态。
- 失败或取消只改变内存任务状态,不把文章本身改成
failed 或 cancelled。
手工修改单句原文是另一条写入:PUT .../source 会覆盖 sentence 并清空旧 furigana,防止读音与新原文不匹配。
按需 TTS
已登录且能读取文章的用户可生成单句,或生成服务器快照中全部无音频的非空句;访客只能读取状态并播放已有音频。Controller 复用现有任务/SSE 链路,活动任务只在响应层覆盖为 generating,不会写进数据库。每句成功时写音频并清空 tts_error;失败时写入清理、截断后的错误,批量继续处理后续句。任务结果逐句列出成功与失败,任务级异常也不会把文章标为 failed。
完成、失败与取消
- 上传或视频任务成功后,文章写为
completed/100;总结失败被单独吞掉。
- 处理抛错或收到取消时,如果已经创建文章,则
_fail 尽力把文章状态改为 failed 或 cancelled;此前已提交的数据不会回滚。
- 如果错误发生在文章创建前,则只有内存任务进入终态,不会产生文章行。
- 用量记录位于另一个服务和数据库,采用尽力写入,不参与
data_server 的事务。
数据库行到 API 响应
读取时还会经过领域与响应转换,不能把 API JSON 当成原始数据库行:
metadata、full_prompt、furigana 从 JSON 文本解码为对象或数组。
is_public、guest_visible 从 0/1 转换为布尔值。
sentence_index 在句子 API 中变为 index;audio_object_id 不直接暴露,改为 has_audio。音频存在性与 tts_error 派生 tts_status;Controller 再用活动任务覆盖 generating 与 tts_task_id。
- 文章详情组合
sentences,并派生 total_sentences、success_rate 和阅读器分组;这些统计不存于 articles 表。
- Controller 的单句讲解响应还会把
explanation 映射为面向客户端的 explain。
调试时先确认观察的是 PostgreSQL 行、data_server 领域字典还是 Controller 响应。物理字段参考见数据库表字段参考。