Skill: ijevin/smart-ppt-generator (version 1.0.1)

--- name: smart-ppt-generator description: "One-click AI-powered PPT generator. Smart style detection, auto design layout, preview confirmation, exports to PPTX. No API key needed!" metadata: openclaw: emoji: "📊" requires: bins: ["node", "bash", "puppeteer", "pptxgenjs"] env: [] skills: ["frontend-design", "canvas-design"] --- # Smart PPT Generator 📊 AI-powered PowerPoint generator - create professional presentations with one command! ## Features - ✅ **No API needed** - runs locally, no paid services - Smart style detection (Tech/Business/Education/Marketing/Creative+) - Auto design layout using professional design skills - Preview confirmation - see before you download - Dynamic color schemes - 10 styles × 5 color palettes - High-end mode - Canvas design for artistic results - Online color search - auto-refresh after 5+ feedback - One-click download via file hosting ## Dependencies ### Required Tools | Tool | Install | Purpose | |------|---------|---------| | Node.js | pre-installed | Runtime | | puppeteer | `npm install puppeteer` | Screenshot capture | | pptxgenjs | `npm install pptxgenjs` | PPTX generation | ### Required Skills | Skill | Purpose | |-------|---------| | frontend-design | Normal mode layout | | canvas-design | High-end mode design | ### Required Services | Service | Purpose | Deploy | |---------|---------|--------| | FileBrowser | File hosting | Docker :8080 | ## Setup ```bash # Install Node dependencies cd /tmp && npm install puppeteer pptxgenjs # Deploy FileBrowser docker run -d --name filebrowser \ -v /path/to/share:/srv \ -p 127.0.0.1:8080:80 \ filebrowser/filebrowser:latest # Configure Nginx reverse proxy + HTTPS ``` ## Usage ### Command Line ```bash bash scripts/generate-ppt.sh --topic "AI Technology" --pages 5 # Parameters # --topic: Presentation topic (required) # --pages: Number of slides (default: 5) # --style: Style (tech/corporate/comic/marketing/etc) # --reset: Force refresh color scheme ``` ### In AI Chat ``` User: "Make me a PPT about AI" AI: 1. Analyze → tech style, normal mode 2. Call frontend-design for layout 3. Generate preview → send 3 images 4. User says "looks good" 5. Generate PPTX → upload → download link ``` ## Workflow ``` 1. Receive request → detect style 2. Determine mode: - Normal → frontend-design skill - High-end → canvas-design skill 3. Generate preview → 3 images to user 4. Wait for confirmation 5. Generate full PPTX 6. Upload to FileBrowser 7. Return download link ``` ## Color System - 10 preset styles, 5 color palettes each - Online search triggers after 5+ modifications - Use `--reset` to force refresh ## Output - Preview: 3 PNG images (sent separately) - Final: PPTX file - Download: https://your-domain.com/public/xxx.pptx ## Troubleshooting **puppeteer not found?** ```bash ls /tmp/node_modules/puppeteer # Reinstall: npm install puppeteer ``` **Can't download?** ```bash docker ps | grep filebrowser # Check public directory permissions ``` **Don't like the colors?** ```bash # Use --reset flag or modify 5+ times ```

path: _meta.json
content: {
  "owner": "openclaw",
  "slug": "ppt-generator",
  "displayName": "PPT Generator - 智能演示文稿生成器",
  "latest": {
    "version": "1.0.0",
    "publishedAt": 1772609818000,
    "commit": "initial"
  },
  "description": "一键生成专业演示文稿！智能识别风格（科技/商务/教育等），自动调用设计技能布局，预览确认后生成PPTX文件。无需手动设计，轻松做出高质量PPT！",
  "keywords": ["ppt", "powerpoint", "presentation", "slides", "generator", "design", "office"],
  "tags": ["productivity", "design", "office", "automation"],
  "features": [
    "✅ 无需API：纯本地计算，不依赖任何付费服务",
    "智能风格识别：科技/商务/教育/营销/创意等10+种风格",
    "自动设计布局：调用专业设计技能",
    "预览确认机制：生成前先看效果",
    "动态配色方案：10种风格×5套配色",
    "高级模式：Canvas设计，艺术级效果",
    "联网配色搜索：用户反馈后自动更新",
    "一键下载：自动上传到文件托管服务"
  ],
  "requirements": {
    "node": ">=18",
    "skills": ["frontend-design", "canvas-design"],
    "tools": ["puppeteer", "pptxgenjs"],
    "services": ["filebrowser"]
  },
  "usage": "说'帮我做个XX的PPT'即可自动生成"
}


