本页只讨论核心业务流程怎样改变数据。数据由谁拥有见领域模型与数据归属,完整字段、默认值与索引见数据库表字段参考

参与者与事务边界

Controller 创建内存任务并立即返回 task_id,然后在后台按步骤调用 Agent 与 data_serverdata_server 用 PostgreSQL 连接,外键由数据库强制;user_manager 的 SQLite 连接启用 WAL 和 PRAGMA foreign_keys=ON。两边 SQL 都使用参数绑定。
一次文章处理会跨越多个 HTTP 请求和多个 PostgreSQL 事务,不存在覆盖整篇文章的全局事务。批量创建句子和 apply-edit 各自在一个连接中提交;翻译、讲解、结果、音频、总结和文章状态则由各自的写方法单独提交。因此处理中间状态可能包含文章行、部分结果或已经写入但尚未关联的音频对象。

文字上传

POST /api/articles/upload 返回任务 ID 后,run_upload 依次执行:
  1. 创建文章。 POST /v1/data/articles 写入 raw_contentoutput_languageexplanation_level 等文章数据,并设置 status=processingprogress=0。缺省等级为 N3
  2. 切句并批量落盘。 Agent 返回有序字符串数组;POST /v1/data/articles/{id}/sentencesINSERT OR REPLACE 写入全部句子,初始为 analysis_status=pending
  3. 翻译与讲解。 只把 strip() 后非空的句子分批送给 Agent。普通路径先逐句写 translation,再在启用讲解时逐句写 explanation;写讲解同时把 explain_status 设为 explained
  4. 生成音频。 启用 TTS 时,非空句子的音频写入或替换 media_objectslocal 模式使用 data BLOB,cos 模式使用内容寻址 storage_keydata=NULL。随后把 audio_object_idaudio_voice 回写到句子。
  5. 生成总结。 Agent 尽力生成 summary,通过独立接口更新文章。总结失败不会让整篇处理失败。
  6. 结束处理。 文章更新为 status=completedprogress=100user_manager 另行记录上传与 LLM 调用用量。
**当前实现限制:**普通上传的分析结果虽然同时包含 translationexplanationstatusfurigana,但该路径为了分阶段展示进度,分别调用“只更新翻译”和“只更新讲解”的接口。它不会同步更新 analysis_statusfurigana;其中讲解写入只会更新 explain_status。视频上传和整篇重解析复用同一普通分析函数,也有相同行为。完整结果写入路径才会更新 analysis_status 与结果中的 furigana,按需假名补全路径则只更新 furigana
explain_mode=split 时,流程在批量保存句子后直接把文章标记为 completed,不执行翻译、讲解、TTS 或总结。completed 因而表示所选模式已结束,不表示每条句子都分析成功。

视频上传

视频分为文件接收和内容处理两步:
  1. POST /api/videos/file 校验扩展名与大小,把上传流转发给 data_server FileStore,写入 tmp/u{uid}/transcription/... 并返回 file_handle。该文件不创建 media_objects 行。
  2. POST /api/videos/uploadinput_handle 创建内存任务。data_server 校验 handle 所有者并返回 10 分钟签名 URL;Agent 只从白名单 COS HTTPS 主机流式下载,禁止重定向并限制大小。
  3. Agent 在自己的临时文件上转写并清理下载;Controller 尽力润色文本,失败时使用原始转写。
  4. 润色后文本成为文章的 raw_content,随后执行与文字上传相同的创建、切句、普通分析、TTS、总结和完成步骤。
  5. 成功时,keep_audio=true 会把临时对象转入用户 sources/ 并把 handle 写入 articles.metadata.source_mediafalse 则删除。任务失败或取消也会清理临时对象。
上传源文件由 FileStore 管理,句子/情景音频由 media_objects + ObjectStore 管理;两者都可落 COS,但数据模型和生命周期不同。

空白布局句

