{"openapi":"3.1.0","info":{"title":"语音生活记录服务端","description":"随身录音设备的云端服务端：接收录音/文本 → 语音识别 → 按日期层级写入思源笔记 → Agent 每日总结。\n\n## 四种设备接入方式\n\n| 方式 | 场景 | 接口 |\n|---|---|---|\n| 分段上传 | 联网时每 499 秒一段 | `POST /api/v1/audio/upload` |\n| 离线补传 | 断网存卡后补传（同一接口） | `POST /api/v1/audio/upload` |\n| 文本上报 | 设备本地识别完直接上报 | `POST /api/v1/text` |\n| 实时音频流 | 边录边推，服务端切段识别 | `WS /api/v1/stream/live`（WebSocket 不在本规范内，用法见下） |\n\n## 鉴权与设备绑定（重要）\n\n- **Token 是设备唯一凭证**：所有设备接口请求头携带 `X-Device-Token`（后台「设备」页生成）\n- device_id 只是登记信息：请求可选携带 `X-Device-Id`；服务端 device_id 为空时自动登记填入，\n  非空时不校验、不覆盖——后台修改 device_id 不影响设备接入\n- WebSocket 用 query 参数：`/api/v1/stream/live?token=xxx&device_id=xxx`；关闭码 `4401`=Token 无效\n- 每个设备可在后台绑定知识库（思源笔记本），未绑定写入全局默认笔记本\n\n## 实时音频流（WebSocket）\n\n```\nws(s)://域名/api/v1/stream/live?token=<token>&device_id=<device_id>\n```\n\n- 上行：二进制帧，**PCM 16kHz 单声道 16bit 小端（s16le）**，建议每帧 100ms\n- 下行 JSON：`{\"type\":\"segment_saved\",\"segment_id\":88,\"duration\":6.42}`\n- 切段规则：能量 VAD 静音 800ms 或单段最长 30 秒（可配置）\n\n## 响应与错误约定\n\n- 成功：`{\"code\": 0, \"data\": {...}}`\n- 失败：HTTP 4xx + `{\"detail\": \"中文错误说明\"}`\n- `401` Token 无效；`403` 设备提交的 device_id 已被其他设备登记占用；`422` 参数缺失或格式错误\n- `client_seq` 为幂等键：重复上传/上报返回原记录（`dedup: true`），不会重复入库\n\n## 后台管理\n\n后台路径默认 `/xinghuo`（由 `.env` 的 `ADMIN_PATH` 配置），人用文档站 `/docs`，\n机器可读：`/openapi.json`（本文件）、`/docs/api/tree`、`/docs/api/content/{id}`。\n","version":"0.2.0"},"paths":{"/api/v1/audio/upload":{"post":{"tags":["设备接口"],"summary":"上传录音段（联网每499秒分段 / 断网补传，同一接口）","description":"接收设备录音文件，落盘后进入识别队列，识别文本自动写入思源当天文档。\n\n**分段上传**：联网时每录满 499 秒切一段调用本接口，`source=live`。\n\n**离线补传**：断网期间存 SD 卡，恢复网络后逐个补传，`source=offline`；\n服务端按 `recorded_at`（真实录音时间）归档日期，识别后文本写入**录音当天**的思源文档。\n\n**幂等**：`client_seq` 为设备端序号，(设备, seq) 唯一；网络重试导致的重复上传\n返回原记录（`dedup: true`），不会重复入库。建议每段用单调递增序号。\n\n**处理流程**：保存 `data/recordings/{yyyyMMdd}/xxx.wav` → recordings 表 pending\n→ 识别队列（失败自动重试 3 次，每 10 分钟扫描）→ 文本入 transcripts → 写思源\n（思源不可用时留 pending，每 5 分钟补写）。\n\n**错误码**：401 Token 无效；403 设备ID校验失败；422 缺少必填字段（file/recorded_at）。","operationId":"upload_audio_api_v1_audio_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_audio_api_v1_audio_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/text":{"post":{"tags":["设备接口"],"summary":"上报设备本地识别的文本","description":"设备端本地完成语音识别后，直接上报文本。服务端入库并**异步**写入思源当天文档\n（接口立即返回，思源写入失败不影响上报成功，失败后每 5 分钟自动补写）。\n\n`recorded_at` 决定写入哪一天的思源文档（`生活记录/{Y}年/{M}月份/{M}月{D}号`）。\n\n**幂等**：`client_seq` 相同的重复上报返回原记录（`dedup: true`）。\n\n**响应**：`{\"code\":0,\"data\":{\"transcript_id\":456}}`；空文本返回 `{\"code\":1}`。","operationId":"report_text_api_v1_text_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextIn"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/login":{"get":{"tags":["后台管理"],"summary":"Login Page","operationId":"login_page_xinghuo_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["后台管理"],"summary":"后台登录（表单提交，成功后设置会话 Cookie）","operationId":"login_xinghuo_login_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/logout":{"get":{"tags":["后台管理"],"summary":"Logout","operationId":"logout_xinghuo_logout_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo":{"get":{"tags":["后台管理"],"summary":"仪表盘页面","operationId":"dashboard_xinghuo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/history":{"get":{"tags":["后台管理"],"summary":"转录历史记录页面（分页列表，支持按设备搜索）","description":"原「录音」与「储存知识库」两页的合并列表。\n\n- 默认分页显示全部录音记录（每页 20 条，倒序）\n- `q` 参数按设备搜索（匹配设备名称或设备ID）\n- 设备列显示设备名称（命中但未命名显示\"未命名设备\"；设备表中无对应显示\"—\"，不显示裸设备ID）\n- 知识库列显示该设备写入目标：[知识库][笔记本] 两个徽标\n  （知识库=全局 `kb.siyuan.name`；笔记本=设备绑定的思源笔记本，未绑定显示全局默认笔记本）","operationId":"history_page_xinghuo_history_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/history/{rid}/retry":{"post":{"tags":["后台管理"],"summary":"重试指定记录（重新转录+写记忆）","description":"仅当状态为 调用语音失败/转录失败/写记忆失败/模型停用 时可重试，且需满足执行条件：\n\n- 写记忆成功（done）的记录不能重试（文件已删除、内容已写入）\n- 调用语音失败/转录失败/模型停用：需要本地音频文件与录音时间都存在（文件没了无法重新转录）\n- 写记忆失败：只需录音时间存在（已有转录文本，只重新写记忆，不重复转录）\n- 缺少录音时间时一律不可重试（无法确定写入哪天的记忆文档）","operationId":"history_retry_xinghuo_history__rid__retry_post","parameters":[{"name":"rid","in":"path","required":true,"schema":{"type":"integer","title":"Rid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/history/{rid}/delete-file":{"post":{"tags":["后台管理"],"summary":"删除该记录的本地音频文件（记录保留）","description":"仅当状态为 调用语音失败/转录失败/写记忆失败 且本地文件仍存在时可删除；\n\n写记忆成功的记录文件已自动删除，无需（也不能）再删。仅删文件，数据库记录保留。","operationId":"history_delete_file_xinghuo_history__rid__delete_file_post","parameters":[{"name":"rid","in":"path","required":true,"schema":{"type":"integer","title":"Rid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/devices":{"get":{"tags":["后台管理"],"summary":"设备管理页面（左列表+右新增按钮，编辑为弹窗：知识库/记事本两级绑定）","operationId":"devices_page_xinghuo_devices_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/devices/add":{"post":{"tags":["后台管理"],"summary":"新增设备token（一键生成，无表单参数）","description":"点击后立即生成一条设备记录：仅含随机 Token（32位hex），设备ID为空、名称空白。\n\n设备**首次调用任意设备接口**时携带 `X-Device-Id` 自动绑定设备ID；名称可后台编辑。\nToken 生成后不可修改。","operationId":"device_add_xinghuo_devices_add_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/devices/{did}/update":{"post":{"tags":["后台管理"],"summary":"编辑设备（弹窗提交；Token 不可修改）","description":"表单字段：`name`、`device_id`（可空）、`kb`（知识库标识，当前固定 siyuan）、\n`notebook_id`（二级：记事本=该知识库下的思源笔记本ID，空=写入默认记事本）、\n`asr_engine_id` / `llm_provider_id`（预留绑定，空=跟随全局）。\n\n- device_id 留空保存 = 解除登记，设备下次接入时若提交 ID 会重新登记\n- 绑定记事本后该设备内容写入该记事本下的日期层级文档；未绑定写全局默认记事本\n- Token 不可修改","operationId":"device_update_xinghuo_devices__did__update_post","parameters":[{"name":"did","in":"path","required":true,"schema":{"type":"integer","title":"Did"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/devices/{did}/toggle":{"post":{"tags":["后台管理"],"summary":"启用/禁用设备（禁用后该 Token 所有请求 401）","operationId":"device_toggle_xinghuo_devices__did__toggle_post","parameters":[{"name":"did","in":"path","required":true,"schema":{"type":"integer","title":"Did"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/devices/{did}/delete":{"post":{"tags":["后台管理"],"summary":"删除设备","operationId":"device_delete_xinghuo_devices__did__delete_post","parameters":[{"name":"did","in":"path","required":true,"schema":{"type":"integer","title":"Did"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/settings":{"get":{"tags":["后台管理"],"summary":"设置页面（左分类右内容：语音识别引擎/知识库/AI/系统）","operationId":"settings_page_xinghuo_settings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["后台管理"],"summary":"保存设置（各分类面板独立提交，保存后即时生效并重排定时任务）","description":"提交后台设置页某个分类面板的表单，仅接受 `app/config.py` DEFAULTS 白名单内的键\n（思源连接、实时流、存储等；ASR 引擎与 LLM 走独立的列表接口）。\n\n保存后：刷新内存配置缓存 + 按各 AI 的 summary_cron 重排每日总结任务。","operationId":"settings_save_xinghuo_settings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/asr/add":{"post":{"tags":["后台管理"],"summary":"新增识别引擎（权重 1-10，越高越优先调用）","operationId":"asr_add_xinghuo_asr_add_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/asr/{eid}/update":{"post":{"tags":["后台管理"],"summary":"编辑识别引擎（API Key 留空=保留原值）","operationId":"asr_update_xinghuo_asr__eid__update_post","parameters":[{"name":"eid","in":"path","required":true,"schema":{"type":"integer","title":"Eid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/asr/{eid}/toggle":{"post":{"tags":["后台管理"],"summary":"启用/停用识别引擎（停用不参与识别）","operationId":"asr_toggle_xinghuo_asr__eid__toggle_post","parameters":[{"name":"eid","in":"path","required":true,"schema":{"type":"integer","title":"Eid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/asr/{eid}/delete":{"post":{"tags":["后台管理"],"summary":"删除识别引擎","operationId":"asr_delete_xinghuo_asr__eid__delete_post","parameters":[{"name":"eid","in":"path","required":true,"schema":{"type":"integer","title":"Eid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/asr/{eid}/test":{"post":{"tags":["后台管理"],"summary":"引擎连通测试（先测连通性，再用固定样例音频识别）","description":"两步测试，返回 JSON：\n1. 连通性：向引擎服务发轻量 GET 请求，网络不通直接返回 `{ok:false, stage:\"connect\"}`；\n2. 音频识别：用 `data/test/000001.mp3` 调用该引擎识别，返回 `{ok:true, text:\"识别内容\"}`；\n   连通但识别报错返回 `{ok:false, stage:\"asr\", error:\"...\"}`。不走识别队列、不影响其他引擎。","operationId":"asr_test_xinghuo_asr__eid__test_post","parameters":[{"name":"eid","in":"path","required":true,"schema":{"type":"integer","title":"Eid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/llm/add":{"post":{"tags":["后台管理"],"summary":"新增 LLM 配置（追加到列表末尾）","operationId":"llm_add_xinghuo_llm_add_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/llm/{lid}/update":{"post":{"tags":["后台管理"],"summary":"编辑 LLM 配置（API Key 留空=保留原值）","operationId":"llm_update_xinghuo_llm__lid__update_post","parameters":[{"name":"lid","in":"path","required":true,"schema":{"type":"integer","title":"Lid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/llm/{lid}/toggle":{"post":{"tags":["后台管理"],"summary":"启用/停用 LLM 配置（停用不参与总结）","operationId":"llm_toggle_xinghuo_llm__lid__toggle_post","parameters":[{"name":"lid","in":"path","required":true,"schema":{"type":"integer","title":"Lid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/llm/{lid}/delete":{"post":{"tags":["后台管理"],"summary":"删除 LLM 配置","operationId":"llm_delete_xinghuo_llm__lid__delete_post","parameters":[{"name":"lid","in":"path","required":true,"schema":{"type":"integer","title":"Lid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/llm/{lid}/move":{"post":{"tags":["后台管理"],"summary":"上移/下移 LLM 配置（调整调用顺序）","operationId":"llm_move_xinghuo_llm__lid__move_post","parameters":[{"name":"lid","in":"path","required":true,"schema":{"type":"integer","title":"Lid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/llm/{lid}/test":{"post":{"tags":["后台管理"],"summary":"测试 AI 连通（按该条配置真实调用一次对话）","description":"按该条 LLM 配置的协议/地址/密钥/模型发送一条测试消息。\n\n成功：`{\"ok\": true}`；失败：`{\"ok\": false, \"error\": \"中文错误说明\"}`。","operationId":"llm_test_xinghuo_llm__lid__test_post","parameters":[{"name":"lid","in":"path","required":true,"schema":{"type":"integer","title":"Lid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/knowledge/set-default":{"post":{"tags":["后台管理"],"summary":"设置默认知识库（设备未绑定笔记本时写入这里）","operationId":"knowledge_set_default_xinghuo_knowledge_set_default_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/knowledge/test":{"post":{"tags":["后台管理"],"summary":"知识库连通测试（测试思源内核连接，返回笔记本列表）","description":"知识库目前仅支持思源笔记，本接口测试思源内核地址与 API Token 是否连通。\n返回 JSON：连通 `{ok:true, notebooks:[\"笔记本名\",...]}`；不通 `{ok:false, error:\"...\"}`。","operationId":"knowledge_test_xinghuo_knowledge_test_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/xinghuo/test/asr":{"post":{"tags":["后台管理"],"summary":"上传样例音频测试识别引擎（不入库）","description":"multipart 表单字段 `file`（音频文件）。\n\n按当前识别引擎配置调用一次识别并返回结果，不产生录音/文本记录。\n成功：`{\"ok\": true, \"text\": \"识别文本\"}`；失败：`{\"ok\": false, \"error\": \"...\"}`。","operationId":"test_asr_xinghuo_test_asr_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_test_asr_xinghuo_test_asr_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/xinghuo/agent/summarize":{"post":{"tags":["后台管理"],"summary":"立即总结指定日期（异步，写入思源当天文档顶部）","description":"表单字段 `date`（格式 YYYY-MM-DD，缺省今天）。\n\n流程：取该天全部文本 → LLM 生成要点 → 替换写入思源当天文档顶部的总结区。\n异步执行，立即返回 `{\"ok\": true, \"message\": \"...\"}`；需要已配置 llm.base_url。\n总结内容同时存 agent_summaries 表（按日期覆盖）。","operationId":"summarize_now_xinghuo_agent_summarize_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_test_asr_xinghuo_test_asr_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_test_asr_xinghuo_test_asr_post"},"Body_upload_audio_api_v1_audio_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File","description":"音频文件，建议 16kHz 单声道 wav/opus"},"recorded_at":{"type":"number","title":"Recorded At","description":"该段开始录音的 Unix 时间戳（秒）；补传时以此归档到真实日期"},"duration":{"type":"number","title":"Duration","description":"录音时长（秒）","default":0.0},"source":{"type":"string","title":"Source","description":"live=实时分段（默认）/ offline=离线补传","default":"live"},"client_seq":{"type":"string","title":"Client Seq","description":"设备端序号（幂等键），重复上传返回原记录","default":""}},"type":"object","required":["file","recorded_at"],"title":"Body_upload_audio_api_v1_audio_upload_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"TextIn":{"properties":{"text":{"type":"string","title":"Text","description":"识别出的文本内容（服务端自动去首尾空白）"},"recorded_at":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Recorded At","description":"该段语音的 Unix 时间戳（秒）；缺省用服务器当前时间，决定写入哪天的思源文档"},"client_seq":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Seq","description":"幂等去重键：相同 (设备, seq) 重复上报返回原记录，不重复入库"}},"type":"object","required":["text"],"title":"TextIn"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}},"tags":[{"name":"设备接口","description":"ESP32 等录音设备调用。鉴权：请求头 `X-Device-Token`；首次接入需携带 `X-Device-Id` 自动绑定。"},{"name":"后台管理","description":"浏览器登录后凭 Cookie 调用，未登录返回 302 到登录页。"}]}