diff --git a/agents/haonan/sessions/sessions.json b/agents/haonan/sessions/sessions.json index d1dfcbe..11de632 100644 --- a/agents/haonan/sessions/sessions.json +++ b/agents/haonan/sessions/sessions.json @@ -715,8 +715,8 @@ "memoryFlushCompactionCount": 0 }, "agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc": { - "sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28", - "updatedAt": 1773856800006, + "sessionId": "971b07ad-12bb-4041-879c-97906de4bf18", + "updatedAt": 1773943200005, "systemSent": true, "model": "glm-5", "contextTokens": 200000, @@ -757,11 +757,11 @@ "version": 0 }, "modelProvider": "zhipu", - "inputTokens": 10815, - "outputTokens": 343, - "totalTokens": 16891, + "inputTokens": 13549, + "outputTokens": 371, + "totalTokens": 16884, "totalTokensFresh": true, - "cacheRead": 39360, + "cacheRead": 19840, "cacheWrite": 0 }, "agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef": { @@ -807,9 +807,9 @@ "version": 0 }, "modelProvider": "zhipu", - "inputTokens": 42108, - "outputTokens": 2001, - "totalTokens": 24584, + "inputTokens": 55959, + "outputTokens": 3146, + "totalTokens": 156912, "deliveryContext": { "channel": "webchat" }, @@ -818,7 +818,7 @@ "sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl", "abortedLastRun": false, "totalTokensFresh": true, - "cacheRead": 39613, + "cacheRead": 100953, "cacheWrite": 0 }, "agent:haonan:cron:3245fca7-045b-4977-8fab-020f6df2e453": { @@ -861,8 +861,8 @@ "cacheWrite": 0 }, "agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc": { - "sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4", - "updatedAt": 1773857400007, + "sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d", + "updatedAt": 1773943800004, "systemSent": true, "label": "Cron: openclaw-git-sync", "skillsSnapshot": { @@ -910,113 +910,6 @@ "cacheRead": 32768, "cacheWrite": 0 }, - "agent:haonan:cron:f03b184f-5f1d-4093-ae1b-fc9fe0b59eef:run:742f547d-e84f-486c-99fe-66589180c4a7": { - "sessionId": "742f547d-e84f-486c-99fe-66589180c4a7", - "updatedAt": 1773771600017, - "systemSent": true, - "model": "glm-4.7-FlashX", - "contextTokens": 200000, - "label": "Cron: capability-evolver-daily", - "skillsSnapshot": { - "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n", - "skills": [ - { - "name": "capability-evolver" - }, - { - "name": "clawlist" - } - ], - "skillFilter": [ - "clawlist", - "capability-evolver", - "self-improving-agent" - ], - "resolvedSkills": [ - { - "name": "capability-evolver", - "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.", - "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md", - "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver", - "source": "openclaw-managed", - "disableModelInvocation": false - }, - { - "name": "clawlist", - "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.", - "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md", - "baseDir": "/home/starmen1265/.openclaw/skills/clawlist", - "source": "openclaw-managed", - "disableModelInvocation": false - } - ], - "version": 0 - }, - "modelProvider": "zhipu", - "inputTokens": 42108, - "outputTokens": 2001, - "totalTokens": 24584, - "deliveryContext": { - "channel": "webchat" - }, - "lastChannel": "webchat", - "channel": "webchat", - "sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl", - "abortedLastRun": false, - "totalTokensFresh": true, - "cacheRead": 39613, - "cacheWrite": 0 - }, - "agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:13cf89c7-ae35-4b61-935d-4b76542f1c28": { - "sessionId": "13cf89c7-ae35-4b61-935d-4b76542f1c28", - "updatedAt": 1773856800006, - "systemSent": true, - "model": "glm-5", - "contextTokens": 200000, - "label": "Cron: backup-openclaw-json", - "skillsSnapshot": { - "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n", - "skills": [ - { - "name": "capability-evolver" - }, - { - "name": "clawlist" - } - ], - "skillFilter": [ - "clawlist", - "capability-evolver", - "self-improving-agent" - ], - "resolvedSkills": [ - { - "name": "capability-evolver", - "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.", - "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md", - "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver", - "source": "openclaw-managed", - "disableModelInvocation": false - }, - { - "name": "clawlist", - "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.", - "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md", - "baseDir": "/home/starmen1265/.openclaw/skills/clawlist", - "source": "openclaw-managed", - "disableModelInvocation": false - } - ], - "version": 0 - }, - "modelProvider": "zhipu", - "inputTokens": 10815, - "outputTokens": 343, - "totalTokens": 16891, - "totalTokensFresh": true, - "cacheRead": 39360, - "cacheWrite": 0 - }, "agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4": { "sessionId": "26e8d6da-1d38-4fc7-9ddb-a1c14c23fde4", "updatedAt": 1773857400007, @@ -1110,9 +1003,9 @@ "version": 0 }, "modelProvider": "zhipu", - "inputTokens": 42108, - "outputTokens": 2001, - "totalTokens": 24584, + "inputTokens": 55959, + "outputTokens": 3146, + "totalTokens": 156912, "deliveryContext": { "channel": "webchat" }, @@ -1121,7 +1014,107 @@ "sessionFile": "/home/starmen1265/.openclaw/agents/haonan/sessions/ccc40ca7-7876-4d82-ab84-7b790feddc19.jsonl", "abortedLastRun": false, "totalTokensFresh": true, - "cacheRead": 39613, + "cacheRead": 100953, + "cacheWrite": 0 + }, + "agent:haonan:cron:41396aeb-6e3a-47b2-9bd3-9156a348c1dc:run:971b07ad-12bb-4041-879c-97906de4bf18": { + "sessionId": "971b07ad-12bb-4041-879c-97906de4bf18", + "updatedAt": 1773943200005, + "systemSent": true, + "model": "glm-5", + "contextTokens": 200000, + "label": "Cron: backup-openclaw-json", + "skillsSnapshot": { + "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n", + "skills": [ + { + "name": "capability-evolver" + }, + { + "name": "clawlist" + } + ], + "skillFilter": [ + "clawlist", + "capability-evolver", + "self-improving-agent" + ], + "resolvedSkills": [ + { + "name": "capability-evolver", + "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.", + "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md", + "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver", + "source": "openclaw-managed", + "disableModelInvocation": false + }, + { + "name": "clawlist", + "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.", + "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md", + "baseDir": "/home/starmen1265/.openclaw/skills/clawlist", + "source": "openclaw-managed", + "disableModelInvocation": false + } + ], + "version": 0 + }, + "modelProvider": "zhipu", + "inputTokens": 13549, + "outputTokens": 371, + "totalTokens": 16884, + "totalTokensFresh": true, + "cacheRead": 19840, + "cacheWrite": 0 + }, + "agent:haonan:cron:aa4d2de9-79be-4262-83ee-af8824793bfc:run:3659e428-8612-437c-9c2b-1ce2f4a0b33d": { + "sessionId": "3659e428-8612-437c-9c2b-1ce2f4a0b33d", + "updatedAt": 1773943800004, + "systemSent": true, + "label": "Cron: openclaw-git-sync", + "skillsSnapshot": { + "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n \n clawlist\n MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.\n ~/.openclaw/skills/clawlist/SKILL.md\n \n", + "skills": [ + { + "name": "capability-evolver" + }, + { + "name": "clawlist" + } + ], + "skillFilter": [ + "clawlist", + "capability-evolver", + "self-improving-agent" + ], + "resolvedSkills": [ + { + "name": "capability-evolver", + "description": "A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.", + "filePath": "/home/starmen1265/.openclaw/skills/capability-evolver/SKILL.md", + "baseDir": "/home/starmen1265/.openclaw/skills/capability-evolver", + "source": "openclaw-managed", + "disableModelInvocation": false + }, + { + "name": "clawlist", + "description": "MUST use for any multi-step project, long-running task, or infinite monitoring workflow. Plan, execute, track, and verify tasks with checkpoint validation. For projects, automation, and ongoing operations.", + "filePath": "/home/starmen1265/.openclaw/skills/clawlist/SKILL.md", + "baseDir": "/home/starmen1265/.openclaw/skills/clawlist", + "source": "openclaw-managed", + "disableModelInvocation": false + } + ], + "version": 0 + }, + "modelProvider": "zhipu", + "model": "glm-5", + "contextTokens": 200000, + "inputTokens": 1734, + "outputTokens": 413, + "totalTokens": 18003, + "totalTokensFresh": true, + "cacheRead": 32768, "cacheWrite": 0 } } \ No newline at end of file diff --git a/agents/knowledge-base/workspace/memory/2026-03-19-1500.md b/agents/knowledge-base/workspace/memory/2026-03-19-1500.md new file mode 100644 index 0000000..6f778cb --- /dev/null +++ b/agents/knowledge-base/workspace/memory/2026-03-19-1500.md @@ -0,0 +1,164 @@ +# Session: 2026-03-19 15:00:36 UTC + +- **Session Key**: agent:knowledge-base:main +- **Session ID**: 731aff7c-67da-45ff-957f-7940849d0aae +- **Source**: feishu + +## Conversation Summary + +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548be4a02ca4b33d43c7a3208be", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:31 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548be4a02ca4b33d43c7a3208be] +张青: 记一下,可可想要一个里面有小人的公交车 +assistant: 已记录到速记本:**可可想要一个里面有小人的公交车** (2026-03-19 21:31) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548be25df0b0b22cdf708250b00", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:31 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548be25df0b0b22cdf708250b00] +张青: 还要带语音控制的 +assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制)** (2026-03-19 21:31) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548be03aaca4b393638a734446f", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:32 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548be03aaca4b393638a734446f] +张青: 还要能放音乐的 +assistant: 已更新:**可可想要一个里面有小人的公交车(带语音控制、能放音乐)** (2026-03-19 21:31) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548bfc0824a4b364c8c0c8977d1", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:33 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548bfc0824a4b364c8c0c8977d1] +张青: 乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件呢,可以动 +assistant: 已记录:**乐乐想要一个库洛米城堡,里面有很多摆件,这些摆件可以动** (2026-03-19 21:33) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548bfd70e48cb22d0cbc00050f8", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:33 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548bfd70e48cb22d0cbc00050f8] +张青: 通过按按钮来控制里面的家具或者家电呃运动 +assistant: 已更新:**乐乐想要一个库洛米城堡,里面有很多摆件,通过按按钮控制家具或家电运动** (2026-03-19 21:33) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548bf82dbca0b2c751a1b709843", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:34 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548bf82dbca0b2c751a1b709843] +张青: 可可还想要一个可以涂颜色的石膏娃娃 +assistant: 已记录:**可可想要一个可以涂颜色的石膏娃娃** (2026-03-19 21:34) +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b548b8e22dcacb2d8d587bb7b14e", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 21:37 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b548b8e22dcacb2d8d587bb7b14e] +张青: 嗯,可可想要一个空的城堡,里面有个绿色按钮。 + +绿色按钮按一下,城堡的顶上就会出现一个美乐蒂,再按一下按钮美乐蒂就会变成一朵花 +assistant: 已记录:**可可想要一个空的城堡,里面有个绿色按钮:按一下城堡顶上出现美乐蒂,再按一下美乐蒂变成一朵花** (2026-03-19 21:37) diff --git a/agents/knowledge-base/workspace/memory/2026-03-19-1612.md b/agents/knowledge-base/workspace/memory/2026-03-19-1612.md new file mode 100644 index 0000000..b7439a1 --- /dev/null +++ b/agents/knowledge-base/workspace/memory/2026-03-19-1612.md @@ -0,0 +1,67 @@ +# Session: 2026-03-19 16:12:12 UTC + +- **Session Key**: agent:knowledge-base:main +- **Session ID**: b11228d2-b3ca-444f-837c-c0a787d3a0cb +- **Source**: feishu + +## Conversation Summary + +assistant: ✅ New session started · model: zhipu/glm-5 +user: A new session was started via /new or /reset. Execute your Session Startup sequence now - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning. +assistant: 📚 蒋先生您好,我是达康书记。已为您准备好知识库与工作区维护服务。 + +今天有什么需要我帮您处理的? +user: Conversation info (untrusted metadata): +```json +{ + "message_id": "om_x100b54f5454c14a4c42e9b6c6ea2cd3", + "sender_id": "ou_9351c287f42069fa30366ac641d24df7", + "sender": "张青", + "timestamp": "Thu 2026-03-19 23:05 GMT+8" +} +``` + +Sender (untrusted metadata): +```json +{ + "label": "张青 (ou_9351c287f42069fa30366ac641d24df7)", + "id": "ou_9351c287f42069fa30366ac641d24df7", + "name": "张青" +} +``` + +[message_id: om_x100b54f5454c14a4c42e9b6c6ea2cd3] +张青: https://www.bilibili.com/video/BV1NwwqzeE55/?t=6&spm_id_from=333.1007.tianma.1-1-1.click&vd_source=1deaab00c5de8885422a7a7690170eba 添加知识库 +assistant: 收到,蒋先生。我立即按照知识库工作流技能处理这个B站视频链接。 +assistant: 好的,蒋先生。我按照知识库工作流技能处理这个B站视频链接。 + +**步骤 1:检查环境变量** +assistant: 好的,蒋先生。工作流已经开始处理这个B站视频。我看到它正在: + +1. ✅ 解析 URL +2. ⚠️ 字幕获取失败(可能视频没有字幕) +3. 🔄 正在进行 AI 深度分析... + +让我继续监控进度: +assistant: ✅ **处理完成!** + +**视频信息:** +- **标题:** 爱因斯坦又对了!科学家首次观测到物质从真空中诞生,【科学快报】265期 +- **作者:** 科学透镜 +- **BV号:** BV1NwwqzeE55 + +**处理流程:** +- ✅ Parse - URL 解析成功 +- ✅ Download - 下载音频和元数据 +- ✅ ASR - 语音识别完成(Whisper) +- ✅ Analyze - AI 深度分析完成(耗时 110.78秒) +- ✅ Archive - 归档成功 + +**核心内容摘要:** +本视频报道了三项颠覆性科学发现: +1. **物质从真空中诞生** - 布鲁克海文国家实验室首次观测到Lambda超子与反粒子的自旋纠缠,证实虚粒子可转化为真实物质 +2. **电子运动延迟** - ETH测得电子响应原子核运动存在30飞秒延迟,打破波恩-奥本海默近似 +3. **半莫比乌斯分子** - 人类首次合成并精准操控具有拓扑结构的分子 + +**归档位置:** +`~/work/zq/30.资源/AI分析/bilibili/20260319_BV1NwwqzeE55_analysis.md` diff --git a/agents/knowledge-base/workspace/memory/2026-03-19.md b/agents/knowledge-base/workspace/memory/2026-03-19.md new file mode 100644 index 0000000..af51648 --- /dev/null +++ b/agents/knowledge-base/workspace/memory/2026-03-19.md @@ -0,0 +1,8 @@ +# 2026-03-19 每日记录 + +## 定时任务执行 + +### 02:30 - 每日回顾整理(daily-review-cleanup) +- 执行脚本:`~/.openclaw/cron/scripts/daily-review.sh` +- 处理日期:2026-03-18 +- 结果:从 NAS 拉取更新完成,无已完成任务需要归档 diff --git a/agents/ui-designer/sessions/sessions.json b/agents/ui-designer/sessions/sessions.json index 6f5d6f0..5ea9fca 100644 --- a/agents/ui-designer/sessions/sessions.json +++ b/agents/ui-designer/sessions/sessions.json @@ -1,7 +1,7 @@ { "agent:ui-designer:main": { - "sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84", - "updatedAt": 1773758631721, + "sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e", + "updatedAt": 1773937164358, "systemSent": true, "abortedLastRun": false, "chatType": "direct", @@ -22,7 +22,7 @@ "to": "user:ou_9847ce84b960736c14554730b41e4957", "accountId": "feishu-art" }, - "sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/4caf7900-b5bc-4f76-a39d-80e506bcea84.jsonl", + "sessionFile": "/home/starmen1265/.openclaw/agents/ui-designer/sessions/60827bbf-d985-4a29-bd62-1783159bb38e.jsonl", "compactionCount": 0, "skillsSnapshot": { "prompt": "\n\nThe following skills provide specialized instructions for specific tasks.\nUse the read tool to load a skill's file when the task matches its description.\nWhen a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.\n\n\n \n capability-evolver\n A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.\n ~/.openclaw/skills/capability-evolver/SKILL.md\n \n", @@ -51,8 +51,8 @@ "model": "glm-5", "systemPromptReport": { "source": "run", - "generatedAt": 1773758614151, - "sessionId": "4caf7900-b5bc-4f76-a39d-80e506bcea84", + "generatedAt": 1773937122951, + "sessionId": "60827bbf-d985-4a29-bd62-1783159bb38e", "sessionKey": "agent:ui-designer:main", "provider": "zhipu", "model": "glm-5", @@ -64,8 +64,8 @@ "sandboxed": false }, "systemPrompt": { - "chars": 24499, - "projectContextChars": 12014, + "chars": 36876, + "projectContextChars": 24391, "nonProjectContextChars": 12485 }, "injectedWorkspaceFiles": [ @@ -73,8 +73,8 @@ "name": "AGENTS.md", "path": "/home/starmen1265/.openclaw/agents/ui-designer/workspace/AGENTS.md", "missing": false, - "rawChars": 2617, - "injectedChars": 2617, + "rawChars": 2663, + "injectedChars": 2663, "truncated": false }, { @@ -187,6 +187,120 @@ "injectedChars": 616, "truncated": false }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, + { + "path": "SELF_IMPROVEMENT_REMINDER.md", + "rawChars": 616, + "injectedChars": 616, + "truncated": false + }, { "path": "SELF_IMPROVEMENT_REMINDER.md", "rawChars": 616, @@ -414,13 +528,13 @@ ] } }, - "cacheRead": 9408, + "cacheRead": 23872, "cacheWrite": 0, "totalTokensFresh": true, "contextTokens": 200000, - "inputTokens": 7026, - "outputTokens": 464, - "totalTokens": 16434 + "inputTokens": 311770, + "outputTokens": 332, + "totalTokens": 170338 }, "agent:ui-designer:cron:78b64c2f-7f91-47ff-9250-d13f546eb21f": { "sessionId": "03e44b46-c707-4a9f-bf3d-1818aafdcde2", diff --git a/cron/jobs.json b/cron/jobs.json index 09b9348..08a3464 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -7,7 +7,7 @@ "name": "backup-openclaw-json", "enabled": true, "createdAtMs": 1772261581796, - "updatedAtMs": 1773856817101, + "updatedAtMs": 1773943214562, "schedule": { "kind": "cron", "expr": "0 2 * * *", @@ -23,10 +23,10 @@ "mode": "announce" }, "state": { - "nextRunAtMs": 1773943200000, - "lastRunAtMs": 1773856800004, + "nextRunAtMs": 1774029600000, + "lastRunAtMs": 1773943200004, "lastStatus": "ok", - "lastDurationMs": 17097, + "lastDurationMs": 14558, "consecutiveErrors": 0, "lastRunStatus": "ok", "lastDelivered": true, @@ -59,7 +59,8 @@ "consecutiveErrors": 0, "lastRunStatus": "ok", "lastDeliveryStatus": "delivered", - "lastDelivered": true + "lastDelivered": true, + "runningAtMs": 1773943800002 }, "delivery": { "mode": "announce" @@ -71,7 +72,7 @@ "name": "capability-evolver-daily", "enabled": true, "createdAtMs": 1772550743528, - "updatedAtMs": 1773771643135, + "updatedAtMs": 1773858232095, "schedule": { "kind": "cron", "expr": "20 2 * * *", @@ -92,16 +93,16 @@ "to": "feishu-pm" }, "state": { - "nextRunAtMs": 1773858000000, - "lastRunAtMs": 1773771600014, + "nextRunAtMs": 1773944400000, + "lastRunAtMs": 1773858000004, "lastStatus": "error", - "lastDurationMs": 43121, - "consecutiveErrors": 5, + "lastDurationMs": 232091, + "consecutiveErrors": 6, "lastRunStatus": "error", "lastDeliveryStatus": "not-delivered", "lastDelivered": false, - "lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed", - "runningAtMs": 1773858000003 + "lastDeliveryError": "⚠️ ✉️ Message failed", + "lastError": "⚠️ ✉️ Message failed" } }, { @@ -111,7 +112,7 @@ "description": "每天凌晨2:30整理昨天的已完成任务到每日回顾", "enabled": true, "createdAtMs": 1741484880000, - "updatedAtMs": 1773772248827, + "updatedAtMs": 1773858626341, "schedule": { "kind": "cron", "expr": "30 2 * * *", @@ -129,11 +130,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773858600000, - "lastRunAtMs": 1773772200010, + "nextRunAtMs": 1773945000000, + "lastRunAtMs": 1773858600004, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 48817, + "lastDurationMs": 26337, "lastDelivered": true, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0 @@ -145,7 +146,7 @@ "name": "zq_workspace_sync", "enabled": true, "createdAtMs": 1772551807455, - "updatedAtMs": 1773772800011, + "updatedAtMs": 1773859200006, "schedule": { "expr": "40 2 * * *", "kind": "cron", @@ -158,8 +159,8 @@ "text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh" }, "state": { - "nextRunAtMs": 1773859200000, - "lastRunAtMs": 1773772800010, + "nextRunAtMs": 1773945600000, + "lastRunAtMs": 1773859200005, "lastStatus": "skipped", "lastDurationMs": 1, "consecutiveErrors": 0, @@ -175,7 +176,7 @@ "description": "每天早上8点整理与各个 AI 代理的对话记录", "enabled": true, "createdAtMs": 1772776369645, - "updatedAtMs": 1773792029087, + "updatedAtMs": 1773878422623, "schedule": { "kind": "cron", "expr": "0 8 * * *", @@ -193,11 +194,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773878400000, - "lastRunAtMs": 1773792000007, + "nextRunAtMs": 1773964800000, + "lastRunAtMs": 1773878400004, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 29080, + "lastDurationMs": 22619, "lastDelivered": true, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0 @@ -210,7 +211,7 @@ "description": "每天7点同步提醒到CalDAV日历", "enabled": true, "createdAtMs": 1772791528528, - "updatedAtMs": 1773788430000, + "updatedAtMs": 1773874820139, "schedule": { "kind": "cron", "expr": "0 7 * * *", @@ -228,11 +229,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773874800000, - "lastRunAtMs": 1773788400005, + "nextRunAtMs": 1773961200000, + "lastRunAtMs": 1773874800006, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 29995, + "lastDurationMs": 20133, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0, "lastDelivered": true diff --git a/cron/jobs.json.bak b/cron/jobs.json.bak index 753feea..2d8376d 100644 --- a/cron/jobs.json.bak +++ b/cron/jobs.json.bak @@ -7,7 +7,7 @@ "name": "backup-openclaw-json", "enabled": true, "createdAtMs": 1772261581796, - "updatedAtMs": 1773856817101, + "updatedAtMs": 1773943214562, "schedule": { "kind": "cron", "expr": "0 2 * * *", @@ -23,10 +23,10 @@ "mode": "announce" }, "state": { - "nextRunAtMs": 1773943200000, - "lastRunAtMs": 1773856800004, + "nextRunAtMs": 1774029600000, + "lastRunAtMs": 1773943200004, "lastStatus": "ok", - "lastDurationMs": 17097, + "lastDurationMs": 14558, "consecutiveErrors": 0, "lastRunStatus": "ok", "lastDelivered": true, @@ -71,7 +71,7 @@ "name": "capability-evolver-daily", "enabled": true, "createdAtMs": 1772550743528, - "updatedAtMs": 1773771643135, + "updatedAtMs": 1773858232095, "schedule": { "kind": "cron", "expr": "20 2 * * *", @@ -92,16 +92,16 @@ "to": "feishu-pm" }, "state": { - "nextRunAtMs": 1773858000000, - "lastRunAtMs": 1773771600014, + "nextRunAtMs": 1773944400000, + "lastRunAtMs": 1773858000004, "lastStatus": "error", - "lastDurationMs": 43121, - "consecutiveErrors": 5, + "lastDurationMs": 232091, + "consecutiveErrors": 6, "lastRunStatus": "error", "lastDeliveryStatus": "not-delivered", "lastDelivered": false, - "lastDeliveryError": "⚠️ ✉️ Message: 蒋先生 failed", - "lastError": "⚠️ ✉️ Message: 蒋先生 failed" + "lastDeliveryError": "⚠️ ✉️ Message failed", + "lastError": "⚠️ ✉️ Message failed" } }, { @@ -111,7 +111,7 @@ "description": "每天凌晨2:30整理昨天的已完成任务到每日回顾", "enabled": true, "createdAtMs": 1741484880000, - "updatedAtMs": 1773772248827, + "updatedAtMs": 1773858626341, "schedule": { "kind": "cron", "expr": "30 2 * * *", @@ -129,11 +129,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773858600000, - "lastRunAtMs": 1773772200010, + "nextRunAtMs": 1773945000000, + "lastRunAtMs": 1773858600004, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 48817, + "lastDurationMs": 26337, "lastDelivered": true, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0 @@ -145,7 +145,7 @@ "name": "zq_workspace_sync", "enabled": true, "createdAtMs": 1772551807455, - "updatedAtMs": 1773772800011, + "updatedAtMs": 1773859200006, "schedule": { "expr": "40 2 * * *", "kind": "cron", @@ -158,8 +158,8 @@ "text": "执行 zq 工作区同步:bash ~/.openclaw/cron/scripts/zq_workspace_sync.sh" }, "state": { - "nextRunAtMs": 1773859200000, - "lastRunAtMs": 1773772800010, + "nextRunAtMs": 1773945600000, + "lastRunAtMs": 1773859200005, "lastStatus": "skipped", "lastDurationMs": 1, "consecutiveErrors": 0, @@ -175,7 +175,7 @@ "description": "每天早上8点整理与各个 AI 代理的对话记录", "enabled": true, "createdAtMs": 1772776369645, - "updatedAtMs": 1773792029087, + "updatedAtMs": 1773878422623, "schedule": { "kind": "cron", "expr": "0 8 * * *", @@ -193,11 +193,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773878400000, - "lastRunAtMs": 1773792000007, + "nextRunAtMs": 1773964800000, + "lastRunAtMs": 1773878400004, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 29080, + "lastDurationMs": 22619, "lastDelivered": true, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0 @@ -210,7 +210,7 @@ "description": "每天7点同步提醒到CalDAV日历", "enabled": true, "createdAtMs": 1772791528528, - "updatedAtMs": 1773788430000, + "updatedAtMs": 1773874820139, "schedule": { "kind": "cron", "expr": "0 7 * * *", @@ -228,11 +228,11 @@ "channel": "last" }, "state": { - "nextRunAtMs": 1773874800000, - "lastRunAtMs": 1773788400005, + "nextRunAtMs": 1773961200000, + "lastRunAtMs": 1773874800006, "lastRunStatus": "ok", "lastStatus": "ok", - "lastDurationMs": 29995, + "lastDurationMs": 20133, "lastDeliveryStatus": "delivered", "consecutiveErrors": 0, "lastDelivered": true diff --git a/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json b/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json new file mode 100644 index 0000000..9a0c273 --- /dev/null +++ b/delivery-queue/bc3f3807-8735-4bef-bb42-3efd115c3f7e.json @@ -0,0 +1,21 @@ +{ + "id": "bc3f3807-8735-4bef-bb42-3efd115c3f7e", + "enqueuedAt": 1773858231892, + "channel": "feishu", + "to": "feishu-pm", + "accountId": "feishu-pm", + "payloads": [ + { + "text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:" + } + ], + "threadId": null, + "mirror": { + "sessionKey": "agent:haonan:main", + "agentId": "haonan", + "text": "我遇到了 Feishu 目标格式的问题。让我直接在当前会话中向您汇报:" + }, + "retryCount": 1, + "lastAttemptAt": 1773858232086, + "lastError": "Request failed with status code 400" +} \ No newline at end of file diff --git a/feishu/dedup/feishu-art.json b/feishu/dedup/feishu-art.json index 3b55670..c9f86f8 100644 --- a/feishu/dedup/feishu-art.json +++ b/feishu/dedup/feishu-art.json @@ -1,5 +1,31 @@ { - "om_x100b54b68a9658a8c3597587eaea165": 1773677638200, - "om_x100b54b69ae988acc4e308675a1599b": 1773677890863, - "om_x100b54a2c799a0b0c2ea37cee3ecec2": 1773758613782 + "om_x100b5480d5bba4b4b2c9a4e8bf5b189": 1773881784103, + "om_x100b54808ff17c90b374531d1cf7751": 1773882388481, + "om_x100b5481441a38a4b20013813040b59": 1773883566093, + "om_x100b54816cbffcb8b36dfe46fd84a26": 1773883944086, + "om_x100b54817240a0a0b49ad1ecc4230d1": 1773884361569, + "om_x100b548114fc64a4b34dda488c6c4cf": 1773884835671, + "om_x100b548129129490b4b1770d7f3efcc": 1773885054622, + "om_x100b5481243380b0b10b5bef1b92ca3": 1773885104561, + "om_x100b54813d7144bcb2da847227b06a8": 1773885244462, + "om_x100b548139e42d10b3ffcf9447ed48c": 1773885299355, + "om_x100b548133fa3ca4b219d22fcf41e18": 1773885396226, + "om_x100b5481ce0418a0b26dc9b2f68f007": 1773885453209, + "om_x100b548c9b6dcc78b49c9ec428e44f3": 1773899098605, + "om_x100b548ca39e00acc3362a9bf3873de": 1773899477853, + "om_x100b548d47a538b0c2c282968797cea": 1773899927180, + "om_x100b5489ba663ca0c4c8bc1d9b97937": 1773920075261, + "om_x100b548a50d788b8b2c1f705241dbc5": 1773920738205, + "om_x100b548a72e7f4a8b286f0d7e9aea31": 1773921219213, + "om_x100b548a83211ca8c44bcbf00eb049e": 1773923551363, + "om_x100b548a95d45530c29580a9d1bad9a": 1773923762295, + "om_x100b548b1fa8c8a4c42e95eb381a75c": 1773925655047, + "om_x100b54f49f9f20b0b2d58df353d8a5b": 1773931798109, + "om_x100b54f51931bca0c361d933da09d2b": 1773933952255, + "om_x100b54f52b61f0a4c3650d280bf0dad": 1773934171242, + "om_x100b54f53b8678b0c3188a42d675405": 1773934421286, + "om_x100b54f533291ca4c2cf74f54da005d": 1773934558803, + "om_x100b54f5b9ebf8a0c33674c32a8da6c": 1773936499279, + "om_x100b54f64c3844acc14333ab25a59e5": 1773936688149, + "om_x100b54f650ef80a0c2d3c76a3fbe9be": 1773937122637 } diff --git a/feishu/dedup/feishu-worker.json b/feishu/dedup/feishu-worker.json index e1d7b1d..dec6427 100644 --- a/feishu/dedup/feishu-worker.json +++ b/feishu/dedup/feishu-worker.json @@ -1,10 +1,18 @@ { - "om_x100b54a3ae6304a0b28ab7519a1acf1": 1773764107460, - "om_x100b54a3a3782938b379c9a1da0010b": 1773764315828, - "om_x100b54a3b60634a0b2910ed3595f0aa": 1773764493305, - "om_x100b54955fb54964c4e180ded6a68b3": 1773801752225, - "om_x100b54955a75ccb4c11c3b4070aa4fd": 1773801804425, - "om_x100b54951cb128bcc361c4fb7da8c71": 1773802792386, - "om_x100b5498aaee84a0c451ce70c732daf": 1773850178765, - "om_x100b5498a8e104a8c37682c5c9f2e15": 1773850211313 + "om_x100b548be4a02ca4b33d43c7a3208be": 1773927079628, + "om_x100b548be25df0b0b22cdf708250b00": 1773927113615, + "om_x100b548be03aaca4b393638a734446f": 1773927152061, + "om_x100b548bfc0824a4b364c8c0c8977d1": 1773927212950, + "om_x100b548bfd70e48cb22d0cbc00050f8": 1773927228439, + "om_x100b548bf82dbca0b2c751a1b709843": 1773927278588, + "om_x100b548b8e22dcacb2d8d587bb7b14e": 1773927439644, + "om_x100b54f4b7f294a0c45989f17b70fb7": 1773932436549, + "om_x100b54f5454c14a4c42e9b6c6ea2cd3": 1773932728615, + "om_x100b54f64b72d4b4c44dfcb4a5b55ab": 1773936732479, + "om_x100b54f644fc18a4c3e0e0a4d345c50": 1773936803575, + "om_x100b54f654df8534c4ee3346acd1bcd": 1773937057577, + "om_x100b54f6091f78a4c4d71668a805f7d": 1773937789758, + "om_x100b54f6cf31b4a0c453538202af189": 1773938720210, + "om_x100b54f6d65a48a4c4e7a9045781192": 1773939082098, + "om_x100b54f6d48980a0c2cc7e00d22cd8e": 1773939108667 } diff --git a/identity/device-auth.json b/identity/device-auth.json index 713caf9..32eb46b 100644 --- a/identity/device-auth.json +++ b/identity/device-auth.json @@ -10,7 +10,7 @@ "operator.approvals", "operator.pairing" ], - "updatedAtMs": 1773857416774 + "updatedAtMs": 1773943213908 } } } diff --git a/logs/commands.log b/logs/commands.log index 75fe861..f5a563a 100644 --- a/logs/commands.log +++ b/logs/commands.log @@ -60,3 +60,5 @@ {"timestamp":"2026-03-17T16:06:09.290Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"} {"timestamp":"2026-03-18T02:42:32.742Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"} {"timestamp":"2026-03-18T16:09:39.151Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"} +{"timestamp":"2026-03-19T15:00:36.886Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"} +{"timestamp":"2026-03-19T16:12:12.817Z","action":"new","sessionKey":"agent:knowledge-base:main","senderId":"ou_9351c287f42069fa30366ac641d24df7","source":"feishu"} diff --git a/skills/kb_workflow/.env.example b/skills/kb_workflow/.env.example index dc8e79b..283b878 100644 --- a/skills/kb_workflow/.env.example +++ b/skills/kb_workflow/.env.example @@ -12,3 +12,6 @@ export KB_BASE_PATH="~/work/knowledge-base" # 可选: Git 配置 # export KB_GIT_USER="Your Name" # export KB_GIT_EMAIL="your@email.com" + +# 可选: 工作流行为配置 +# export KB_OVERWRITE_ARCHIVES="false" # 非交互模式下是否覆盖已存在的归档文件(true/false) diff --git a/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc index 135bf9e..fcfe64b 100644 Binary files a/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc b/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc index 43a557e..a2df455 100644 Binary files a/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc and b/skills/kb_workflow/__pycache__/__main__.cpython-312.pyc differ diff --git a/skills/kb_workflow/__pycache__/cli.cpython-312.pyc b/skills/kb_workflow/__pycache__/cli.cpython-312.pyc index 9333d1c..93f868e 100644 Binary files a/skills/kb_workflow/__pycache__/cli.cpython-312.pyc and b/skills/kb_workflow/__pycache__/cli.cpython-312.pyc differ diff --git a/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc index 9586cdd..abed6ec 100644 Binary files a/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/auth/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc b/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc index 9143470..55283bd 100644 Binary files a/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc and b/skills/kb_workflow/auth/__pycache__/zhihu_login.cpython-312.pyc differ diff --git a/skills/kb_workflow/cli.py b/skills/kb_workflow/cli.py index ca2fe0f..d8440a1 100644 --- a/skills/kb_workflow/cli.py +++ b/skills/kb_workflow/cli.py @@ -49,7 +49,7 @@ def process_command( from .pipeline.pipeline import Pipeline from .pipeline.processors import ( ASRProcessor, AnalyzeProcessor, ArchiveProcessor, - DownloadProcessor, ParseProcessor + DownloadProcessor, ParseProcessor, SRTProcessor ) from .pipeline.schemas import PipelineConfig @@ -63,13 +63,14 @@ def process_command( ParseProcessor(), DownloadProcessor(), ASRProcessor(), - AnalyzeProcessor(), + SRTProcessor(), # B站视频使用 + AnalyzeProcessor(), # 知乎/文章使用 ArchiveProcessor(), ] # Create pipeline config config = PipelineConfig( - steps=["parse", "download", "asr", "analyze", "archive"], + steps=["parse", "download", "asr", "srt", "analyze", "archive"], output_dir=out_dir, skip_completed=skip_completed, ) @@ -165,7 +166,7 @@ def login_command( result = login.login_by_qrcode() if result.success and result.cookie_str: manager.save_cookie(result.cookie_str) - return result.model_dump() + return result.model_dump(mode='json') except Exception as e: return {"success": False, "error": str(e)} diff --git a/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc index f098ea1..2e8f7b3 100644 Binary files a/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc index c7577fe..0695e00 100644 Binary files a/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/config.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc index 7ff5237..daccb74 100644 Binary files a/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/models.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc index 4b37a08..28fd0ca 100644 Binary files a/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/proxy_pool.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc b/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc index 8b45c2f..ab455b7 100644 Binary files a/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc and b/skills/kb_workflow/crawler/__pycache__/zhihu_crawler.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc index 70a8de0..efdc635 100644 Binary files a/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/crawler/stealth/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc b/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc index c047be4..88d3160 100644 Binary files a/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc and b/skills/kb_workflow/crawler/stealth/__pycache__/stealth_config.cpython-312.pyc differ diff --git a/skills/kb_workflow/deep_analyzer/__init__.py b/skills/kb_workflow/deep_analyzer/__init__.py index 2f947ce..4b06248 100644 --- a/skills/kb_workflow/deep_analyzer/__init__.py +++ b/skills/kb_workflow/deep_analyzer/__init__.py @@ -90,22 +90,35 @@ class SimpleAIClient: def _call_zhipu(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str: import requests + import sys + base_url = base_url or "https://open.bigmodel.cn/api/coding/paas/v4" url = f"{base_url}/chat/completions" headers = {"Authorization": f"Bearer {api_key}"} - payload = {"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": max_tokens} + # GLM-4.5/4.6/4.7/5 默认启用thinking mode,使用 thinking.type 参数禁用 + payload = { + "model": model, + "messages": [{"role": "user", "content": prompt}], + "max_tokens": max_tokens, + "thinking": { + "type": "disabled" # 禁用思考模式,获取直接响应 + } + } response = requests.post(url, headers=headers, json=payload, timeout=600) response.raise_for_status() data = response.json() message = data.get('choices', [{}])[0].get('message', {}) content = message.get('content', '') - reasoning = message.get('reasoning_content', '') - if not content and reasoning: - sentences = reasoning.strip().split('。') - useful = [s.strip() for s in sentences if s.strip() and len(s.strip()) > 10] - if useful: - return '。'.join(useful[-3:]) + '。' - return reasoning.strip()[:500] + + if not content: + # 如果仍然为空,检查是否有reasoning_content(用于调试) + reasoning = message.get('reasoning_content', '') + if reasoning: + print(f"[DEBUG] Zhipu API返回空的content,但有reasoning_content。", file=sys.stderr) + print(f"[DEBUG] 这可能表示thinking.type参数未生效或模型不支持。", file=sys.stderr) + print(f"[DEBUG] reasoning_content前200字符: {reasoning[:200]}", file=sys.stderr) + raise ValueError(f"Zhipu API返回空内容。请检查API配置和模型参数。响应数据: {data}") + return content def _call_minimax(self, prompt: str, max_tokens: int, api_key: str, model: str, base_url: str) -> str: @@ -129,6 +142,13 @@ class DeepAnalyzer: def __init__(self, ai_client: SimpleAIClient = None): self.ai_client = ai_client or SimpleAIClient() + # 弃用警告:B站视频应使用 SRTProcessor + import logging + logger = logging.getLogger(__name__) + logger.warning( + "DeepAnalyzer is deprecated for Bilibili video processing. " + "Use SRTProcessor instead." + ) def analyze(self, metadata: Dict[str, Any], content: Dict[str, Any], content_type: str = "video") -> AnalysisResult: """一次性调用 AI 生成完整分析报告 diff --git a/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc index d9d9811..b4bd08e 100644 Binary files a/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/deep_analyzer/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc index 6c78a3a..ab88287 100644 Binary files a/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc index d2f6498..c1b973e 100644 Binary files a/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/bilibili_extractor.cpython-312.pyc differ diff --git a/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc index e5585ea..a29235d 100644 Binary files a/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/markdown_converter.cpython-312.pyc differ diff --git a/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc b/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc index a2e251f..e102397 100644 Binary files a/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc and b/skills/kb_workflow/metadata/__pycache__/zhihu_extractor.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc index 1e74f3c..99e0733 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc index b10ce9a..5688789 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/config.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc index 90ff78e..5cd8e69 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/errors.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc index c7fd3e3..a1c1db0 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/idempotency.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc index bb7f393..07eceb8 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/link_parser.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc index 12dac89..0135d0a 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/pipeline.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc index 1acd865..21536d2 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/retry.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc index fdaf977..ceccc30 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/schemas.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc b/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc index 569b93e..90f9f2a 100644 Binary files a/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc and b/skills/kb_workflow/pipeline/__pycache__/state.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/config.py b/skills/kb_workflow/pipeline/config.py index 855a113..3422b5b 100644 --- a/skills/kb_workflow/pipeline/config.py +++ b/skills/kb_workflow/pipeline/config.py @@ -58,6 +58,11 @@ class KBSettings(BaseSettings): default=SecretStr(""), description="B站 Cookie (for video download)" ) + # 工作流行为配置 + overwrite_archives: bool = Field( + default=False, description="覆盖已存在的归档文件(非交互模式)" + ) + @lru_cache def get_settings() -> KBSettings: diff --git a/skills/kb_workflow/pipeline/pipeline.py b/skills/kb_workflow/pipeline/pipeline.py index 60d57fc..f96f62d 100644 --- a/skills/kb_workflow/pipeline/pipeline.py +++ b/skills/kb_workflow/pipeline/pipeline.py @@ -21,7 +21,7 @@ from typing import Dict, List, Optional from kb_workflow.pipeline.config import get_settings from kb_workflow.pipeline.link_parser import LinkParser, ParsedLink from kb_workflow.pipeline.processors.base import BaseProcessor -from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput +from kb_workflow.pipeline.schemas import PipelineConfig, PipelineInput, PipelineOutput, SRTInput logger = logging.getLogger(__name__) @@ -174,6 +174,20 @@ class Pipeline: logger.info(f"Skipping ASR for text content type: {parsed.content_type}") continue + # B站视频:跳过 analyze 步骤,使用 srt 步骤替代 + is_bilibili_video = ( + parsed.platform == "bilibili" and + parsed.content_type == "video" + ) + if step_name == "analyze" and is_bilibili_video: + logger.info(f"Skipping analyze for Bilibili video, using SRT processor instead") + continue + + # B站文章/知乎等:跳过 srt 步骤 + if step_name == "srt" and not is_bilibili_video: + logger.info(f"Skipping SRT for non-Bilibili-video content: {parsed.platform}/{parsed.content_type}") + continue + processor = self.processors[step_name] # Mark step as started @@ -228,10 +242,26 @@ class Pipeline: content_type=parsed.content_type, output_file=str(content_dir / "analysis.md"), ) + elif step_name == "srt": + # SRT needs srt file from ASR step and metadata + download_result = results.get("download", {}) + asr_result = results.get("asr", {}) + metadata_file = download_result.get("metadata_file", "") + # SRT file from ASR output + srt_file = asr_result.get("output_files", {}).get("srt", "") + + step_input = SRTInput( + content_id=parsed.content_id, + platform=parsed.platform, + srt_file=srt_file, + metadata_file=metadata_file, + output_file=str(content_dir / "analysis.md"), + content_type=parsed.content_type, + ) elif step_name == "archive": - # Archive needs analysis file from analyze step - analyze_result = results.get("analyze", {}) - analysis_file = analyze_result.get("analysis_file", "") + # Archive needs analysis file from analyze step (or srt step for Bilibili video) + analyze_result = results.get("analyze", {}) or results.get("srt", {}) + analysis_file = analyze_result.get("analysis_file", "") or analyze_result.get("output_file", "") step_input = ArchiveInput( content_id=parsed.content_id, @@ -261,7 +291,7 @@ class Pipeline: # Add output fields to results for field_name in ["metadata_file", "content_file", "title", "author", "analysis_file", "summary", "output_files", - "archive_file", "skipped"]: + "archive_file", "skipped", "output_file", "paragraphs", "tags", "corrected"]: if hasattr(step_output, field_name): results[step_name][field_name] = getattr(step_output, field_name) @@ -397,3 +427,14 @@ class Pipeline: "analysis_file": str(analysis_path), } logger.info(f"Found existing analysis: {analysis_path}") + + elif step_name == "srt": + # Scan for SRT output (same file as analyze - analysis.md) + analysis_path = content_dir / "analysis.md" + if analysis_path.exists(): + results["srt"] = { + "success": True, + "error_message": None, + "output_file": str(analysis_path), + } + logger.info(f"Found existing SRT output: {analysis_path}") diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc index cbe3eae..fe3e842 100644 Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc index 39e5d42..2a395db 100644 Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/base.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc index 2c4ec8e..45e8bae 100644 Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/bilibili.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc b/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc index 06650be..d108772 100644 Binary files a/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc and b/skills/kb_workflow/pipeline/platforms/__pycache__/zhihu.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__init__.py b/skills/kb_workflow/pipeline/processors/__init__.py index 5354519..b7f91d0 100644 --- a/skills/kb_workflow/pipeline/processors/__init__.py +++ b/skills/kb_workflow/pipeline/processors/__init__.py @@ -5,6 +5,7 @@ This module contains processors for various content transformation tasks: - ParseProcessor: URL parsing for various platforms - DownloadProcessor: Metadata and content downloading - ASRProcessor: Audio-to-text conversion (subtitle download + Whisper.cpp) +- SRTProcessor: SRT subtitle processing (parse, segment, correct, markdown) - AnalyzeProcessor: AI-powered content analysis using DeepAnalyzer - ArchiveProcessor: Copy analysis reports to archive directory """ @@ -13,6 +14,7 @@ from kb_workflow.pipeline.processors.base import BaseProcessor from kb_workflow.pipeline.processors.parse import ParseProcessor from kb_workflow.pipeline.processors.download import DownloadProcessor from kb_workflow.pipeline.processors.asr import ASRProcessor +from kb_workflow.pipeline.processors.srt import SRTProcessor from kb_workflow.pipeline.processors.analyze import AnalyzeProcessor from kb_workflow.pipeline.processors.archive import ArchiveProcessor @@ -21,6 +23,7 @@ __all__ = [ "ParseProcessor", "DownloadProcessor", "ASRProcessor", + "SRTProcessor", "AnalyzeProcessor", "ArchiveProcessor", ] diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc index f701381..7684ca7 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc index ae02829..7de22a1 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/analyze.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc index 38dd7cd..0195504 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/archive.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc index 073a6ae..13283d5 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/asr.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc index 64bd84b..5e229ee 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/base.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc index 5000219..65c9098 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/download.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc index 46fe483..c9795a4 100644 Binary files a/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc and b/skills/kb_workflow/pipeline/processors/__pycache__/parse.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc b/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc new file mode 100644 index 0000000..a8d8f69 Binary files /dev/null and b/skills/kb_workflow/pipeline/processors/__pycache__/srt.cpython-312.pyc differ diff --git a/skills/kb_workflow/pipeline/processors/archive.py b/skills/kb_workflow/pipeline/processors/archive.py index 9866a52..e9c20ac 100644 --- a/skills/kb_workflow/pipeline/processors/archive.py +++ b/skills/kb_workflow/pipeline/processors/archive.py @@ -46,6 +46,8 @@ class ArchiveProcessor(BaseProcessor): Archive 处理结果 """ try: + import sys + # Step 1: 检查是否已完成(幂等执行) if self.state_manager: state = self.state_manager.load() @@ -73,7 +75,9 @@ class ArchiveProcessor(BaseProcessor): platform_dir, input_data.content_id ) if existing_file: - should_overwrite = self._ask_overwrite(existing_file) + # 检测是否为非交互式环境 + is_non_interactive = not sys.stdin.isatty() + should_overwrite = self._ask_overwrite(existing_file, is_non_interactive) if not should_overwrite: logger.info(f"User chose not to overwrite, skipping archive") return ArchiveOutput( @@ -143,17 +147,28 @@ class ArchiveProcessor(BaseProcessor): matches = list(platform_dir.glob(pattern)) return matches[0] if matches else None - def _ask_overwrite(self, existing_file: Path) -> bool: + def _ask_overwrite(self, existing_file: Path, is_non_interactive: bool = False) -> bool: """询问用户是否覆盖已存在的文件 使用 rich.prompt.Confirm 模式(与 Pipeline._ask_rerun_step 相同) Args: existing_file: 已存在的文件路径 + is_non_interactive: 是否为非交互式环境(stdin不是tty) Returns: True 如果用户选择覆盖 """ + # 非交互式模式:使用配置决定 + if is_non_interactive: + if self.settings.overwrite_archives: + logger.info(f"Non-interactive mode: Overwriting existing archive (KB_OVERWRITE_ARCHIVES=true): {existing_file.name}") + return True + else: + logger.info(f"Non-interactive mode: Skipping existing archive (KB_OVERWRITE_ARCHIVES=false): {existing_file.name}") + return False + + # 交互式模式:询问用户 try: from rich.console import Console from rich.prompt import Confirm diff --git a/skills/kb_workflow/pipeline/processors/srt.py b/skills/kb_workflow/pipeline/processors/srt.py new file mode 100644 index 0000000..2723e38 --- /dev/null +++ b/skills/kb_workflow/pipeline/processors/srt.py @@ -0,0 +1,333 @@ +"""SRTProcessor - B站字幕处理处理器 + +将 B站 SRT 字幕文件转换为简化的 Markdown 文档。 + +处理流程: +1. 解析 SRT 文件(调用 parser.parse_srt_file) +2. 语义分段(调用 segmenter.segment) +3. ASR 纠错(调用 corrector.correct,可选) +4. 生成 Markdown 文件 + +简化版:不做深度观点分析,只保留字幕内容和元数据。 +""" + +from __future__ import annotations + +import json +import logging +from datetime import datetime +from pathlib import Path +from typing import Dict, Any, List, Optional + +from kb_workflow.pipeline.config import get_settings +from kb_workflow.pipeline.processors.base import BaseProcessor +from kb_workflow.pipeline.schemas import SRTInput, SRTOutput +from kb_workflow.pipeline.state import WorkflowStateManager +from kb_workflow.srt.parser import parse_srt_file +from kb_workflow.srt.segmenter import SemanticSegmenter + +logger = logging.getLogger(__name__) + + +class SRTProcessor(BaseProcessor): + """SRT 字幕处理器 + + 将 SRT 字幕文件转换为简化的 Markdown 文档。 + 继承 BaseProcessor,遵循 Pipeline 处理器模式。 + + Attributes: + STEP_NAME: 步骤名称 "srt" + settings: KB 配置 + state_manager: 状态管理器 + segmenter: 语义分段器 + corrector: ASR 纠错器(可选) + """ + + STEP_NAME = "srt" + + def __init__( + self, + state_manager: Optional[WorkflowStateManager] = None, + enable_correction: bool = True + ): + """初始化 SRT 处理器 + + Args: + state_manager: 状态管理器 + enable_correction: 是否启用 ASR 纠错(默认 True) + """ + super().__init__(state_manager) + self.enable_correction = enable_correction + self.segmenter = SemanticSegmenter() + self.corrector = None + + # 延迟初始化纠错器 + if enable_correction: + try: + from kb_workflow.srt.corrector import ASRCorrector + from kb_workflow.deep_analyzer import SimpleAIClient + + # 尝试创建 AI 客户端 + ai_client = SimpleAIClient() + if ai_client.api_key: + self.corrector = ASRCorrector(ai_client=ai_client) + logger.info("ASR corrector initialized") + else: + logger.info("AI API key not configured, ASR correction disabled") + except Exception as e: + logger.warning(f"Failed to initialize ASR corrector: {e}") + + async def process(self, input_data: SRTInput) -> SRTOutput: + """处理 SRT 文件 + + Args: + input_data: SRT 输入数据 + + Returns: + SRT 处理结果 + + Raises: + Exception: 处理失败时抛出异常 + """ + try: + # 记录开始状态 + if self.state_manager: + state = self.state_manager.load() + if state: + state.mark_step_started(self.STEP_NAME) + self.state_manager.save(state) + + # Step 1: 解析 SRT 文件 + logger.info(f"Parsing SRT file: {input_data.srt_file}") + segments = parse_srt_file(Path(input_data.srt_file)) + logger.info(f"Parsed {len(segments)} segments") + + # Step 2: 语义分段 + logger.info("Segmenting subtitles into paragraphs") + paragraphs = self.segmenter.segment(segments) + logger.info(f"Generated {len(paragraphs)} paragraphs") + + # Step 3: ASR 纠错(可选) + corrected = False + if self.corrector and paragraphs: + logger.info("Applying ASR correction") + corrected_paragraphs = [] + for para in paragraphs: + corrected_para = self.corrector.correct(para) + corrected_paragraphs.append(corrected_para) + if corrected_para != para: + corrected = True + paragraphs = corrected_paragraphs + logger.info(f"ASR correction applied: {corrected}") + + # Step 4: 加载元数据 + metadata = self._load_metadata(input_data.metadata_file) + + # Step 5: 生成 Markdown + logger.info(f"Generating Markdown: {input_data.output_file}") + tags = self._extract_tags(paragraphs) + self._generate_markdown( + output_file=Path(input_data.output_file), + metadata=metadata, + paragraphs=paragraphs, + tags=tags + ) + + # 记录完成状态 + if self.state_manager: + state = self.state_manager.load() + if state: + summary = f"paragraphs={len(paragraphs)}, corrected={corrected}" + state.mark_step_completed(self.STEP_NAME, summary) + self.state_manager.save(state) + + return SRTOutput( + success=True, + output_file=str(input_data.output_file), + paragraphs=paragraphs, + tags=tags, + corrected=corrected + ) + + except Exception as e: + logger.error(f"SRT processing failed: {e}") + # 记录失败状态 + if self.state_manager: + state = self.state_manager.load() + if state: + state.mark_step_failed(self.STEP_NAME, str(e)) + self.state_manager.save(state) + raise + + def _load_metadata(self, metadata_file: str) -> Dict[str, Any]: + """加载元数据文件 + + Args: + metadata_file: 元数据文件路径 + + Returns: + 元数据字典 + """ + path = Path(metadata_file) + if not path.exists(): + logger.warning(f"Metadata file not found: {metadata_file}") + return {} + + with open(path, 'r', encoding='utf-8') as f: + return json.load(f) + + def _extract_tags(self, paragraphs: List[str]) -> List[str]: + """从段落中提取标签 + + 简单实现:不使用 LLM,只返回默认标签。 + 未来可以增强为 LLM 提取。 + + Args: + paragraphs: 段落列表 + + Returns: + 标签列表 + """ + # 简单实现:返回空列表 + # TODO: 可以使用 LLM 提取标签 + return [] + + def _generate_markdown( + self, + output_file: Path, + metadata: Dict[str, Any], + paragraphs: List[str], + tags: List[str] + ) -> None: + """生成 Markdown 文件 + + Args: + output_file: 输出文件路径 + metadata: 元数据 + paragraphs: 段落列表 + tags: 标签列表 + """ + # 确保输出目录存在 + output_file.parent.mkdir(parents=True, exist_ok=True) + + # 生成 frontmatter + frontmatter = self._generate_frontmatter(metadata, tags) + + # 构建内容 + content_parts = [frontmatter, ""] + + # 添加摘要(可选) + if paragraphs: + content_parts.append("## 摘要") + content_parts.append("") + # 使用第一段作为简单摘要 + first_para = paragraphs[0][:200] if len(paragraphs[0]) > 200 else paragraphs[0] + content_parts.append(first_para + ("..." if len(paragraphs[0]) > 200 else "")) + content_parts.append("") + + # 添加内容 + content_parts.append("## 内容") + content_parts.append("") + for para in paragraphs: + content_parts.append(para) + content_parts.append("") + + # 写入文件 + output_file.write_text('\n'.join(content_parts), encoding='utf-8') + logger.info(f"Markdown file generated: {output_file}") + + def _generate_frontmatter(self, metadata: Dict[str, Any], tags: List[str]) -> str: + """生成 YAML frontmatter + + Args: + metadata: 元数据 + tags: 标签列表 + + Returns: + YAML frontmatter 字符串 + """ + lines = ["---"] + + # 标题 + title = metadata.get('title', '未命名') + lines.append(f'title: "{self._escape_yaml(title)}"') + + # 日期 + pubtime = metadata.get('pubtime', '') or metadata.get('published_at', '') + if pubtime: + date_str = str(pubtime)[:10] + else: + date_str = datetime.now().strftime('%Y-%m-%d') + lines.append(f'date: {date_str}') + + # 作者 + author = metadata.get('author', '未知') + lines.append(f'author: {self._escape_yaml(author)}') + + # 平台 + platform = metadata.get('platform', 'bilibili') + lines.append(f'platform: {platform}') + + # 时长(视频) + duration = metadata.get('duration', 0) + if duration: + duration_str = self._format_duration(duration) + lines.append(f'duration: {duration_str}') + + # BVID + bvid = metadata.get('bvid', '') + if bvid: + lines.append(f'bvid: {bvid}') + + # URL + url = metadata.get('url', '') + if url: + lines.append(f'url: {url}') + + # 标签 + if tags: + tags_str = ', '.join(f'"{t}"' for t in tags) + lines.append(f'tags: [{tags_str}]') + + lines.append("---") + return '\n'.join(lines) + + def _escape_yaml(self, text: str) -> str: + """转义 YAML 特殊字符 + + Args: + text: 原始文本 + + Returns: + 转义后的文本 + """ + # 移除零宽度字符 + invisible_chars = ['\u200b', '\u200c', '\u200d', '\ufeff', '\u00ad'] + for char in invisible_chars: + text = text.replace(char, '') + + # 移除换行符 + text = text.replace('\n', ' ').strip() + + # 如果包含特殊字符,用引号包裹 + if any(c in text for c in [':', '"', "'", '\n', '#']): + text = text.replace('"', '\\"') + return text + + return text + + def _format_duration(self, seconds: int) -> str: + """格式化时长 + + Args: + seconds: 秒数 + + Returns: + 格式化的时长字符串 (HH:MM:SS 或 MM:SS) + """ + h = seconds // 3600 + m = (seconds % 3600) // 60 + s = seconds % 60 + if h > 0: + return f"{h}:{m:02d}:{s:02d}" + return f"{m}:{s:02d}" diff --git a/skills/kb_workflow/pipeline/schemas.py b/skills/kb_workflow/pipeline/schemas.py index 83a3e02..1f5922a 100644 --- a/skills/kb_workflow/pipeline/schemas.py +++ b/skills/kb_workflow/pipeline/schemas.py @@ -444,3 +444,42 @@ class ArchiveOutput(StepOutput): archive_file: str = Field(default="", description="Path to archived file") skipped: bool = Field(default=False, description="Whether archiving was skipped") platform: str = Field(default="", description="Platform name") + + +# ============================================================================= +# SRT Schemas (Phase 12) +# ============================================================================= + + +class SRTInput(StepInput): + """SRT 步骤输入 + + 用于 SRTProcessor 处理 B站字幕。 + + Attributes: + srt_file: SRT 字幕文件路径 + metadata_file: 元数据 JSON 文件路径 + output_file: 输出 Markdown 文件路径 + content_type: 内容类型(默认 video) + """ + srt_file: str = Field(..., min_length=1, description="Path to SRT subtitle file") + metadata_file: str = Field(..., min_length=1, description="Path to metadata JSON file") + output_file: str = Field(..., min_length=1, description="Path to output Markdown file") + content_type: str = Field(default="video", description="Content type") + + +class SRTOutput(StepOutput): + """SRT 步骤输出 + + 继承 StepOutput 的基础字段,添加 SRT 处理特定字段。 + + Attributes: + output_file: 生成的 Markdown 文件路径 + paragraphs: 生成的段落列表 + tags: 提取的标签列表 + corrected: 是否进行了 ASR 纠错 + """ + output_file: str = Field(..., description="Path to generated Markdown file") + paragraphs: List[str] = Field(default_factory=list, description="Generated paragraphs") + tags: List[str] = Field(default_factory=list, description="Extracted tags") + corrected: bool = Field(default=False, description="Whether ASR correction was applied") diff --git a/skills/kb_workflow/srt/__init__.py b/skills/kb_workflow/srt/__init__.py new file mode 100644 index 0000000..581c0aa --- /dev/null +++ b/skills/kb_workflow/srt/__init__.py @@ -0,0 +1,27 @@ +"""SRT Processing Module + +B站字幕处理模块,包含: +- SRT 解析器 (parser) +- 语义分段器 (segmenter) +- ASR 纠错器 (corrector) +""" + +from kb_workflow.srt.parser import parse_srt, parse_srt_file, Segment + +__all__ = [ + "parse_srt", + "parse_srt_file", + "Segment", + "SemanticSegmenter", + "ASRCorrector", +] + +# 延迟导入,避免循环依赖 +def __getattr__(name: str): + if name == "SemanticSegmenter": + from kb_workflow.srt.segmenter import SemanticSegmenter + return SemanticSegmenter + if name == "ASRCorrector": + from kb_workflow.srt.corrector import ASRCorrector + return ASRCorrector + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..c873d85 Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/__init__.cpython-312.pyc differ diff --git a/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc new file mode 100644 index 0000000..9d3e087 Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/corrector.cpython-312.pyc differ diff --git a/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000..0c1b175 Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/parser.cpython-312.pyc differ diff --git a/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc b/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc new file mode 100644 index 0000000..00d6540 Binary files /dev/null and b/skills/kb_workflow/srt/__pycache__/segmenter.cpython-312.pyc differ diff --git a/skills/kb_workflow/srt/corrector.py b/skills/kb_workflow/srt/corrector.py new file mode 100644 index 0000000..5609578 --- /dev/null +++ b/skills/kb_workflow/srt/corrector.py @@ -0,0 +1,157 @@ +"""ASR Corrector Module + +使用 LLM 纠正语音识别错误。复用 SimpleAIClient。 + +功能: +- 纠正明显的语音识别错误(同音字、漏字、错字) +- 分批处理长文本 +- AI 不可用时返回原文 +""" + +from __future__ import annotations + +import logging +from typing import Optional, Protocol + +logger = logging.getLogger(__name__) + + +class AIClientProtocol(Protocol): + """AI 客户端协议""" + + def generate(self, prompt: str, max_tokens: int = 16000) -> str: + """生成文本""" + ... + + +# 纠错 Prompt 模板 +CORRECTION_PROMPT = """你是一个文本校对专家。以下是语音识别生成的字幕,可能包含识别错误。 +请只纠正明显的错误(如同音字、漏字、错字),不要改变原意和风格。 + +原文: +{text} + +纠正后的文本(只输出纠正后的文本,不要解释):""" + + +class ASRCorrector: + """ASR 纠错器 + + 使用 LLM 纠正语音识别中的明显错误。 + + Attributes: + ai_client: AI 客户端(必须实现 generate 方法) + batch_size: 分批处理的字符数,默认 5000 + """ + + def __init__( + self, + ai_client: Optional[AIClientProtocol] = None, + batch_size: int = 5000 + ): + """初始化纠错器 + + Args: + ai_client: AI 客户端实例,如果为 None 则不进行纠错 + batch_size: 分批处理的字符数 + """ + self.ai_client = ai_client + self.batch_size = batch_size + + def correct(self, text: str) -> str: + """纠正文本中的语音识别错误 + + Args: + text: 原始文本 + + Returns: + 纠正后的文本。如果 AI 不可用或文本为空,返回原文。 + + Example: + >>> corrector = ASRCorrector(ai_client=my_client) + >>> result = corrector.correct("什么是机器学习") + >>> print(result) + 什么是机器学习 + """ + # 空文本直接返回 + if not text or not text.strip(): + return text or "" + + # AI 不可用,返回原文 + if self.ai_client is None: + logger.info("AI client not available, returning original text") + return text + + # 短文本直接处理 + if len(text) <= self.batch_size: + return self._correct_batch(text) + + # 长文本分批处理 + return self._correct_long_text(text) + + def _correct_batch(self, text: str) -> str: + """处理单个批次的文本 + + Args: + text: 文本内容 + + Returns: + 纠正后的文本 + """ + try: + prompt = CORRECTION_PROMPT.format(text=text) + result = self.ai_client.generate(prompt, max_tokens=len(text) + 500) + return result.strip() + except Exception as e: + logger.warning(f"ASR correction failed: {e}, returning original text") + return text + + def _correct_long_text(self, text: str) -> str: + """分批处理长文本 + + 按句子分割,每批不超过 batch_size 字符。 + + Args: + text: 长文本内容 + + Returns: + 纠正后的文本 + """ + # 按句子分割(简单分割,基于句号、问号、感叹号) + sentences = [] + current = "" + for char in text: + current += char + if char in '。!?\n': + sentences.append(current) + current = "" + + # 添加最后一个不完整的句子 + if current: + sentences.append(current) + + # 分批处理 + batches = [] + current_batch = [] + current_length = 0 + + for sentence in sentences: + if current_length + len(sentence) > self.batch_size: + if current_batch: + batches.append(''.join(current_batch)) + current_batch = [sentence] + current_length = len(sentence) + else: + current_batch.append(sentence) + current_length += len(sentence) + + if current_batch: + batches.append(''.join(current_batch)) + + # 处理每个批次 + corrected_batches = [] + for batch in batches: + corrected = self._correct_batch(batch) + corrected_batches.append(corrected) + + return ''.join(corrected_batches) diff --git a/skills/kb_workflow/srt/parser.py b/skills/kb_workflow/srt/parser.py new file mode 100644 index 0000000..6565792 --- /dev/null +++ b/skills/kb_workflow/srt/parser.py @@ -0,0 +1,161 @@ +"""SRT Parser Module + +解析 SRT 字幕文件格式,将字幕转换为结构化的 Segment 对象。 + +SRT 格式示例: + 1 + 00:00:00,000 --> 00:00:05,000 + 第一句字幕内容 + + 2 + 00:00:05,000 --> 00:00:10,000 + 第二句字幕内容 +""" + +from __future__ import annotations + +import re +from dataclasses import dataclass +from pathlib import Path +from typing import List + + +@dataclass +class Segment: + """字幕分段数据结构 + + Attributes: + id: 字幕序号 + start_time: 开始时间(秒) + end_time: 结束时间(秒) + text: 字幕文本 + """ + id: int + start_time: float + end_time: float + text: str + + +# SRT 块正则表达式 +# 格式: 序号\n时间码\n文本(可能多行)\n\n +SRT_BLOCK_PATTERN = re.compile( + r'(\d+)\s*\n' + r'(\d{2}:\d{2}:\d{2},\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2},\d{3})\s*\n' + r'(.*?)(?=\n\n|\Z)', + re.DOTALL +) + + +def parse_srt_time(time_str: str) -> float: + """解析 SRT 时间格式 (HH:MM:SS,mmm) 为秒数 + + Args: + time_str: SRT 时间字符串,如 "00:05:30,500" + + Returns: + 秒数,如 330.5 + + Example: + >>> parse_srt_time("00:05:30,500") + 330.5 + >>> parse_srt_time("01:02:03,004") + 3723.004 + """ + # 分割时间和毫秒 + main_part, millis = time_str.rsplit(',', 1) + hours, minutes, seconds = main_part.split(':') + + total_seconds = ( + int(hours) * 3600 + + int(minutes) * 60 + + int(seconds) + + int(millis) / 1000 + ) + return total_seconds + + +def parse_srt(content: str) -> List[Segment]: + """解析 SRT 格式字符串 + + 将 SRT 格式的字幕内容解析为 Segment 对象列表。 + + Args: + content: SRT 格式的字符串内容 + + Returns: + Segment 对象列表 + + Raises: + ValueError: 内容为空或格式无效 + + Example: + >>> content = "1\\n00:00:00,000 --> 00:00:05,000\\n第一句\\n\\n" + >>> segments = parse_srt(content) + >>> len(segments) + 1 + >>> segments[0].text + '第一句' + """ + # 检查空内容 + if not content or not content.strip(): + raise ValueError("Empty SRT content") + + segments = [] + + # 查找所有 SRT 块 + matches = SRT_BLOCK_PATTERN.findall(content) + + if not matches: + raise ValueError("Invalid SRT format: no valid blocks found") + + for match in matches: + try: + segment_id = int(match[0]) + start_time = parse_srt_time(match[1]) + end_time = parse_srt_time(match[2]) + # 清理文本:移除前后空白,合并多行为单行 + text = ' '.join(match[3].strip().split()) + + segment = Segment( + id=segment_id, + start_time=start_time, + end_time=end_time, + text=text + ) + segments.append(segment) + except (ValueError, IndexError) as e: + # 跳过解析失败的块 + continue + + if not segments: + raise ValueError("Invalid SRT format: no valid segments parsed") + + return segments + + +def parse_srt_file(file_path: Path) -> List[Segment]: + """从文件解析 SRT 格式 + + 读取文件内容并解析为 Segment 对象列表。 + + Args: + file_path: SRT 文件路径 + + Returns: + Segment 对象列表 + + Raises: + FileNotFoundError: 文件不存在 + ValueError: 内容格式无效 + + Example: + >>> from pathlib import Path + >>> segments = parse_srt_file(Path("subtitles.srt")) + >>> len(segments) + 10 + """ + if not file_path.exists(): + raise FileNotFoundError(f"SRT file not found: {file_path}") + + content = file_path.read_text(encoding='utf-8') + return parse_srt(content) diff --git a/skills/kb_workflow/srt/segmenter.py b/skills/kb_workflow/srt/segmenter.py new file mode 100644 index 0000000..7af8d12 --- /dev/null +++ b/skills/kb_workflow/srt/segmenter.py @@ -0,0 +1,97 @@ +"""Semantic Segmenter Module + +将字幕分段按语义合并为段落。基于简单启发式规则: +1. 时间间隔超过阈值时分段 +2. 段落长度限制 + +不使用 LLM 进行分段判断,保持简单和快速。 +""" + +from __future__ import annotations + +from typing import List + +from kb_workflow.srt.parser import Segment + + +class SemanticSegmenter: + """语义分段器 + + 将字幕分段转换为语义段落。基于时间和长度规则: + - 长时间停顿(>gap_threshold 秒)作为分段点 + - 段落长度限制(max_paragraph_chars 字符) + + Attributes: + gap_threshold: 时间间隔阈值(秒),默认 3.0 + max_paragraph_chars: 单段落最大字符数,默认 500 + """ + + def __init__( + self, + gap_threshold: float = 3.0, + max_paragraph_chars: int = 500 + ): + """初始化分段器 + + Args: + gap_threshold: 时间间隔阈值(秒),超过此值时分段 + max_paragraph_chars: 单段落最大字符数 + """ + self.gap_threshold = gap_threshold + self.max_paragraph_chars = max_paragraph_chars + + def segment(self, segments: List[Segment]) -> List[str]: + """将字幕分段转换为语义段落 + + Args: + segments: 字幕分段列表 + + Returns: + 段落文本列表(已去除时间戳) + + Example: + >>> segmenter = SemanticSegmenter() + >>> segments = [Segment(1, 0, 5, "第一句"), Segment(2, 5, 10, "第二句")] + >>> paragraphs = segmenter.segment(segments) + >>> len(paragraphs) + 1 + """ + if not segments: + return [] + + paragraphs = [] + current_paragraph = [] + current_length = 0 + last_end_time = None + + for seg in segments: + # 检查是否需要分段 + should_start_new = False + + # 规则1: 时间间隔超过阈值 + if last_end_time is not None: + gap = seg.start_time - last_end_time + if gap > self.gap_threshold: + should_start_new = True + + # 规则2: 段落长度超过限制 + if current_length + len(seg.text) > self.max_paragraph_chars: + if current_paragraph: # 只有当前段落有内容时才分段 + should_start_new = True + + # 执行分段 + if should_start_new and current_paragraph: + paragraphs.append(' '.join(current_paragraph)) + current_paragraph = [] + current_length = 0 + + # 添加当前分段 + current_paragraph.append(seg.text) + current_length += len(seg.text) + last_end_time = seg.end_time + + # 添加最后一个段落 + if current_paragraph: + paragraphs.append(' '.join(current_paragraph)) + + return paragraphs