Agent 的切句结果可以包含空字符串或纯空白项,用于还原段落、访谈换行或歌词间隔。它们会保留在 sentences 并参与索引排序,但不会送去翻译、讲解或 TTS,也不计入内容句总数。其分析字段可继续保持默认值。 genre=song 时,Agent 只按原文中的换行符切分:每个非空歌词行都是一个完整内容句,行内空格、Tab 与句末标点都不会触发二次分句。换行符和连续空行作为布局项原样保留,因此重新拼接句子序列仍可精确还原清理 Markdown 后的输入。

编辑后的最小重算

普通文章编辑由 run_edit 处理:
  1. 重新切分新的 raw_content,并根据请求单独更新标题、作者或介绍。
  2. apply-edit一个 PostgreSQL 事务内更新 articles.raw_content,删除旧句子行,再按 diff 结果重建句子序列。
  3. 未变化句子被映射到新索引,保留翻译、讲解、假名、分析状态、音频引用、voice、tts_error、笔记和 metadata。
  4. 修改或新增句子创建为 pending 行;删除句子不再插回。Controller 只对返回的非空 to_process_indices 重新调用 Agent。
  5. 这些变更句子通过完整结果接口写入,在一次句子结果提交中更新 translationexplanationanalysis_statusfurigana 和可选的 explain_status
  6. 有重算内容时,最后把文章标记为 completed/100;没有待重算句子时直接结束任务。

整篇重解析

重解析从数据库读取既有 raw_contentintro、模型、output_languageexplanation_level,重新切句并调用 apply-edit。与最小重算不同,它随后把全部非空句子送入普通分析路径,因此会重新写翻译和讲解;其状态与假名限制与普通上传相同。explain_mode=split 时只更新句子边界并完成任务。

按需假名补全

POST /api/articles/{id}/generate-furigana 是非破坏性的补全流程:
  1. 读取文章与全部非空句子,沿用文章的模型、output_languageexplanation_level;Prompt 预览与单句重新生成也使用同一等级,自定义 Prompt 仍优先。
  2. 调用 Agent 时关闭语法讲解,只取每句的 furigana
  3. PUT .../sentences/{idx}/furigana 只覆盖 furigana JSON 文本,保留已有翻译、讲解和状态。
  4. 失败或取消只改变内存任务状态,不把文章本身改成 failedcancelled
手工修改单句原文是另一条写入:PUT .../source 会覆盖 sentence 并清空旧 furigana,防止读音与新原文不匹配。

按需 TTS

已登录且能读取文章的用户可生成单句,或生成服务器快照中全部无音频的非空句;访客只能读取状态并播放已有音频。Controller 复用现有任务/SSE 链路,活动任务只在响应层覆盖为 generating,不会写进数据库。每句成功时写音频并清空 tts_error;失败时写入清理、截断后的错误,批量继续处理后续句。任务结果逐句列出成功与失败,任务级异常也不会把文章标为 failed

完成、失败与取消

  • 上传或视频任务成功后,文章写为 completed/100;总结失败被单独吞掉。
  • 处理抛错或收到取消时,如果已经创建文章,则 _fail 尽力把文章状态改为 failedcancelled;此前已提交的数据不会回滚。
  • 如果错误发生在文章创建前,则只有内存任务进入终态,不会产生文章行。
  • 用量记录位于另一个服务和数据库,采用尽力写入,不参与 data_server 的事务。

数据库行到 API 响应

读取时还会经过领域与响应转换,不能把 API JSON 当成原始数据库行:
  • metadatafull_promptfurigana 从 JSON 文本解码为对象或数组。
  • is_publicguest_visible0/1 转换为布尔值。
  • sentence_index 在句子 API 中变为 indexaudio_object_id 不直接暴露,改为 has_audio。音频存在性与 tts_error 派生 tts_status;Controller 再用活动任务覆盖 generatingtts_task_id
  • 文章详情组合 sentences,并派生 total_sentencessuccess_rate 和阅读器分组;这些统计不存于 articles 表。
  • Controller 的单句讲解响应还会把 explanation 映射为面向客户端的 explain
调试时先确认观察的是 PostgreSQL 行、data_server 领域字典还是 Controller 响应。物理字段参考见数据库表字段参考