@clawhub-jeyeshield-97ec921625
翻译技能文档的工具。当用户想要翻译 SKILL.md 文件的内容为中文时使用此技能。用户输入技能路径,工具会读取该路径下的 SKILL.md 文件,将其内容翻译成中文,并输出命名为 SKILL(1).md 的文件到同一文件夹中。
--- name: skill-translator description: 翻译技能文档的工具。当用户想要翻译 SKILL.md 文件的内容为中文时使用此技能。用户输入技能路径,工具会读取该路径下的 SKILL.md 文件,将其内容翻译成中文,并输出命名为 SKILL(1).md 的文件到同一文件夹中。 --- # 技能文档翻译工具 这是一个用于翻译技能文档的工具,可以将 SKILL.md 文件的内容翻译成中文。 ## 使用方法 用户输入技能路径,工具将执行以下步骤: 1. **读取源文件**:从用户提供的路径读取 SKILL.md 文件 2. **翻译内容**:将文件内容翻译成中文 3. **生成输出**:在相同路径下创建 SKILL(1).md 文件 ## 工作流程 ### 输入 - 用户提供的技能路径(例如:`C:\Users\LJT\.agents\skills\autoglm-browser-agent\`) ### 处理步骤 1. 读取指定路径下的 SKILL.md 文件 2. 将文件内容翻译成中文 3. 在相同目录下创建 SKILL(1).md 文件 ### 输出 - 新文件:`SKILL(1).md`(翻译后的中文内容) - 位置:与原 SKILL.md 文件相同目录 ## 注意事项 - 确保输入的路径正确且包含 SKILL.md 文件 - 翻译时会保持原有的 Markdown 格式 - 输出文件会覆盖同名的 SKILL(1).md 文件(如果存在) - 保留原有的 YAML frontmatter(名称和描述字段除外,需要翻译) ## 示例 **用户输入**: ``` 帮我翻译这个技能文档:C:\Users\LJT\.agents\skills\autoglm-browser-agent\ ``` **工具执行**: 1. 读取 `C:\Users\LJT\.agents\skills\autoglm-browser-agent\SKILL.md` 2. 翻译内容为中文 3. 创建 `C:\Users\LJT\.agents\skills\autoglm-browser-agent\SKILL(1).md` --- ## 触发条件 当用户提到: - "翻译技能文档" - "把 SKILL.md 翻译成中文" - "技能路径" + "翻译" - 提供具体的技能路径并要求翻译 时使用此技能。
翻译JSON文件中的文本内容,特别是description字段。当用户提到需要翻译JSON文件、翻译JSON中的字段内容、翻译description字段,或者需要将JSON文件翻译成其他语言时,使用此技能。这个技能非常适合处理产品描述、API文档、配置文件、数据集等需要多语言翻译的JSON内容。
---
name: json-translator
description: 翻译JSON文件中的文本内容,特别是description字段。当用户提到需要翻译JSON文件、翻译JSON中的字段内容、翻译description字段,或者需要将JSON文件翻译成其他语言时,使用此技能。这个技能非常适合处理产品描述、API文档、配置文件、数据集等需要多语言翻译的JSON内容。
---
# JSON 文件翻译助手
翻译 JSON 文件中的指定字段(默认为 `description`),支持多种语言互译,并保持原始 JSON 结构完整。
## 核心功能
- **智能字段识别**:自动查找 JSON 中的目标字段(默认 `description`),支持自定义指定多个字段
- **多语言支持**:支持中文、英文、日文、韩文四种语言的互译
- **真实翻译 API**:集成 MyMemory 和 LibreTranslate 两个在线翻译服务
- **进度反馈**:显示翻译进度和当前处理的字段内容
- **结构保持**:只翻译目标字段,保持 JSON 结构和其他字段不变
- **错误处理**:翻译失败时保留原文并添加标记,不会中断整个翻译过程
- **文件导出**:提供翻译后的 JSON 文件下载和结果展示
## 工作流程
### 1. 读取用户需求
先理解用户需要翻译的内容:
- JSON 文件的路径
- 需要翻译的字段名(默认 `description`,可指定多个逗号分隔)
- 源语言和目标语言
- 是否需要查看翻译结果或直接下载文件
### 2. 调用翻译脚本
使用技能目录中的 `scripts/translate_json.py` 脚本进行翻译:
```bash
python scripts/translate_json.py <输入文件> --target-language <语言代码> [选项]
```
#### 脚本参数
| 参数 | 说明 | 示例 |
|------|------|------|
| `--target-language` | 目标语言代码 | `zh`, `en`, `ja`, `ko` |
| `--source-language` | 源语言代码(可选,默认 auto) | `zh`, `en`, `ja`, `ko` 或 `auto` |
| `--fields` | 要翻译的字段名(逗号分隔,可选) | `name,description` |
| `--output` | 输出文件路径(可选) | `output.json` |
#### 支持的语言代码
| 语言代码 | 语言名称 |
|---------|---------|
| `zh` | 中文(简体) |
| `en` | 英文 |
| `ja` | 日文 |
| `ko` | 韩文 |
### 3. 查看翻译结果
脚本执行后会显示:
- 翻译进度(当前/总数)
- 原文和译文预览
- 翻译统计信息
- 输出文件路径
### 4. 下载翻译后的文件
翻译完成后:
- 控制台会显示输出文件路径
- 用户可以下载该文件查看完整内容
- 可以选择性地将结果返回给用户
## 使用示例
### 示例 1:翻译 description 字段到中文
用户说: "帮我翻译这个 JSON 文件的 description 字段到中文"
```bash
python scripts/translate_json.py data.json --target-language zh
```
### 示例 2:翻译多个字段到英文
用户说: "把这个 JSON 文件的 name 和 description 字段翻译成英文"
```bash
python scripts/translate_json.py data.json --target-language en --fields name,description
```
### 示例 3:指定源语言翻译
用户说: "将这个文件从中文翻译成日文"
```bash
python scripts/translate_json.py data.json --target-language ja --source-language zh
```
### 示例 4:指定输出文件
用户说: "翻译这个文件,输出到 translation.json"
```bash
python scripts/translate_json.py data.json --target-language en --output translation.json
```
## 输出说明
### 控制台输出
脚本执行时会在控制台显示:
- `[当前/总数]` - 翻译进度
- 字段路径 - 当前正在翻译的字段位置
- 原文预览 - 50个字符
- 译文预览 - 50个字符
- 完成信息 - 翻译成功/失败统计
### 文件输出
- 默认文件名格式:`{原文件名}_translated.json`
- 用户也可以通过 `--output` 参数指定输出路径
- JSON 文件保持原始缩进格式,使用 UTF-8 编码
### 错误处理
脚本会处理以下错误情况:
- JSON 文件格式错误 - 显示错误信息并退出
- 文件不存在 - 提示文件路径错误
- 字段不存在 - 显示警告信息但继续处理
- 翻译失败 - 保留原文并添加错误标记
## 注意事项
1. **API 限流**:脚本会自动在每次翻译之间添加 0.1 秒延迟,避免 API 限流
2. **大文件处理**:建议 JSON 文件大小不超过 10MB
3. **网络要求**:需要能够访问 MyMemory API (`https://api.mymemory.translated.net`)
4. **字段存在性**:如果指定的字段在 JSON 中不存在,会提示缺失字段但继续处理其他字段
5. **结构保持**:只翻译目标字段,其他字段和 JSON 结构完全保持不变
6. **依赖**:需要安装 `requests` 库(脚本会自动处理,如果未安装会提示)
## 技术实现
### 翻译服务
脚本使用 **MyMemory** 作为主要翻译服务,**LibreTranslate** 作为备用服务:
- MyMemory:免费在线翻译 API,主要翻译服务
- LibreTranslate:开源机器翻译 API,作为备用服务
- 自动降级:主服务失败时自动切换到备用服务
- 错误处理:API 失败时保留原文并添加标记
### 字段查找
脚本使用深度递归算法查找 JSON 中的目标字段:
- 递归遍历所有对象和数组
- 识别指定字段名
- 记录字段路径和值
- 保持原始 JSON 结构
### 进度反馈
脚本提供实时翻译进度:
- 显示当前翻译的字段编号
- 显示字段路径和内容预览
- 显示翻译状态(成功/失败)
- 显示完成统计信息
FILE:README.md
# JSON Translator 技能
JSON 文件翻译助手,用于翻译 JSON 文件中的文本内容,特别是 `description` 字段。
## 文件结构
```
json-translator/
├── SKILL.md # 技能说明文档
├── README.md # 使用说明(本文件)
└── scripts/
└── translate_json.py # 翻译脚本
```
## 快速开始
### 1. 翻译 description 字段到中文
```bash
python scripts/translate_json.py data.json --target-language zh
```
### 2. 翻译多个字段
```bash
python scripts/translate_json.py data.json --target-language en --fields name,description
```
### 3. 指定源语言
```bash
python scripts/translate_json.py data.json --target-language ja --source-language zh
```
## 参数说明
| 参数 | 必需 | 说明 | 示例 |
|------|------|------|------|
| 输入文件 | 是 | JSON 文件路径 | `data.json` |
| `--target-language` | 是 | 目标语言代码 | `zh`, `en`, `ja`, `ko` |
| `--source-language` | 否 | 源语言代码 | `zh`, `en`, `ja`, `ko`, `auto` |
| `--fields` | 否 | 要翻译的字段名(逗号分隔) | `name,description` |
| `--output` | 否 | 输出文件路径 | `output.json` |
## 支持的语言
| 语言代码 | 语言名称 |
|---------|---------|
| `zh` | 中文(简体) |
| `en` | 英文 |
| `ja` | 日文 |
| `ko` | 韩文 |
## 使用场景
- **产品描述翻译**:翻译产品信息中的描述字段
- **API 文档翻译**:翻译 API 文档的多语言版本
- **配置文件翻译**:翻译配置文件中的说明文本
- **数据集翻译**:翻译数据集中的文本字段
- **国际化开发**:快速生成 JSON 文件的多语言版本
## 注意事项
1. 需要安装 `requests` 库:`pip install requests`
2. 确保网络可以访问 MyMemory API
3. 建议文件大小不超过 10MB
4. 翻译失败时会保留原文并添加错误标记
## 更多信息
详细的使用说明和示例请参考 [SKILL.md](./SKILL.md)
FILE:scripts/translate_json.py
#!/usr/bin/env python3
"""
JSON 文件翻译脚本
功能:
- 读取 JSON 文件
- 翻译指定字段的内容
- 保存翻译后的文件
使用方法:
python translate_json.py <输入文件> --target-language <语言代码> [--fields <字段名>] [--source-language <语言代码>] [--output <输出文件>]
示例:
python translate_json.py data.json --target-language zh
python translate_json.py data.json --target-language en --fields name,description
"""
import json
import requests
import sys
from pathlib import Path
from typing import List, Dict, Any, Tuple, Optional
# 语言配置
LANGUAGES = {
'zh': 'zh-cn',
'en': 'en',
'ja': 'ja',
'ko': 'ko'
}
SOURCE_LANGUAGES = {
'auto': 'auto',
'zh': 'zh-cn',
'en': 'en',
'ja': 'ja',
'ko': 'ko'
}
def translate_text(text: str, from_lang: str = 'auto', to_lang: str = 'zh') -> str:
"""
翻译文本
:param text: 要翻译的文本
:param from_lang: 源语言代码 ('auto' 或语言代码)
:param to_lang: 目标语言代码
:return: 翻译后的文本
"""
if not text or not text.strip():
return text
try:
# 使用 MyMemory API 进行翻译
url = f"https://api.mymemory.translated.net/get"
params = {
'q': text,
'langpair': f"{SOURCE_LANGUAGES.get(from_lang, 'auto')}|{LANGUAGES[to_lang]}"
}
response = requests.get(url, params=params, timeout=10)
data = response.json()
if 'responseData' in data and 'translatedText' in data['responseData']:
return data['responseData']['translatedText']
else:
# API失败,返回带标记的原文
return _get_error_marker(to_lang) + text
except Exception as e:
print(f"翻译错误: {e}")
return _get_error_marker(to_lang) + text
def _get_error_marker(language: str) -> str:
"""获取错误标记"""
markers = {
'zh': '【翻译失败-保持原文】',
'en': '【Translation Failed-Kept Original】',
'ja': '【翻訳失敗-原文保持】',
'ko': '【번역 실패-원문 유지】'
}
return markers.get(language, '【翻译失败】')
def find_all_fields(obj: Any, path: str = '', fields: set = None) -> set:
"""
递归查找JSON中所有字段名
:param obj: JSON对象
:param path: 当前路径
:param fields: 字段集合
:return: 所有字段名集合
"""
if fields is None:
fields = set()
if isinstance(obj, dict):
for key, value in obj.items():
current_path = f"{path}.{key}" if path else key
fields.add(current_path)
# 如果值是对象或数组,继续递归
if isinstance(value, (dict, list)):
find_all_fields(value, current_path, fields)
return fields
def find_fields_by_name(obj: Any, target_fields: List[str], path: str = '') -> List[Dict[str, Any]]:
"""
递归查找指定字段的位置
:param obj: JSON对象
:param target_fields: 目标字段名列表
:param path: 当前路径
:return: 字段信息列表
"""
results: List[Dict[str, Any]] = []
if isinstance(obj, dict):
for key, value in obj.items():
current_path = f"{path}.{key}" if path else key
if key in target_fields and isinstance(value, str):
results.append({
'path': current_path,
'parent': obj,
'key': key,
'value': value
})
if isinstance(value, (dict, list)):
results.extend(find_fields_by_name(value, target_fields, current_path))
return results
def read_json_file(file_path: str) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
"""
读取JSON文件
:param file_path: 文件路径
:return: (数据, 错误信息)
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data, None
except json.JSONDecodeError as e:
return None, f"JSON格式错误: {e}"
except Exception as e:
return None, f"读取文件错误: {e}"
def save_json_file(data: Dict[str, Any], output_path: str) -> bool:
"""
保存JSON文件
:param data: JSON数据
:param output_path: 输出文件路径
:return: 是否成功
"""
try:
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"保存文件错误: {e}")
return False
def translate_json(
input_file: str,
target_language: str,
source_language: str = 'auto',
target_fields: List[str] = None,
output_file: str = None
) -> Tuple[bool, int, List[str], Optional[Dict[str, Any]]]:
"""
翻译 JSON 文件中的指定字段
:param input_file: 输入文件路径
:param target_language: 目标语言代码
:param source_language: 源语言代码
:param target_fields: 目标字段名列表
:param output_file: 输出文件路径
:return: (成功状态, 翻译数量, 缺失字段列表, 翻译后的数据)
"""
# 解析字段列表
if target_fields is None or not target_fields:
target_fields = ['description']
# 读取文件
data, error = read_json_file(input_file)
if error:
return False, 0, [], None
# 查找所有存在的字段
all_fields = find_all_fields(data)
missing_fields = [f for f in target_fields if f not in all_fields]
existing_fields = [f for f in target_fields if f in all_fields]
if not existing_fields:
print(f"错误: 未找到指定的字段: {target_fields}")
return False, 0, missing_fields, data
# 查找所有目标字段的位置
field_locations = find_fields_by_name(data, existing_fields)
if not field_locations:
print(f"错误: 指定字段中没有可翻译的字符串内容")
return False, 0, missing_fields, data
# 执行翻译
total_count = len(field_locations)
translated_count = 0
print(f"\n开始翻译,共 {total_count} 个字段...")
print(f"目标语言: {LANGUAGES.get(target_language, target_language)}")
print(f"源语言: {source_language if source_language != 'auto' else '自动检测'}")
print(f"字段: {', '.join(existing_fields)}")
print()
for i, field_info in enumerate(field_locations, 1):
field_name = field_info['key']
field_path = field_info['path']
try:
print(f"[{i}/{total_count}] 翻译字段: {field_name} ({field_path})")
print(f" 原文: {field_info['value'][:50]}{'...' if len(field_info['value']) > 50 else ''}")
# 翻译文本
translated = translate_text(field_info['value'], source_language, target_language)
# 更新字段值
field_info['parent'][field_info['key']] = translated
translated_count += 1
print(f" 译文: {translated[:50]}{'...' if len(translated) > 50 else ''}")
# 添加延迟避免 API 限流
if i < total_count:
import time
time.sleep(0.1)
except Exception as e:
print(f"翻译失败: {field_name} - {e}")
print()
print(f"翻译完成!共翻译 {translated_count}/{total_count} 个字段")
# 生成输出文件名
if output_file is None:
input_path = Path(input_file)
output_file = str(input_path.parent / f"{input_path.stem}_translated.json")
# 保存文件
if save_json_file(data, output_file):
print(f"已保存到: {output_file}")
return True, translated_count, missing_fields, data
def main():
"""命令行入口"""
if len(sys.argv) < 3:
print(__doc__)
print("\n使用示例:")
print(" python translate_json.py data.json --target-language zh")
print(" python translate_json.py data.json --target-language en --fields name,description")
print(" python translate_json.py data.json --target-language ja --source-language zh --output output.json")
sys.exit(1)
# 解析参数
input_file = sys.argv[1]
target_language = None
source_language = 'auto'
target_fields = ['description']
output_file = None
i = 2
while i < len(sys.argv):
arg = sys.argv[i]
if arg == '--target-language' and i + 1 < len(sys.argv):
target_language = sys.argv[i + 1]
i += 2
elif arg == '--source-language' and i + 1 < len(sys.argv):
source_language = sys.argv[i + 1]
i += 2
elif arg == '--fields' and i + 1 < len(sys.argv):
target_fields = sys.argv[i + 1].split(',')
i += 2
elif arg == '--output' and i + 1 < len(sys.argv):
output_file = sys.argv[i + 1]
i += 2
else:
i += 1
# 验证目标语言
if target_language not in LANGUAGES:
print(f"错误: 不支持的目标语言。可用: {', '.join(LANGUAGES.keys())}")
sys.exit(1)
# 执行翻译
success, count, missing, data = translate_json(
input_file=input_file,
target_language=target_language,
source_language=source_language,
target_fields=target_fields,
output_file=output_file
)
if missing:
print(f"\n警告: 以下字段在文件中不存在: {', '.join(missing)}")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
工作流编排中心 - 协调全流程,管理任务依赖和状态
---
name: "Workflow Orchestrator"
slug: ad-production-workflow-orchestrator
version: "1.0.0"
description: "工作流编排中心 - 协调全流程,管理任务依赖和状态"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"🔄","requires":{"bins":[]}}}
---
# Workflow Orchestrator - 工作流编排
负责协调广告创意生产的全流程,管理任务依赖和状态。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
- 创建和管理工作流
- 协调多个技能协作
- 管理任务依赖关系
- 监控工作流状态
## Architecture
```
workflow-orchestrator/
├── index.ts # 主入口,工作流编排逻辑
├── package.json # 依赖配置
└── README.md # 详细文档
```
## Core Commands
### 创建工作流
```typescript
await api.executeAction('workflow-orchestrator.create', {
name: string, // 工作流名称
steps: Array<{ // 步骤定义
skill: string, // 使用的技能
action: string, // 动作
input?: any, // 输入数据
dependsOn?: string[] // 依赖的步骤
}>
});
```
### 启动工作流
```typescript
await api.executeAction('workflow-orchestrator.start', {
workflowId: string // 工作流ID
});
```
### 暂停工作流
```typescript
await api.executeAction('workflow-orchestrator.pause', {
workflowId: string // 工作流ID
});
```
### 恢复工作流
```typescript
await api.executeAction('workflow-orchestrator.resume', {
workflowId: string // 工作流ID
});
```
### 获取状态
```typescript
await api.executeAction('workflow-orchestrator.get-status', {
workflowId: string // 工作流ID
});
```
### 终止工作流
```typescript
await api.executeAction('workflow-orchestrator.terminate', {
workflowId: string // 工作流ID
});
```
## 响应事件
- `workflow-orchestrator.started` - 工作流启动
- `workflow-orchestrator.paused` - 工作流暂停
- `workflow-orchestrator.resumed` - 工作流恢复
- `workflow-orchestrator.completed` - 工作流完成
- `workflow-orchestrator.failed` - 工作流失败
- `workflow-orchestrator.step-completed` - 步骤完成
## Configuration
工作流配置示例:
```json
{
"timeout": 3600, // 超时时间(秒)
"maxRetries": 3, // 最大重试次数
"parallelLimit": 5 // 最大并行数
}
```
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface Workflow {
id: string;
name: string;
definition: WorkflowStep[];
status: "draft" | "running" | "paused" | "completed" | "failed";
context: Record<string, any>;
currentStep?: number;
startTime?: Date;
endTime?: Date;
error?: string;
createdAt: Date;
}
interface WorkflowStep {
id: string;
name: string;
skill: string;
command: string;
args: Record<string, any>;
dependsOn?: string[]; // step IDs
condition?: string; // expression evaluated against context
retry?: {
maxAttempts: number;
delayMs: number;
};
timeoutMs?: number;
}
interface Execution {
workflowId: string;
stepId: string;
status: "pending" | "running" | "success" | "failed" | "skipped";
attempts: number;
lastError?: string;
startedAt?: Date;
completedAt?: Date;
}
class WorkflowOrchestrator {
private workflows: Map<string, Workflow> = new Map();
private executions: Map<string, Execution[]> = new Map();
private api: OpenClawSkillApi;
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async createWorkflow(workflow: {
name: string;
steps: WorkflowStep[];
context?: Record<string, any>;
}): Promise<string> {
const id = `WF-Date.now()-Math.random().toString(36).substr(2, 9)`;
const wf: Workflow = {
id,
name: workflow.name,
definition: workflow.steps,
status: "draft",
context: workflow.context || {},
createdAt: new Date(),
};
this.workflows.set(id, wf);
this.executions.set(id, []);
this.api.log(`info`, `Workflow created: id - workflow.name`);
return id;
}
async getWorkflow(id: string): Promise<Workflow | null> {
return this.workflows.get(id) || null;
}
async listWorkflows(status?: Workflow["status"]): Promise<Workflow[]> {
const all = Array.from(this.workflows.values());
if (status) {
return all.filter(w => w.status === status);
}
return all;
}
async startWorkflow(id: string, overrides?: Record<string, any>): Promise<boolean> {
const wf = this.workflows.get(id);
if (!wf) {
throw new Error(`Workflow not found: id`);
}
if (wf.status !== "draft" && wf.status !== "paused") {
throw new Error(`Cannot start workflow in status: wf.status`);
}
wf.status = "running";
wf.startTime = new Date();
wf.currentStep = 0;
wf.context = { ...wf.context, ...overrides };
this.workflows.set(id, wf);
this.api.log(`info`, `Workflow started: id`);
// Begin execution in background
this.executeWorkflow(id);
return true;
}
async pauseWorkflow(id: string): Promise<boolean> {
const wf = this.workflows.get(id);
if (!wf) {
throw new Error(`Workflow not found: id`);
}
if (wf.status !== "running") {
throw new Error(`Cannot pause workflow in status: wf.status`);
}
wf.status = "paused";
this.workflows.set(id, wf);
this.api.log(`info`, `Workflow paused: id`);
return true;
}
async resumeWorkflow(id: string): Promise<boolean> {
const wf = this.workflows.get(id);
if (!wf) {
throw new Error(`Workflow not found: id`);
}
if (wf.status !== "paused") {
throw new Error(`Cannot resume workflow in status: wf.status`);
}
wf.status = "running";
this.workflows.set(id, wf);
this.api.log(`info`, `Workflow resumed: id`);
this.executeWorkflow(id);
return true;
}
async cancelWorkflow(id: string): Promise<boolean> {
const wf = this.workflows.get(id);
if (!wf) {
throw new Error(`Workflow not found: id`);
}
wf.status = "failed";
wf.error = "Cancelled by user";
wf.endTime = new Date();
this.workflows.set(id, wf);
this.api.log(`info`, `Workflow cancelled: id`);
return true;
}
async getStatus(id: string): Promise<any> {
const wf = this.workflows.get(id);
if (!wf) {
throw new Error(`Workflow not found: id`);
}
const execs = this.executions.get(id) || [];
const currentExec = wf.currentStep !== undefined
? execs.find(e => e.stepId === wf.definition[wf.currentStep]?.id)
: null;
return {
workflow: wf,
currentStep: wf.currentStep !== undefined ? wf.definition[wf.currentStep] : null,
execution: currentExec,
completedSteps: execs.filter(e => e.status === "success").length,
totalSteps: wf.definition.length,
failedSteps: execs.filter(e => e.status === "failed").length
};
}
async getExecutions(id: string): Promise<Execution[]> {
return this.executions.get(id) || [];
}
// Built-in templates
async createStandardAdWorkflow(params: {
demandId: string;
prompt: string;
count: number;
platforms: string[];
targetModels?: string[];
}): Promise<string> {
const steps: WorkflowStep[] = [
{
id: "fetch-demand",
name: "获取需求详情",
skill: "demand-management",
command: "demand.get",
args: { id: params.demandId }
},
{
id: "generate-assets",
name: "AI生成素材",
skill: "ai-generation",
command: "ai.generate",
args: {
prompt: params.prompt,
model: params.targetModels?.[0] || "flux",
count: params.count
},
dependsOn: ["fetch-demand"],
retry: { maxAttempts: 3, delayMs: 5000 }
},
{
id: "auto-check",
name: "自动质检",
skill: "review-quality",
command: "review.check",
args: { materialId: "{{results.generate-assets.materialIds}}" },
dependsOn: ["generate-assets"]
},
{
id: "manual-review",
name: "人工审核",
skill: "review-quality",
command: "review.submit",
args: {
taskId: "{{executionId}}",
materialId: "{{results.auto-check.materialId}}",
score: 0,
passed: false
},
dependsOn: ["auto-check"],
// Skip if auto-check score is high
condition: "results.auto-check.score < 8"
},
{
id: "prepare-delivery",
name: "准备交付",
skill: "delivery-distribution",
command: "delivery.create",
args: {
name: "交付包-{{demand.title}}",
materialIds: "{{results.manual-review.approvedMaterials || results.auto-check.materialIds}}",
platforms: params.platforms,
format: "zip"
},
dependsOn: ["manual-review", "auto-check"]
},
{
id: "distribute",
name: "分发到平台",
skill: "delivery-distribution",
command: "delivery.distribute",
args: { id: "{{results.prepare-delivery.packageId}}" },
dependsOn: ["prepare-delivery"]
},
{
id: "record-analytics",
name: "记录分析数据",
skill: "analytics-feedback",
command: "analytics.ingest",
args: {
materialId: "{{results.distribute.materialIds}}",
impressions: 0, // Will be updated later via API
clicks: 0,
conversions: 0,
cost: 0,
revenue: 0
},
dependsOn: ["distribute"]
}
];
return this.createWorkflow({
name: `广告生产流程 - params.demandId`,
steps: steps,
context: { demandId: params.demandId, platforms: params.platforms }
});
}
// Execution engine
private async executeWorkflow(workflowId: string): Promise<void> {
const wf = this.workflows.get(workflowId);
if (!wf) return;
const executions = this.executions.get(workflowId) || [];
for (let i = 0; i < wf.definition.length; i++) {
if (wf.status !== "running") {
this.api.log(`info`, `Workflow workflowId stopped (status: wf.status)`);
return;
}
const step = wf.definition[i];
wf.currentStep = i;
this.workflows.set(workflowId, wf);
// Check dependencies
if (step.dependsOn) {
const allDepsSucceeded = step.dependsOn.every(depId => {
const depExec = executions.find(e => e.stepId === depId);
return depExec && depExec.status === "success";
});
if (!allDepsSucceeded) {
this.api.log(`warn`, `Skipping step step.id: dependencies not met`);
const skipped: Execution = {
workflowId,
stepId: step.id,
status: "skipped",
attempts: 0,
startedAt: new Date(),
completedAt: new Date()
};
executions.push(skipped);
this.executions.set(workflowId, executions);
continue;
}
}
// Check condition
if (step.condition) {
const context = this.buildContext(workflowId, wf, executions);
const conditionMet = this.evaluateCondition(step.condition, context);
if (!conditionMet) {
this.api.log(`info`, `Skipping step step.id: condition not met`);
const skipped: Execution = {
workflowId,
stepId: step.id,
status: "skipped",
attempts: 0,
startedAt: new Date(),
completedAt: new Date()
};
executions.push(skipped);
this.executions.set(workflowId, executions);
continue;
}
}
// Execute step with retry
const execResult = await this.executeStepWithRetry(workflowId, step, wf.context);
executions.push(execResult);
this.executions.set(workflowId, executions);
// Update context with results
if (execResult.status === "success" && execResult.result) {
wf.context = {
...wf.context,
[`results.step.id`]: execResult.result
};
this.workflows.set(workflowId, wf);
}
// Check if step failed after retries
if (execResult.status === "failed") {
wf.status = "failed";
wf.error = `Step step.id failed: execResult.lastError`;
wf.endTime = new Date();
this.workflows.set(workflowId, wf);
this.api.log(`error`, `Workflow workflowId failed at step step.id: wf.error`);
return;
}
}
// All steps completed
wf.status = "completed";
wf.endTime = new Date();
this.workflows.set(workflowId, wf);
this.api.log(`info`, `Workflow workflowId completed successfully`);
// Emit completion event
this.api.emit("workflow.completed", { workflowId, context: wf.context });
}
private async executeStepWithRetry(
workflowId: string,
step: WorkflowStep,
context: Record<string, any>
): Promise<Execution> {
const exec: Execution = {
workflowId,
stepId: step.id,
status: "pending",
attempts: 0
};
const maxAttempts = step.retry?.maxAttempts || 1;
const delayMs = step.retry?.delayMs || 1000;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
exec.attempts = attempt;
exec.status = "running";
exec.startedAt = new Date();
this.updateExecution(workflowId, exec);
try {
// Resolve args with context variables
const resolvedArgs = this.resolveArgs(step.args, context);
// Add workflow metadata
resolvedArgs._workflow = { workflowId, stepId: step.id, attempt };
this.api.log(`debug`, `Executing step.skill:step.command with args: JSON.stringify(resolvedArgs)`);
// Execute command via API
const result = await this.api.executeCommand(step.skill, step.command, resolvedArgs);
exec.status = "success";
exec.completedAt = new Date();
exec.result = result;
this.updateExecution(workflowId, exec);
this.api.log(`info`, `Step step.id completed successfully`);
return exec;
} catch (error: any) {
exec.lastError = error.message;
exec.completedAt = new Date();
if (attempt < maxAttempts) {
this.api.log(`warn`, `Step step.id failed, retrying in delayMsms: error.message`);
await this.sleep(delayMs);
continue;
} else {
exec.status = "failed";
this.updateExecution(workflowId, exec);
this.api.log(`error`, `Step step.id failed after maxAttempts attempts: error.message`);
return exec;
}
}
}
exec.status = "failed";
return exec;
}
private resolveArgs(args: Record<string, any>, context: Record<string, any>): Record<string, any> {
const resolved: Record<string, any> = {};
for (const [key, value] of Object.entries(args)) {
if (typeof value === "string" && value.startsWith("{{") && value.endsWith("}}")) {
// Template variable
const path = value.slice(2, -2).trim();
resolved[key] = this.getContextValue(path, context);
} else {
resolved[key] = value;
}
}
return resolved;
}
private getContextValue(path: string, context: Record<string, any>): any {
const parts = path.split(".");
let current: any = context;
for (const part of parts) {
if (current && typeof current === "object") {
current = current[part];
} else {
return undefined;
}
}
return current;
}
private buildContext(workflowId: string, wf: Workflow, executions: Execution[]): Record<string, any> {
const context: Record<string, any> = {
workflowId,
workflowName: wf.name,
stepResults: {},
executionId: workflowId // For simplicity
};
for (const exec of executions) {
if (exec.status === "success" && exec.result) {
context.stepResults[exec.stepId] = exec.result;
}
}
return { ...context, ...wf.context };
}
private evaluateCondition(condition: string, context: Record<string, any>): boolean {
try {
// Very simple condition evaluation - in production use a proper expression parser
// For now, just check if a context variable exists and is truthy
if (condition.startsWith("results.")) {
const path = condition.slice(7);
const value = this.getContextValue(path, context);
return value ? true : false;
}
// Default: condition must be a boolean expression
// This is a placeholder - real implementation would use a safe eval
return true;
} catch (error) {
this.api.log(`error`, `Condition evaluation failed: condition - error`);
return false;
}
}
private updateExecution(workflowId: string, exec: Execution): void {
const executions = this.executions.get(workflowId) || [];
const index = executions.findIndex(e => e.stepId === exec.stepId);
if (index >= 0) {
executions[index] = exec;
} else {
executions.push(exec);
}
this.executions.set(workflowId, executions);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Monitoring
async getWorkflowStats(): Promise<{
total: number;
byStatus: Record<string, number>;
averageDurationMs?: number;
}> {
const all = Array.from(this.workflows.values());
const byStatus: Record<string, number> = {};
for (const wf of all) {
byStatus[wf.status] = (byStatus[wf.status] || 0) + 1;
}
// Calculate average duration for completed workflows
const completed = all.filter(w => w.status === "completed" && w.startTime && w.endTime);
const avgDuration = completed.length > 0
? completed.reduce((sum, w) => sum + (w.endTime!.getTime() - w.startTime!.getTime()), 0) / completed.length
: undefined;
return {
total: all.length,
byStatus,
averageDurationMs: avgDuration
};
}
}
export async function register(api: OpenClawSkillApi) {
const orchestrator = new WorkflowOrchestrator(api);
api.registerCommand("workflow", {
description: "工作流编排中心命令",
subcommands: {
create: {
description: "创建工作流 (JSON定义)",
arguments: {
definition: { type: "object", required: true, help: "工作流定义 (steps数组)" },
name: { type: "string", required: true, help: "工作流名称" },
context: { type: "object", help: "初始上下文" }
},
execute: async (args) => {
const id = await orchestrator.createWorkflow({
name: args.name,
steps: args.definition,
context: args.context
});
return { success: true, workflowId: id, message: `工作流已创建: id` };
}
},
start: {
description: "启动工作流",
arguments: {
id: { type: "string", required: true, help: "工作流ID" },
overrides: { type: "object", help: "覆盖上下文变量" }
},
execute: async (args) => {
await orchestrator.startWorkflow(args.id, args.overrides);
return { success: true, message: `工作流已启动: args.id` };
}
},
pause: {
description: "暂停工作流",
arguments: {
id: { type: "string", required: true, help: "工作流ID" }
},
execute: async (args) => {
await orchestrator.pauseWorkflow(args.id);
return { success: true, message: `工作流已暂停: args.id` };
}
},
resume: {
description: "恢复工作流",
arguments: {
id: { type: "string", required: true, help: "工作流ID" }
},
execute: async (args) => {
await orchestrator.resumeWorkflow(args.id);
return { success: true, message: `工作流已恢复: args.id` };
}
},
cancel: {
description: "取消工作流",
arguments: {
id: { type: "string", required: true, help: "工作流ID" }
},
execute: async (args) => {
await orchestrator.cancelWorkflow(args.id);
return { success: true, message: `工作流已取消: args.id` };
}
},
status: {
description: "查看工作流状态",
arguments: {
id: { type: "string", required: true, help: "工作流ID" }
},
execute: async (args) => {
const status = await orchestrator.getStatus(args.id);
return { success: true, ...status };
}
},
list: {
description: "列出工作流",
arguments: {
status: {
type: "enum",
options: ["draft", "running", "paused", "completed", "failed"]
}
},
execute: async (args) => {
const workflows = await orchestrator.listWorkflows(args.status);
return { success: true, count: workflows.length, workflows };
}
},
template: {
description: "使用标准模板创建工作流",
arguments: {
type: {
type: "enum",
required: true,
options: ["ad-production"],
help: "模板类型"
},
demandId: { type: "string", required: true, help: "需求ID" },
prompt: { type: "string", required: true, help: "生成提示词" },
count: { type: "number", help: "生成数量 (默认10)", default: 10 },
platforms: { type: "string[]", required: true, help: "目标平台列表" }
},
execute: async (args) => {
let workflowId: string;
switch (args.type) {
case "ad-production":
workflowId = await orchestrator.createStandardAdWorkflow({
demandId: args.demandId,
prompt: args.prompt,
count: args.count,
platforms: args.platforms
});
break;
default:
return { success: false, error: `Unknown template type: args.type` };
}
return { success: true, workflowId, message: `工作流已创建 (args.type): workflowId` };
}
},
stats: {
description: "工作流统计",
execute: async () => {
const stats = await orchestrator.getWorkflowStats();
return { success: true, stats };
}
},
executions: {
description: "查看执行历史",
arguments: {
id: { type: "string", required: true, help: "工作流ID" }
},
execute: async (args) => {
const execs = await orchestrator.getExecutions(args.id);
return { success: true, count: execs.length, executions: execs };
}
}
}
});
// Listen for events from other skills to auto-trigger workflows
api.on("demand.approved", async (data) => {
const { demandId, demand } = data as { demandId: string; demand: any };
api.log("info", `Demand approved: demandId, creating auto-workflow`);
// Create a standard ad production workflow
try {
const wfId = await orchestrator.createStandardAdWorkflow({
demandId,
prompt: demand.description || "Standard creative",
count: 10,
platforms: ["抖音", "穿山甲"] // Default platforms
});
api.log("info", `Auto-created workflow: wfId`);
// Optionally start immediately
await orchestrator.startWorkflow(wfId);
api.log("info", `Workflow wfId started automatically`);
} catch (error) {
api.log("error", `Failed to create auto-workflow: error`);
}
});
// Listen for delivery completion to close the loop
api.on("delivery.completed", async (data) => {
const { packageId, results } = data as { packageId: string; results: any };
api.log("info", `Delivery completed: packageId, analytics tracking initiated`);
// Would trigger analytics setup here
});
api.log("info", "Workflow Orchestrator skill loaded");
}
FILE:package.json
{
"name": "ad-production-workflow-orchestrator",
"version": "1.0.0",
"description": "工作流编排中心 - 协调全流程,管理任务依赖和状态",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [
"openclaw",
"skill",
"workflow",
"orchestrator",
"pipeline"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}审核质检技能 - 自动化质量评估和人工审核工作流
---
name: "Review Quality"
slug: ad-production-review-quality
version: "1.0.0"
description: "审核质检技能 - 自动化质量评估和人工审核工作流"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"✅","requires":{"bins":[]}}}
---
# Review Quality - 审核质检
负责广告素材的质量评估和审核工作流。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
- 自动审核广告素材质量
- 人工审核流程管理
- 质量评估和打分
- 合规性检查
## Architecture
```
review-quality/
├── index.ts # 主入口,审核质检逻辑
├── package.json # 依赖配置
└── README.md # 详细文档
```
## Core Commands
### 自动审核
```typescript
await api.executeAction('review-quality.auto-review', {
materialId: string, // 素材ID
criteria?: { // 审核标准
resolution?: { minWidth: number, minHeight: number },
format?: string[],
maxFileSize?: number
}
});
```
### 人工审核
```typescript
await api.executeAction('review-quality.manual-review', {
materialId: string, // 素材ID
reviewer: string, // 审核人
decision: 'approved' | 'rejected' | 'revise',
comments?: string
});
```
### 质量评估
```typescript
await api.executeAction('review-quality.assess', {
materialId: string, // 素材ID
dimensions: string[] // 评估维度
});
```
### 获取审核状态
```typescript
await api.executeAction('review-quality.get-status', {
materialId: string // 素材ID
});
```
## 响应事件
- `review-quality.auto-reviewed` - 自动审核完成
- `review-quality.manual-reviewed` - 人工审核完成
- `review-quality.assessed` - 质量评估完成
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface Review {
id: string;
taskId: string;
materialId: string;
reviewer: string;
score: number;
passed: boolean;
comments?: string;
issues?: string[];
createdAt: Date;
updatedAt: Date;
}
interface QualityCheck {
materialId: string;
automatedScore: number;
checks: {
resolution: boolean;
artifacts: boolean;
watermark: boolean;
nsfw: boolean;
blur: boolean;
};
suggestions: string[];
}
class ReviewQuality {
private reviews: Map<string, Review> = new Map();
private api: OpenClawSkillApi;
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async autoCheck(materialId: string): Promise<QualityCheck> {
// Would integrate with material-library to get material
// For now, return mock check
const check: QualityCheck = {
materialId,
automatedScore: Math.random() * 10,
checks: {
resolution: true,
artifacts: false,
watermark: false,
nsfw: false,
blur: false
},
suggestions: []
};
// Add suggestions based on issues
if (!check.checks.resolution) {
check.suggestions.push("分辨率不符合要求");
}
if (check.checks.artifacts) {
check.suggestions.push("检测到 artifacts,建议重新生成");
}
if (check.checks.blur) {
check.suggestions.push("图片模糊,建议提高清晰度");
}
return check;
}
async submitReview(review: {
taskId: string;
materialId: string;
score: number;
passed: boolean;
comments?: string;
issues?: string[];
}): Promise<string> {
const id = `REV-Date.now()-Math.random().toString(36).substr(2, 9)`;
const newReview: Review = {
id,
taskId: review.taskId,
materialId: review.materialId,
reviewer: this.api.user?.id || "anonymous",
score: review.score,
passed: review.passed,
comments: review.comments,
issues: review.issues || [],
createdAt: new Date(),
updatedAt: new Date(),
};
this.reviews.set(id, newReview);
this.api.log(`info`, `Review submitted: id for material review.materialId`);
// Emit event
this.api.emit("review.completed", { reviewId: id, review: newReview });
// If review fails, trigger regeneration request
if (!review.passed) {
this.api.emit("review.rejected", { materialId: review.materialId, review: newReview });
}
return id;
}
async getReview(id: string): Promise<Review | null> {
return this.reviews.get(id) || null;
}
async getReviewsForTask(taskId: string): Promise<Review[]> {
return Array.from(this.reviews.values()).filter(r => r.taskId === taskId);
}
async getReviewsForMaterial(materialId: string): Promise<Review[]> {
return Array.from(this.reviews.values()).filter(r => r.materialId === materialId);
}
async batchReview(reviews: Array<{
taskId: string;
materialId: string;
score: number;
passed: boolean;
comments?: string;
issues?: string[];
}>): Promise<{submitted: number; ids: string[]}> {
const ids: string[] = [];
for (const review of reviews) {
const id = await this.submitReview(review);
ids.push(id);
}
this.api.log(`info`, `Batch review completed: ids.length reviews submitted`);
return { submitted: ids.length, ids };
}
async approveBatch(taskId: string, materialIds: string[]): Promise<{approved: number; reviewIds: string[]}> {
const reviews = materialIds.map(mid => ({
taskId,
materialId: mid,
score: 10,
passed: true,
comments: "批量批准"
}));
const result = await this.batchReview(reviews);
return { approved: result.submitted, reviewIds: result.ids };
}
async rejectBatch(taskId: string, materialIds: string[], reason: string): Promise<{rejected: number; reviewIds: string[]}> {
const reviews = materialIds.map(mid => ({
taskId,
materialId: mid,
score: 0,
passed: false,
comments: reason,
issues: [reason]
}));
const result = await this.batchReview(reviews);
return { rejected: result.submitted, reviewIds: result.ids };
}
async getStats(taskId?: string): Promise<{
total: number;
passed: number;
failed: number;
averageScore: number;
}> {
let reviews = Array.from(this.reviews.values());
if (taskId) {
reviews = reviews.filter(r => r.taskId === taskId);
}
const total = reviews.length;
const passed = reviews.filter(r => r.passed).length;
const failed = total - passed;
const averageScore = total > 0
? reviews.reduce((sum, r) => sum + r.score, 0) / total
: 0;
return { total, passed, failed, averageScore };
}
async resolveIssue(reviewId: string): Promise<boolean> {
const review = this.reviews.get(reviewId);
if (!review) {
throw new Error(`Review not found: reviewId`);
}
// Mark issues as resolved
review.issues = [];
review.updatedAt = new Date();
this.reviews.set(reviewId, review);
this.api.log(`info`, `Issues resolved for review: reviewId`);
return true;
}
}
export async function register(api: OpenClawSkillApi) {
const reviewer = new ReviewQuality(api);
api.registerCommand("review", {
description: "审核质检命令",
subcommands: {
check: {
description: "自动质量检查",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const result = await reviewer.autoCheck(args.materialId);
return {
success: true,
materialId: args.materialId,
score: result.automatedScore,
checks: result.checks,
suggestions: result.suggestions
};
}
},
submit: {
description: "提交审核",
arguments: {
taskId: { type: "string", required: true, help: "任务ID" },
materialId: { type: "string", required: true, help: "素材ID" },
score: { type: "number", required: true, help: "评分 (0-10)" },
passed: { type: "boolean", required: true, help: "是否通过" },
comments: { type: "string", help: "审核意见" },
issues: { type: "string[]", help: "问题列表" }
},
execute: async (args) => {
const reviewId = await reviewer.submitReview({
taskId: args.taskId,
materialId: args.materialId,
score: args.score,
passed: args.passed,
comments: args.comments,
issues: args.issues
});
return { success: true, reviewId, message: `审核已提交: reviewId` };
}
},
get: {
description: "查看审核记录",
arguments: {
id: { type: "string", required: true, help: "审核ID" }
},
execute: async (args) => {
const review = await reviewer.getReview(args.id);
if (!review) {
return { success: false, error: `审核记录未找到: args.id` };
}
return { success: true, review };
}
},
list: {
description: "列出审核记录",
arguments: {
taskId: { type: "string", help: "按任务筛选" },
materialId: { type: "string", help: "按素材筛选" }
},
execute: async (args) => {
let reviews: Review[];
if (args.taskId) {
reviews = await reviewer.getReviewsForTask(args.taskId);
} else if (args.materialId) {
reviews = await reviewer.getReviewsForMaterial(args.materialId);
} else {
reviews = Array.from(reviewer['reviews'].values());
}
return { success: true, count: reviews.length, reviews };
}
},
batch: {
description: "批量审核",
arguments: {
taskId: { type: "string", required: true, help: "任务ID" },
passed: { type: "boolean", required: true, help: "是否通过" },
materialIds: { type: "string[]", required: true, help: "素材ID列表" },
comments: { type: "string", help: "批量评论" }
},
execute: async (args) => {
if (args.passed) {
const result = await reviewer.approveBatch(args.taskId, args.materialIds);
return { success: true, ...result, message: `已批准 result.approved 个素材` };
} else {
const result = await reviewer.rejectBatch(args.taskId, args.materialIds, args.comments || "未通过审核");
return { success: true, ...result, message: `已拒绝 result.rejected 个素材` };
}
}
},
resolve: {
description: "标记问题已解决",
arguments: {
reviewId: { type: "string", required: true, help: "审核ID" }
},
execute: async (args) => {
await reviewer.resolveIssue(args.reviewId);
return { success: true, message: `问题已解决: args.reviewId` };
}
},
stats: {
description: "审核统计",
arguments: {
taskId: { type: "string", help: "任务ID (可选)" }
},
execute: async (args) => {
const stats = await reviewer.getStats(args.taskId);
return { success: true, stats };
}
}
}
});
// Listen for generation completion to trigger auto-check
api.on("generation.completed", async (data) => {
const { result } = data as { result: any };
api.log("info", `Auto-checking generated material: result.materialId`);
try {
const check = await reviewer.autoCheck(result.materialId);
if (check.automatedScore < 5) {
api.log("warn", `Low quality score for result.materialId: check.automatedScore`);
}
} catch (error) {
api.log("error", `Auto-check failed: error`);
}
});
api.log("info", "Review & Quality skill loaded");
}
FILE:package.json
{
"name": "ad-production-review-quality",
"version": "1.0.0",
"description": "审核质检技能 - 自动化质量评估和人工审核工作流",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [
"openclaw",
"skill",
"review",
"quality",
"qc"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}素材库管理技能 - 提供素材的存储、检索、版本管理
---
name: "Material Library"
slug: ad-production-material-library
version: "1.0.0"
description: "素材库管理技能 - 提供素材的存储、检索、版本管理"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"📚","requires":{"bins":[]}}}
---
# Material Library - 素材库管理
负责广告素材的存储、检索和版本管理。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
- 存储生成的广告素材
- 检索历史素材
- 管理素材版本
- 素材分类和标签
## Architecture
```
material-library/
├── index.ts # 主入口,素材管理
├── package.json # 依赖配置
└── README.md # 详细文档
```
## Core Commands
### 存储素材
```typescript
await api.executeAction('material-library.store', {
file: Buffer, // 文件内容
metadata: { // 元数据
name: string,
type: string,
tags?: string[],
campaignId?: string
}
});
```
### 检索素材
```typescript
await api.executeAction('material-library.search', {
query?: string, // 搜索关键词
filters?: { // 筛选条件
type?: string,
tags?: string[],
dateRange?: { start: string, end: string }
},
limit?: number
});
```
### 获取素材
```typescript
await api.executeAction('material-library.get', {
materialId: string // 素材ID
});
```
### 更新版本
```typescript
await api.executeAction('material-library.update-version', {
materialId: string, // 素材ID
file: Buffer, // 新版本文件
changelog?: string // 版本说明
});
```
## 响应事件
- `material-library.stored` - 素材存储完成
- `material-library.updated` - 素材更新完成
- `material-library.deleted` - 素材删除
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface Material {
id: string;
name: string;
type: "image" | "video" | "audio" | "other";
format: string;
size: { width?: number; height?: number; duration?: number };
source: string;
tags: string[];
prompt?: string;
generationParams?: Record<string, any>;
version: number;
status: "uploaded" | "processing" | "available" | "archived";
createdAt: Date;
updatedAt: Date;
metadata?: Record<string, any>;
fileToken?: string;
}
class MaterialLibrary {
private materials: Map<string, Material> = new Map();
private api: OpenClawSkillApi;
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async upload(file: Buffer, metadata: {
name: string;
type: Material["type"];
format: string;
tags?: string[];
prompt?: string;
}): Promise<string> {
const id = `MAT-Date.now()-Math.random().toString(36).substr(2, 9)`;
// Store file (would integrate with drive or storage)
const fileToken = await this.storeFile(file, id, metadata.format);
const material: Material = {
id,
name: metadata.name,
type: metadata.type,
format: metadata.format,
size: {}, // Extract from file
source: "upload",
tags: metadata.tags || [],
prompt: metadata.prompt,
version: 1,
status: "uploaded",
createdAt: new Date(),
updatedAt: new Date(),
fileToken,
};
this.materials.set(id, material);
// Auto-tagging if configured
if (this.api.config?.autoTagging) {
await this.autoTag(id);
}
this.api.log(`info`, `Material uploaded: id (metadata.name)`);
return id;
}
async get(id: string): Promise<Material | null> {
return this.materials.get(id) || null;
}
async search(query: {
tags?: string[];
type?: Material["type"];
format?: string;
hasPrompt?: boolean;
minVersion?: number;
}): Promise<Material[]> {
const all = Array.from(this.materials.values());
return all.filter(m => {
if (query.tags && !query.tags.some(t => m.tags.includes(t))) return false;
if (query.type && m.type !== query.type) return false;
if (query.format && m.format !== query.format) return false;
if (query.hasPrompt && !m.prompt) return false;
if (query.minVersion && m.version < query.minVersion) return false;
return true;
});
}
async findSimilar(materialId: string, limit: number = 10): Promise<Material[]> {
const source = this.materials.get(materialId);
if (!source) {
throw new Error(`Material not found: materialId`);
}
// Find materials with overlapping tags and similar type/format
const all = Array.from(this.materials.values());
const scores = all
.filter(m => m.id !== materialId)
.map(m => ({
material: m,
score: this.calculateSimilarity(source, m)
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit)
.map(s => s.material);
return scores;
}
async createVersion(materialId: string, changes: {
newFile?: Buffer;
newTags?: string[];
newMetadata?: Record<string, any>;
}): Promise<string> {
const original = this.materials.get(materialId);
if (!original) {
throw new Error(`Material not found: materialId`);
}
const newId = `materialId.voriginal.version + 1`;
const newMaterial: Material = {
...original,
id: newId,
version: original.version + 1,
updatedAt: new Date(),
tags: changes.newTags || original.tags,
metadata: { ...original.metadata, ...changes.newMetadata },
};
if (changes.newFile) {
newMaterial.fileToken = await this.storeFile(changes.newFile, newId, original.format);
}
this.materials.set(newId, newMaterial);
this.api.log(`info`, `Created new version: materialId -> newId`);
return newId;
}
async archive(materialId: string): Promise<boolean> {
const material = this.materials.get(materialId);
if (!material) {
throw new Error(`Material not found: materialId`);
}
material.status = "archived";
material.updatedAt = new Date();
this.materials.set(materialId, material);
this.api.log(`info`, `Material archived: materialId`);
return true;
}
async addTags(materialId: string, tags: string[]): Promise<boolean> {
const material = this.materials.get(materialId);
if (!material) {
throw new Error(`Material not found: materialId`);
}
for (const tag of tags) {
if (!material.tags.includes(tag)) {
material.tags.push(tag);
}
}
material.updatedAt = new Date();
this.materials.set(materialId, material);
this.api.log(`info`, `Added tags to materialId: tags.join(", ")`);
return true;
}
async getStats(): Promise<{
total: number;
byType: Record<string, number>;
byStatus: Record<string, number>;
totalSize: number;
}> {
const all = Array.from(this.materials.values());
const byType: Record<string, number> = {};
const byStatus: Record<string, number> = {};
for (const m of all) {
byType[m.type] = (byType[m.type] || 0) + 1;
byStatus[m.status] = (byStatus[m.status] || 0) + 1;
}
return {
total: all.length,
byType,
byStatus,
totalSize: 0, // Calculate from storage
};
}
private async storeFile(file: Buffer, id: string, format: string): Promise<string> {
// This would integrate with feishu_drive_file or other storage
// For now, return a mock token
return `token-id-Date.now()`;
}
private async autoTag(materialId: string): Promise<void> {
const material = this.materials.get(materialId);
if (!material) return;
// AI-based auto-tagging would go here
// For now, add some basic tags
if (material.type === "video") {
material.tags.push("video", "motion");
} else if (material.type === "image") {
material.tags.push("static", "image");
}
this.api.log(`debug`, `Auto-tagged materialId: material.tags.join(", ")`);
}
private calculateSimilarity(a: Material, b: Material): number {
let score = 0;
// Same type increases score
if (a.type === b.type) score += 10;
// Overlapping tags
const commonTags = a.tags.filter(t => b.tags.includes(t));
score += commonTags.length * 5;
// Same format
if (a.format === b.format) score += 5;
// Similar prompt keywords (very basic)
if (a.prompt && b.prompt) {
const aWords = new Set(a.prompt.toLowerCase().split(/\s+/));
const bWords = new Set(b.prompt.toLowerCase().split(/\s+/));
const commonWords = [...aWords].filter(w => bWords.has(w));
score += commonWords.length * 2;
}
return score;
}
}
export async function register(api: OpenClawSkillApi) {
const library = new MaterialLibrary(api);
api.registerCommand("material", {
description: "素材库管理命令",
subcommands: {
upload: {
description: "上传素材",
arguments: {
name: { type: "string", required: true, help: "素材名称" },
type: {
type: "enum",
required: true,
options: ["image", "video", "audio", "other"],
help: "素材类型"
},
format: { type: "string", required: true, help: "格式 (如: png, mp4, wav)" },
tags: { type: "string[]", help: "标签列表" },
prompt: { type: "string", help: "生成提示词 (可选)" }
},
execute: async (args) => {
// Note: file upload would need different handling in OpenClaw
// This is a simplified version
return {
success: false,
error: "文件上传需要额外的OpenClaw集成,请使用飞书文件上传或本地路径"
};
}
},
get: {
description: "获取素材详情",
arguments: {
id: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const material = await library.get(args.id);
if (!material) {
return { success: false, error: `素材未找到: args.id` };
}
return { success: true, material };
}
},
search: {
description: "搜索素材",
arguments: {
tags: { type: "string[]", help: "标签筛选" },
type: { type: "string", help: "类型筛选" },
format: { type: "string", help: "格式筛选" }
},
execute: async (args) => {
const results = await library.search({
tags: args.tags,
type: args.type,
format: args.format
});
return { success: true, count: results.length, materials: results };
}
},
similar: {
description: "查找相似素材",
arguments: {
id: { type: "string", required: true, help: "素材ID" },
limit: { type: "number", help: "返回数量 (默认10)" }
},
execute: async (args) => {
const results = await library.findSimilar(args.id, args.limit);
return { success: true, count: results.length, materials: results };
}
},
stats: {
description: "获取库统计信息",
execute: async () => {
const stats = await library.getStats();
return { success: true, stats };
}
},
version: {
description: "创建新版本",
arguments: {
id: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const newId = await library.createVersion(args.id, {});
return { success: true, newId, message: `新版本: newId` };
}
},
archive: {
description: "归档素材",
arguments: {
id: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
await library.archive(args.id);
return { success: true, message: `素材已归档: args.id` };
}
}
}
});
api.log("info", "Material Library skill loaded");
}
FILE:package.json
{
"name": "ad-production-material-library",
"version": "1.0.0",
"description": "素材库管理技能 - 提供素材的存储、检索、版本管理",
"main": "index.js",
"scripts": {
"build": "tsc",
"watch": "tsc --watch"
},
"keywords": [
"openclaw",
"skill",
"material",
"library",
"asset",
"storage"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}广告创意需求管理技能 - 处理需求发起、评审、拆解全流程
---
name: "Demand Management"
slug: ad-production-demand-management
version: "1.0.0"
description: "广告创意需求管理技能 - 处理需求发起、评审、拆解全流程"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"📋","requires":{"bins":[]}}}
---
# Demand Management - 需求管理
负责广告创意需求的全流程管理,包括需求发起、评审和拆解。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
- 创建新的广告创意需求
- 需求评审和确认
- 需求拆解为子任务
- 跟踪需求状态
## Architecture
```
demand-management/
├── index.ts # 主入口,需求管理逻辑
├── package.json # 依赖配置
├── README.md # 详细文档
└── scripts/ # 辅助脚本
```
## Core Commands
### 创建需求
```typescript
await api.executeAction('demand-management.create', {
title: string, // 需求标题
description: string, // 需求描述
requester: string, // 需求方
priority?: 'low' | 'medium' | 'high',
deadline?: string // 截止时间
});
```
### 评审需求
```typescript
await api.executeAction('demand-management.review', {
demandId: string, // 需求ID
status: 'approved' | 'rejected' | 'revised',
comments?: string // 评审意见
});
```
### 拆解需求
```typescript
await api.executeAction('demand-management.breakdown', {
demandId: string, // 需求ID
tasks: Array<{ // 任务列表
title: string,
description: string,
assignee?: string,
estimatedHours?: number
}>
});
```
### 更新状态
```typescript
await api.executeAction('demand-management.update-status', {
demandId: string, // 需求ID
status: string // 新状态
});
```
## 响应事件
- `demand-management.created` - 需求创建完成
- `demand-management.reviewed` - 需求评审完成
- `demand-management.broken-down` - 需求拆解完成
FILE:README.md
# Demand Management Skill
管理广告创意素材的需求发起、评审和拆解流程。
## Features
- ✅ 需求提交与标准化
- ✅ 需求评审会议安排
- ✅ 需求拆解为生产任务
- ✅ 需求状态跟踪
- ✅ 需求文档自动生成
## Usage
```bash
# 创建新需求
claw demand create --title "Q2游戏广告素材" --type "new_material" \
--channel "抖音" --style "现代奇幻" --size "9:16" \
--description "需要一批奇幻风格的9:16竖版视频素材"
# 查看需求列表
claw demand list --status "pending"
# 评审需求
claw demand review --id "REQ-001" --result "approved" --comments "通过"
# 拆解需求为生产任务
claw demand拆解 --id "REQ-001" --prompt-template "fantasy_character"
```
## Integration
这个技能会与其他技能协同:
- 产出需求文档给 `ai-generation`
- 触发 `workflow-orchestrator` 创建流程
- 更新 `material-library` 的元数据标签
## Configuration
在 `openclaw.json` 中配置:
```json
{
"skills": {
"demand-management": {
"enabled": true,
"config": {
"reviewers": ["ou_xxx", "ou_yyy"],
"autoAssign": true,
"defaultDeadline": 7
}
}
}
}
```
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface Demand {
id: string;
title: string;
type: "new_material" | "reuse" | "modification";
channel: string;
style: string;
size: string;
description: string;
status: "draft" | "pending_review" | "approved" | "rejected" | "in_production" | "completed";
createdAt: Date;
updatedAt: Date;
reviewer?: string;
reviewComments?: string;
promptTemplate?: string;
assignedTasks?: string[];
}
class DemandManager {
private demands: Map<string, Demand> = new Map();
private api: OpenClawSkillApi;
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async create(demand: Omit<Demand, "id" | "status" | "createdAt" | "updatedAt">): Promise<string> {
const id = `REQ-Date.now()-Math.random().toString(36).substr(2, 9)`;
const now = new Date();
const newDemand: Demand = {
...demand,
id,
status: "draft",
createdAt: now,
updatedAt: now,
};
this.demands.set(id, newDemand);
// Log the creation
this.api.log(`info`, `Demand created: id - demand.title`);
// Auto-generate standard demand document if configured
if (this.api.config?.autoDocument) {
await this.generateDocument(id);
}
return id;
}
async get(id: string): Promise<Demand | null> {
return this.demands.get(id) || null;
}
async list(status?: Demand["status"]): Promise<Demand[]> {
const all = Array.from(this.demands.values());
if (status) {
return all.filter(d => d.status === status);
}
return all;
}
async review(id: string, result: "approved" | "rejected", comments?: string, reviewer?: string): Promise<boolean> {
const demand = this.demands.get(id);
if (!demand) {
throw new Error(`Demand not found: id`);
}
if (demand.status !== "pending_review") {
throw new Error(`Cannot review demand in status: demand.status`);
}
demand.status = result === "approved" ? "approved" : "rejected";
demand.reviewer = reviewer || this.api.user?.id;
demand.reviewComments = comments;
demand.updatedAt = new Date();
this.api.log(`info`, `Demand id reviewed: result`);
// If approved, trigger production pipeline
if (result === "approved") {
await this.triggerProduction(id);
}
return true;
}
async submitForReview(id: string): Promise<boolean> {
const demand = this.demands.get(id);
if (!demand) {
throw new Error(`Demand not found: id`);
}
if (demand.status !== "draft") {
throw new Error(`Cannot submit demand in status: demand.status`);
}
demand.status = "pending_review";
demand.updatedAt = new Date();
this.api.log(`info`, `Demand id submitted for review`);
// Notify reviewers if configured
if (this.api.config?.reviewers?.length) {
await this.notifyReviewers(id);
}
return true;
}
async拆解(id: string, promptTemplate?: string): Promise<{tasks: string[]}> {
const demand = this.demands.get(id);
if (!demand) {
throw new Error(`Demand not found: id`);
}
if (demand.status !== "approved") {
throw new Error(`Cannot拆解demand in status: demand.status`);
}
// Store the prompt template
if (promptTemplate) {
demand.promptTemplate = promptTemplate;
}
// Generate production tasks based on demand parameters
const tasks = await this.generateTasks(demand);
demand.assignedTasks = tasks;
demand.status = "in_production";
demand.updatedAt = new Date();
this.api.log(`info`, `Demand id拆解完成, 生成 tasks.length 个任务`);
return { tasks };
}
async complete(id: string): Promise<boolean> {
const demand = this.demands.get(id);
if (!demand) {
throw new Error(`Demand not found: id`);
}
demand.status = "completed";
demand.updatedAt = new Date();
this.api.log(`info`, `Demand id completed`);
return true;
}
private async generateTasks(demand: Demand): Promise<string[]> {
const tasks: string[] = [];
// Task 1: Generate initial batch
tasks.push(`GEN-demand.id-1`);
// Task 2: Quality review
tasks.push(`REVIEW-demand.id-1`);
// Task 3: Final delivery
tasks.push(`DELIVERY-demand.id-1`);
// Here we could also create separate tasks for different formats/sizes
return tasks;
}
private async generateDocument(id: string): Promise<void> {
const demand = this.demands.get(id);
if (!demand) return;
const docContent = `
# 需求文档: demand.title
**需求ID**: demand.id
**类型**: demand.type
**渠道**: demand.channel
**风格**: demand.style
**尺寸**: demand.size
## 需求描述
demand.description
## 生产要求
- Prompt 模板: demand.promptTemplate || "待定"
- 素材格式: 待定
- 交付时间: 待定
---
*自动生成于 new Date().toISOString()*
`;
// Create a document using feishu_create_doc or other service
// This would need the appropriate API access
this.api.log(`debug`, `Generated document for id`);
}
private async notifyReviewers(id: string): Promise<void> {
const demand = this.demands.get(id);
if (!demand) return;
const reviewers = this.api.config?.reviewers as string[] || [];
for (const reviewer of reviewers) {
try {
// Send notification (would use feishu_im_user_message or similar)
this.api.log(`info`, `Notified reviewer reviewer for demand id`);
} catch (error) {
this.api.log(`error`, `Failed to notify reviewer reviewer: error`);
}
}
}
private async triggerProduction(id: string): Promise<void> {
const demand = this.demands.get(id);
if (!demand) return;
// This would trigger the AI generation workflow
this.api.log(`info`, `Triggering production for demand id`);
// Dispatch event for other skills to listen
this.api.emit("demand.approved", { demandId: id, demand });
}
}
export async function register(api: OpenClawSkillApi) {
const manager = new DemandManager(api);
// Register commands
api.registerCommand("demand", {
description: "需求管理命令",
subcommands: {
create: {
description: "创建新需求",
arguments: {
title: { type: "string", required: true, help: "需求标题" },
type: {
type: "enum",
required: true,
options: ["new_material", "reuse", "modification"],
help: "需求类型"
},
channel: { type: "string", required: true, help: "投放渠道" },
style: { type: "string", required: true, help: "素材风格" },
size: { type: "string", required: true, help: "素材尺寸" },
description: { type: "string", help: "需求描述" }
},
execute: async (args) => {
const id = await manager.create({
title: args.title,
type: args.type,
channel: args.channel,
style: args.style,
size: args.size,
description: args.description || ""
});
return { success: true, demandId: id, message: `需求创建成功: id` };
}
},
get: {
description: "查看需求详情",
arguments: {
id: { type: "string", required: true, help: "需求ID" }
},
execute: async (args) => {
const demand = await manager.get(args.id);
if (!demand) {
return { success: false, error: `需求未找到: args.id` };
}
return { success: true, demand };
}
},
list: {
description: "列出需求",
arguments: {
status: {
type: "enum",
options: ["draft", "pending_review", "approved", "rejected", "in_production", "completed"],
help: "按状态筛选"
}
},
execute: async (args) => {
const demands = await manager.list(args.status);
return { success: true, count: demands.length, demands };
}
},
review: {
description: "评审需求",
arguments: {
id: { type: "string", required: true, help: "需求ID" },
result: {
type: "enum",
required: true,
options: ["approved", "rejected"],
help: "评审结果"
},
comments: { type: "string", help: "评审意见" }
},
execute: async (args) => {
await manager.review(args.id, args.result, args.comments);
return { success: true, message: `需求 args.id 已"拒绝"` };
}
},
submit: {
description: "提交需求评审",
arguments: {
id: { type: "string", required: true, help: "需求ID" }
},
execute: async (args) => {
await manager.submitForReview(args.id);
return { success: true, message: `需求 args.id 已提交评审` };
}
},
"拆解": {
description: "拆解需求为生产任务",
arguments: {
id: { type: "string", required: true, help: "需求ID" },
promptTemplate: { type: "string", help: "Prompt模板" }
},
execute: async (args) => {
const result = await manager.拆解(args.id, args.promptTemplate);
return { success: true, ...result, message: `需求 args.id 拆解为 result.tasks.length 个任务` };
}
},
complete: {
description: "标记需求完成",
arguments: {
id: { type: "string", required: true, help: "需求ID" }
},
execute: async (args) => {
await manager.complete(args.id);
return { success: true, message: `需求 args.id 已完成` };
}
}
}
});
// Subscribe to events from other skills
api.on("demand.created", async (data) => {
api.log("info", `Received demand.created event: JSON.stringify(data)`);
});
api.log("info", "Demand Management skill loaded");
}
FILE:package.json
{
"name": "ad-production-demand-management",
"version": "1.0.0",
"description": "广告创意需求管理技能 - 处理需求发起、评审、拆解全流程",
"main": "index.js",
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"test": "jest"
},
"keywords": [
"openclaw",
"skill",
"demand",
"workflow",
"ad-production",
"ai"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}资源交付分发技能 - 处理素材导出、格式转换和多平台分发
---
name: "Delivery Distribution"
slug: ad-production-delivery-distribution
version: "1.0.0"
description: "资源交付分发技能 - 处理素材导出、格式转换和多平台分发"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"🚚","requires":{"bins":[]}}}
---
# Delivery Distribution - 资源交付分发
负责素材的导出、格式转换和多平台分发。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
需要交付广告素材时使用:
- 导出生成完成的素材
- 格式转换(PNG/JPEG/MP4等)
- 多平台分发(社交媒体、广告平台等)
- 素材打包下载
## Architecture
```
delivery-distribution/
├── index.ts # 主入口,处理交付分发
├── package.json # 依赖配置
└── README.md # 详细文档
```
## Core Commands
### 导出素材
```typescript
await api.executeAction('delivery-distribution.export', {
materialId: string, // 素材ID
format: string, // 目标格式
quality?: string // 质量设置
});
```
### 分发到平台
```typescript
await api.executeAction('delivery-distribution.distribute', {
materialIds: string[], // 素材ID列表
platform: string, // 目标平台
config?: Record<string, any> // 平台配置
});
```
### 打包下载
```typescript
await api.executeAction('delivery-distribution.package', {
materialIds: string[], // 素材ID列表
format: string // 打包格式(zip/rar)
});
```
## 响应事件
- `delivery-distribution.export-complete` - 导出完成
- `delivery-distribution.distributed` - 分发完成
- `delivery-distribution.package-ready` - 打包完成
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface DeliveryPackage {
id: string;
name: string;
materials: string[]; // material IDs
format: "zip" | "folder" | "direct_upload";
platforms: string[];
status: "preparing" | "ready" | "uploading" | "completed" | "failed";
downloadUrl?: string;
uploadStatus?: Record<string, "pending" | "success" | "failed">;
createdAt: Date;
completedAt?: Date;
}
class DeliveryDistribution {
private packages: Map<string, DeliveryPackage> = new Map();
private api: OpenClawSkillApi;
// Platform-specific requirements
private platformRequirements: Record<string, {
formats: string[];
maxSizeMB: number;
dimensions?: { width: number; height: number }[];
duration?: { min: number; max: number };
}> = {
"抖音": {
formats: ["mp4", "mov"],
maxSizeMB: 100,
dimensions: [
{ width: 1080, height: 1920 }, // 9:16
{ width: 720, height: 1280 }
],
duration: { min: 1, max: 60 }
},
"穿山甲": {
formats: ["mp4", "jpg", "png"],
maxSizeMB: 50,
dimensions: [
{ width: 1080, height: 1920 },
{ width: 1920, height: 1080 }
]
},
"Google Ads": {
formats: ["mp4", "jpg", "png", "gif"],
maxSizeMB: 150,
dimensions: [
{ width: 1080, height: 1080 }, // Square
{ width: 1920, height: 1080 }, // 16:9
{ width: 1080, height: 1920 } // 9:16
]
},
"Facebook": {
formats: ["mp4", "jpg", "png"],
maxSizeMB: 100,
dimensions: [
{ width: 1080, height: 1080 },
{ width: 1200, height: 628 },
{ width: 1080, height: 1350 }
]
}
};
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async createPackage(packageData: {
name: string;
materialIds: string[];
platforms: string[];
format?: DeliveryPackage["format"];
}): Promise<string> {
const id = `PKG-Date.now()-Math.random().toString(36).substr(2, 9)`;
const format = packageData.format || "zip";
const pkg: DeliveryPackage = {
id,
name: packageData.name,
materials: packageData.materialIds,
format,
platforms: packageData.platforms,
status: "preparing",
createdAt: new Date()
};
this.packages.set(id, pkg);
this.api.log(`info`, `Delivery package created: id`);
// Start preparation
await this.preparePackage(id);
return id;
}
async getPackage(id: string): Promise<DeliveryPackage | null> {
return this.packages.get(id) || null;
}
async listPackages(status?: DeliveryPackage["status"]): Promise<DeliveryPackage[]> {
const all = Array.from(this.packages.values());
if (status) {
return all.filter(p => p.status === status);
}
return all;
}
async addMaterials(packageId: string, materialIds: string[]): Promise<boolean> {
const pkg = this.packages.get(packageId);
if (!pkg) {
throw new Error(`Package not found: packageId`);
}
if (pkg.status !== "preparing") {
throw new Error(`Cannot add materials to package in status: pkg.status`);
}
pkg.materials.push(...materialIds);
pkg.updatedAt = new Date();
this.packages.set(packageId, pkg);
this.api.log(`info`, `Added materialIds.length materials to package packageId`);
return true;
}
async removeMaterial(packageId: string, materialId: string): Promise<boolean> {
const pkg = this.packages.get(packageId);
if (!pkg) {
throw new Error(`Package not found: packageId`);
}
const index = pkg.materials.indexOf(materialId);
if (index > -1) {
pkg.materials.splice(index, 1);
pkg.updatedAt = new Date();
this.packages.set(packageId, pkg);
this.api.log(`info`, `Removed material materialId from package packageId`);
}
return true;
}
async preparePackage(packageId: string): Promise<boolean> {
const pkg = this.packages.get(packageId);
if (!pkg) {
throw new Error(`Package not found: packageId`);
}
pkg.status = "preparing";
this.packages.set(packageId, pkg);
try {
// Validate materials for each platform
const validationResults = await this.validateMaterials(pkg);
// Check if any materials don't meet requirements
for (const [platform, issues] of Object.entries(validationResults)) {
if (issues.length > 0) {
this.api.log(`warn`, `Validation issues for platform: issues.join(", ")`);
}
}
// Prepare package based on format
switch (pkg.format) {
case "zip":
pkg.downloadUrl = await this.createZip(pkg);
break;
case "folder":
// Create a shared folder
pkg.downloadUrl = await this.createFolder(pkg);
break;
case "direct_upload":
// Will upload directly to platforms
break;
}
pkg.status = "ready";
pkg.updatedAt = new Date();
this.packages.set(packageId, pkg);
this.api.log(`info`, `Package prepared: packageId`);
return true;
} catch (error) {
pkg.status = "failed";
pkg.updatedAt = new Date();
this.packages.set(packageId, pkg);
this.api.log(`error`, `Package preparation failed: error`);
throw error;
}
}
async distribute(packageId: string): Promise<Record<string, "success" | "failed">> {
const pkg = this.packages.get(packageId);
if (!pkg) {
throw new Error(`Package not found: packageId`);
}
if (pkg.status !== "ready") {
throw new Error(`Package not ready for distribution: pkg.status`);
}
pkg.status = "uploading";
pkg.uploadStatus = {};
this.packages.set(packageId, pkg);
const results: Record<string, "success" | "failed"> = {};
for (const platform of pkg.platforms) {
try {
await this.uploadToPlatform(packageId, platform);
results[platform] = "success";
if (pkg.uploadStatus) {
pkg.uploadStatus[platform] = "success";
}
} catch (error) {
results[platform] = "failed";
if (pkg.uploadStatus) {
pkg.uploadStatus[platform] = "failed";
}
this.api.log(`error`, `Upload to platform failed: error`);
}
}
pkg.status = "completed";
pkg.completedAt = new Date();
pkg.updatedAt = new Date();
this.packages.set(packageId, pkg);
this.api.log(`info`, `Distribution completed for packageId: JSON.stringify(results)`);
this.api.emit("delivery.completed", { packageId, results });
return results;
}
async getPlatformRequirements(platform: string): Promise<any> {
return this.platformRequirements[platform] || null;
}
async listPlatforms(): Promise<string[]> {
return Object.keys(this.platformRequirements);
}
private async validateMaterials(pkg: DeliveryPackage): Promise<Record<string, string[]>> {
const issues: Record<string, string[]> = {};
for (const platform of pkg.platforms) {
const req = this.platformRequirements[platform];
if (!req) {
issues[platform] = [`Unknown platform: platform`];
continue;
}
// Would validate each material against platform requirements
// For now, return empty (no issues)
issues[platform] = [];
}
return issues;
}
private async createZip(pkg: DeliveryPackage): Promise<string> {
// Would create a zip file with all materials
// For now, return a mock URL
return `/tmp/delivery/pkg.id.zip`;
}
private async createFolder(pkg: DeliveryPackage): Promise<string> {
// Would create a shared folder
return `/tmp/delivery/pkg.id/`;
}
private async uploadToPlatform(packageId: string, platform: string): Promise<void> {
const pkg = this.packages.get(packageId);
if (!pkg) throw new Error(`Package not found: packageId`);
// This would integrate with platform APIs
// For now, simulate upload
this.api.log(`info`, `Uploading packageId to platform`);
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 1000));
// Random failure for demo (10% chance)
if (Math.random() < 0.1) {
throw new Error(`Upload failed: simulated error`);
}
}
async convertFormat(materialId: string, targetFormat: string): Promise<{convertedId: string; path: string}> {
// Would convert material to different format
// For now, return mock
return {
convertedId: `materialId-conv-Date.now()`,
path: `/tmp/conversions/materialId.targetFormat`
};
}
async generateManifest(packageId: string): Promise<any> {
const pkg = this.packages.get(packageId);
if (!pkg) {
throw new Error(`Package not found: packageId`);
}
return {
packageId: pkg.id,
name: pkg.name,
created: pkg.createdAt,
format: pkg.format,
platforms: pkg.platforms,
materials: pkg.materials,
status: pkg.status,
downloadUrl: pkg.downloadUrl,
uploadStatus: pkg.uploadStatus
};
}
}
export async function register(api: OpenClawSkillApi) {
const delivery = new DeliveryDistribution(api);
api.registerCommand("delivery", {
description: "资源交付分发命令",
subcommands: {
create: {
description: "创建交付包",
arguments: {
name: { type: "string", required: true, help: "包名称" },
materialIds: { type: "string[]", required: true, help: "素材ID列表" },
platforms: { type: "string[]", required: true, help: "目标平台列表" },
format: {
type: "enum",
options: ["zip", "folder", "direct_upload"],
help: "打包格式"
}
},
execute: async (args) => {
const packageId = await delivery.createPackage({
name: args.name,
materialIds: args.materialIds,
platforms: args.platforms,
format: args.format
});
return { success: true, packageId, message: `交付包已创建: packageId` };
}
},
get: {
description: "查看交付包",
arguments: {
id: { type: "string", required: true, help: "包ID" }
},
execute: async (args) => {
const pkg = await delivery.getPackage(args.id);
if (!pkg) {
return { success: false, error: `交付包未找到: args.id` };
}
return { success: true, package: pkg };
}
},
list: {
description: "列出交付包",
arguments: {
status: {
type: "enum",
options: ["preparing", "ready", "uploading", "completed", "failed"]
}
},
execute: async (args) => {
const packages = await delivery.listPackages(args.status);
return { success: true, count: packages.length, packages };
}
},
add: {
description: "向包添加素材",
arguments: {
id: { type: "string", required: true, help: "包ID" },
materialIds: { type: "string[]", required: true, help: "素材ID列表" }
},
execute: async (args) => {
await delivery.addMaterials(args.id, args.materialIds);
return { success: true, message: `已添加素材到包 args.id` };
}
},
prepare: {
description: "准备交付包",
arguments: {
id: { type: "string", required: true, help: "包ID" }
},
execute: async (args) => {
await delivery.preparePackage(args.id);
const pkg = await delivery.getPackage(args.id);
return {
success: true,
message: `包已准备就绪: args.id`,
downloadUrl: pkg?.downloadUrl
};
}
},
distribute: {
description: "分发到目标平台",
arguments: {
id: { type: "string", required: true, help: "包ID" }
},
execute: async (args) => {
const results = await delivery.distribute(args.id);
return { success: true, results, message: `分发完成: JSON.stringify(results)` };
}
},
platforms: {
description: "列出支持的平台",
execute: async () => {
const platforms = await delivery.listPlatforms();
return { success: true, platforms };
}
},
requirements: {
description: "获取平台要求",
arguments: {
platform: { type: "string", required: true, help: "平台名称" }
},
execute: async (args) => {
const req = await delivery.getPlatformRequirements(args.platform);
if (!req) {
return { success: false, error: `未知平台: args.platform` };
}
return { success: true, platform: args.platform, requirements: req };
}
},
convert: {
description: "转换素材格式",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" },
format: { type: "string", required: true, help: "目标格式 (如: mp4, png)" }
},
execute: async (args) => {
const result = await delivery.convertFormat(args.materialId, args.format);
return { success: true, ...result, message: `格式转换完成` };
}
},
manifest: {
description: "生成清单",
arguments: {
id: { type: "string", required: true, help: "包ID" }
},
execute: async (args) => {
const manifest = await delivery.generateManifest(args.id);
return { success: true, manifest };
}
}
}
});
// Listen for review completions to auto-package approved materials
api.on("review.completed", async (data) => {
const { review } = data as { review: any };
if (review.passed) {
api.log("info", `Material review.materialId approved, could auto-package`);
// Could auto-create delivery package for approved materials
}
});
api.log("info", "Delivery & Distribution skill loaded");
}
FILE:package.json
{
"name": "ad-production-delivery-distribution",
"version": "1.0.0",
"description": "资源交付分发技能 - 处理素材导出、格式转换和多平台分发",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [
"openclaw",
"skill",
"delivery",
"distribution",
"export"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}数据分析与反馈技能 - 投放效果数据采集、分析与模型迭代建议
---
name: "Analytics Feedback"
slug: ad-production-analytics-feedback
version: "1.0.0"
description: "数据分析与反馈技能 - 投放效果数据采集、分析与模型迭代建议"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"📊","requires":{"bins":[]}}}
---
# Analytics Feedback - 数据分析与反馈
负责广告投放效果的数据采集、分析和反馈,为模型优化提供建议。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
- 收集广告投放数据
- 分析投放效果
- 生成数据报告
- 提供优化建议
## Architecture
```
analytics-feedback/
├── index.ts # 主入口,数据处理和分析
├── package.json # 依赖配置
└── README.md # 详细文档
```
## Core Commands
### 采集数据
```typescript
await api.executeAction('analytics-feedback.collect', {
campaignId: string, // 广告活动ID
startTime: string, // 开始时间
endTime: string, // 结束时间
metrics?: string[] // 指标列表
});
```
### 分析数据
```typescript
await api.executeAction('analytics-feedback.analyze', {
data: any[], // 原始数据
analysisType?: string // 分析类型
});
```
### 生成报告
```typescript
await api.executeAction('analytics-feedback.report', {
analysisId: string, // 分析ID
format?: string // 报告格式
});
```
## 响应事件
- `analytics-feedback.data-collected` - 数据采集完成
- `analytics-feedback.analysis-complete` - 分析完成
- `analytics-feedback.report-ready` - 报告就绪
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface MaterialPerformance {
materialId: string;
impressions: number;
clicks: number;
conversions: number;
cost: number;
revenue: number;
ctr: number; // click-through rate
cvr: number; // conversion rate
roas: number; // return on ad spend
avgCpc: number; // average cost per click
startDate: Date;
endDate: Date;
}
interface Campaign {
id: string;
name: string;
platform: string;
materials: string[];
startDate: Date;
endDate?: Date;
budget: number;
spent: number;
status: "active" | "paused" | "completed";
}
interface Report {
id: string;
period: "daily" | "weekly" | "monthly" | "custom";
startDate: Date;
endDate: Date;
summary: {
totalImpressions: number;
totalClicks: number;
totalConversions: number;
totalCost: number;
totalRevenue: number;
avgCtr: number;
avgCvr: number;
avgRoas: number;
};
topPerformers: MaterialPerformance[];
underperformers: MaterialPerformance[];
insights: string[];
createdAt: Date;
}
class AnalyticsFeedback {
private campaigns: Map<string, Campaign> = new Map();
private performance: Map<string, MaterialPerformance> = new Map();
private reports: Map<string, Report> = new Map();
private api: OpenClawSkillApi;
// Thresholds for alerts
private readonly POOR_CTR_THRESHOLD = 0.01; // 1%
private readonly POOR_CVR_THRESHOLD = 0.005; // 0.5%
private readonly LOW_ROAS_THRESHOLD = 2.0; // < 2x return
constructor(api: OpenClawSkillApi) {
this.api = api;
}
// Data Ingestion
async ingestPerformance(data: {
materialId: string;
campaignId?: string;
impressions: number;
clicks: number;
conversions: number;
cost: number;
revenue: number;
date: Date;
}): Promise<boolean> {
const existing = this.performance.get(data.materialId) || {
materialId: data.materialId,
impressions: 0,
clicks: 0,
conversions: 0,
cost: 0,
revenue: 0,
startDate: data.date,
endDate: data.date,
};
// Aggregate (in real system, would store time-series)
const updated: MaterialPerformance = {
...existing,
impressions: existing.impressions + data.impressions,
clicks: existing.clicks + data.clicks,
conversions: existing.conversions + data.conversions,
cost: existing.cost + data.cost,
revenue: existing.revenue + data.revenue,
endDate: data.date,
};
// Calculate metrics
updated.ctr = updated.clicks / updated.impressions || 0;
updated.cvr = updated.conversions / updated.clicks || 0;
updated.roas = updated.revenue / updated.cost || 0;
updated.avgCpc = updated.cost / updated.clicks || 0;
this.performance.set(data.materialId, updated);
// Check for alerts
await this.checkPerformanceAlerts(updated);
this.api.log("debug", `Ingested performance for data.materialId`);
return true;
}
async registerCampaign(campaign: {
name: string;
platform: string;
materials: string[];
budget: number;
startDate: Date;
}): Promise<string> {
const id = `CAMP-Date.now()-Math.random().toString(36).substr(2, 9)`;
const newCampaign: Campaign = {
id,
name: campaign.name,
platform: campaign.platform,
materials: campaign.materials,
startDate: campaign.startDate,
budget: campaign.budget,
spent: 0,
status: "active",
};
this.campaigns.set(id, newCampaign);
this.api.log("info", `Campaign registered: id - campaign.name`);
return id;
}
async getPerformance(materialId: string): Promise<MaterialPerformance | null> {
return this.performance.get(materialId) || null;
}
async getCampaignPerformance(campaignId: string): Promise<MaterialPerformance[]> {
const campaign = this.campaigns.get(campaignId);
if (!campaign) {
throw new Error(`Campaign not found: campaignId`);
}
return campaign.materials
.map(mid => this.performance.get(mid))
.filter(p => p !== undefined) as MaterialPerformance[];
}
// Reporting
async generateReport(params: {
period: "daily" | "weekly" | "monthly" | "custom";
startDate: Date;
endDate: Date;
campaignIds?: string[];
includeTopN?: number;
}): Promise<Report> {
const id = `RPT-Date.now()-Math.random().toString(36).substr(2, 9)`;
// Gather all relevant performance data
let allPerformance = Array.from(this.performance.values());
// Filter by campaign if specified
if (params.campaignIds) {
const materialSet = new Set<string>();
for (const campId of params.campaignIds) {
const camp = this.campaigns.get(campId);
if (camp) {
camp.materials.forEach(mid => materialSet.add(mid));
}
}
allPerformance = allPerformance.filter(p => materialSet.has(p.materialId));
}
// Filter by date range
allPerformance = allPerformance.filter(p =>
p.startDate <= params.endDate && (!p.endDate || p.endDate >= params.startDate)
);
// Calculate summary
const summary = {
totalImpressions: allPerformance.reduce((sum, p) => sum + p.impressions, 0),
totalClicks: allPerformance.reduce((sum, p) => sum + p.clicks, 0),
totalConversions: allPerformance.reduce((sum, p) => sum + p.conversions, 0),
totalCost: allPerformance.reduce((sum, p) => sum + p.cost, 0),
totalRevenue: allPerformance.reduce((sum, p) => sum + p.revenue, 0),
avgCtr: allPerformance.reduce((sum, p) => sum + p.ctr, 0) / allPerformance.length,
avgCvr: allPerformance.reduce((sum, p) => sum + p.cvr, 0) / allPerformance.length,
avgRoas: allPerformance.reduce((sum, p) => sum + p.roas, 0) / allPerformance.length,
};
summary.avgCtr = isNaN(summary.avgCtr) ? 0 : summary.avgCtr;
summary.avgCvr = isNaN(summary.avgCvr) ? 0 : summary.avgCvr;
summary.avgRoas = isNaN(summary.avgRoas) ? 0 : summary.avgRoas;
// Sort by performance
const topPerformers = [...allPerformance]
.sort((a, b) => b.roas - a.roas)
.slice(0, params.includeTopN || 10);
const underperformers = allPerformance
.filter(p => p.roas < this.LOW_ROAS_THRESHOLD || p.ctr < this.POOR_CTR_THRESHOLD)
.sort((a, b) => a.roas - b.roas)
.slice(0, 10);
// Generate insights
const insights = this.generateInsights(allPerformance, summary);
const report: Report = {
id,
period: params.period,
startDate: params.startDate,
endDate: params.endDate,
summary,
topPerformers,
underperformers,
insights,
createdAt: new Date(),
};
this.reports.set(id, report);
this.api.log("info", `Report generated: id`);
return report;
}
async getReport(id: string): Promise<Report | null> {
return this.reports.get(id) || null;
}
async listReports(): Promise<Report[]> {
return Array.from(this.reports.values()).sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
}
// Feedback Loop
async generateFeedback(materialId: string): Promise<{
suggestions: string[];
promptImprovements: string[];
modelRecommendations: string[];
}> {
const perf = this.performance.get(materialId);
if (!perf) {
throw new Error(`No performance data for material: materialId`);
}
const suggestions: string[] = [];
const promptImprovements: string[] = [];
const modelRecommendations: string[] = [];
// Analyze CTR
if (perf.ctr < this.POOR_CTR_THRESHOLD) {
suggestions.push("点击率偏低,建议优化创意吸引力");
promptImprovements.push("增加动态元素", "使用更醒目的配色", "添加行动号召");
}
// Analyze CVR
if (perf.cvr < this.POOR_CVR_THRESHOLD) {
suggestions.push("转化率偏低,建议优化落地页或素材相关性");
promptImprovements.push("突出产品核心卖点", "添加信任要素", "明确价值主张");
}
// Analyze ROAS
if (perf.roas < this.LOW_ROAS_THRESHOLD) {
suggestions.push("ROI较低,建议调整投放策略或重新设计素材");
} else if (perf.roas > 5) {
suggestions.push("表现优异,考虑扩大投放或复用成功元素");
}
// Model recommendations based on performance
if (perf.roas > 4) {
modelRecommendations.push("当前模型表现优秀,建议保持");
} else {
modelRecommendations.push("尝试其他模型 (如Flux, SDXL)");
modelRecommendations.push("增加生成多样性");
}
// Additional data-driven suggestions
if (perf.avgCpc > 2) {
suggestions.push("CPC偏高,建议优化受众定位");
}
this.api.log("info", `Generated feedback for materialId`);
return {
suggestions,
promptImprovements,
modelRecommendations
};
}
async getMaterialInsights(materialId: string): Promise<any> {
const perf = this.performance.get(materialId);
if (!perf) {
return { error: "No performance data" };
}
const feedback = await this.generateFeedback(materialId);
return {
materialId,
performance: perf,
feedback,
benchmarks: {
avgCtr: 0.02, // Industry average 2%
avgCvr: 0.01, // Industry average 1%
avgRoas: 3.0, // Industry average 3x
},
rating: this.calculateRating(perf)
};
}
async exportCSV(reportId: string): Promise<string> {
const report = this.reports.get(reportId);
if (!report) {
throw new Error(`Report not found: reportId`);
}
// Generate CSV
const headers = ["Material ID", "Impressions", "Clicks", "CTR", "Conversions", "CVR", "Cost", "Revenue", "ROAS"];
const rows = report.topPerformers.map(p => [
p.materialId,
p.impressions.toString(),
p.clicks.toString(),
(p.ctr * 100).toFixed(2) + "%",
p.conversions.toString(),
(p.cvr * 100).toFixed(2) + "%",
p.cost.toFixed(2),
p.revenue.toFixed(2),
p.roas.toFixed(2)
]);
const csv = [headers, ...rows].map(row => row.join(",")).join("\n");
return csv;
}
// Alerts & Notifications
private async checkPerformanceAlerts(perf: MaterialPerformance): Promise<void> {
const alerts: string[] = [];
if (perf.ctr < this.POOR_CTR_THRESHOLD) {
alerts.push(`低点击率: (perf.ctr * 100).toFixed(2)% < 1%`);
}
if (perf.cvr < this.POOR_CVR_THRESHOLD) {
alerts.push(`低转化率: (perf.cvr * 100).toFixed(2)% < 0.5%`);
}
if (perf.roas < this.LOW_ROAS_THRESHOLD) {
alerts.push(`低ROI: perf.roas.toFixed(2)x < 2x`);
}
if (alerts.length > 0) {
this.api.emit("analytics.alert", {
materialId: perf.materialId,
alerts,
performance: perf
});
// Send notification if configured
if (this.api.config?.notifyOnAlerts) {
await this.sendAlertNotification(perf.materialId, alerts);
}
}
}
private async sendAlertNotification(materialId: string, alerts: string[]): Promise<void> {
// Would send via feishu_im_user_message or similar
this.api.log("warn", `Alerts for materialId: alerts.join(", ")`);
}
private generateInsights(performance: MaterialPerformance[], summary: any): string[] {
const insights: string[] = [];
if (summary.totalImpressions > 0) {
insights.push(`总计 summary.totalImpressions.toLocaleString() 次展示`);
}
if (summary.avgCtr > 0.02) {
insights.push(`平均点击率 (summary.avgCtr * 100).toFixed(2)% 高于行业基准`);
} else {
insights.push(`平均点击率 (summary.avgCtr * 100).toFixed(2)% 低于行业基准,需优化`);
}
if (summary.avgRoas > 3) {
insights.push(`平均ROI summary.avgRoas.toFixed(2)x 表现优秀`);
} else {
insights.push(`平均ROI summary.avgRoas.toFixed(2)x 有提升空间`);
}
// Top performer highlight
if (performance.length > 0) {
const top = performance.reduce((max, p) => p.roas > max.roas ? p : performance[0]);
insights.push(`最佳表现: top.materialId (ROI: top.roas.toFixed(2)x)`);
}
return insights;
}
private calculateRating(perf: MaterialPerformance): "excellent" | "good" | "average" | "poor" {
if (perf.roas >= 5 && perf.ctr >= 0.03) return "excellent";
if (perf.roas >= 3 && perf.ctr >= 0.02) return "good";
if (perf.roas >= 1.5 && perf.ctr >= 0.01) return "average";
return "poor";
}
}
export async function register(api: OpenClawSkillApi) {
const analytics = new AnalyticsFeedback(api);
api.registerCommand("analytics", {
description: "数据分析与反馈命令",
subcommands: {
ingest: {
description: "录入投放数据",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" },
campaignId: { type: "string", help: "活动ID" },
impressions: { type: "number", required: true, help: "展示数" },
clicks: { type: "number", required: true, help: "点击数" },
conversions: { type: "number", required: true, help: "转化数" },
cost: { type: "number", required: true, help: "花费" },
revenue: { type: "number", required: true, help: "收入" },
date: { type: "string", help: "日期 (ISO 8601)" }
},
execute: async (args) => {
const date = args.date ? new Date(args.date) : new Date();
await analytics.ingestPerformance({
materialId: args.materialId,
campaignId: args.campaignId,
impressions: args.impressions,
clicks: args.clicks,
conversions: args.conversions,
cost: args.cost,
revenue: args.revenue,
date
});
return { success: true, message: `数据已录入: args.materialId` };
}
},
performance: {
description: "查看素材表现",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const perf = await analytics.getPerformance(args.materialId);
if (!perf) {
return { success: false, error: `无性能数据: args.materialId` };
}
return { success: true, performance: perf };
}
},
campaign: {
description: "查看活动表现",
arguments: {
campaignId: { type: "string", required: true, help: "活动ID" }
},
execute: async (args) => {
const materials = await analytics.getCampaignPerformance(args.campaignId);
return { success: true, count: materials.length, materials };
}
},
report: {
description: "生成报告",
arguments: {
period: {
type: "enum",
required: true,
options: ["daily", "weekly", "monthly", "custom"],
help: "报告周期"
},
startDate: { type: "string", required: true, help: "开始日期 (ISO 8601)" },
endDate: { type: "string", required: true, help: "结束日期 (ISO 8601)" },
campaignIds: { type: "string[]", help: "活动ID列表" },
topN: { type: "number", help: "包含前N名" }
},
execute: async (args) => {
const report = await analytics.generateReport({
period: args.period,
startDate: new Date(args.startDate),
endDate: new Date(args.endDate),
campaignIds: args.campaignIds,
includeTopN: args.topN
});
return { success: true, reportId: report.id, report };
}
},
"feedback": {
description: "获取素材改进反馈",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const feedback = await analytics.generateFeedback(args.materialId);
return { success: true, ...feedback };
}
},
insights: {
description: "获取素材洞察",
arguments: {
materialId: { type: "string", required: true, help: "素材ID" }
},
execute: async (args) => {
const insights = await analytics.getMaterialInsights(args.materialId);
return { success: true, ...insights };
}
},
export: {
description: "导出报告CSV",
arguments: {
reportId: { type: "string", required: true, help: "报告ID" }
},
execute: async (args) => {
const csv = await analytics.exportCSV(args.reportId);
return { success: true, csv, message: "CSV导出成功" };
}
},
alerts: {
description: "查看告警",
execute: async () => {
// Would fetch recent alerts from event store
return { success: true, alerts: [], message: "告警功能待集成事件存储" };
}
}
}
});
// Listen for delivery completion to trigger data collection setup
api.on("delivery.completed", async (data) => {
const { packageId, results } = data as { packageId: string; results: any };
api.log("info", `Delivery packageId completed, setting up tracking`);
// Would create tracking campaigns for delivered materials
});
// Listen for new materials to initialize performance tracking
api.on("material.created", async (data) => {
api.log("debug", `New material created: JSON.stringify(data)`);
// Initialize performance tracking record
});
api.log("info", "Analytics & Feedback skill loaded");
}
FILE:package.json
{
"name": "ad-production-analytics-feedback",
"version": "1.0.0",
"description": "数据分析与反馈技能 - 投放效果数据采集、分析与模型迭代建议",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [
"openclaw",
"skill",
"analytics",
"feedback",
"data"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}AI生成技能 - 负责广告创意的AI生成处理
---
name: "AI Generation"
slug: ad-production-ai-generation
version: "1.0.0"
description: "AI生成技能 - 负责广告创意的AI生成处理"
changelog: "初始版本"
metadata: {"clawdbot":{"emoji":"🎨","requires":{"bins":[]}}}
---
# AI Generation - 广告创意AI生成
负责广告创意的AI生成处理,包括文案生成、图像生成、视频生成等功能。
## Setup
无需额外依赖,TypeScript编译后使用。
## When to Use
需要生成广告创意内容时使用:
- 广告文案生成
- 营销图片生成
- 视频素材制作
- 创意素材批量生成
## Architecture
```
ai-generation/
├── index.ts # 主入口,处理生成任务
└── package.json # 依赖配置
```
## Core Commands
### 创建生成任务
```typescript
await api.executeAction('ai-generation.create', {
demandId?: string, // 关联的需求ID
prompt: string, // 生成提示词
model?: string, // 使用的模型(默认flux)
params?: Record<string, any>, // 额外参数
count: number // 生成数量
});
```
### 查询生成任务
```typescript
await api.executeAction('ai-generation.get', {
taskId: string // 任务ID
});
```
### 取消生成任务
```typescript
await api.executeAction('ai-generation.cancel', {
taskId: string // 任务ID
});
```
## 响应事件
- `ai-generation.completed` - 生成完成
- `ai-generation.failed` - 生成失败
- `ai-generation.progress` - 进度更新
## Configuration
配置项(通过OpenClaw配置系统):
- `defaultModel`: 默认使用的AI模型
- `maxConcurrent`: 最大并发数
- `timeout`: 超时时间(秒)
FILE:index.ts
import { OpenClawSkillApi } from "openclaw/skill-sdk";
interface GenerationTask {
id: string;
demandId?: string;
prompt: string;
model: string;
params: Record<string, any>;
count: number;
status: "queued" | "generating" | "completed" | "failed" | "cancelled";
results?: GenerationResult[];
error?: string;
createdAt: Date;
updatedAt: Date;
}
interface GenerationResult {
materialId: string;
prompt: string;
model: string;
params: Record<string, any>;
score?: number;
status: "generated" | "selected" | "rejected";
}
class AIGeneration {
private tasks: Map<string, GenerationTask> = new Map();
private api: OpenClawSkillApi;
constructor(api: OpenClawSkillApi) {
this.api = api;
}
async create(task: {
demandId?: string;
prompt: string;
model?: string;
params?: Record<string, any>;
count: number;
}): Promise<string> {
const id = `GEN-Date.now()-Math.random().toString(36).substr(2, 9)`;
const model = task.model || this.api.config?.defaultModel || "flux";
const genTask: GenerationTask = {
id,
demandId: task.demandId,
prompt: task.prompt,
model,
params: task.params || {},
count: task.count,
status: "queued",
createdAt: new Date(),
updatedAt: new Date(),
};
this.tasks.set(id, genTask);
this.api.log(`info`, `Generation task created: id`);
// Queue for processing
await this.queueTask(id);
return id;
}
async get(id: string): Promise<GenerationTask | null> {
return this.tasks.get(id) || null;
}
async list(status?: GenerationTask["status"]): Promise<GenerationTask[]> {
const all = Array.from(this.tasks.values());
if (status) {
return all.filter(t => t.status === status);
}
return all;
}
async cancel(id: string): Promise<boolean> {
const task = this.tasks.get(id);
if (!task) {
throw new Error(`Task not found: id`);
}
if (task.status === "generating") {
task.status = "cancelled";
task.updatedAt = new Date();
this.tasks.set(id, task);
this.api.log(`info`, `Generation task cancelled: id`);
} else {
throw new Error(`Cannot cancel task in status: task.status`);
}
return true;
}
async selectResult(taskId: string, resultIndex: number): Promise<boolean> {
const task = this.tasks.get(taskId);
if (!task) {
throw new Error(`Task not found: taskId`);
}
if (!task.results || task.results.length <= resultIndex) {
throw new Error(`Result index out of range: resultIndex`);
}
// Mark selected result
for (let i = 0; i < task.results.length; i++) {
task.results[i].status = i === resultIndex ? "selected" : "rejected";
}
task.status = "completed";
task.updatedAt = new Date();
this.tasks.set(taskId, task);
// Emit event for review process
this.api.emit("generation.completed", { taskId, result: task.results[resultIndex] });
this.api.log(`info`, `Result selected for task taskId: index resultIndex`);
return true;
}
async getBestResults(taskId: string, limit: number = 5): Promise<GenerationResult[]> {
const task = this.tasks.get(taskId);
if (!task) {
throw new Error(`Task not found: taskId`);
}
if (!task.results) {
return [];
}
// Sort by score (if available)
const scored = task.results
.filter(r => r.status !== "rejected")
.map(r => ({
...r,
score: r.score || this.estimateQuality(r)
}))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
return scored;
}
async optimizePrompt(prompt: string, feedback?: string): Promise<{optimizedPrompt: string; suggestions: string[]}> {
// This would integrate with an LLM to optimize prompts
// For now, return mock optimization
const suggestions = [
"添加更多细节描述",
"指定艺术风格",
"明确光照条件",
"定义构图",
"指定质量关键词"
];
// Simple optimization: add quality keywords
const optimizedPrompt = `prompt, high quality, detailed, 8k, professional`;
return {
optimizedPrompt,
suggestions: feedback ? [feedback, ...suggestions] : suggestions
};
}
async deduplicate(taskId: string, threshold: number = 0.9): Promise<{unique: number; duplicates: number}> {
const task = this.tasks.get(taskId);
if (!task || !task.results) {
throw new Error(`Task not found or no results: taskId`);
}
// Simple deduplication based on prompt similarity
const unique: GenerationResult[] = [];
const duplicates: GenerationResult[] = [];
for (const result of task.results) {
let isDuplicate = false;
for (const existing of unique) {
if (this.calculateSimilarity(result.prompt, existing.prompt) > threshold) {
isDuplicate = true;
duplicates.push(result);
break;
}
}
if (!isDuplicate) {
unique.push(result);
}
}
// Update results status
for (const result of task.results) {
result.status = duplicates.includes(result) ? "rejected" : "generated";
}
this.api.log(`info`, `Deduplication complete for taskId: unique.length unique, duplicates.length duplicates`);
return { unique: unique.length, duplicates: duplicates.length };
}
private async queueTask(id: string): Promise<void> {
const task = this.tasks.get(id);
if (!task) return;
task.status = "generating";
this.tasks.set(id, task);
// Simulate generation (would integrate with actual AI models)
this.api.log(`info`, `Starting generation for task id`);
// Process with configured concurrency
const maxConcurrent = this.api.config?.maxConcurrent || 3;
// In real implementation, would manage a queue
// For demo, simulate completion after delay
setTimeout(async () => {
await this.simulateCompletion(id);
}, 2000);
}
private async simulateCompletion(id: string): Promise<void> {
const task = this.tasks.get(id);
if (!task) return;
// Generate mock results
task.results = [];
for (let i = 0; i < task.count; i++) {
task.results.push({
materialId: `MAT-id-i`,
prompt: task.prompt,
model: task.model,
params: task.params,
score: Math.random() * 10, // Random score 0-10
status: "generated"
});
}
task.status = "completed";
task.updatedAt = new Date();
this.tasks.set(id, task);
this.api.log(`info`, `Generation completed for task id: task.count results`);
this.api.emit("generation.batch_completed", { taskId: id, count: task.count });
}
private estimateQuality(result: GenerationResult): number {
// Very basic quality estimation
const base = result.score || Math.random() * 10;
// Boost for certain models
if (result.model === "flux") base += 1;
if (result.model === "sdxl") base += 0.5;
return Math.min(10, base);
}
private calculateSimilarity(a: string, b: string): number {
const wordsA = new Set(a.toLowerCase().split(/\s+/));
const wordsB = new Set(b.toLowerCase().split(/\s+/));
const intersection = [...wordsA].filter(w => wordsB.has(w));
const union = new Set([...wordsA, ...wordsB]);
return intersection.length / union.size;
}
}
export async function register(api: OpenClawSkillApi) {
const generator = new AIGeneration(api);
api.registerCommand("ai", {
description: "AI资源生成命令",
subcommands: {
generate: {
description: "创建生成任务",
arguments: {
prompt: { type: "string", required: true, help: "生成提示词" },
model: {
type: "enum",
options: ["flux", "sdxl", "sd3", "dalle3", "midjourney"],
help: "AI模型"
},
count: { type: "number", help: "生成数量 (默认4)", default: 4 },
params: { type: "object", help: "额外参数 (JSON)" }
},
execute: async (args) => {
const id = await generator.create({
prompt: args.prompt,
model: args.model,
count: args.count || 4,
params: args.params
});
return { success: true, taskId: id, message: `生成任务已创建: id` };
}
},
status: {
description: "查看任务状态",
arguments: {
id: { type: "string", required: true, help: "任务ID" }
},
execute: async (args) => {
const task = await generator.get(args.id);
if (!task) {
return { success: false, error: `任务未找到: args.id` };
}
return { success: true, task };
}
},
list: {
description: "列出任务",
arguments: {
status: {
type: "enum",
options: ["queued", "generating", "completed", "failed", "cancelled"]
}
},
execute: async (args) => {
const tasks = await generator.list(args.status);
return { success: true, count: tasks.length, tasks };
}
},
cancel: {
description: "取消任务",
arguments: {
id: { type: "string", required: true, help: "任务ID" }
},
execute: async (args) => {
await generator.cancel(args.id);
return { success: true, message: `任务已取消: args.id` };
}
},
select: {
description: "选择生成结果",
arguments: {
taskId: { type: "string", required: true, help: "任务ID" },
index: { type: "number", required: true, help: "结果索引" }
},
execute: async (args) => {
await generator.selectResult(args.taskId, args.index);
return { success: true, message: `已选择结果 args.index` };
}
},
best: {
description: "获取最佳结果",
arguments: {
taskId: { type: "string", required: true, help: "任务ID" },
limit: { type: "number", help: "数量 (默认5)" }
},
execute: async (args) => {
const results = await generator.getBestResults(args.taskId, args.limit);
return { success: true, count: results.length, results };
}
},
optimize: {
description: "优化提示词",
arguments: {
prompt: { type: "string", required: true, help: "原始提示词" },
feedback: { type: "string", help: "反馈意见" }
},
execute: async (args) => {
const result = await generator.optimizePrompt(args.prompt, args.feedback);
return { success: true, ...result };
}
},
dedup: {
description: "去重",
arguments: {
taskId: { type: "string", required: true, help: "任务ID" },
threshold: { type: "number", help: "相似度阈值 (默认0.9)" }
},
execute: async (args) => {
const result = await generator.deduplicate(args.taskId, args.threshold);
return { success: true, ...result, message: `去重完成: result.unique 个唯一, result.duplicates 个重复` };
}
}
}
});
// Listen for demand approval to auto-generate
api.on("demand.approved", async (data) => {
const { demandId, demand } = data as { demandId: string; demand: any };
api.log("info", `Auto-starting generation for approved demand: demandId`);
// Would create generation task from demand
// await generator.create({
// demandId,
// prompt: demand.description,
// count: 10
// });
});
api.log("info", "AI Generation skill loaded");
}
FILE:package.json
{
"name": "ad-production-ai-generation",
"version": "1.0.0",
"description": "AI生成技能 - 负责广告创意的AI生成处理",
"main": "index.js",
"scripts": {
"build": "tsc"
},
"keywords": [
"openclaw",
"skill",
"ai",
"generation",
"ad-production"
],
"author": "Your Team",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0"
}
}