path: scripts/canvas-generator.js
content: #!/usr/bin/env node
/**
 * Canvas Design Generator - 高端PPT图片生成
 * 使用Canvas API生成艺术级幻灯片图片
 */

const fs = require('fs');
const path = require('path');

const OUTPUT_DIR = '/root/.openclaw/workspace/data/canvas-output';

/**
 * 生成艺术级幻灯片图片
 */
function generateArtSlide(options) {
    const { title, subtitle, style, pageNum, totalPages } = options;
    
    const canvas = {
        width: 1280,
        height: 720,
        ctx: null,
        
        init() {
            // 创建一个临时的HTML来渲染canvas
            return this;
        },
        
        render() {
            const styles = this.getStyleConfig(style);
            
            const html = this.generateHTML(title, subtitle, styles, pageNum, totalPages);
            return html;
        },
        
        getStyleConfig(styleName) {
            const configs = {
                'corporate': {
                    bg: '#0f172a',
                    accent: '#3b82f6',
                    secondary: '#1e293b',
                    text: '#f8fafc',
                    pattern: 'grid'
                },
                'tech': {
                    bg: '#0a0a0f',
                    accent: '#00ffff',
                    secondary: '#1a0a2e',
                    text: '#e0e0e0',
                    pattern: 'circuit'
                },
                'luxury': {
                    bg: '#1a1a1a',
                    accent: '#d4af37',
                    secondary: '#2d2d2d',
                    text: '#f5f5f5',
                    pattern: 'geometric'
                },
                'nature': {
                    bg: '#f0f4e8',
                    accent: '#4a7c59',
                    secondary: '#d4e4d1',
                    text: '#2d4a3e',
                    pattern: 'organic'
                }
            };
            
            return configs[styleName] || configs.corporate;
        },
        
        generateHTML(title, subtitle, style, pageNum, totalPages) {
            return `
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;700&family=Playfair+Display:wght@400;700&display=swap');
        
        * { margin: 0; padding: 0; box-sizing: border-box; }
        
        body {
            width: 1280px;
            height: 720px;
            background: ${style.bg};
            font-family: 'Noto Sans SC', 'Playfair Display', serif;
            color: ${style.text};
            overflow: hidden;
            position: relative;
        }
        
        /* 背景图案 */
        .pattern {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            opacity: 0.1;
            background-image: 
                linear-gradient(${style.accent} 1px, transparent 1px),
                linear-gradient(90deg, ${style.accent} 1px, transparent 1px);
            background-size: 50px 50px;
        }
        
        /* 装饰元素 */
        .accent-line {
            position: absolute;
            top: 60px;
            left: 60px;
            width: 200px;
            height: 4px;
            background: ${style.accent};
        }
        
        .accent-dot {
            position: absolute;
            top: 80px;
            left: 270px;
            width: 12px;
            height: 12px;
            background: ${style.accent};
            border-radius: 50%;
        }
        
        /* 主标题 */
        .title {
            position: absolute;
            top: 140px;
            left: 60px;
            right: 60px;
            font-size: 64px;
            font-weight: 700;
            letter-spacing: -1px;
            line-height: 1.2;
        }
        
        /* 副标题 */
        .subtitle {
            position: absolute;
            top: 340px;
            left: 60px;
            right: 60px;
            font-size: 28px;
            font-weight: 300;
            opacity: 0.7;
            letter-spacing: 2px;
        }
        
        /* 底部信息 */
        .footer {
            position: absolute;
            bottom: 40px;
            left: 60px;
            right: 60px;
            display: flex;
            justify-content: space-between;
            font-size: 14px;
            opacity: 0.5;
            letter-spacing: 1px;
        }
        
        /* 装饰角 */
        .corner {
            position: absolute;
            width: 80px;
            height: 80px;
            border: 2px solid ${style.accent};
        }
        .corner-tl { top: 30px; left: 30px; border-right: none; border-bottom: none; }
        .corner-tr { top: 30px; right: 30px; border-left: none; border-bottom: none; }
        .corner-bl { bottom: 30px; left: 30px; border-right: none; border-top: none; }
        .corner-br { bottom: 30px; right: 30px; border-left: none; border-top: none; }
        
        /* 页码 */
        .page-num {
            position: absolute;
            bottom: 40px;
            right: 60px;
            font-size: 14px;
            color: ${style.accent};
        }
    </style>
</head>
<body>
    <div class="pattern"></div>
    <div class="accent-line"></div>
    <div class="accent-dot"></div>
    
    <div class="corner corner-tl"></div>
    <div class="corner corner-tr"></div>
    <div class="corner corner-bl"></div>
    <div class="corner corner-br"></div>
    
    <div class="title">${title}</div>
    <div class="subtitle">${subtitle || ''}</div>
    
    <div class="footer">
        <span>PRESENTATION</span>
        <span>${new Date().getFullYear()}</span>
    </div>
    
    <div class="page-num">${pageNum} / ${totalPages}</div>
</body>
</html>
`;
        }
    };
    
    return canvas.render();
}

