PPT Generator
⚠️ 依赖和权限声明
必需依赖
| 依赖 | 用途 | 配置要求 |
|---|---|---|
| nanobanana-skill | AI 配图生成(可选功能) | 需要安装此 skill |
| 支持图片识别的 LLM | 风格分析、大纲生成 | 用户自行配置任意支持图片识别的模型 |
文件系统访问
| 权限 | 用途 | 说明 |
|---|---|---|
| 读取本地文件 | 读取用户提供的风格示例图、文稿文件 | 仅读取用户明确指定的文件 |
| 写入本地文件 | 保存生成的 HTML 预览、PPTX 文件、AI 配图 | 默认保存到 ~/clawd/output/ 目录 |
| 读取 skill 目录 | 读取 assets/ 和 references/ 中的模板文件 | 仅限本 skill 目录内 |
外部 API 调用
| API | 用途 | 凭证要求 |
|---|---|---|
| Google Gemini API | AI 配图生成(通过 nanobanana-skill) | 需要在 nanobanana-skill 中配置 GEMINI_API_KEY |
| LLM API | 风格分析、内容生成 | 用户自行配置(支持图片识别的任意模型) |
可选功能
| 功能 | 依赖 | 是否必需 |
|---|---|---|
| AI 配图生成 | nanobanana-skill + Gemini API Key | ❌ 可选 |
| 风格分析 | 支持图片识别的 LLM | ✅ 必需 |
| HTML 预览 | Canvas 工具或本地服务器 | ✅ 必需 |
| PPTX 导出 | pptxgenjs (内置) | ✅ 必需 |
Generate professional, editable PowerPoint presentations from text manuscripts with style matching.
Workflow
用户输入(文稿 + 风格示例图 + 可选图片)
│
▼
┌─────────────────┐
│ Step 1: 风格分析 │
│ 支持图片识别的 │
│ LLM 模型 │
│ 提取配色/布局 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 用户确认/调整 │ ← 对话迭代
└────────┬────────┘
│
▼
┌─────────────────┐
│ Step 2: 大纲生成 │
│ 总结文稿内容 │
│ 规划每页结构 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 是否需要AI配图?│ ← 可选项
│ (Step 2.5) │
└────────┬────────┘
│
┌────────┴────────┐
│ │
是 否
│ │
▼ │
┌─────────────┐ │
│ AI配图生成 │ │
│ nanobanana │ │
└──────┬──────┘ │
│ │
└──────┬───────┘
│
▼
┌─────────────────┐
│ Step 3: HTML生成 │
│ 16:9 横屏页面 │
│ 应用风格 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Canvas 预览 │ ← 左右翻页、对话修改
│ 用户确认 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Step 4: 导出PPTX │
│ pptxgenjs │
│ 可编辑文件 │
└─────────────────┘
Step 1: 风格分析
输入
- 风格示例图:用户提供的一张或多张 PPT 截图
- 可选:用户的风格偏好说明
执行
使用 sessions_spawn 调用支持图片识别的 LLM 模型分析风格(使用用户配置的默认模型):
sessions_spawn({
model: "default", // 使用用户配置的默认模型
task: `分析这张 PPT 截图的视觉风格,提取以下信息:
1. 配色方案:
- 主色(用于标题/重点)
- 辅助色(用于副标题/装饰)
- 背景色
- 文字色
2. 布局结构:
- 标题位置和样式
- 内容区域划分
- 留白比例
3. 字体风格:
- 标题字体(粗细/大小层次)
- 正文字体
4. 装饰元素:
- 线条/形状
- 图标/图形
- 其他装饰
请以 JSON 格式输出风格描述。`,
// 图片通过会话上下文传入
})
输出格式
{
"colors": {
"primary": "#1E3A8A",
"secondary": "#3B82F6",
"background": "#FFFFFF",
"text": "#1F2937",
"accent": "#60A5FA"
},
"layout": {
"titlePosition": "top-left",
"titleSize": "large",
"contentArea": "center",
"whitespaceRatio": 0.3
},
"typography": {
"titleFont": "bold, 36-48px",
"bodyFont": "regular, 18-24px",
"hierarchy": "clear distinction between title and body"
},
"decorations": {
"shapes": ["rounded rectangles", "lines"],
"icons": "minimal, line-style",
"other": "subtle shadows"
}
}
用户确认
展示分析结果,询问用户:
我分析了您提供的风格示例,提取出以下风格:
- 主色调:#1E3A8A(深蓝)
- 背景:白色
- ...
是否符合预期?需要调整哪些方面?
用户可以:
- 确认使用
- 修改颜色值
- 调整布局偏好
- 提供新的风格描述
Step 2: 大纲生成
输入
- 文稿内容(文本)
- 可选图片列表(本地路径或 URL)
- 已确认的风格
执行
分析文稿结构,生成 PPT 大纲:
1. 封面页
- 标题:{主标题}
- 副标题:{副标题}
- 图片:{可选封面图}
2. 目录页
- 章节:{章节列表}
3. 章节页 x N
- 标题:{章节标题}
- 内容:{要点列表,每项 1-2 行}
- 图片:{配图路径,注明位置意图}
- 布局建议:{左文右图/上文下图/全图等}
4. 总结页
- 关键结论
- 下一步行动
5. 结束页
- 感谢语
- 联系方式
图片处理
如果用户提供了图片:
- 识别图片意图:根据上下文判断图片应该放在哪页
- 规划图片位置:
- 数据图表 → 右侧或中央
- 配图/照片 → 背景或侧边
- 图标 → 标题旁或要点前
- 记录图片路径:本地路径或 URL
用户确认
展示大纲,询问:
我根据您的文稿生成了 PPT 大纲:
第1页:封面 - "季度工作汇报"
第2页:目录 - 3个章节
第3页:项目进展 - 左文右图布局
...
共 N 页。确认后我开始生成 HTML 预览。
需要调整页面顺序、合并/拆分页面吗?
Step 2.5: AI 配图生成(可选)
询问用户
大纲确认后,询问用户是否需要 AI 生成配图:
您的 PPT 大纲已生成。是否需要我为您生成 AI 配图?
选择:
1. 全部页面生成配图
2. 仅部分页面生成配图(请告诉我哪些页)
3. 不需要配图,继续下一步
配图策略
根据页面类型生成不同风格的配图:
| 页面类型 | 配图风格 | 示例提示词 |
|---|---|---|
| 封面页 | 抽象科技/城市/行业背景 | "抽象科技背景,蓝色渐变,现代化数字城市,简洁几何图形" |
| 概述页 | 概念插图/流程图风格 | "业务流程概念图,简洁扁平风格,蓝色主色调" |
| 数据页 | 数据可视化风格 | "数据分析概念插图,图表和仪表盘,科技感" |
| 总结页 | 积极向上的概念图 | "成功合作概念图,握手或团队,商务风格" |
执行流程
1. 分析页面内容,生成提示词
// 对每个需要配图的页面
const imagePrompt = await generateImagePrompt(slideContent, styleColors);
// 示例输出
{
"slide": 3,
"type": "概念插图",
"prompt": "数字化道路概念图,智慧交通,蓝色科技风格,简洁扁平化设计,现代城市道路俯视图,带有数据流线条,16:9横幅"
}
2. 调用 nanobanana 生成图片
python3 ${CLAUDE_PLUGIN_ROOT}/skills/nanobanana-skill/nanobanana.py \
--prompt "数字化道路概念图,智慧交通,蓝色科技风格,简洁扁平化设计" \
--size 1344x768 \
--output "/path/to/output/slide-3-illustration.png"
3. 图片参数设置
const imageConfig = {
// 横版配图(适合 PPT)
size: "1344x768", // 16:9 横幅
// 方形配图(适合图标、小插图)
sizeSquare: "1024x1024",
// 默认模型
model: "gemini-3-pro-image-preview",
// 快速模型(可选)
modelFast: "gemini-2.5-flash-image",
// 分辨率
resolution: "1K"
};
配图提示词模板
封面配图
{主题}概念背景图,{风格描述},{主色调}渐变,
现代化{行业}场景,简洁几何图形装饰,
专业商务风格,适合PPT封面,16:9横幅
内容页配图
{页面主题}概念插图,{具体描述},
扁平化设计风格,{主色调}主色调,
简洁专业,适合PPT内容页,16:9横幅
数据/图表配图
数据分析可视化概念图,{具体描述},
科技感设计,{主色调}配色,
简洁现代,适合PPT数据展示页,16:9横幅
配图风格指南
根据提取的风格调整配图:
function adaptPromptToStyle(basePrompt, extractedStyle) {
const colorWords = {
"#003366": "深蓝色",
"#3366CC": "蓝色",
"#1E3A8A": "海军蓝",
"#22C55E": "绿色",
"#F97316": "橙色"
};
const primaryColorWord = colorWords[extractedStyle.colors.primary] || "蓝色";
return `${basePrompt},${primaryColorWord}主色调,与PPT风格协调`;
}
生成后的处理
- 保存图片到工作目录
- 更新大纲中的图片路径
- 展示预览给用户确认
- 可选调整:用户可以说"重新生成第3页配图"
Step 3: HTML 生成
16:9 页面规格
.slide {
width: 1920px;
height: 1080px;
aspect-ratio: 16 / 9;
/* 或响应式: */
width: 100%;
aspect-ratio: 16 / 9;
}
模板参考
使用 assets/html-template/ 中的模板:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="base.css">
<style>
/* 注入用户风格 */
:root {
--primary-color: {{colors.primary}};
--secondary-color: {{colors.secondary}};
--background-color: {{colors.background}};
--text-color: {{colors.text}};
}
</style>
</head>
<body>
<div class="slide" id="slide-1">
<!-- 内容 -->
</div>
</body>
</html>
布局类型
参考 references/work-report-layouts.md 中的布局模板:
- 封面布局
- 标题+正文布局
- 左文右图布局
- 对比布局
- 数据展示布局
图片嵌入
<!-- AI生成的配图 -->
<img src="/path/to/ai-generated-illustration.png" alt="配图描述">
<!-- 用户提供的图片 -->
<img src="/local/path/to/image.png" alt="描述">
<!-- 网络图片 -->
<img src="https://example.com/image.png" alt="描述">
<!-- 样式:自动缩放适配 -->
<style>
.slide img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
</style>
Step 4: Canvas 预览
展示方式
使用 canvas 工具或本地服务器展示 HTML:
// 方式1: canvas(需要 node)
canvas({
action: "present",
url: "file:///path/to/slides.html"
})
// 方式2: 本地服务器
// 启动服务器后用浏览器预览
交互功能
用户可以:
- 翻页:点击左右箭头或按钮
- 对话修改:说"把标题改成蓝色"、"调整第3页布局"
- 预览截图:查看渲染效果
迭代修改
接收用户修改指令,更新 HTML,刷新预览。
Step 5: 导出 PPTX
使用 pptxgenjs
import pptxgen from "pptxgenjs";
let pres = new pptxgen();
pres.layout = "LAYOUT_16x9";
// 应用风格
pres.theme = {
headFontFace: "Microsoft YaHei",
bodyFontFace: "Microsoft YaHei"
};
// 添加幻灯片
let slide = pres.addSlide();
// 添加文本
slide.addText("标题", {
x: 0.5, y: 0.5, w: "90%", h: 1,
fontSize: 36, color: "1E3A8A", bold: true
});
// 添加 AI 生成的配图
slide.addImage({
path: "/path/to/ai-generated-illustration.png",
x: 6, y: 2, w: 4, h: 3
});
// 保存
pres.writeFile({ fileName: "output.pptx" });
风格映射
| HTML/CSS | pptxgenjs |
|---|---|
color: #1E3A8A | color: "1E3A8A" |
font-size: 36px | fontSize: 36 |
font-weight: bold | bold: true |
background: #FFF | fill: "FFFFFF" |
图片处理
// 本地图片(包括 AI 生成的配图)
slide.addImage({ path: "/path/to/image.png", ... });
// 网络图片(先下载)
const imageData = await fetch(imageUrl);
slide.addImage({ data: imageData, ... });
// 自动缩放
// pptxgenjs 会自动处理,可指定 w/h 或使用 sizing
HTML 转 PPTX 经验教训
⚠️ 常见陷阱
1. 正则匹配嵌套 HTML 标签
问题:使用简单正则 /class="slide"/ 会误匹配 class="slide-content"、class="slide-header" 等。
错误示例:
// ❌ 错误:会匹配到 slide-content, slide-header 等
const regex = /<div class="slide[^"]*"/g;
正确做法:使用栈匹配嵌套结构
// ✅ 正确:精确匹配顶层 slide div
function extractSlides(html) {
const slides = [];
// 精确匹配 slide 的开始标签,捕获类型
const regex = /<div class="slide( cover| end)?"[^>]*>/g;
let match;
while ((match = regex.exec(html)) !== null) {
const startPos = match.index;
const slideType = match[1] ? match[1].trim() : 'content';
// 用栈找到匹配的结束 </div>
let depth = 1;
let pos = startPos + match[0].length;
while (depth > 0 && pos < html.length) {
const openIdx = html.indexOf('<div', pos);
const closeIdx = html.indexOf('</div>', pos);
if (closeIdx === -1) break;
if (openIdx !== -1 && openIdx < closeIdx) {
depth++;
pos = openIdx + 4;
} else {
depth--;
pos = closeIdx + 6;
}
}
slides.push({
html: html.substring(startPos, pos),
type: slideType
});
}
return slides;
}
2. 提取嵌套 div 内容
问题:text-section 内部还有嵌套 div,简单正则无法正确提取。
正确做法:
function extractTextSection(slideHtml) {
const startMatch = slideHtml.match(/class="text-section"[^>]*>/);
if (!startMatch) return null;
const startPos = startMatch.index + startMatch[0].length;
// 用栈找结束位置
let depth = 1;
let pos = startPos;
while (depth > 0 && pos < slideHtml.length) {
const openIdx = slideHtml.indexOf('<div', pos);
const closeIdx = slideHtml.indexOf('</div>', pos);
if (closeIdx === -1) break;
if (openIdx !== -1 && openIdx < closeIdx) {
depth++;
pos = openIdx + 4;
} else {
depth--;
pos = closeIdx + 6;
if (depth === 0) {
return slideHtml.substring(startPos, closeIdx);
}
}
}
return null;
}
3. 背景图处理
问题:HTML 中的背景图(如 cover_bg.png)可能被误过滤,或路径处理不正确。
正确做法:
// 封面页:先添加背景图,再添加半透明遮罩
function createCoverSlide(slide, slideHtml) {
// 1. 背景图
const bgPath = `${imgBasePath}/cover_bg.png`;
if (fs.existsSync(bgPath)) {
slide.addImage({ path: bgPath, x: 0, y: 0, w: 10, h: 5.625 });
}
// 2. 半透明遮罩(让文字更清晰)
slide.addShape('rect', {
x: 0, y: 0, w: 10, h: 5.625,
fill: { color: '0d47a1', transparency: 20 }
});
// 3. 文字内容...
}
4. 图片路径验证
问题:HTML 中的图片路径可能不存在,直接添加会报错。
正确做法:
// 过滤有效图片
const validImages = images.filter(p => fs.existsSync(p));
if (validImages.length > 0) {
// 添加图片...
} else {
// 显示占位符
slide.addShape('rect', {
x: imgX, y: imgY, w: imgW, h: imgH,
fill: { color: 'f5f5f5' },
line: { color: 'cccccc', width: 1, dashType: 'dash' }
});
slide.addText('图片区域', {
x: imgX, y: imgY + imgH / 2 - 0.2, w: imgW, h: 0.4,
fontSize: 14, color: '999999', align: 'center'
});
}
5. 内容提取顺序
问题:HTML 中的内容顺序和预期不一致,导致解析遗漏。
推荐顺序:
function createContentSlide(slide, slideHtml) {
// 1. 提取标题栏
const headerMatch = slideHtml.match(/class="slide-header"[^>]*>([\s\S]*?)<\/div>/);
// 2. 提取 text-section(用栈匹配)
const textHtml = extractTextSection(slideHtml);
// 3. 提取图片(排除背景图)
const images = extractImages(slideHtml);
// 4. 处理文本内容
if (textHtml) {
// h2 标题
// h3 之前的段落
// 网格布局(five-grid, three-grid, four-grid)
// 流程图(flow-chart)
// h3 及其内容(需要 split 处理多个 h3)
}
}
✅ 完整转换脚本模板
参考文件:assets/scripts/html-to-pptx.js
关键要点:
- 使用栈匹配嵌套 HTML 结构
- 分别处理不同类型的内容(段落、列表、网格、流程图)
- 验证图片路径是否存在
- 按正确顺序添加 PPT 元素(背景 → 遮罩 → 文字 → 图片)
Resources
references/
style-extraction.md- 风格提取详细指南work-report-layouts.md- 工作汇报常用布局模板image-handling.md- 图片处理详细说明ai-illustration-prompts.md- AI 配图提示词模板
assets/
html-template/- HTML/CSS 模板文件base.css- 基础样式slide.html- 页面模板