自动同步: 2026-03-20 02:10
This commit is contained in:
@@ -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<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <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.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
|
||||
"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<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <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.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
|
||||
"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<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <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.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
|
||||
"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<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n <skill>\n <name>clawlist</name>\n <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.</description>\n <location>~/.openclaw/skills/clawlist/SKILL.md</location>\n </skill>\n</available_skills>",
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -0,0 +1,8 @@
|
||||
# 2026-03-19 每日记录
|
||||
|
||||
## 定时任务执行
|
||||
|
||||
### 02:30 - 每日回顾整理(daily-review-cleanup)
|
||||
- 执行脚本:`~/.openclaw/cron/scripts/daily-review.sh`
|
||||
- 处理日期:2026-03-18
|
||||
- 结果:从 NAS 拉取更新完成,无已完成任务需要归档
|
||||
@@ -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<available_skills>\n <skill>\n <name>capability-evolver</name>\n <description>A self-evolution engine for AI agents. Analyzes runtime history to identify improvements and applies protocol-constrained evolution.</description>\n <location>~/.openclaw/skills/capability-evolver/SKILL.md</location>\n </skill>\n</available_skills>",
|
||||
@@ -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",
|
||||
|
||||
+28
-27
@@ -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
|
||||
|
||||
+26
-26
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"operator.approvals",
|
||||
"operator.pairing"
|
||||
],
|
||||
"updatedAtMs": 1773857416774
|
||||
"updatedAtMs": 1773943213908
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 生成完整分析报告
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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",
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
@@ -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")
|
||||
|
||||
@@ -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}")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user