添加技能模块
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# KB Workflow 环境变量配置
|
||||
export KB_BASE_PATH="~/work/knowledge-base"
|
||||
|
||||
# 可选: B站 Cookie(用于下载会员视频)
|
||||
# export BILIBILI_COOKIE="SESSDATA=xxx; bili_jct=xxx"
|
||||
|
||||
# 可选: AI 分析配置
|
||||
# export AI_API_KEY="your-api-key"
|
||||
# export AI_PROVIDER="openai" # 或 zhipu, minimax
|
||||
# export AI_MODEL="gpt-4"
|
||||
|
||||
# 可选: Git 配置
|
||||
# export KB_GIT_USER="Your Name"
|
||||
# export KB_GIT_EMAIL="your@email.com"
|
||||
@@ -0,0 +1,14 @@
|
||||
# KB Workflow 环境变量配置
|
||||
export KB_BASE_PATH="~/work/knowledge-base"
|
||||
|
||||
# 可选: B站 Cookie(用于下载会员视频)
|
||||
# export BILIBILI_COOKIE="SESSDATA=xxx; bili_jct=xxx"
|
||||
|
||||
# 可选: AI 分析配置
|
||||
# export AI_API_KEY="your-api-key"
|
||||
# export AI_PROVIDER="openai" # 或 zhipu, minimax
|
||||
# export AI_MODEL="gpt-4"
|
||||
|
||||
# 可选: Git 配置
|
||||
# export KB_GIT_USER="Your Name"
|
||||
# export KB_GIT_EMAIL="your@email.com"
|
||||
@@ -0,0 +1,247 @@
|
||||
# KB Workflow 环境安装脚本
|
||||
|
||||
一键检查并安装 kb-workflow 知识库工作流所需的全部环境依赖。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 自动检测系统类型 (Ubuntu/Debian/Fedora/Arch)
|
||||
- ✅ 检查系统依赖 (Python3, pip3, ffmpeg, tesseract, git)
|
||||
- ✅ 检查 Python 依赖包 (httpx, aiohttp, playwright, easyocr 等)
|
||||
- ✅ 自动创建知识库目录结构
|
||||
- ✅ 生成环境变量配置示例
|
||||
- ✅ 汇总显示所有需要执行的安装命令
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 运行检查脚本
|
||||
|
||||
```bash
|
||||
cd /home/starmen1265/.openclaw/workspace/skills
|
||||
./install-kb-workflow.sh
|
||||
```
|
||||
|
||||
脚本会检查所有依赖,并显示需要执行的安装命令。
|
||||
|
||||
### 示例输出
|
||||
|
||||
```
|
||||
========================================
|
||||
第一步: 检查系统依赖
|
||||
========================================
|
||||
[SUCCESS] Python3 已安装: Python 3.12.3
|
||||
[WARN] pip3 未安装
|
||||
[WARN] ffmpeg 未安装
|
||||
...
|
||||
|
||||
========================================
|
||||
需要安装的系统依赖
|
||||
========================================
|
||||
|
||||
检测到以下依赖未安装:
|
||||
- python3-pip
|
||||
- ffmpeg
|
||||
- tesseract-ocr
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
请运行以下命令安装:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-pip ffmpeg tesseract-ocr tesseract-ocr-chi-sim tesseract-ocr-eng
|
||||
```
|
||||
|
||||
## 依赖清单
|
||||
|
||||
### 系统依赖
|
||||
|
||||
| 依赖 | 用途 | 安装命令 |
|
||||
|------|------|----------|
|
||||
| **python3** | Python 运行时 | `sudo apt-get install python3` |
|
||||
| **python3-pip** | Python 包管理器 | `sudo apt-get install python3-pip` |
|
||||
| **ffmpeg** | 视频处理(OCR 截帧) | `sudo apt-get install ffmpeg` |
|
||||
| **tesseract-ocr** | OCR 引擎(备用方案) | `sudo apt-get install tesseract-ocr` |
|
||||
| **git** | 版本控制(归档提交) | `sudo apt-get install git` |
|
||||
|
||||
### Tesseract 语言包
|
||||
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt-get install tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-eng
|
||||
|
||||
# Fedora/RHEL
|
||||
sudo dnf install tesseract-langpack-chi_sim tesseract-langpack-chi_tra tesseract-langpack-eng
|
||||
```
|
||||
|
||||
### Python 依赖
|
||||
|
||||
```bash
|
||||
# 基础 HTTP 库
|
||||
pip3 install --user httpx aiohttp requests
|
||||
|
||||
# 图像和 OCR
|
||||
pip3 install --user Pillow easyocr pytesseract
|
||||
|
||||
# 浏览器自动化
|
||||
pip3 install --user playwright
|
||||
python3 -m playwright install chromium
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
安装完成后会创建以下目录结构:
|
||||
|
||||
```
|
||||
~/work/knowledge-base/ # 知识库根目录(可配置)
|
||||
├── archive/ # 归档目录
|
||||
│ ├── pending/ # 待处理
|
||||
│ └── subscriptions/ # 订阅信息
|
||||
├── metadata/ # 元数据文件
|
||||
├── content/ # 内容文件
|
||||
│ ├── downloads/ # 视频下载
|
||||
│ └── subtitles/ # 字幕文件
|
||||
└── analyzed/ # 深度分析报告
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
### 必需
|
||||
- `KB_BASE_PATH` - 知识库根目录(默认: `~/work/knowledge-base`)
|
||||
|
||||
### 可选
|
||||
- `BILIBILI_COOKIE` - B站 Cookie,用于下载会员视频
|
||||
- `AI_API_KEY` - AI API 密钥,用于深度内容分析
|
||||
- `AI_PROVIDER` - AI 提供商(openai/zhipu/minimax)
|
||||
- `AI_MODEL` - AI 模型名称(如 gpt-4)
|
||||
- `AI_BASE_URL` - 自定义 API 端点
|
||||
- `KB_GIT_USER` - Git 用户名(用于归档提交)
|
||||
- `KB_GIT_EMAIL` - Git 邮箱(用于归档提交)
|
||||
- `KB_GIT_REMOTE` - Git 远程仓库地址
|
||||
|
||||
### 配置方法
|
||||
|
||||
将以下内容添加到 `~/.bashrc` 或 `~/.zshrc`:
|
||||
|
||||
```bash
|
||||
# KB Workflow 配置
|
||||
export KB_BASE_PATH="~/work/knowledge-base"
|
||||
|
||||
# 可选:B站 Cookie
|
||||
export BILIBILI_COOKIE="SESSDATA=xxx; bili_jct=xxx"
|
||||
|
||||
# 可选:AI 配置
|
||||
export AI_API_KEY="your-api-key"
|
||||
export AI_PROVIDER="openai"
|
||||
export AI_MODEL="gpt-4"
|
||||
```
|
||||
|
||||
然后重新加载配置:
|
||||
```bash
|
||||
source ~/.bashrc # 或 source ~/.zshrc
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. pip3 命令不存在
|
||||
|
||||
```bash
|
||||
# 方法 1: 安装 pip3
|
||||
sudo apt-get install python3-pip
|
||||
|
||||
# 方法 2: 使用 python3 -m pip
|
||||
python3 -m pip install --user <package>
|
||||
```
|
||||
|
||||
### 2. Playwright 浏览器安装失败
|
||||
|
||||
```bash
|
||||
# 手动安装 Chromium
|
||||
python3 -m playwright install chromium
|
||||
|
||||
# 如果仍然失败,尝试安装依赖
|
||||
sudo apt-get install libnss3 libatk-bridge2.0-0 libxss1 libgtk-3-0
|
||||
```
|
||||
|
||||
### 3. EasyOCR 安装失败
|
||||
|
||||
EasyOCR 依赖 PyTorch,可能需要较长时间安装:
|
||||
|
||||
```bash
|
||||
# 先安装 PyTorch
|
||||
pip3 install --user torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
||||
|
||||
# 再安装 easyocr
|
||||
pip3 install --user easyocr
|
||||
```
|
||||
|
||||
### 4. 权限问题
|
||||
|
||||
如果使用 `--user` 安装的包无法找到,检查 PATH:
|
||||
|
||||
```bash
|
||||
# 查看用户安装路径
|
||||
python3 -m site --user-base
|
||||
|
||||
# 添加到 PATH(添加到 ~/.bashrc)
|
||||
export PATH="$PATH:$(python3 -m site --user-base)/bin"
|
||||
```
|
||||
|
||||
### 5. OCR 中文识别问题
|
||||
|
||||
确保已安装中文语言包:
|
||||
|
||||
```bash
|
||||
# 检查已安装的语言包
|
||||
tesseract --list-langs
|
||||
|
||||
# 应该能看到 chi_sim 和 chi_tra
|
||||
```
|
||||
|
||||
## 更新
|
||||
|
||||
如果技能更新了依赖,可以重新运行检查脚本:
|
||||
|
||||
```bash
|
||||
./install-kb-workflow.sh
|
||||
```
|
||||
|
||||
## 子技能清单
|
||||
|
||||
| 技能 | 路径 | 功能 |
|
||||
|------|------|------|
|
||||
| kb-workflow | `kb-workflow/kb_workflow.py` | 主流程协调 |
|
||||
| kb-link-parser | `kb-link-parser/link-parser.py` | 链接解析 |
|
||||
| kb-bilibili-metadata | `kb-bilibili-metadata/bilibili_metadata.py` | B站元数据提取 |
|
||||
| kb-bilibili-content | `kb-bilibili-content/bilibili_content.py` | B站内容下载 |
|
||||
| kb-ocr-processor | `kb-ocr-processor/ocr_processor.py` | 视频 OCR 识别 |
|
||||
| kb-deep-analyzer | `kb-deep-analyzer/deep_analyzer.py` | AI 深度分析 |
|
||||
|
||||
## 使用 kb-workflow
|
||||
|
||||
安装完成后,可以通过以下方式使用:
|
||||
|
||||
### 方式 1:通过主代理
|
||||
|
||||
直接向主代理发送消息:
|
||||
```
|
||||
添加到知识库 https://www.bilibili.com/video/BVxxxxx
|
||||
```
|
||||
|
||||
### 方式 2:手动运行子技能
|
||||
|
||||
```bash
|
||||
# 1. 链接解析
|
||||
python3 kb-link-parser/link-parser.py --url "https://www.bilibili.com/video/BVxxxxx"
|
||||
|
||||
# 2. 元数据提取
|
||||
python3 kb-bilibili-metadata/bilibili_metadata.py --id "BVxxxxx" --type video
|
||||
|
||||
# 3. 内容下载
|
||||
python3 kb-bilibili-content/bilibili_content.py --id "BVxxxxx" --type subtitle
|
||||
|
||||
# 4. 深度分析
|
||||
python3 kb-deep-analyzer/deep_analyzer.py --metadata meta.json --content content.json --output analysis.md
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
@@ -0,0 +1,360 @@
|
||||
---
|
||||
name: kb-workflow
|
||||
description: 当收到"添加到知识库 + 链接" 或"保存到知识库 + 链接"时使用
|
||||
---
|
||||
|
||||
# KB Workflow
|
||||
|
||||
## 什么时候使用
|
||||
|
||||
当用户发送包含以下模式的消息时,此技能自动触发:
|
||||
|
||||
| 触发词 | 行为 | 示例 |
|
||||
|--------|------|------|
|
||||
| `添加到知识库` + 链接 | 添加到知识库并处理 | "添加到知识库 https://www.bilibili.com/video/BVxxx" |
|
||||
| `保存到知识库` + 链接 | 同上 | "保存到知识库 https://www.bilibili.com/video/BVxxx" |
|
||||
|
||||
## 环境变量
|
||||
|
||||
### 执行前必须检查
|
||||
|
||||
在执行 workflow 之前,必须检查环境变量是否已经设置。
|
||||
|
||||
**必须的环境变量**(缺失则报错退出):
|
||||
|
||||
配置方式:将变量写入 `~/.kb_env` 文件:
|
||||
```bash
|
||||
export AI_API_KEY="your-api-key"
|
||||
export AI_PROVIDER="zhipu"
|
||||
export AI_MODEL="glm-5"
|
||||
export AI_BASE_URL="https://open.bigmodel.cn/api/coding/paas/v4"
|
||||
```
|
||||
|
||||
**可选的环境变量**:
|
||||
| 变量名 | 用途 | 示例 |
|
||||
|--------|------|------|
|
||||
| `BILIBILI_COOKIE` | 下载会员视频 | `SESSDATA=xxx; bili_jct=xxx` |
|
||||
| `KB_GIT_USER` | Git提交用户名 | `Your Name` |
|
||||
| `KB_GIT_EMAIL` | Git提交邮箱 | `your@email.com` |
|
||||
|
||||
### 检查命令
|
||||
|
||||
```bash
|
||||
# 检查必须的环境变量
|
||||
echo "AI_API_KEY: ${AI_API_KEY:0:10}..."
|
||||
echo "AI_PROVIDER: $AI_PROVIDER"
|
||||
echo "AI_MODEL: $AI_MODEL"
|
||||
echo "AI_BASE_URL: $AI_BASE_URL"
|
||||
|
||||
# 验证方式1: 检查变量是否存在
|
||||
if [ -z "$AI_API_KEY" ]; then
|
||||
echo "❌ AI_API_KEY 未设置"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证方式2: 执行测试调用
|
||||
python3 -c "
|
||||
import os
|
||||
if not os.environ.get('AI_API_KEY'):
|
||||
raise RuntimeError('AI_API_KEY 未设置')
|
||||
print('✅ 环境变量检查通过')
|
||||
"
|
||||
```
|
||||
|
||||
|
||||
## Workflow 执行方式
|
||||
|
||||
主代理直接执行以下步骤,无需启动 kb-workflow 子代理:
|
||||
|
||||
1. 主代理启动子代理调用 kb-link-parser
|
||||
2. 主代理启动子代理调用 kb-平台-metadata
|
||||
3. 主代理启动子代理调用 kb-平台-content
|
||||
4. 主代理启动子代理调用 kb-ocr-processor(如需要)
|
||||
5. 主代理启动子代理调用 kb-deep-analyzer
|
||||
6. 主代理执行 `kb_workflow.py --archive-file` 归档
|
||||
|
||||
|
||||
## Workflow 执行步骤
|
||||
|
||||
当触发后,按照以下步骤执行:
|
||||
|
||||
### 步骤 1: 环境变量检查
|
||||
|
||||
**❌ 禁止跳过此步骤!必须先检查环境变量。**
|
||||
|
||||
```bash
|
||||
# 0. 从 ~/.kb_env 加载环境变量(专门的知识库配置文件,不受 bashrc 交互式检查影响)
|
||||
if [ -f ~/.kb_env ]; then
|
||||
source ~/.kb_env
|
||||
fi
|
||||
|
||||
# 1. 检查必须的环境变量是否存在
|
||||
if [ -z "$AI_API_KEY" ] || [ -z "$AI_PROVIDER" ] || [ -z "$AI_MODEL" ] || [ -z "$AI_BASE_URL" ]; then
|
||||
echo "❌ 错误:必须的环境变量未设置"
|
||||
echo "请配置以下环境变量:"
|
||||
echo " - AI_API_KEY"
|
||||
echo " - AI_PROVIDER (zhipu/openai/minimax)"
|
||||
echo " - AI_MODEL"
|
||||
echo " - AI_BASE_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ 环境变量检查通过"
|
||||
echo " AI_PROVIDER: $AI_PROVIDER"
|
||||
echo " AI_MODEL: $AI_MODEL"
|
||||
```
|
||||
|
||||
**如果检查不通过,必须**:
|
||||
1. 报错提示用户
|
||||
2. 退出,不执行后续步骤
|
||||
3. 等待用户配置好环境变量后重新执行
|
||||
|
||||
## ⚠️ 严格失败处理原则
|
||||
|
||||
> **深度分析步骤必须成功,否则整个流程失败**
|
||||
|
||||
在执行工作流时,必须遵守以下原则:
|
||||
1. **深度分析不可跳过**:kb-deep-analyzer 是必须成功执行的步骤
|
||||
2. **失败即终止**:如果深度分析返回 `success: false`,必须立即停止并汇报失败
|
||||
3. **禁止降级处理**:不能使用预设模板或简单摘要来"假装成功"
|
||||
4. **每次执行都要使用 AI**:必须有真实的 AI API 调用记录
|
||||
|
||||
### 步骤 2: 链接解析
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --pending
|
||||
```
|
||||
|
||||
2. 启动子代理调用 kb-link-parser:
|
||||
```bash
|
||||
python3 /path/to/link-parser.py --url "[URL]"
|
||||
```
|
||||
|
||||
3. 解析输出 JSON,提取 filename 和 platform
|
||||
|
||||
### 步骤 3: 元数据提取
|
||||
|
||||
1. 获取元数据目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --metadata
|
||||
# 返回: {"path": "/home/admin/work/knowledge-base/metadata"}
|
||||
```
|
||||
|
||||
2. 构建输出文件路径:`${metadata_dir}/${platform}_${id}.meta.json`
|
||||
|
||||
3. 启动子代理调用对应平台的 metadata 技能(传入 `--output`):
|
||||
```bash
|
||||
# Bilibili
|
||||
python3 /path/to/bilibili_metadata.py --id "[ID]" --type video --output "${metadata_dir}/bilibili_[ID].meta.json"
|
||||
|
||||
# Zhihu
|
||||
python3 /path/to/zhihu_metadata.py --id "[ID]" --type question --output "${metadata_dir}/zhihu_[ID].meta.json"
|
||||
```
|
||||
|
||||
4. 解析输出 JSON,提取 filename
|
||||
|
||||
### 步骤 4: 内容下载
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --content_downloads
|
||||
python3 kb_workflow.py --content
|
||||
```
|
||||
|
||||
2. 启动子代理调用对应平台的 content 技能:
|
||||
```bash
|
||||
# Bilibili 字幕下载(自动检测是否有官方字幕)
|
||||
python3 /path/to/bilibili_content.py --id "[ID]" --type subtitle-download --output [content_dir]
|
||||
|
||||
# 返回 JSON 包含 has_subtitle 字段
|
||||
```
|
||||
|
||||
3. 解析输出 JSON:
|
||||
- **has_subtitle = true**: 有官方字幕,直接进入步骤 6
|
||||
- **has_subtitle = false**: 没有官方字幕,继续步骤 5
|
||||
|
||||
> ⚠️ **重要提示**:没有找到官方字幕是**正常的**!很多视频没有自动生成的字幕。不要因为没有字幕就停止工作流,继续执行 Whisper 步骤即可。
|
||||
|
||||
### 步骤 5: 语音识别转文字(仅当没有官方字幕时执行)
|
||||
|
||||
> ⚠️ **重要**:此步骤仅在**没有官方字幕**时执行。如果有官方字幕,**跳过此步骤**。
|
||||
|
||||
此步骤使用 Whisper 进行语音识别:
|
||||
- **严禁跳过**:必须等待识别完成
|
||||
- **定时汇报**:每2-3分钟向用户汇报进度
|
||||
- **超时时间**:30分钟
|
||||
- **耐心等待**:不要中途启动其他步骤,必须串行执行
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --content
|
||||
```
|
||||
|
||||
2. 启动子代理调用 kb-whisper(传入视频路径):
|
||||
```bash
|
||||
# 必须传入 --output 完整路径
|
||||
python3 /path/to/whisper.py --input [video_path] --model base --output [content_dir]
|
||||
```
|
||||
|
||||
3. 等待 Whisper 完成(定时汇报进度)
|
||||
|
||||
4. 解析输出 JSON,生成 content 文件供步骤6使用
|
||||
|
||||
### 步骤 6: 深度分析
|
||||
|
||||
1. 获取目录路径:
|
||||
```bash
|
||||
python3 kb_workflow.py --analyzed
|
||||
```
|
||||
|
||||
2. 准备 content JSON 文件(包含 subtitle/description)
|
||||
|
||||
3. 如果有官方字幕,使用官方字幕的 SRT 文件
|
||||
4. 如果没有官方字幕,使用 Whisper 识别的结果
|
||||
|
||||
5. 启动子代理调用 kb-deep-analyzer(使用 g5 模型):
|
||||
```bash
|
||||
python3 /path/to/deep_analyzer.py --metadata [meta_path] --content [content_path] --output [analyzed_dir]/[filename].md --provider zhipu --model glm-5
|
||||
```
|
||||
|
||||
**环境变量设置**(在子代理中设置):
|
||||
- `AI_PROVIDER=zhipu`
|
||||
- `AI_MODEL=glm-5`
|
||||
|
||||
6. 解析输出 JSON,检查 `success` 字段:
|
||||
|
||||
> ⚠️ **深度分析失败 = 整个流程失败**
|
||||
> - 如果 `success = true`:继续步骤 7(归档)
|
||||
> - 如果 `success = false`:**立即终止流程**,向用户汇报失败原因
|
||||
> - **严禁**继续执行后续步骤或使用降级内容"假装成功"
|
||||
|
||||
### 步骤 7: 归档
|
||||
|
||||
运行归档命令:
|
||||
```bash
|
||||
python3 kb_workflow.py --archive-file "[分析报告文件路径]"
|
||||
```
|
||||
|
||||
### 步骤 8: 汇报完成
|
||||
|
||||
向用户汇报:
|
||||
- 视频标题
|
||||
- 处理阶段完成情况
|
||||
- 最终文件路径
|
||||
- Git commit 信息
|
||||
|
||||
|
||||
## 子技能清单
|
||||
|
||||
| 步骤 | 子技能 | 命令示例 | 输出 | 备注 |
|
||||
|------|--------|----------|------|------|
|
||||
| 2 | kb-link-parser | `--url "BVxxx"` | `{p}_{id}.link.json` | |
|
||||
| 3 | kb-bilibili-metadata | `--id "BVxxx" --type video` | `{p}_{id}.meta.json` | |
|
||||
| 3 | kb-zhihu-metadata | `--id "xxx" --type question` | `{p}_{id}.meta.json` | |
|
||||
| 4 | kb-bilibili-content | `--id "BVxxx" --type subtitle-download` | `{p}_{id}.srt` | 如果没有字幕是正常的 |
|
||||
| 4 | kb-bilibili-content | `--id "BVxxx" --type video` | `{p}_{id}.mp4` | 如果需要下载视频 |
|
||||
| 5 | kb-whisper | `--input xxx.mp4 --model base` | `{p}_{id}.json`, `{p}_{id}.txt` | 仅当没有官方字幕时执行 |
|
||||
| 6 | kb-deep-analyzer | `--metadata x.meta.json --content x.json --output x.md --provider zhipu --model glm-5` | `{p}_{id}.md` | **使用 g5 模型,必须成功** |
|
||||
|
||||
## 条件分支逻辑
|
||||
|
||||
```
|
||||
步骤3: 获取元数据
|
||||
↓
|
||||
步骤4: 下载字幕 (--type subtitle-download)
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ has_subtitle = true? │
|
||||
├─────────────────────────────────────┤
|
||||
│ 是 → 直接进入步骤6(深度分析) │
|
||||
│ 否 → 进入步骤5(Whisper语音识别) │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
步骤6: 深度分析
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ kb-deep-analyzer 执行结果 │
|
||||
├─────────────────────────────────────┤
|
||||
│ success = true → 步骤7(归档) │
|
||||
│ success = false → ❌ 整个流程失败 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
> ⚠️ **重要:深度分析失败则整个流程失败**
|
||||
> - 深度分析必须使用正确的 AI 模型(glm-4.7)完整分析字幕内容
|
||||
> - 如果 `kb-deep-analyzer` 返回 `success: false`,**必须汇报失败**,不能继续执行后续步骤
|
||||
> - 不能使用预设模板或降级内容来"假装成功"
|
||||
> - 失败时必须向用户报告具体的错误原因
|
||||
|
||||
> ⚠️ **重要**:没有找到官方字幕是**正常的**!很多视频没有自动生成的字幕。
|
||||
> - 如果 `has_subtitle = false`,继续执行 Whisper 步骤
|
||||
> - 如果 `has_subtitle = true`,跳过 Whisper 步骤,直接使用官方字幕
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
~/work/knowledge-base/
|
||||
├── archive/
|
||||
│ └── YYYY-MM-DD/
|
||||
├── pending/
|
||||
│ └── {platform}_{id}.link.json
|
||||
├── metadata/
|
||||
│ └── {platform}_{id}.meta.json
|
||||
├── content/
|
||||
│ ├── downloads/
|
||||
│ │ └── {platform}_{id}.mp4
|
||||
│ ├── {platform}_{id}.txt # Whisper 识别的文本
|
||||
│ └── {platform}_{id}.json # Whisper 完整结果
|
||||
└── analyzed/
|
||||
└── {platform}_{id}.md
|
||||
```
|
||||
|
||||
## 辅助命令
|
||||
|
||||
```bash
|
||||
# ------------------- 查重与归档 -------------------
|
||||
|
||||
# 查重
|
||||
python3 kb_workflow.py --check --url "[URL]"
|
||||
|
||||
# 归档分析报告
|
||||
python3 kb_workflow.py --archive-file "[分析报告路径]"
|
||||
|
||||
|
||||
# ------------------- 目录路径查询 -------------------
|
||||
|
||||
# 获取知识库根目录(默认 ~/work/knowledge-base)
|
||||
python3 kb_workflow.py --base-path "~/custom/path"
|
||||
|
||||
# 获取待处理目录(pending)
|
||||
python3 kb_workflow.py --pending
|
||||
|
||||
# 获取元数据目录
|
||||
python3 kb_workflow.py --metadata
|
||||
|
||||
# 获取内容目录
|
||||
python3 kb_workflow.py --content
|
||||
|
||||
# 获取内容下载目录
|
||||
python3 kb_workflow.py --content_downloads
|
||||
|
||||
# 获取字幕目录
|
||||
python3 kb_workflow.py --content_subtitles
|
||||
|
||||
# 获取深度分析目录
|
||||
python3 kb_workflow.py --analyzed
|
||||
|
||||
# 获取归档目录
|
||||
python3 kb_workflow.py --archive
|
||||
|
||||
# 获取订阅信息目录
|
||||
python3 kb_workflow.py --archive_subscriptions
|
||||
|
||||
|
||||
# ------------------- 工具函数 -------------------
|
||||
|
||||
# 从文件名提取平台名称
|
||||
python3 kb_workflow.py --extract_platform "bilibili_BVxxx.mp4"
|
||||
```
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# SkillBug.md
|
||||
|
||||
## 问题记录
|
||||
|
||||
### 2026-02-10: 子代理权限问题
|
||||
|
||||
**问题:** `sessions_spawn` 调用子代理时返回 `forbidden` 错误。
|
||||
|
||||
**原因:** OpenClaw 配置中 allowlist 为空,Python 脚本没有权限调用子代理。
|
||||
|
||||
**解决方案:** 改用 Prompt-based 模式(方案B)。
|
||||
|
||||
---
|
||||
|
||||
## 架构重构
|
||||
|
||||
### 之前(Script-based)
|
||||
|
||||
```
|
||||
kb_workflow.py (Python脚本)
|
||||
↓
|
||||
sessions_spawn() # ❌ 失败(脚本没有权限)
|
||||
```
|
||||
|
||||
### 之后(Prompt-based)
|
||||
|
||||
```
|
||||
主代理读取 SKILL.md
|
||||
↓
|
||||
按照指导调用 sessions_spawn # ✅ 成功(主代理有权限)
|
||||
```
|
||||
|
||||
### 文件变更
|
||||
|
||||
| 文件 | 变更 | 说明 |
|
||||
|------|------|------|
|
||||
| `SKILL.md` | 重写 | 详细定义 workflow 步骤,主代理直接读取执行 |
|
||||
| `kb_workflow.py` | 简化 | 保留为辅助工具函数库(查重、归档、Git) |
|
||||
| `skill_main.py` | 简化 | 只做触发检测,返回指导信息 |
|
||||
| `SkillBug.md` | 更新 | 记录架构变更 |
|
||||
|
||||
### SKILL.md 结构
|
||||
|
||||
```
|
||||
1. 触发条件
|
||||
2. 步骤 1: 查重
|
||||
3. 步骤 2: kb-link-parser
|
||||
4. 步骤 3: 元数据技能
|
||||
5. 步骤 4: 内容下载
|
||||
6. 步骤 5: OCR(30分钟超时)
|
||||
7. 步骤 6: 深度分析
|
||||
8. 步骤 7: 归档和 Git
|
||||
9. 步骤 8: 汇报完成
|
||||
10. 目录结构
|
||||
11. 支持平台
|
||||
12. 注意事项
|
||||
```
|
||||
|
||||
### kb_workflow.py 保留的函数
|
||||
|
||||
```python
|
||||
extract_platform(filename) # 从文件名提取平台
|
||||
extract_base_name(filename) # 从文件名提取基础名称
|
||||
get_knowledge_base_path() # 获取知识库根目录
|
||||
ensure_directories(base_path) # 确保目录存在
|
||||
check_duplicate(url) # 查重
|
||||
archive_and_git(analyzed_file) # 归档和 Git
|
||||
generate_summary(...) # 生成汇报
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2026-02-12: B站视频下载失败 - 403 Forbidden
|
||||
|
||||
**问题:** 使用 kb-bilibili-content 下载视频时返回 403 Forbidden 错误。
|
||||
|
||||
**原因:**
|
||||
1. 视频可能需要B站会员权限
|
||||
2. 或者需要登录cookie才能下载
|
||||
3. 当前未配置 `BILIBILI_COOKIE` 环境变量
|
||||
|
||||
**复现场景:**
|
||||
- 链接:https://www.bilibili.com/video/BV1Z9nXzUEhv
|
||||
- 下载命令:`python3 bilibili_content.py --id "BV1Z9nXzUEhv" --type video`
|
||||
- 结果:返回403错误页面,文件只有548字节
|
||||
|
||||
**影响:** 无法进行后续的OCR处理和深度分析
|
||||
|
||||
**建议解决方案:**
|
||||
1. 配置 `BILIBILI_COOKIE` 环境变量
|
||||
2. 或提供cookies文件路径
|
||||
3. 或跳过OCR步骤,使用元数据+简介进行深度分析
|
||||
|
||||
**当前进度:**
|
||||
- ✅ 步骤1:环境变量检查完成
|
||||
- ✅ 步骤2:链接解析成功(bilibili_BV1Z9nXzUEhv)
|
||||
- ✅ 步骤3:元数据提取成功(标题:新加坡分区漫游指南...)
|
||||
- ❌ 步骤4:视频下载失败(403)
|
||||
- ⏸️ 步骤5:OCR处理(阻塞)
|
||||
- ⏸️ 步骤6:深度分析(阻塞)
|
||||
- ⏸️ 步骤7:归档(阻塞)
|
||||
@@ -0,0 +1,321 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# KB Workflow 环境一键安装脚本
|
||||
# =============================================================================
|
||||
# 功能: 检查并安装 kb-workflow 技能所需的全部环境依赖
|
||||
# 支持: Ubuntu/Debian 系统
|
||||
#
|
||||
# 使用方法:
|
||||
# ./install-env.sh # 检查环境并提示安装命令(检查模式)
|
||||
# ./install-env.sh --auto # 自动安装所有缺失依赖(自动模式)
|
||||
# =============================================================================
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_section() {
|
||||
echo ""
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
}
|
||||
|
||||
# 获取脚本目录
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
DEFAULT_KB_PATH="${HOME}/work/knowledge-base"
|
||||
|
||||
# 解析参数
|
||||
AUTO_MODE=false
|
||||
if [ "$1" = "--auto" ]; then
|
||||
AUTO_MODE=true
|
||||
log_info "自动安装模式"
|
||||
fi
|
||||
|
||||
# 检测 Linux 发行版
|
||||
detect_distro() {
|
||||
if [ -f /etc/os-release ]; then
|
||||
. /etc/os-release
|
||||
echo "$ID"
|
||||
elif [ -f /etc/debian_version ]; then
|
||||
echo "debian"
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
echo "rhel"
|
||||
fi
|
||||
}
|
||||
DISTRO=$(detect_distro)
|
||||
|
||||
# 检查命令是否存在
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# 检查 Python 包
|
||||
check_python_package() {
|
||||
python3 -c "import $1" 2>/dev/null
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# 1. 检查系统依赖
|
||||
# =============================================================================
|
||||
log_section "第一步: 检查系统依赖"
|
||||
|
||||
MISSING_DEPS=()
|
||||
|
||||
# 检查 Python3
|
||||
if command_exists python3; then
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
|
||||
log_success "Python3 已安装: $PYTHON_VERSION"
|
||||
else
|
||||
log_warn "Python3 未安装"
|
||||
MISSING_DEPS+=("python3")
|
||||
fi
|
||||
|
||||
# 检查 pip3
|
||||
if python3 -m pip --version > /dev/null 2>&1 || command_exists pip3; then
|
||||
log_success "pip3 已安装"
|
||||
else
|
||||
log_warn "pip3 未安装"
|
||||
MISSING_DEPS+=("python3-pip")
|
||||
fi
|
||||
|
||||
# 检查 ffmpeg
|
||||
if command_exists ffmpeg; then
|
||||
log_success "ffmpeg 已安装"
|
||||
else
|
||||
log_warn "ffmpeg 未安装"
|
||||
MISSING_DEPS+=("ffmpeg")
|
||||
fi
|
||||
|
||||
# 检查 tesseract
|
||||
if command_exists tesseract; then
|
||||
log_success "Tesseract OCR 已安装"
|
||||
else
|
||||
log_warn "Tesseract OCR 未安装"
|
||||
MISSING_DEPS+=("tesseract-ocr")
|
||||
fi
|
||||
|
||||
# 检查 git
|
||||
if command_exists git; then
|
||||
log_success "Git 已安装"
|
||||
else
|
||||
log_warn "Git 未安装"
|
||||
MISSING_DEPS+=("git")
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 2. 自动安装系统依赖(自动模式)
|
||||
# =============================================================================
|
||||
if [ ${#MISSING_DEPS[@]} -gt 0 ]; then
|
||||
if [ "$AUTO_MODE" = true ]; then
|
||||
log_section "自动安装系统依赖"
|
||||
log_info "检测到系统: $DISTRO"
|
||||
|
||||
case "$DISTRO" in
|
||||
ubuntu|debian)
|
||||
log_info "更新包列表..."
|
||||
sudo apt-get update -qq
|
||||
|
||||
log_info "安装依赖: ${MISSING_DEPS[*]}"
|
||||
sudo apt-get install -y -qq "${MISSING_DEPS[@]}" tesseract-ocr-chi-sim tesseract-ocr-eng
|
||||
;;
|
||||
fedora|rhel|centos)
|
||||
log_info "更新包列表..."
|
||||
sudo dnf check-update -y || true
|
||||
|
||||
log_info "安装依赖: ${MISSING_DEPS[*]}"
|
||||
sudo dnf install -y "${MISSING_DEPS[@]}" tesseract-langpack-chi_sim tesseract-langpack-eng
|
||||
;;
|
||||
arch)
|
||||
log_info "安装依赖: ${MISSING_DEPS[*]}"
|
||||
sudo pacman -S --noconfirm "${MISSING_DEPS[@]}" tesseract-data-chi_sim tesseract-data-eng
|
||||
;;
|
||||
*)
|
||||
log_error "不支持的系统: $DISTRO"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log_success "系统依赖安装完成"
|
||||
MISSING_DEPS=()
|
||||
else
|
||||
log_section "需要安装的系统依赖"
|
||||
echo ""
|
||||
echo "检测到以下依赖未安装:"
|
||||
for dep in "${MISSING_DEPS[@]}"; do
|
||||
echo " - $dep"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "请运行以下命令安装:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo -e "${YELLOW}sudo apt-get update${NC}"
|
||||
echo -e "${YELLOW}sudo apt-get install -y ${MISSING_DEPS[*]} tesseract-ocr-chi-sim tesseract-ocr-eng${NC}"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 3. 检查 Python 依赖
|
||||
# =============================================================================
|
||||
log_section "第二步: 检查 Python 依赖"
|
||||
|
||||
PYTHON_PACKAGES=(
|
||||
"httpx:httpx"
|
||||
"aiohttp:aiohttp"
|
||||
"requests:requests"
|
||||
"Pillow:PIL"
|
||||
)
|
||||
|
||||
MISSING_PY_PKGS=()
|
||||
|
||||
for pkg_info in "${PYTHON_PACKAGES[@]}"; do
|
||||
pkg_name=$(echo "$pkg_info" | cut -d':' -f1)
|
||||
import_name=$(echo "$pkg_info" | cut -d':' -f2)
|
||||
if check_python_package "$import_name" 2>/dev/null; then
|
||||
log_success "$pkg_name 已安装"
|
||||
else
|
||||
log_warn "$pkg_name 未安装"
|
||||
MISSING_PY_PKGS+=("$pkg_name")
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查特殊包
|
||||
check_python_package "playwright" 2>/dev/null && log_success "playwright 已安装" || MISSING_PY_PKGS+=("playwright")
|
||||
check_python_package "easyocr" 2>/dev/null && log_success "easyocr 已安装" || MISSING_PY_PKGS+=("easyocr")
|
||||
check_python_package "pytesseract" 2>/dev/null && log_success "pytesseract 已安装" || MISSING_PY_PKGS+=("pytesseract")
|
||||
|
||||
# =============================================================================
|
||||
# 4. 自动安装 Python 依赖(自动模式)
|
||||
# =============================================================================
|
||||
if [ ${#MISSING_PY_PKGS[@]} -gt 0 ]; then
|
||||
if [ "$AUTO_MODE" = true ]; then
|
||||
log_section "自动安装 Python 包"
|
||||
|
||||
log_info "安装: ${MISSING_PY_PKGS[*]}"
|
||||
pip3 install --user "${MISSING_PY_PKGS[@]}" -q
|
||||
|
||||
log_success "Python 包安装完成"
|
||||
|
||||
# 安装 Playwright 浏览器
|
||||
if [[ " ${MISSING_PY_PKGS[*]} " =~ " playwright " ]]; then
|
||||
log_info "安装 Playwright Chromium 浏览器..."
|
||||
python3 -m playwright install chromium
|
||||
log_success "Chromium 浏览器安装完成"
|
||||
fi
|
||||
|
||||
MISSING_PY_PKGS=()
|
||||
else
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "需要安装以下 Python 包:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
for pkg in "${MISSING_PY_PKGS[@]}"; do
|
||||
echo " - $pkg"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "请运行以下命令安装:"
|
||||
echo ""
|
||||
echo -e "${YELLOW}pip3 install --user ${MISSING_PY_PKGS[*]}${NC}"
|
||||
|
||||
if [[ " ${MISSING_PY_PKGS[*]} " =~ " playwright " ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}安装完 playwright 后,还需要运行:${NC}"
|
||||
echo -e "${YELLOW} python3 -m playwright install chromium${NC}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 5. 创建知识库目录结构
|
||||
# =============================================================================
|
||||
log_section "第三步: 创建知识库目录结构"
|
||||
|
||||
KB_BASE_PATH="${KB_BASE_PATH:-$DEFAULT_KB_PATH}"
|
||||
log_info "知识库路径: $KB_BASE_PATH"
|
||||
|
||||
mkdir -p "$KB_BASE_PATH"/{archive/{subscriptions,pending},metadata,content/{downloads,subtitles},analyzed}
|
||||
log_success "目录结构创建完成"
|
||||
|
||||
# =============================================================================
|
||||
# 6. 环境变量配置提示
|
||||
# =============================================================================
|
||||
log_section "第四步: 环境变量配置"
|
||||
|
||||
if [ -z "$KB_BASE_PATH_SET" ]; then
|
||||
log_warn "KB_BASE_PATH 环境变量未设置"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "建议添加到 ~/.bashrc 或 ~/.zshrc:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo -e "${YELLOW}export KB_BASE_PATH=\"$KB_BASE_PATH\"${NC}"
|
||||
echo ""
|
||||
else
|
||||
log_success "KB_BASE_PATH 已设置: $KB_BASE_PATH"
|
||||
fi
|
||||
|
||||
# 创建环境变量示例文件
|
||||
ENV_FILE="${SCRIPT_DIR}/.env.example"
|
||||
cat > "$ENV_FILE" << 'EOF'
|
||||
# KB Workflow 环境变量配置
|
||||
export KB_BASE_PATH="~/work/knowledge-base"
|
||||
|
||||
# 可选: B站 Cookie(用于下载会员视频)
|
||||
# export BILIBILI_COOKIE="SESSDATA=xxx; bili_jct=xxx"
|
||||
|
||||
# 可选: AI 分析配置
|
||||
# export AI_API_KEY="your-api-key"
|
||||
# export AI_PROVIDER="openai" # 或 zhipu, minimax
|
||||
# export AI_MODEL="gpt-4"
|
||||
|
||||
# 可选: Git 配置
|
||||
# export KB_GIT_USER="Your Name"
|
||||
# export KB_GIT_EMAIL="your@email.com"
|
||||
EOF
|
||||
|
||||
log_info "环境变量示例已保存到: $ENV_FILE"
|
||||
|
||||
# =============================================================================
|
||||
# 7. 完成总结
|
||||
# =============================================================================
|
||||
log_section "安装完成"
|
||||
|
||||
echo ""
|
||||
if [ ${#MISSING_DEPS[@]} -eq 0 ] && [ ${#MISSING_PY_PKGS[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}✅ 所有依赖已安装完成!${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ 部分依赖需要手动安装${NC}"
|
||||
echo ""
|
||||
|
||||
if [ ${#MISSING_DEPS[@]} -gt 0 ]; then
|
||||
echo "系统依赖:"
|
||||
echo -e "${YELLOW}sudo apt-get install -y ${MISSING_DEPS[*]} tesseract-ocr-chi-sim tesseract-ocr-eng${NC}"
|
||||
fi
|
||||
|
||||
if [ ${#MISSING_PY_PKGS[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Python 包:"
|
||||
echo -e "${YELLOW}pip3 install --user ${MISSING_PY_PKGS[*]}${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "使用方法:"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " export KB_BASE_PATH=\"~/work/knowledge-base\"
|
||||
echo " python3 kb_workflow.py --check --url \"[URL]\""
|
||||
echo ""
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
KB Workflow - Knowledge Base Workflow Orchestrator
|
||||
|
||||
知识库工作流辅助工具函数。
|
||||
|
||||
此模块提供查重、文件操作、Git 管理等工具函数,
|
||||
供主代理在执行 SKILL.md 中定义的 workflow 时调用。
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import subprocess
|
||||
import shutil
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
|
||||
# ==================== 子技能映射 ====================
|
||||
|
||||
METADATA_AGENTS = {
|
||||
"bilibili": "kb-bilibili-metadata",
|
||||
"zhihu": "kb-zhihu-metadata",
|
||||
}
|
||||
|
||||
CONTENT_AGENTS = {
|
||||
"bilibili": "kb-bilibili-content",
|
||||
}
|
||||
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
|
||||
def extract_platform(filename: str) -> str:
|
||||
"""从文件名提取平台(第一个下划线之前)"""
|
||||
match = re.match(r'^([a-zA-Z0-9]+)_(.+)$', filename)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return "unknown"
|
||||
|
||||
|
||||
def extract_base_name(filename: str) -> str:
|
||||
"""从文件名提取基础名称(去掉扩展名)"""
|
||||
return Path(filename).stem
|
||||
|
||||
|
||||
def get_knowledge_base_path() -> Path:
|
||||
"""获取知识库根目录"""
|
||||
base_path_str = os.environ.get('KB_BASE_PATH', '')
|
||||
if base_path_str:
|
||||
return Path(base_path_str).expanduser()
|
||||
return Path.home() / "work" / "knowledge-base"
|
||||
|
||||
|
||||
def ensure_directories(base_path: Path = None) -> Dict[str, Path]:
|
||||
"""确保目录存在"""
|
||||
if base_path is None:
|
||||
base_path = get_knowledge_base_path()
|
||||
|
||||
dirs = {
|
||||
"base": base_path,
|
||||
"pending": base_path / "pending",
|
||||
"metadata": base_path / "metadata",
|
||||
"content": base_path / "content",
|
||||
"content_downloads": base_path / "content" / "downloads",
|
||||
"analyzed": base_path / "analyzed",
|
||||
"archive": base_path / "archive",
|
||||
"archive_subscriptions": base_path / "archive" / "subscriptions",
|
||||
}
|
||||
|
||||
for name, dir_path in dirs.items():
|
||||
if isinstance(dir_path, Path):
|
||||
dir_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
return dirs
|
||||
|
||||
|
||||
def check_duplicate(base_path: Path = None, url: str = None, platform: str = None, content_id: str = None) -> Dict[str, Any]:
|
||||
"""查重检查"""
|
||||
if base_path is None:
|
||||
base_path = get_knowledge_base_path()
|
||||
|
||||
metadata_dir = base_path / "metadata"
|
||||
archive_dir = base_path / "archive"
|
||||
|
||||
# 从URL提取ID
|
||||
if url and not content_id:
|
||||
bv_match = re.search(r'BV[a-zA-Z0-9]+', url)
|
||||
content_id = bv_match.group() if bv_match else None
|
||||
|
||||
result = {"duplicate": False, "status": None, "files": []}
|
||||
|
||||
if metadata_dir.exists():
|
||||
for f in metadata_dir.iterdir():
|
||||
if f.is_file() and content_id and content_id in f.name:
|
||||
result["duplicate"] = True
|
||||
result["status"] = "metadata"
|
||||
result["files"].append(str(f))
|
||||
|
||||
if archive_dir.exists():
|
||||
for date_dir in archive_dir.iterdir():
|
||||
if date_dir.is_dir():
|
||||
for f in date_dir.iterdir():
|
||||
if f.is_file() and content_id and content_id in f.name:
|
||||
result["duplicate"] = True
|
||||
result["status"] = "archived"
|
||||
result["files"].append(str(f))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def archive_and_git(
|
||||
analyzed_file: str,
|
||||
base_path: Path = None,
|
||||
git_root: Path = None,
|
||||
git_user: str = None,
|
||||
git_email: str = None,
|
||||
git_remote: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""归档并提交Git"""
|
||||
if base_path is None:
|
||||
base_path = get_knowledge_base_path()
|
||||
if git_root is None:
|
||||
git_root = base_path / "archive"
|
||||
if git_user is None:
|
||||
git_user = os.environ.get('KB_GIT_USER', '')
|
||||
if git_email is None:
|
||||
git_email = os.environ.get('KB_GIT_EMAIL', '')
|
||||
if git_remote is None:
|
||||
git_remote = os.environ.get('KB_GIT_REMOTE', '')
|
||||
|
||||
result = {
|
||||
"success": False,
|
||||
"archive_path": "",
|
||||
"git_commit": False,
|
||||
"git_push": None,
|
||||
"error": None
|
||||
}
|
||||
|
||||
try:
|
||||
# 1. 检查并初始化 Git 仓库
|
||||
git_dir = git_root / ".git"
|
||||
if not git_dir.exists():
|
||||
# 初始化仓库
|
||||
subprocess.run(['git', 'init'], cwd=str(git_root), capture_output=True)
|
||||
subprocess.run(['git', 'config', 'user.name', git_user], cwd=str(git_root))
|
||||
subprocess.run(['git', 'config', 'user.email', git_email], cwd=str(git_root))
|
||||
|
||||
# 关联远端仓库
|
||||
if git_remote:
|
||||
subprocess.run(['git', 'remote', 'add', 'origin', git_remote], cwd=str(git_root), capture_output=True)
|
||||
# 拉取远端分支
|
||||
subprocess.run(['git', 'fetch', 'origin'], cwd=str(git_root), capture_output=True)
|
||||
# 设置 main 分支并拉取
|
||||
subprocess.run(['git', 'branch', '--set-upstream-to=origin/main', 'main'], cwd=str(git_root), capture_output=True)
|
||||
subprocess.run(['git', 'pull', 'origin', 'main', '--rebase'], cwd=str(git_root), capture_output=True, timeout=120)
|
||||
else:
|
||||
# 已有仓库,检查远端是否配置
|
||||
if git_remote:
|
||||
remote_check = subprocess.run(['git', 'remote', '-v'], cwd=str(git_root), capture_output=True, text=True)
|
||||
if 'origin' not in remote_check.stdout:
|
||||
subprocess.run(['git', 'remote', 'add', 'origin', git_remote], cwd=str(git_root), capture_output=True)
|
||||
|
||||
# 拉取远端最新代码
|
||||
subprocess.run(['git', 'fetch', 'origin'], cwd=str(git_root), capture_output=True)
|
||||
# 尝试拉取 main 分支,如果失败则尝试 master
|
||||
pull_result = subprocess.run(['git', 'pull', 'origin', 'main', '--rebase'], cwd=str(git_root), capture_output=True, timeout=120)
|
||||
if pull_result.returncode != 0:
|
||||
subprocess.run(['git', 'pull', 'origin', 'master', '--rebase'], cwd=str(git_root), capture_output=True, timeout=120)
|
||||
|
||||
# 2. 创建归档目录
|
||||
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||
date_dir = base_path / "archive" / date_str
|
||||
date_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 3. 复制分析报告
|
||||
src_file = Path(analyzed_file)
|
||||
archive_file = date_dir / src_file.name
|
||||
shutil.copy(src_file, archive_file)
|
||||
result["archive_path"] = str(archive_file)
|
||||
|
||||
# 4. 复制元数据
|
||||
meta_file = base_path / "metadata" / f"{src_file.stem}.meta.json"
|
||||
if meta_file.exists():
|
||||
shutil.copy(meta_file, date_dir / meta_file.name)
|
||||
|
||||
# 5. Git 提交并推送
|
||||
if (git_root / ".git").exists():
|
||||
# 添加所有变更
|
||||
subprocess.run(['git', 'add', '.'], cwd=str(git_root), capture_output=True)
|
||||
|
||||
# 检查是否有变更需要提交
|
||||
status_result = subprocess.run(['git', 'status', '--porcelain'], cwd=str(git_root), capture_output=True, text=True)
|
||||
|
||||
if status_result.stdout.strip():
|
||||
commit_msg = f"Archive: {src_file.name} - {datetime.now().isoformat()}"
|
||||
commit_result = subprocess.run(
|
||||
['git', 'commit', '-m', commit_msg],
|
||||
cwd=str(git_root), capture_output=True, text=True
|
||||
)
|
||||
result["git_commit"] = commit_result.returncode == 0
|
||||
|
||||
# 推送到远端
|
||||
if git_remote and commit_result.returncode == 0:
|
||||
push_result = subprocess.run(
|
||||
['git', 'push', 'origin', 'main'],
|
||||
cwd=str(git_root), capture_output=True, text=True, timeout=60
|
||||
)
|
||||
if push_result.returncode != 0:
|
||||
# 如果 main 分支推送失败,尝试 master
|
||||
push_result = subprocess.run(
|
||||
['git', 'push', 'origin', 'master'],
|
||||
cwd=str(git_root), capture_output=True, text=True, timeout=60
|
||||
)
|
||||
result["git_push"] = {"success": push_result.returncode == 0, "output": push_result.stdout}
|
||||
|
||||
result["success"] = True
|
||||
|
||||
except Exception as e:
|
||||
result["error"] = str(e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def generate_summary(url: str, stages: List[Dict], final_file: str, archive_result: Dict = None) -> str:
|
||||
"""生成处理完成汇报"""
|
||||
# 提取视频标题(如果有)
|
||||
title_match = re.search(r'com/video/([^?]+)', url)
|
||||
title = title_match.group(1) if title_match else "未知"
|
||||
|
||||
summary = f"""
|
||||
✅ **处理完成!**
|
||||
|
||||
**视频:** {title}
|
||||
|
||||
**处理阶段:**
|
||||
"""
|
||||
for stage in stages:
|
||||
stage_name = stage.get('stage', 'unknown')
|
||||
emoji = "✓" if stage.get('success') else "○"
|
||||
summary += f"{emoji} {stage_name}\n"
|
||||
|
||||
if final_file:
|
||||
summary += f"\n**最终文件:** `{final_file}`"
|
||||
|
||||
if archive_result:
|
||||
if archive_result.get('git_commit'):
|
||||
summary += f"\n**Git:** 已提交"
|
||||
if archive_result.get('git_push', {}).get('success'):
|
||||
summary += " ✓"
|
||||
elif archive_result.get('git_push'):
|
||||
summary += " ✗"
|
||||
|
||||
return summary
|
||||
|
||||
|
||||
# ==================== CLI ====================
|
||||
|
||||
def main():
|
||||
"""CLI入口 - 用于命令行测试"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='KB Workflow - Knowledge Base Workflow Orchestrator',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
使用示例:
|
||||
# 查重
|
||||
python kb_workflow.py --check --url "https://www.bilibili.com/video/BVxxx"
|
||||
|
||||
# 归档
|
||||
python kb_workflow.py --archive "/path/to/analyzed.md"
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('--check', action='store_true', help='查重')
|
||||
parser.add_argument('--url', type=str, help='要检查的URL')
|
||||
parser.add_argument('--archive-file', type=str, help='归档的文件路径')
|
||||
parser.add_argument('--base-path', type=str, help='获取知识库根目录')
|
||||
|
||||
parser.add_argument('--pending', action='store_true', help='获取url解析目录')
|
||||
parser.add_argument('--metadata', action='store_true', help='获取元数据目录')
|
||||
parser.add_argument('--content', action='store_true', help='获取内容目录')
|
||||
parser.add_argument('--content_downloads', action='store_true', help='获取主内容下载目录')
|
||||
parser.add_argument('--analyzed', action='store_true', help='获取深度分析目录')
|
||||
parser.add_argument('--archive_dir', action='store_true', help='获取归档目录')
|
||||
parser.add_argument('--archive_subscriptions', action='store_true', help='获取订阅信息目录')
|
||||
parser.add_argument('--extract_platform', type=str, help='要提取平台名称的文件名')
|
||||
args = parser.parse_args()
|
||||
|
||||
base_path = Path(args.base_path) if args.base_path else None
|
||||
dirs = ensure_directories(base_path)
|
||||
|
||||
if args.check and args.url:
|
||||
result = check_duplicate(base_path, args.url)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.archive_file:
|
||||
result = archive_and_git(args.archive_file, base_path)
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.pending:
|
||||
print(json.dumps({"path": str(dirs["pending"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.metadata:
|
||||
print(json.dumps({"path": str(dirs["metadata"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.content:
|
||||
print(json.dumps({"path": str(dirs["content"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.content_downloads:
|
||||
print(json.dumps({"path": str(dirs["content_downloads"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.analyzed:
|
||||
print(json.dumps({"path": str(dirs["analyzed"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.archive_dir:
|
||||
print(json.dumps({"path": str(dirs["archive"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.archive_subscriptions:
|
||||
print(json.dumps({"path": str(dirs["archive_subscriptions"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.extract_platform:
|
||||
platform = extract_platform(args.extract_platform)
|
||||
print(json.dumps({"platform": platform}, ensure_ascii=False, indent=2))
|
||||
|
||||
elif args.base_path:
|
||||
print(json.dumps({"path": str(dirs["base"])}, ensure_ascii=False, indent=2))
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user