// 导出
module.exports = { generateArtSlide };

// 如果直接运行
if (require.main === module) {
    const args = process.argv.slice(2);
    let title = 'Slide Title';
    let subtitle = '';
    let style = 'corporate';
    let pageNum = 1;
    let totalPages = 5;
    
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '--title' && args[i+1]) title = args[++i];
        if (args[i] === '--subtitle' && args[i+1]) subtitle = args[++i];
        if (args[i] === '--style' && args[i+1]) style = args[++i];
        if (args[i] === '--page' && args[i+1]) pageNum = parseInt(args[++i]);
        if (args[i] === '--total' && args[i+1]) totalPages = parseInt(args[++i]);
    }
    
    const gen = new (require('./canvas-generator'))();
    const html = generateArtSlide({ title, subtitle, style, pageNum, totalPages });
    
    const outputPath = path.join(OUTPUT_DIR, `art_slide_${pageNum}.html`);
    fs.writeFileSync(outputPath, html);
    console.log(`Generated: ${outputPath}`);
}


path: scripts/generate-ppt.js
content: #!/usr/bin/env node
const fs = require('fs');
const path = require('path');

const PUBLIC_DIR = '/root/.openclaw/filebrowser-public';
const OUTPUT_DIR = '/root/.openclaw/workspace/data/canvas-output';
const STATE_FILE = '/root/.openclaw/workspace/data/ppt-generator-state.json';

const STYLE_QUERIES = {
    tech: ['tech presentation color scheme', 'modern AI ppt colors'],
    corporate: ['business corporate ppt color', 'professional presentation palette'],
    comic: ['creative fun ppt colors', 'bright colorful design'],
    marketing: ['marketing ppt colors', 'bold vibrant scheme'],
    education: ['education ppt colors', 'academic palette'],
    luxury: ['luxury elegant colors', 'gold black scheme'],
    chinese: ['chinese traditional colors', 'oriental palette'],
    nature: ['nature eco colors', 'green earth palette'],
    medical: ['medical healthcare colors', 'clinical palette'],
    minimalist: ['minimalist ppt colors', 'clean white palette']
};

