commit 2301497ef15623ec36f852c46a413eb2c02e8641 Author: zlei9 Date: Sun Mar 29 09:47:03 2026 +0800 Initial commit with translated description diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..f50e242 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,85 @@ +--- +name: ai-ppt-generator +description: "使用百度AI生成PPT。基于内容的智能模板选择。" +metadata: { "openclaw": { "emoji": "📑", "requires": { "bins": ["python3"], "env":["BAIDU_API_KEY"]},"primaryEnv":"BAIDU_API_KEY" } } +--- + +# AI PPT Generator + +Generate PPT using Baidu AI with intelligent template selection. + +## Smart Workflow +1. **User provides PPT topic** +2. **Agent asks**: "Want to choose a template style?" +3. **If yes** → Show styles from `ppt_theme_list.py` → User picks → Use `generate_ppt.py` with chosen `tpl_id` and real `style_id` +4. **If no** → Use `random_ppt_theme.py` (auto-selects appropriate template based on topic content) + +## Intelligent Template Selection +`random_ppt_theme.py` analyzes the topic and suggests appropriate template: +- **Business topics** → 企业商务 style +- **Technology topics** → 未来科技 style +- **Education topics** → 卡通手绘 style +- **Creative topics** → 创意趣味 style +- **Cultural topics** → 中国风 or 文化艺术 style +- **Year-end reports** → 年终总结 style +- **Minimalist design** → 扁平简约 style +- **Artistic content** → 文艺清新 style + +## Scripts +- `scripts/ppt_theme_list.py` - List all available templates with style_id and tpl_id +- `scripts/random_ppt_theme.py` - Smart template selection + generate PPT +- `scripts/generate_ppt.py` - Generate PPT with specific template (uses real style_id and tpl_id from API) + +## Key Features +- **Smart categorization**: Analyzes topic content to suggest appropriate style +- **Fallback logic**: If template not found, automatically uses random selection +- **Complete parameters**: Properly passes both style_id and tpl_id to API + +## Usage Examples +```bash +# List all templates with IDs +python3 scripts/ppt_theme_list.py + +# Smart automatic selection (recommended for most users) +python3 scripts/random_ppt_theme.py --query "人工智能发展趋势报告" + +# Specific template with proper style_id +python3 scripts/generate_ppt.py --query "儿童英语课件" --tpl_id 106 + +# Specific template with auto-suggested category +python3 scripts/random_ppt_theme.py --query "企业年度总结" --category "企业商务" +``` + +## Agent Steps +1. Get PPT topic from user +2. Ask: "Want to choose a template style?" +3. **If user says YES**: + - Run `ppt_theme_list.py` to show available templates + - User selects a template (note the tpl_id) + - Run `generate_ppt.py --query "TOPIC" --tpl_id ID` +4. **If user says NO**: + - Run `random_ppt_theme.py --query "TOPIC"` + - Script will auto-select appropriate template based on topic +5. Set timeout to 300 seconds (PPT generation takes 2-5 minutes) +6. Monitor output, wait for `is_end: true` to get final PPT URL + +## Output Examples +**During generation:** +```json +{"status": "PPT生成中", "run_time": 45} +``` + +**Final result:** +```json +{ + "status": "PPT导出结束", + "is_end": true, + "data": {"ppt_url": "https://image0.bj.bcebos.com/...ppt"} +} +``` + +## Technical Notes +- **API integration**: Fetches real style_id from Baidu API for each template +- **Error handling**: If template not found, falls back to random selection +- **Timeout**: Generation takes 2-5 minutes, set sufficient timeout +- **Streaming**: Uses streaming API, wait for `is_end: true` before considering complete \ No newline at end of file diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..c58276d --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn7akgt520t01vgs2tzx7yk6m180kt26", + "slug": "ai-ppt-generator", + "version": "1.1.4", + "publishedAt": 1773656502997 +} \ No newline at end of file diff --git a/scripts/generate_ppt.py b/scripts/generate_ppt.py new file mode 100644 index 0000000..2d3ed58 --- /dev/null +++ b/scripts/generate_ppt.py @@ -0,0 +1,148 @@ +import os +import random +import sys +import time + +import requests +import json +import argparse + +URL_PREFIX = "https://qianfan.baidubce.com/v2/tools/ai_ppt/" + + +class Style: + def __init__(self, style_id, tpl_id): + self.style_id = style_id + self.tpl_id = tpl_id + + +class Outline: + def __init__(self, chat_id, query_id, title, outline): + self.chat_id = chat_id + self.query_id = query_id + self.title = title + self.outline = outline + + +def get_ppt_theme(api_key: str): + """Get a random PPT theme""" + headers = { + "Authorization": "Bearer %s" % api_key, + } + response = requests.post(URL_PREFIX + "get_ppt_theme", headers=headers) + result = response.json() + if "errno" in result and result["errno"] != 0: + raise RuntimeError(result["errmsg"]) + + style_index = random.randint(0, len(result["data"]["ppt_themes"]) - 1) + theme = result["data"]["ppt_themes"][style_index] + return Style(style_id=theme["style_id"], tpl_id=theme["tpl_id"]) + + +def ppt_outline_generate(api_key: str, query: str): + """Generate PPT outline""" + headers = { + "Authorization": "Bearer %s" % api_key, + "X-Appbuilder-From": "openclaw", + "Content-Type": "application/json" + } + headers.setdefault('Accept', 'text/event-stream') + headers.setdefault('Cache-Control', 'no-cache') + headers.setdefault('Connection', 'keep-alive') + params = { + "query": query, + } + title = "" + outline = "" + chat_id = "" + query_id = "" + with requests.post(URL_PREFIX + "generate_outline", headers=headers, json=params, stream=True) as response: + for line in response.iter_lines(): + line = line.decode('utf-8') + if line and line.startswith("data:"): + data_str = line[5:].strip() + delta = json.loads(data_str) + if not title: + title = delta["title"] + chat_id = delta["chat_id"] + query_id = delta["query_id"] + outline += delta["outline"] + + return Outline(chat_id=chat_id, query_id=query_id, title=title, outline=outline) + + +def ppt_generate(api_key: str, query: str, style_id: int = 0, tpl_id: int = None, web_content: str = None): + """Generate PPT - simple version""" + headers = { + "Authorization": "Bearer %s" % api_key, + "Content-Type": "application/json", + "X-Appbuilder-From": "openclaw", + } + + # Get theme + if tpl_id is None: + # Random theme + style = get_ppt_theme(api_key) + style_id = style.style_id + tpl_id = style.tpl_id + print(f"Using random template (tpl_id: {tpl_id})", file=sys.stderr) + else: + # Specific theme - use provided style_id (default 0) + print(f"Using template tpl_id: {tpl_id}, style_id: {style_id}", file=sys.stderr) + + # Generate outline + outline = ppt_outline_generate(api_key, query) + + # Generate PPT + headers.setdefault('Accept', 'text/event-stream') + headers.setdefault('Cache-Control', 'no-cache') + headers.setdefault('Connection', 'keep-alive') + params = { + "query_id": int(outline.query_id), + "chat_id": int(outline.chat_id), + "query": query, + "outline": outline.outline, + "title": outline.title, + "style_id": style_id, + "tpl_id": tpl_id, + "web_content": web_content, + "enable_save_bos": True, + } + with requests.post(URL_PREFIX + "generate_ppt_by_outline", headers=headers, json=params, stream=True) as response: + if response.status_code != 200: + print(f"request failed, status code is {response.status_code}, error message is {response.text}") + return [] + for line in response.iter_lines(): + line = line.decode('utf-8') + if line and line.startswith("data:"): + data_str = line[5:].strip() + yield json.loads(data_str) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate PPT") + parser.add_argument("--query", "-q", type=str, required=True, help="PPT topic") + parser.add_argument("--style_id", "-si", type=int, default=0, help="Style ID (default: 0)") + parser.add_argument("--tpl_id", "-tp", type=int, help="Template ID (optional)") + parser.add_argument("--web_content", "-wc", type=str, default=None, help="Web content") + args = parser.parse_args() + + api_key = os.getenv("BAIDU_API_KEY") + if not api_key: + print("Error: BAIDU_API_KEY must be set in environment.") + sys.exit(1) + + try: + start_time = int(time.time()) + results = ppt_generate(api_key, args.query, args.style_id, args.tpl_id, args.web_content) + + for result in results: + if "is_end" in result and result["is_end"]: + print(json.dumps(result, ensure_ascii=False, indent=2)) + else: + end_time = int(time.time()) + print(json.dumps({"status": result["status"], "run_time": end_time - start_time})) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) \ No newline at end of file diff --git a/scripts/ppt_theme_list.py b/scripts/ppt_theme_list.py new file mode 100644 index 0000000..05f8b9f --- /dev/null +++ b/scripts/ppt_theme_list.py @@ -0,0 +1,43 @@ +import os +import sys +import requests +import json + + +def ppt_theme_list(api_key: str): + url = "https://qianfan.baidubce.com/v2/tools/ai_ppt/get_ppt_theme" + headers = { + "Authorization": "Bearer %s" % api_key, + "X-Appbuilder-From": "openclaw", + } + response = requests.post(url, headers=headers) + result = response.json() + if "errno" in result and result["errno"] != 0: + raise RuntimeError(result["errmsg"]) + themes = [] + count = 0 + for theme in result["data"]["ppt_themes"]: + count += 1 + if count > 100: + break + themes.append({ + "style_name_list": theme["style_name_list"], + "style_id": theme["style_id"], + "tpl_id": theme["tpl_id"], + }) + return themes + + +if __name__ == "__main__": + api_key = os.getenv("BAIDU_API_KEY") + if not api_key: + print("Error: BAIDU_API_KEY must be set in environment.") + sys.exit(1) + try: + results = ppt_theme_list(api_key) + print(json.dumps(results, ensure_ascii=False, indent=2)) + except Exception as e: + exc_type, exc_value, exc_traceback = sys.exc_info() + print(f"error type:{exc_type}") + print(f"error message:{exc_value}") + sys.exit(1) \ No newline at end of file diff --git a/scripts/random_ppt_theme.py b/scripts/random_ppt_theme.py new file mode 100644 index 0000000..b3c47df --- /dev/null +++ b/scripts/random_ppt_theme.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python3 +""" +Random PPT Theme Selector +If user doesn't select a PPT template, this script will randomly select one +from the available templates and generate PPT. +""" + +import os +import sys +import json +import random +import argparse +import subprocess +import time +def get_available_themes(): + """Get available PPT themes""" + try: + api_key = os.getenv("BAIDU_API_KEY") + if not api_key: + print("Error: BAIDU_API_KEY environment variable not set", file=sys.stderr) + return [] + + # Import the function from ppt_theme_list.py + script_dir = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, script_dir) + + from ppt_theme_list import ppt_theme_list as get_themes + themes = get_themes(api_key) + return themes + except Exception as e: + print(f"Error getting themes: {e}", file=sys.stderr) + return [] + + + +def categorize_themes(themes): + """Categorize themes by style for better random selection""" + categorized = { + "企业商务": [], + "文艺清新": [], + "卡通手绘": [], + "扁平简约": [], + "中国风": [], + "年终总结": [], + "创意趣味": [], + "文化艺术": [], + "未来科技": [], + "默认": [] + } + + for theme in themes: + style_names = theme.get("style_name_list", []) + if not style_names: + categorized["默认"].append(theme) + continue + + added = False + for style_name in style_names: + if style_name in categorized: + categorized[style_name].append(theme) + added = True + break + + if not added: + categorized["默认"].append(theme) + + return categorized + + +def select_random_theme_by_category(categorized_themes, preferred_category=None): + """Select a random theme, optionally preferring a specific category""" + # If preferred category specified and has themes, use it + if preferred_category and preferred_category in categorized_themes: + if categorized_themes[preferred_category]: + return random.choice(categorized_themes[preferred_category]) + + # Otherwise, select from all non-empty categories + available_categories = [] + for category, themes in categorized_themes.items(): + if themes: + available_categories.append(category) + + if not available_categories: + return None + + # Weighted random selection: prefer non-default categories + weights = [] + for category in available_categories: + if category == "默认": + weights.append(0.5) # Lower weight for default + else: + weights.append(2.0) # Higher weight for specific styles + + # Normalize weights + total_weight = sum(weights) + weights = [w/total_weight for w in weights] + + selected_category = random.choices(available_categories, weights=weights, k=1)[0] + return random.choice(categorized_themes[selected_category]) + + +def suggest_category_by_query(query): + """Suggest template category based on query keywords - enhanced version""" + query_lower = query.lower() + + # Comprehensive keyword mapping with priority order + keyword_mapping = [ + # Business & Corporate (highest priority for formal content) + ("企业商务", [ + "企业", "公司", "商务", "商业", "商务", "商业计划", "商业报告", + "营销", "市场", "销售", "财务", "会计", "审计", "投资", "融资", + "战略", "管理", "运营", "人力资源", "hr", "董事会", "股东", + "年报", "季报", "财报", "业绩", "kpi", "okr", "商业计划书", + "提案", "策划", "方案", "报告", "总结", "规划", "计划" + ]), + + # Technology & Future Tech + ("未来科技", [ + "未来", "科技", "人工智能", "ai", "机器学习", "深度学习", + "大数据", "云计算", "区块链", "物联网", "iot", "5g", "6g", + "量子计算", "机器人", "自动化", "智能制造", "智慧城市", + "虚拟现实", "vr", "增强现实", "ar", "元宇宙", "数字孪生", + "芯片", "半导体", "集成电路", "电子", "通信", "网络", + "网络安全", "信息安全", "数字化", "数字化转型", + "科幻", "高科技", "前沿科技", "科技创新", "技术" + ]), + + # Education & Children + ("卡通手绘", [ + "卡通", "动画", "动漫", "儿童", "幼儿", "小学生", "中学生", + "教育", "教学", "课件", "教案", "学习", "培训", "教程", + "趣味", "有趣", "可爱", "活泼", "生动", "绘本", "漫画", + "手绘", "插画", "图画", "图形", "游戏", "玩乐", "娱乐" + ]), + + # Year-end & Summary + ("年终总结", [ + "年终", "年度", "季度", "月度", "周报", "日报", + "总结", "回顾", "汇报", "述职", "考核", "评估", + "成果", "成绩", "业绩", "绩效", "目标", "完成", + "工作汇报", "工作总结", "年度报告", "季度报告" + ]), + + # Minimalist & Modern Design + ("扁平简约", [ + "简约", "简洁", "简单", "极简", "现代", "当代", + "设计", "视觉", "ui", "ux", "用户体验", "用户界面", + "科技感", "数字感", "数据", "图表", "图形", "信息图", + "分析", "统计", "报表", "dashboard", "仪表板", + "互联网", "web", "移动", "app", "应用", "软件" + ]), + + # Chinese Traditional + ("中国风", [ + "中国", "中华", "传统", "古典", "古风", "古代", + "文化", "文明", "历史", "国学", "东方", "水墨", + "书法", "国画", "诗词", "古文", "经典", "传统节日", + "春节", "中秋", "端午", "节气", "风水", "易经", + "儒", "道", "佛", "禅", "茶道", "瓷器", "丝绸" + ]), + + # Cultural & Artistic + ("文化艺术", [ + "文化", "艺术", "文艺", "美学", "审美", "创意", + "创作", "作品", "展览", "博物馆", "美术馆", "画廊", + "音乐", "舞蹈", "戏剧", "戏曲", "电影", "影视", + "摄影", "绘画", "雕塑", "建筑", "设计", "时尚", + "文学", "诗歌", "小说", "散文", "哲学", "思想" + ]), + + # Artistic & Fresh + ("文艺清新", [ + "文艺", "清新", "小清新", "治愈", "温暖", "温柔", + "浪漫", "唯美", "优雅", "精致", "细腻", "柔和", + "自然", "生态", "环保", "绿色", "植物", "花卉", + "风景", "旅行", "游记", "生活", "日常", "情感" + ]), + + # Creative & Fun + ("创意趣味", [ + "创意", "创新", "创造", "发明", "新奇", "新颖", + "独特", "个性", "特色", "趣味", "有趣", "好玩", + "幽默", "搞笑", "笑话", "娱乐", "休闲", "放松", + "脑洞", "想象力", "灵感", "点子", "想法", "概念" + ]), + + # Academic & Research + ("默认", [ + "研究", "学术", "科学", "论文", "课题", "项目", + "实验", "调查", "分析", "理论", "方法", "技术", + "医学", "健康", "医疗", "生物", "化学", "物理", + "数学", "工程", "建筑", "法律", "政治", "经济", + "社会", "心理", "教育", "学习", "知识", "信息" + ]) + ] + + # Check each category with its keywords + for category, keywords in keyword_mapping: + for keyword in keywords: + if keyword in query_lower: + return category + + # If no match found, analyze query length and content + words = query_lower.split() + if len(words) <= 3: + # Short query, likely specific - use "默认" or tech-related + if any(word in query_lower for word in ["ai", "vr", "ar", "iot", "5g", "tech"]): + return "未来科技" + return "默认" + else: + # Longer query, analyze word frequency + word_counts = {} + for word in words: + if len(word) > 1: # Ignore single characters + word_counts[word] = word_counts.get(word, 0) + 1 + + # Check for business indicators + business_words = ["报告", "总结", "计划", "方案", "业绩", "销售", "市场"] + if any(word in word_counts for word in business_words): + return "企业商务" + + # Check for tech indicators + tech_words = ["技术", "科技", "数据", "数字", "智能", "系统"] + if any(word in word_counts for word in tech_words): + return "未来科技" + + # Default fallback + return "默认" + + +def generate_ppt_with_random_theme(query, preferred_category=None): + """Generate PPT with randomly selected theme""" + # Get available themes + themes = get_available_themes() + if not themes: + print("Error: No available themes found", file=sys.stderr) + return False + + # Categorize themes + categorized = categorize_themes(themes) + + # Select random theme + selected_theme = select_random_theme_by_category(categorized, preferred_category) + if not selected_theme: + print("Error: Could not select a theme", file=sys.stderr) + return False + + style_id = selected_theme.get("style_id", 0) + tpl_id = selected_theme.get("tpl_id") + style_names = selected_theme.get("style_name_list", ["默认"]) + + print(f"Selected template: {style_names[0]} (tpl_id: {tpl_id})", file=sys.stderr) + + # Generate PPT + script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "generate_ppt.py") + + try: + # Run generate_ppt.py with the selected theme + cmd = [ + sys.executable, script_path, + "--query", query, + "--tpl_id", str(tpl_id), + "--style_id", str(style_id) + ] + + start_time = int(time.time()) + process = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + universal_newlines=True + ) + + # Stream output + for line in process.stdout: + line = line.strip() + if line: + try: + data = json.loads(line) + if "is_end" in data and data["is_end"]: + print(json.dumps(data, ensure_ascii=False)) + else: + end_time = int(time.time()) + print(json.dumps({"status": data.get("status", "生成中"), "run_time": end_time - start_time}, ensure_ascii=False)) + except json.JSONDecodeError: + # Just print non-JSON output + print(line) + + process.wait() + return process.returncode == 0 + + except Exception as e: + print(f"Error generating PPT: {e}", file=sys.stderr) + return False + + +def main(): + parser = argparse.ArgumentParser(description="Generate PPT with random theme selection") + parser.add_argument("--query", "-q", type=str, required=True, help="PPT主题/内容") + parser.add_argument("--category", "-c", type=str, help="Preferred category (企业商务/文艺清新/卡通手绘/扁平简约/中国风/年终总结/创意趣味/文化艺术/未来科技)") + + args = parser.parse_args() + + # Determine preferred category + preferred_category = args.category + if not preferred_category: + preferred_category = suggest_category_by_query(args.query) + if preferred_category: + print(f"Auto-suggested category: {preferred_category}", file=sys.stderr) + + # Generate PPT + success = generate_ppt_with_random_theme(args.query, preferred_category) + + if not success: + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file