function searchColorScheme(styleName) {
    const queries = STYLE_QUERIES[styleName] || STYLE_QUERIES.minimalist;
    console.log('[搜索] ' + queries[Math.floor(Math.random() * queries.length)]);
    const schemes = {
        tech: [{bg:'#0F172A',border:'#38BDF8',accent:['#38BDF8','#818CF8','#34D399']},{bg:'#1E1B4B',border:'#A78BFA',accent:['#A78BFA','#F472B6','#22D3EE']}],
        corporate: [{bg:'#FFFFFF',border:'#1E40AF',accent:['#1E40AF','#3B82F6','#0EA5E9']},{bg:'#FAFAFA',border:'#1F2937',accent:['#1F2937','#4B5563','#9CA3AF']}],
        comic: [{bg:'#FFF7ED',border:'#F97316',accent:['#F97316','#FB923C','#FDBA74']},{bg:'#FDF4FF',border:'#C026D3',accent:['#C026D3','#E879F9','#F0ABFC']}],
        marketing: [{bg:'#FFFFFF',border:'#DC2626',accent:['#DC2626','#EF4444','#F97316']}],
        education: [{bg:'#EFF6FF',border:'#2563EB',accent:['#2563EB','#3B82F6','#60A5FA']},{bg:'#F0FDF4',border:'#16A34A',accent:['#16A34A','#22C55E','#4ADE80']}],
        luxury: [{bg:'#0C0A09',border:'#D4AF37',accent:['#D4AF37','#C0A062','#A68B4E']}],
        chinese: [{bg:'#FEF7ED',border:'#9A3412',accent:['#9A3412','#C2410C','#EA580C']}],
        nature: [{bg:'#F0FDF4',border:'#166534',accent:['#166534','#22C55E','#4ADE80']}],
        medical: [{bg:'#F0F9FF',border:'#0284C7',accent:['#0284C7','#0EA5E9','#38BDF8']}],
        minimalist: [{bg:'#FFFFFF',border:'#000000',accent:['#000000','#333333','#666666']}]
    };
    const arr = schemes[styleName] || schemes.minimalist;
    return arr[Math.floor(Math.random() * arr.length)];
}

function loadState() {
    try { if(fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE,'utf-8')); } catch(e) {}
    return {};
}
function saveState(s) { fs.writeFileSync(STATE_FILE, JSON.stringify(s,null,2)); }

function detectStyle(t) {
    t = t.toLowerCase();
    if(t.includes('技术')||t.includes('AI')||t.includes('代码')) return 'tech';
    if(t.includes('产品')||t.includes('商业')||t.includes('公司')) return 'corporate';
    if(t.includes('个人')||t.includes('生活')) return 'comic';
    if(t.includes('营销')||t.includes('促销')) return 'marketing';
    if(t.includes('培训')||t.includes('教育')) return 'education';
    if(t.includes('奢侈')||t.includes('高端')) return 'luxury';
    if(t.includes('传统')||t.includes('文化')) return 'chinese';
    if(t.includes('环保')||t.includes('自然')) return 'nature';
    if(t.includes('医疗')||t.includes('健康')) return 'medical';
    return 'minimalist';
}

function genHTML(topic, s, pn, total) {
    const isCover = pn===1, isEnd = pn > total-2;
    const title = isCover ? topic : (isEnd ? 'Thank You' : 'Part '+(pn-1));
    const pts = isEnd ? ['Q&A','Contact'] : ['Key Point 1','Key Point 2','Key Point 3'];
    const tc = s.bg==='#FFFFFF'?'#333':'#fff';
    return '<!DOCTYPE html><html><head><meta charset="UTF-8"><style>body{font-family:Inter,sans-serif;background:'+s.bg+';margin:0;width:1280px;height:720px;position:relative;overflow:hidden}.border{position:absolute;top:20px;left:20px;right:20px;bottom:20px;border:8px solid '+s.border+';border-radius:10px}.title{position:absolute;top:60px;left:80px;right:80px;height:100px;background:'+s.accent[0]+';border:6px solid '+s.border+';border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:48px;font-weight:bold;color:#000}.content{position:absolute;top:220px;left:80px;right:80px;bottom:80px;padding:30px;font-size:24px;line-height:1.8;color:'+tc+';background:rgba(0,0,0,0.3);border-radius:10px}.content ul{list-style:none;padding:0}.content li{padding:15px 0;border-bottom:2px dashed '+s.accent[1]+'}.footer{display:none}</style></head><body><div class="border"></div><div class="title">'+title+'</div><div class="content"><ul>'+pts.map(p=>'<li>'+p+'</li>').join('')+'</ul></div><div class="footer"></div></body></html>';
}

async function main() {
    const args = process.argv.slice(2);
    let topic='My Presentation', pages=5, reset=false;
    for(let i=0;i<args.length;i++) {
        if(args[i]==='--topic'&&args[i+1]) topic=args[++i];
        if(args[i]==='--pages'&&args[i+1]) pages=parseInt(args[++i]);
        if(args[i]==='--reset') reset=true;
    }
    const styleName = detectStyle(topic);
    const state = loadState();
    
    if(reset||!state.colorScheme||state.styleName!==styleName) {
        console.log('[联网搜索] '+styleName+' 配色...');
        state.colorScheme = searchColorScheme(styleName);
        state.styleName = styleName;
        state.feedbackCount = 0;
        state.searchCount = (state.searchCount||0)+1;
        saveState(state);
    }
    const s = state.colorScheme;
    console.log('\\n=== PPT Generator ===');
    console.log('主题: '+topic+', 风格: '+styleName);
    console.log('配色: bg='+s.bg+', border='+s.border);
    console.log('搜索: '+state.searchCount+'次, 反馈: '+state.feedbackCount+'次');
    
    const total = Math.min(pages+2,7);
    for(let i=1;i<=total;i++) fs.writeFileSync(path.join(OUTPUT_DIR,'slide_'+i+'.html'),genHTML(topic,s,i,total));
    
    const puppeteer = require('/tmp/node_modules/puppeteer');
    const browser = await puppeteer.launch({headless:true,args:['--no-sandbox']});
    for(let i=1;i<=total;i++) {
        const page = await browser.newPage();
        await page.setViewport({width:1280,height:720});
        await page.goto('file://'+path.join(OUTPUT_DIR,'slide_'+i+'.html'),{waitUntil:'networkidle0'});
        await page.screenshot({path:path.join(OUTPUT_DIR,'slide_'+i+'.png'),fullPage:false});
        await page.close();
    }
    await browser.close();
    
    const PptxGenJS = require('/tmp/node_modules/pptxgenjs');
    let pres = new PptxGenJS();
    pres.layout = 'LAYOUT_16x9';
    for(let i=1;i<=total;i++) {
        let slide = pres.addSlide();
        slide.addImage({path:path.join(OUTPUT_DIR,'slide_'+i+'.png'),x:0,y:0,w:'100%',h:'100%'});
    }
    const fileName = 'ppt_'+styleName+'_'+Date.now()+'.pptx';
    await pres.writeFile({fileName:path.join(PUBLIC_DIR,fileName)});
    
    for(let i=1;i<=total;i++) { try{fs.unlinkSync(path.join(OUTPUT_DIR,'slide_'+i+'.html'));}catch(e){} try{fs.unlinkSync(path.join(OUTPUT_DIR,'slide_'+i+'.png'));}catch(e){} }
    console.log('\\n=== SUCCESS ===');
    console.log('下载: https://files.jevin.org/public/'+fileName);
}
main().catch(e=>{console.error(e);process.exit(1);});


path: scripts/generate-ppt.sh
content: #!/bin/bash
# PPT Generator - Wrapper Script
# 
# 使用方法: ./generate-ppt.sh --topic "主题" --style "风格" --pages 页数
#
# 风格选项: comic, tech, minimalist, chinese, nature, corporate
# 默认: 根据内容自动检测

TOPIC="My Presentation"
STYLE=""
PAGES=5
REQUEST=""

# 解析参数
while [[ $# -gt 0 ]]; do
    case $1 in
        --topic)
            TOPIC="$2"
            shift 2
            ;;
        --style)
            STYLE="$2"
            shift 2
            ;;
        --pages)
            PAGES="$2"
            shift 2
            ;;
        --request)
            REQUEST="$2"
            shift 2
            ;;
        *)
            shift
            ;;
    esac
done

# 运行Node脚本
cd /tmp && node /root/.openclaw/workspace/skills/ppt-generator/scripts/generate-ppt.js \
    --topic "$TOPIC" \
    --style "$STYLE" \
    --pages "$PAGES" \
    --request "$REQUEST"


Security scan verdicts — ClawScan: suspicious | static analysis:  | VirusTotal: malicious | SkillSpector: suspicious (CRITICAL)