@clawhub-luych888-design-58869c294d
PPT自动生成工具,通过调用外部接口实现从主题到完整PPT文件的自动化生成流程。支持大纲生成、大纲修改、模板选择、PPT生成等完整流程。当用户要求生成PPT、制作幻灯片、创建演示文稿、年终总结PPT、项目汇报PPT时使用此skill。
---
name: ppt-generator
description: PPT自动生成工具,通过调用外部接口实现从主题到完整PPT文件的自动化生成流程。支持大纲生成、大纲修改、模板选择、PPT生成等完整流程。当用户要求生成PPT、制作幻灯片、创建演示文稿、年终总结PPT、项目汇报PPT时使用此skill。
---
# PPT Generator
## ⚠️ 重要:交互式流程说明
**这是一个交互式流程,不是自动化脚本!**
在以下关键步骤必须停止执行,等待用户确认:
1. **Step 4: 大纲确认** - 展示大纲后必须停止,等待用户回复"1"或"2"
2. **Step 8: 选择模版** - 展示模版列表后必须停止,等待用户输入模版编号
**禁止行为**:
- ❌ 不要自动选择大纲确认选项
- ❌ 不要自动选择模版
- ❌ 不要在用户回复前继续下一步
**正确做法**:
- ✅ 展示内容后立即停止
- ✅ 明确告诉用户需要回复什么
- ✅ 等待用户回复后再继续
---
## Overview
通过调用统一API接口实现PPT的自动化生成。完整流程包括:主题输入 → 大纲生成 → 大纲确认/修改 → 模板选择 → PPT生成 → 文件交付。
**统一接口地址**: `https://ai.mingyangtek.com/aippt/api/c=15109`
**重要说明**:
- 所有操作使用同一个URL
- 通过请求参数区分不同操作
- 使用POST方法
---
## Complete Workflow
### Step 1: 用户输入主题
当用户请求生成PPT时,首先确认主题:
```
用户: 帮我生成一个年终总结PPT
用户: 制作一个水浒传赏析的幻灯片
用户: 创建一个产品发布演示文稿
```
**操作**:
1. 从消息上下文中提取用户信息:`sender_id`, `sender`, `chat_id`, `channel`
2. 确认PPT主题(如果用户未明确说明)
### Step 2: 生成大纲
调用统一接口生成大纲:
**Request**:
```http
POST http://ai.mingyangtek.com/aippt/api/c=15109 HTTP/1.1
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"mainIdea": "年终总结"
}
```
**参数说明**:
- `mainIdea`: PPT主题/主要想法(必需)
**Response** (示例):
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": "9f247d90c8044b10b46dc536bdd724e8",
"mainIdea": "年终总结",
"markdown": "# 年终总结\n## 工作回顾\n...",
"outLineTree": "{...}"
}
}
```
**重要字段说明**:
- `code`: 状态码,200表示成功
- `message`: 消息,SUCCESS表示成功
- `data.id`: 大纲唯一标识(用于后续操作)
- `data.mainIdea`: PPT主题
- `data.markdown`: Markdown格式的内容
- `data.outLineTree`: JSON字符串格式的大纲树结构,需要解析使用
### Step 3: 大纲渲染展示
根据不同渠道,选择不同的渲染方式:
| 渠道 | 推荐格式 | 原因 | 说明 |
|------|---------|---------|------|
| wecom/feishu | emoji | 支持emoji,用户体验好 | 用emoji增强可读性 |
| telegram/discord | markdown | 支持markdown,格式清晰 | 使用标题和列表 |
| slack | simple | 不支持复杂格式 | 使用纯文本编号 |
**格式化函数调用**:
```python
from scripts.ppt_api import format_outline
# 根据渠道自动选择格式
channel = "wecom"
if channel in ["telegram", "discord"]:
style = "markdown"
elif channel == "slack":
style = "simple"
else:
style = "emoji"
formatted_outline = format_outline(result, style=style)
print(formatted_outline)
```
### Step 4: 大纲确认
**⚠️ 重要:必须等待用户确认!展示大纲后必须停止执行,等待用户回复。**
展示大纲后,询问用户是否需要修改:
**选项**:
1. **不修改** - 使用当前大纲
2. **修改大纲** - 调整内容
**必须的操作**:
1. 展示完整大纲内容
2. **停止执行,等待用户回复"1"或"2"**
3. **不要自动选择,不要继续下一步**
4. 根据用户回复执行对应操作
**用户回复**:
- 用户回复 "1" → 确认大纲,继续下一步
- 用户回复 "2" → 进入大纲修改流程
### Step 5: 大纲修改(可选)
如果用户选择修改大纲,调用编辑大纲接口:
**Request**:
```http
POST https://ai.mingyangtek.com/aippt/api/c=15110 HTTP/1.1
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"outlineId": "9f247d90c8044b10b46dc536bdd724e8",
"markDownStr": "# 年终总结\n## 修改后的内容\n...",
"userId": "{sender_id}"
}
```
**参数说明**:
- `outlineId`: 大纲ID(必需)
- `markDownStr`: Markdown格式的完整大纲内容(必需)
- `userId`: 用户ID(必需)
**Response** (示例):
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": "9f247d90c8044b10b46dc536bdd724e8",
"mainIdea": "年终总结",
"markdown": "# 年终总结\n## 修改后的内容\n..."
}
}
```
### Step 6: 智能标签推荐
根据PPT主题推荐合适的标签:
```python
from scripts.ppt_api import recommend_tags
tags = recommend_tags("年终总结")
# 返回: {"style_tags": ["商务风"], "color_tags": ["蓝色"]}
tags = recommend_tags("春天")
# 返回: {"style_tags": ["中国风"], "color_tags": ["绿色"]}
```
**标签范围**:
- 风格标签:简约风、小清新、商务风、中国风、可爱卡通、科技风、手绘风格、欧美风、党政风、黑板风
- 颜色标签:蓝色、红色、粉色、黄色、绿色、橙色、黑色、白色、灰色、紫色
### Step 7: 查询模版列表
**Request**:
```http
POST https://ai.mingyangtek.com/aippt/api/c=15111 HTTP/1.1
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"keywords": ["商务风", "蓝色"],
"userId": "{sender_id}"
}
```
**参数说明**:
- `keywords`: 标签数组(必需)- 可以传多个标签
- `userId`: 用户ID(必需)
**Response** (示例):
```json
{
"code": 200,
"message": "SUCCESS",
"data": [
{
"templateId": 39,
"fileName": "简约蓝色商务汇报PPT模版.pptx",
"preview": "https://aipptx.oss-cn-shanghai.aliyuncs.com/templates/39/view_xxx.pptx"
}
]
}
```
**模版列表展示**:
```python
from scripts.ppt_api import format_templates
# 格式化模版列表(简单列表格式,不显示ID和预览)
formatted = format_templates(templates, channel="wecom")
# 输出:
# 找到 5 个模版:
#
# 1. 简约蓝色商务汇报PPT模版.pptx
# 2. 绿色简约校园通用PPT.pptx
# ...
#
# 请选择模版编号(1-5):
```
### Step 8: 选择模版
**⚠️ 重要:必须等待用户选择!展示模版列表后必须停止执行,等待用户输入模版编号。**
展示模版列表后,询问用户选择模版:
**必须的操作**:
1. 展示模版列表(使用format_templates函数)
2. **停止执行,等待用户输入模版编号(如"1"、"2"等)**
3. **不要自动选择模版,不要继续下一步**
4. 根据用户输入的编号,提取对应的templateId
**用户选择模版编号后**,提取对应的 templateId:
```python
# 用户选择编号 1
selected_template_id = templates[0]['templateId'] # 39
```
### Step 9: 提交PPT任务
**Request**:
```http
POST https://ai.mingyangtek.com/aippt/api/c=15112 HTTP/1.1
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"templateId": 39,
"outlineId": "9f247d90c8044b10b46dc536bdd724e8",
"reporter": "贾俊豪"
}
```
**参数说明**:
- `templateId`: 模版ID(必需)- 数字类型
- `outlineId`: 大纲ID(必需)
- `reporter`: 汇报人/作者名称(可选)
**Response** (示例):
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": 2086770
}
}
```
**重要**:返回的是 `id` 字段(数字类型),不是 `taskId`
### Step 10: 查询PPT生成结果
PPT生成是异步的,需要轮询查询状态:
**Request**:
```http
POST https://ai.mingyangtek.com/aippt/api/c=15113 HTTP/1.1
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"pptId": 2086770,
"userId": "{sender_id}"
}
```
**参数说明**:
- `pptId`: PPT任务ID(必需)- 数字类型
- `userId`: 用户ID(必需)
**Response - 生成中**:
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": 2086770,
"status": 0,
"fileurl": null
}
}
```
**Response - 生成完成**:
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": 2086770,
"status": 1,
"fileurl": "https://aipptx.oss-cn-shanghai.aliyuncs.com/worksdate/xxx.pptx"
}
}
```
**状态说明**:
- `status=0`: 生成中
- `status=1`: 生成完成
**轮询策略**:
```python
# 使用 wait_for_ppt 函数自动轮询
result = client.wait_for_ppt(ppt_id, max_attempts=60, interval=5)
# 每5秒查询一次,最多查询60次(约5分钟)
```
### Step 11: 交付PPT文件
**格式化输出**:
```python
from scripts.ppt_api import format_ppt_result
# 格式化PPT生成结果(包含推广文案)
output = format_ppt_result(
theme="年终总结",
ppt_id="2086770",
download_url="https://aipptx.oss-cn-shanghai.aliyuncs.com/worksdate/xxx.pptx",
channel="wecom"
)
print(output)
```
**输出格式**:
```markdown
## ✅ PPT生成成功!
### 📋 生成信息:
- **主题**: 年终总结
- **PPT ID**: `2086770`
---
### 📥 PPT下载链接:
```
https://aipptx.oss-cn-shanghai.aliyuncs.com/worksdate/xxx.pptx
```
**点击链接即可下载PPT文件!**
---
## 🎉 PPT生成完成!
---
**本功能由名阳信息技术有限公司提供**
如需使用完整功能,请下载APP应用或访问网站:
- 📱 APP:各大应用商店搜索"mindppt"
- 🌐 网站:https://mindppt.net
```
---
## User Information Passing
所有API调用都需要携带用户信息:
### Header字段
`http
X-Userid: {user_id} # 用户唯一标识(必需)
X-Sender: {sender} # 用户名称
X-Chatid: {chat_id} # 会话ID
X-Channel: {channel} # 渠道(wecom/feishu/telegram等)
`
### 获取方式
从消息上下文中获取:
- sender_id - 从消息上下文获取
- sender - 从 Sender.label 获取
- chat_id - 从 inbound_meta.chat_id 获取
- channel - 从 inbound_meta.channel 获取
---
## Error Handling
### 常见错误
1. **网络连接失败** - 检查网络连通性
2. **请求频率超限(429)** - 等待后重试
3. **资源不存在(404)** - 检查ID是否正确
4. **服务器错误(5xx)** - 等待后重试
---
## References
- API接口文档:
eferences/api-endpoints.md
- Python客户端:scripts/ppt_api.py
FILE:scripts/ppt_api.py
#!/usr/bin/env python3
"""
PPT Generator API Client
用于调用 PPT生成API接口
"""
import requests
import json
import sys
import hashlib
import uuid
from typing import Dict, Any, Optional, List
# API 统一接口地址
API_URL = "https://ai.mingyangtek.com/aippt/api/c=15109"
# 有效的标签范围
VALID_STYLE_TAGS = [
"简约风", "小清新", "商务风", "中国风", "可爱卡通",
"科技风", "手绘风格", "欧美风", "党政风", "黑板风"
]
VALID_COLOR_TAGS = [
"蓝色", "红色", "粉色", "黄色", "绿色",
"橙色", "黑色", "白色", "灰色", "紫色"
]
def get_mac_address() -> str:
"""
获取本机MAC地址
Returns:
MAC地址字符串(去掉冒号和横线)
"""
try:
# 获取本机MAC地址
mac = uuid.getnode()
mac_str = ':'.join(['{:02x}'.format((mac >> elements) & 0xff) for elements in range(0, 8*6, 8)][::-1])
# 去掉冒号,转大写
return mac_str.replace(':', '').upper()
except Exception:
return "UNKNOWN"
def generate_user_id(sender_id: str) -> str:
"""
生成组合用户ID:sender_id + MAC地址哈希值
Args:
sender_id: 用户的sender_id
Returns:
组合后的用户ID(格式:sender_id_mac_hash前8位)
"""
mac = get_mac_address()
# 对MAC地址进行MD5哈希
mac_hash = hashlib.md5(mac.encode()).hexdigest()[:8]
# 组合:sender_id_mac_hash
combined_id = f"{sender_id}_{mac_hash}"
return combined_id
class PPTAPIClient:
"""PPT API客户端"""
def __init__(self, sender_id: str, sender: str, chat_id: str, channel: str):
"""
初始化客户端
Args:
sender_id: 用户标识
sender: 用户名称
chat_id: 会话ID
channel: 渠道(wecom/feishu/telegram等)
"""
# 使用组合用户ID
self.sender_id = generate_user_id(sender_id)
self.sender = sender
self.chat_id = chat_id
self.channel = channel
self.headers = {
"Content-Type": "application/json",
"X-Userid": self.sender_id,
"X-Sender": sender,
"X-Chatid": chat_id,
"X-Channel": channel
}
def _make_request(
self,
method: str,
data: Optional[Dict] = None
) -> Dict[str, Any]:
"""
发送HTTP请求(统一接口地址)
Args:
method: HTTP方法(GET/POST)
data: 请求体数据
Returns:
响应JSON数据
Raises:
Exception: API调用失败
"""
url = API_URL # 使用统一接口地址
try:
if method.upper() == "GET":
response = requests.get(url, headers=self.headers, timeout=300)
elif method.upper() == "POST":
response = requests.post(url, headers=self.headers, json=data, timeout=300)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
# 检查响应状态
if response.status_code == 429:
raise Exception("请求频率超限,请稍后再试")
elif response.status_code == 404:
raise Exception("资源不存在")
elif response.status_code >= 500:
raise Exception(f"服务器错误: {response.status_code}")
elif response.status_code >= 400:
error_data = response.json()
error_msg = error_data.get("error", {}).get("message", "请求失败")
raise Exception(f"请求失败: {error_msg}")
return response.json()
except requests.exceptions.Timeout:
raise Exception("请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
raise Exception("网络连接失败,请检查网络")
except json.JSONDecodeError:
raise Exception("响应格式错误")
def generate_outline(self, main_idea: str) -> Dict[str, Any]:
"""
生成大纲
Args:
main_idea: PPT主题/主要想法
Returns:
包含code, message, data的字典
"""
data = {
"mainIdea": main_idea, # 注意:使用驼峰命名
"userId": self.sender_id # 添加userId参数
}
return self._make_request("POST", data)
def modify_outline_with_markdown(
self,
outline_id: str,
user_id: str,
markdown_str: str
) -> Dict[str, Any]:
"""
使用Markdown内容修改大纲
Args:
outline_id: 大纲ID
user_id: 用户ID
markdown_str: Markdown格式的大纲内容
Returns:
更新后的大纲
"""
# 编辑大纲使用不同的接口地址
url = "https://ai.mingyangtek.com/aippt/api/c=15110"
data = {
"outlineId": outline_id, # 驼峰命名
"userId": user_id, # 驼峰命名
"markDownStr": markdown_str # 驼峰命名
}
try:
response = requests.post(url, headers=self.headers, json=data, timeout=300)
if response.status_code == 429:
raise Exception("请求频率超限,请稍后再试")
elif response.status_code == 404:
raise Exception("资源不存在")
elif response.status_code >= 500:
raise Exception(f"服务器错误: {response.status_code}")
elif response.status_code >= 400:
error_data = response.json()
error_msg = error_data.get("error", {}).get("message", "请求失败")
raise Exception(f"请求失败: {error_msg}")
return response.json()
except requests.exceptions.Timeout:
raise Exception("请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
raise Exception("网络连接失败,请检查网络")
except json.JSONDecodeError:
raise Exception("响应格式错误")
def get_templates(self, keywords: List[str]) -> Dict[str, Any]:
"""
获取模版列表
Args:
keywords: 标签数组,可以传多个标签(风格标签和颜色标签)
Returns:
包含模版列表的字典
"""
# 获取模版列表使用不同的接口地址
url = "https://ai.mingyangtek.com/aippt/api/c=15111"
data = {
"keywords": keywords,
"userId": self.sender_id
}
try:
response = requests.post(url, headers=self.headers, json=data, timeout=300)
if response.status_code == 429:
raise Exception("请求频率超限,请稍后再试")
elif response.status_code == 404:
raise Exception("资源不存在")
elif response.status_code >= 500:
raise Exception(f"服务器错误: {response.status_code}")
elif response.status_code >= 400:
error_data = response.json()
error_msg = error_data.get("error", {}).get("message", "请求失败")
raise Exception(f"请求失败: {error_msg}")
result = response.json()
# 处理模版列表数据:data.record 是模版列表
if result.get("code") == 200 and isinstance(result.get("data"), dict):
if "record" in result["data"]:
result["data"] = result["data"]["record"]
return result
except requests.exceptions.Timeout:
raise Exception("请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
raise Exception("网络连接失败,请检查网络")
except json.JSONDecodeError:
raise Exception("响应格式错误")
def submit_ppt_task(
self,
template_id: int,
outline_id: str,
reporter: str
) -> Dict[str, Any]:
"""
提交创作PPT任务(异步)
Args:
template_id: 模版ID(数字类型)
outline_id: 大纲ID
reporter: 汇报人姓名
Returns:
包含taskId和status的字典
"""
# 提交PPT任务使用不同的接口地址
url = "https://ai.mingyangtek.com/aippt/api/c=15112"
data = {
"templateId": template_id, # 数字类型
"outlineId": outline_id,
"reporter": reporter,
"userId": self.sender_id
}
try:
response = requests.post(url, headers=self.headers, json=data, timeout=300)
if response.status_code == 429:
raise Exception("请求频率超限,请稍后再试")
elif response.status_code == 404:
raise Exception("资源不存在")
elif response.status_code >= 500:
raise Exception(f"服务器错误: {response.status_code}")
elif response.status_code >= 400:
error_data = response.json()
error_msg = error_data.get("error", {}).get("message", "请求失败")
raise Exception(f"请求失败: {error_msg}")
return response.json()
except requests.exceptions.Timeout:
raise Exception("请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
raise Exception("网络连接失败,请检查网络")
except json.JSONDecodeError:
raise Exception("响应格式错误")
def get_ppt_result(self, ppt_id: int) -> Dict[str, Any]:
"""
查询PPT生成结果
Args:
ppt_id: PPT任务ID(数字类型)
Returns:
包含status和fileurl的字典
"""
# 查询PPT结果使用不同的接口地址
url = "https://ai.mingyangtek.com/aippt/api/c=15113"
data = {
"pptId": ppt_id,
"userId": self.sender_id
}
try:
response = requests.post(url, headers=self.headers, json=data, timeout=300)
if response.status_code == 429:
raise Exception("请求频率超限,请稍后再试")
elif response.status_code == 404:
raise Exception("资源不存在")
elif response.status_code >= 500:
raise Exception(f"服务器错误: {response.status_code}")
elif response.status_code >= 400:
error_data = response.json()
error_msg = error_data.get("error", {}).get("message", "请求失败")
raise Exception(f"请求失败: {error_msg}")
return response.json()
except requests.exceptions.Timeout:
raise Exception("请求超时,请检查网络连接")
except requests.exceptions.ConnectionError:
raise Exception("网络连接失败,请检查网络")
except json.JSONDecodeError:
raise Exception("响应格式错误")
def wait_for_ppt(self, ppt_id: int, max_attempts: int = 60, interval: int = 5) -> Dict[str, Any]:
"""
轮询等待PPT生成完成
Args:
ppt_id: PPT任务ID
max_attempts: 最大查询次数(默认60次,约10分钟)
interval: 查询间隔秒数(默认5秒)
Returns:
包含fileurl的字典
Raises:
Exception: 超时未完成
"""
import time
for attempt in range(max_attempts):
result = self.get_ppt_result(ppt_id)
if result.get("code") == 200:
status = result.get("data", {}).get("status", 0)
if status == 1:
# 生成完成
return result
elif status == 0:
# 生成中,继续轮询
if attempt < max_attempts - 1:
time.sleep(interval)
continue
raise Exception(f"PPT生成超时,已等待{max_attempts * interval}秒")
def format_outline(response_data: Dict[str, Any], style: str = "emoji") -> str:
"""
格式化大纲为可读文本(支持多种渲染风格)
Args:
response_data: API响应数据
style: 渲染风格
- "emoji": Emoji增强格式(默认,适合企业微信/飞书)
- "markdown": Markdown标题格式(适合支持Markdown的平台)
- "simple": 编号列表格式(简洁)
- "tree": 树状结构(层级清晰)
Returns:
格式化后的文本
"""
# 检查响应状态
if response_data.get("code") != 200:
return f"❌ 错误: {response_data.get('message', '未知错误')}"
data = response_data.get("data", {})
# 解析outLineTree(JSON字符串)
out_line_tree_str = data.get("outLineTree", "{}")
try:
out_line_tree = json.loads(out_line_tree_str)
except json.JSONDecodeError:
return "❌ 错误: 大纲格式解析失败"
title = out_line_tree.get("title", "未命名大纲")
bullets_map = out_line_tree.get("titleBulletsListMap", {})
# 获取第一个ppt的章节
ppt_key = f"{title}_mypptpid_1"
sections = bullets_map.get(ppt_key, {})
# 根据风格选择格式化方式
if style == "markdown":
return _format_markdown(title, sections)
elif style == "simple":
return _format_simple(title, sections)
elif style == "tree":
return _format_tree(title, sections)
else: # emoji
return _format_emoji(title, sections)
def _format_emoji(title: str, sections: Dict[str, str]) -> str:
"""Emoji增强格式(适合企业微信/飞书)"""
text = f"📊 **{title}**\n\n"
emoji_numbers = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]
for i, (section_title, content) in enumerate(sections.items()):
emoji_num = emoji_numbers[i] if i < len(emoji_numbers) else f"{i+1}."
text += f"{emoji_num} **{section_title}**\n"
if content:
preview = content[:80] + "..." if len(content) > 80 else content
text += f" 📝 {preview}\n\n"
return text
def _format_markdown(title: str, sections: Dict[str, str]) -> str:
"""Markdown标题格式(适合支持Markdown的平台)"""
text = f"## 📊 {title}\n\n"
for i, (section_title, content) in enumerate(sections.items(), 1):
text += f"### {i}. {section_title}\n"
if content:
preview = content[:100] + "..." if len(content) > 100 else content
text += f"> {preview}\n\n"
return text
def _format_simple(title: str, sections: Dict[str, str]) -> str:
"""编号列表格式(简洁)"""
text = f"【{title}】\n\n"
for i, (section_title, content) in enumerate(sections.items(), 1):
text += f"**{i}. {section_title}**\n"
if content:
preview = content[:80] + "..." if len(content) > 80 else content
text += f" {preview}\n\n"
return text
def _format_tree(title: str, sections: Dict[str, str]) -> str:
"""树状结构(层级清晰)"""
text = f"📚 {title}\n"
for i, (section_title, content) in enumerate(sections.items()):
is_last = (i == len(sections) - 1)
prefix = "└──" if is_last else "├──"
text += f"{prefix} 📌 {section_title}\n"
if content:
preview = content[:60] + "..." if len(content) > 60 else content
indent = " " if is_last else "│ "
text += f"{indent}└── {preview}\n"
return text
def format_templates(templates_data: Dict[str, Any]) -> str:
"""
格式化模板列表为可读文本
Args:
templates_data: 模板数据
Returns:
格式化后的文本
"""
templates = templates_data.get("templates", [])
text = "请选择PPT模板:\n\n"
for i, template in enumerate(templates, 1):
name = template.get("name", "")
description = template.get("description", "")
preview_url = template.get("previewurl", "")
text += f"{i}. {name}\n"
text += f" {description}\n"
if preview_url:
text += f" [预览]({preview_url})\n"
text += "\n"
text += "请回复模板编号选择模板。"
return text
def recommend_tags(main_idea: str, outline_content: Optional[str] = None) -> Dict[str, List[str]]:
"""
根据主题智能推荐风格和颜色标签
Args:
main_idea: PPT主题
outline_content: 大纲内容(可选)
Returns:
{
"style_tags": ["商务风"], # 在范围内的风格标签
"color_tags": ["蓝色"] # 在范围内的颜色标签
}
"""
style_tags = []
color_tags = []
# 关键词匹配规则
style_keywords = {
"简约风": ["简约", "简单", "清晰", "简洁", "现代"],
"小清新": ["清新", "青春", "校园", "自然", "文艺"],
"商务风": ["商务", "企业", "公司", "汇报", "总结", "工作", "项目", "年终", "季度"],
"中国风": ["中国", "传统", "古典", "国风", "历史", "文化", "水浒", "西游", "红楼", "三国"],
"可爱卡通": ["可爱", "卡通", "儿童", "幼儿园", "童趣", "小朋友"],
"科技风": ["科技", "技术", "互联网", "数字", "智能", "AI", "创新", "产品发布"],
"手绘风格": ["手绘", "插画", "艺术", "创意"],
"欧美风": ["欧美", "国际", "西方"],
"党政风": ["党政", "政府", "党建", "红色", "革命", "思想", "春天", "邓小平"],
"黑板风": ["教育", "培训", "教学", "课程", "学习", "知识"]
}
color_keywords = {
"蓝色": ["商务", "科技", "企业", "稳重", "专业"],
"红色": ["热情", "重要", "党政", "革命", "中国", "传统"],
"粉色": ["可爱", "女性", "温柔", "少女", "儿童"],
"黄色": ["活力", "阳光", "温暖", "创意", "青春"],
"绿色": ["环保", "自然", "健康", "清新", "春天"],
"橙色": ["活力", "创新", "年轻"],
"黑色": ["专业", "高端", "商务", "稳重"],
"白色": ["简约", "纯净", "现代"],
"灰色": ["商务", "专业", "稳重"],
"紫色": ["优雅", "创意", "科技"]
}
# 合并主题和大纲内容进行分析
text_to_analyze = main_idea
if outline_content:
text_to_analyze += " " + outline_content
text_lower = text_to_analyze.lower()
# 匹配风格标签
for style, keywords in style_keywords.items():
if any(keyword in text_lower for keyword in keywords):
style_tags.append(style)
# 匹配颜色标签
for color, keywords in color_keywords.items():
if any(keyword in text_lower for keyword in keywords):
color_tags.append(color)
# 去重并限制数量
style_tags = list(dict.fromkeys(style_tags))[:2] # 最多2个风格标签,保持顺序
color_tags = list(dict.fromkeys(color_tags))[:2] # 最多2个颜色标签,保持顺序
# 验证标签是否在范围内
valid_style = [tag for tag in style_tags if tag in VALID_STYLE_TAGS]
valid_color = [tag for tag in color_tags if tag in VALID_COLOR_TAGS]
return {
"style_tags": valid_style,
"color_tags": valid_color
}
def format_templates(templates: List[Dict[str, Any]], channel: str = "wecom") -> str:
"""
格式化模版列表展示
Args:
templates: 模版列表
channel: 渠道(wecom/feishu/telegram等)
Returns:
格式化后的模版列表字符串
"""
if not templates:
return "未找到合适的模版"
output = f"找到 {len(templates)} 个模版:\n\n"
# 使用简单列表格式(不显示模版ID和预览)
for i, t in enumerate(templates, 1):
name = t.get('fileName', '未命名')
output += f"{i}. {name}\n"
output += f"\n请选择模版编号(1-{len(templates)}):"
return output
def format_ppt_result(theme: str, ppt_id: str, download_url: str, channel: str = "wecom") -> str:
"""
格式化PPT生成结果输出
Args:
theme: PPT主题
ppt_id: PPT ID
download_url: 下载链接
channel: 渠道
Returns:
格式化后的输出字符串
"""
output = f"""## ✅ PPT生成成功!
### 📋 生成信息:
- **主题**: {theme}
- **PPT ID**: `{ppt_id}`
---
### 📥 PPT下载链接:
```
{download_url}
```
**点击链接即可下载PPT文件!**
---
## 🎉 PPT生成完成!
---
**本功能由名阳信息技术有限公司提供**
如需使用完整功能,请下载APP应用或访问网站:
- 📱 APP:各大应用商店搜索"mindppt"
- 🌐 网站:https://mindppt.net"""
return output
# 示例用法
if __name__ == "__main__":
# 示例:从命令行参数获取用户信息
if len(sys.argv) < 5:
print("用法: python ppt_api.py <sender_id> <sender> <chat_id> <channel>")
print("示例: python ppt_api.py user123 张三 wecom:chat001 wecom")
sys.exit(1)
sender_id = sys.argv[1]
sender = sys.argv[2]
chat_id = sys.argv[3]
channel = sys.argv[4]
# 创建客户端
client = PPTAPIClient(sender_id, sender, chat_id, channel)
# 完整流程示例
try:
print("=" * 60)
print("PPT生成完整流程")
print("=" * 60)
# 1. 生成大纲
print("\n[Step 1] 生成大纲...")
result = client.generate_outline("年终总结")
if result.get("code") == 200:
outline_id = result.get("data", {}).get("id")
print(f"大纲ID: {outline_id}")
# 2. 智能推荐标签
print("\n[Step 2] 智能推荐标签...")
tags = recommend_tags("年终总结")
keywords = tags["style_tags"] + tags["color_tags"]
print(f"推荐标签: {keywords}")
# 3. 查询模版
print("\n[Step 3] 查询模版...")
templates = client.get_templates(keywords)
if templates.get("code") == 200 and templates.get("data"):
template_id = templates["data"][0].get("templateId")
print(f"选择模版ID: {template_id}")
# 4. 提交PPT任务
print("\n[Step 4] 提交PPT任务...")
task = client.submit_ppt_task(
template_id=template_id,
outline_id=outline_id,
reporter="测试用户"
)
if task.get("code") == 200:
ppt_id = task.get("data", {}).get("id")
print(f"PPT任务ID: {ppt_id}")
# 5. 轮询查询结果
print("\n[Step 5] 等待PPT生成...")
print("(提示:实际使用时建议异步处理)")
# 注意:这里只是示例,实际使用时建议异步轮询
# final_result = client.wait_for_ppt(ppt_id)
# fileurl = final_result.get("data", {}).get("fileurl")
# print(f"下载链接: {fileurl}")
else:
print(f"提交任务失败: {task}")
else:
print("未找到合适的模版")
else:
print(f"生成大纲失败: {result.get('message', '未知错误')}")
except Exception as e:
print(f"[错误] {e}")
import traceback
traceback.print_exc()
FILE:references/api-endpoints.md
# API Endpoints Reference
PPT生成服务API接口文档
**重要说明**:
- **统一接口地址**:所有操作使用同一个URL
- **通过请求参数区分不同操作**
- **接口地址**: `https://ai.mingyangtek.com/aippt/api/c=15109`
**通用Header**:
```http
Content-Type: application/json
X-Userid: {sender_id} # 用户标识
X-Sender: {sender} # 用户名称
X-Chatid: {chat_id} # 会话ID
X-Channel: {channel} # 渠道(wecom/feishu/telegram等)
```
**命名规范**:
- **Header字段**:使用小写,如 `X-Userid`, `X-Chatid`
- **Body字段**:使用驼峰命名,如 `mainIdea`, `outLineTree`
- **Response字段**:使用驼峰命名,如 `mainIdea`, `outLineTree`
---
## 1. 生成大纲接口
### 请求
```http
POST https://ai.mingyangtek.com/aippt/api/c=15109
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"mainIdea": "沁园春", // 必需 - PPT主题/主要想法(驼峰命名)
}
```
### 响应示例
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": "9f247d90c8044b10b46dc536bdd724e8", // 大纲唯一标识
"userId": null,
"mainIdea": "沁园春",
"markdown": "# 水浒传\n## 水浒传的文本与历史渊源\n### 水浒传的历史背景\n...",
"outLineTree": "{\"children\":[...],\"level\":1,\"title\":\"水浒传\",\"titleBulletsListMap\":{...}}",
"outputLanguage": "en",
"depth": 5,
"modelCode": "Default"
}
}
```
**重要字段说明**:
- `code`: 状态码,200表示成功
- `message`: 消息,SUCCESS表示成功
- `data.id`: 大纲唯一标识(用于后续操作)
- `data.mainIdea`: PPT主题
- `data.markdown`: Markdown格式的内容
- `data.outLineTree`: JSON字符串格式的大纲树结构,需要解析使用
**outLineTree 解析后的结构**:
```json
{
"children": [...],
"level": 1,
"title": "水浒传",
"titleBulletsListMap": {
"水浒传的历史背景_mypptpid_1": {
"宋江末年农民起义": "宋江末年农民起义",
"农民起义领袖": "农民起义领袖",
"被压迫反抗的真实": "被压迫反抗的真实"
},
...
}
}
```
---
## 2. 编辑大纲接口
### 请求
```http
POST https://ai.mingyangtek.com/aippt/api/c=15110
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"outlineId": "9f247d90c8044b10b46dc536bdd724e8",
"userId": "1234",
"markDownStr": "# 水浒传\n## 水浒传的背景与起源\n### 水浒传的历史背景\n- 北宋末年社会动荡\n- 农民起义频发\n..."
}
```
**参数说明**:
- `outlineId`:大纲ID(从生成大纲接口返回的`data.id`)
- `userId`:用户ID
- `markDownStr`:完整的Markdown格式大纲内容
**注意**:
- 接口地址不同于生成大纲:`c=15110`
- 所有参数使用驼峰命名
- `markDownStr`需要包含完整的大纲结构
### 响应示例
```json
{
"code": 200,
"message": "SUCCESS",
"data": "编辑成功"
}
```
**响应字段说明**:
- `code`: 状态码,200表示成功
- `message`: 消息,SUCCESS表示成功
- `data`: 返回"编辑成功"字符串
---
## 3. 查询模版列表接口
### 请求
```http
POST https://ai.mingyangtek.com/aippt/api/c=15111
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"keywords": [
"简约风",
"蓝色"
],
"userId": "1234"
}
```
**参数说明**:
- `keywords`:标签数组,可以传多个标签(风格标签和颜色标签)
- `userId`:用户ID
**标签范围**:
**风格标签**:
- 简约风、小清新、商务风、中国风、可爱卡通
- 科技风、手绘风格、欧美风、党政风、黑板风
**颜色标签**:
- 蓝色、红色、粉色、黄色、绿色
- 橙色、黑色、白色、灰色、紫色
**注意**:
- keywords可以传多个标签
- 标签可以是风格标签、颜色标签的任意组合
- 建议使用智能标签推荐功能自动生成
### 响应示例
```json
{
"code": 200,
"message": "SUCCESS",
"data": [
{
"templateId": "template_001",
"templateName": "商务简约蓝",
"templateUrl": "https://...",
"previewUrl": "https://...",
"keywords": ["简约风", "蓝色"]
},
{
"templateId": "template_002",
"templateName": "科技蓝调",
"templateUrl": "https://...",
"previewUrl": "https://...",
"keywords": ["科技风", "蓝色"]
}
]
}
```
**响应字段说明**:
- `code`: 200表示成功
- `message`: SUCCESS表示成功
- `data`: 模版列表数组
- `templateId`: 模版ID
- `templateName`: 模版名称
- `templateUrl`: 模版文件URL
- `previewUrl`: 预览URL
- `keywords`: 模版标签
---
## 4. 提交创作PPT任务接口(异步)
### 请求
```http
POST https://ai.mingyangtek.com/aippt/api/c=15112
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"templateId": 39,
"outlineId": "9f247d90c8044b10b46dc536bdd724e8",
"reporter": "汇报人",
"userId": "1234"
}
```
**参数说明**:
- `templateId`:模版ID(数字类型,从查询模版列表接口返回)
- `outlineId`:大纲ID(字符串,从生成大纲接口返回)
- `reporter`:汇报人姓名(字符串)
- `userId`:用户ID
**注意**:
- 这是异步接口,提交后会立即返回任务ID
- PPT生成需要一定时间,需要轮询查询状态
- `templateId`是数字类型,不是字符串
### 响应示例
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"id": 2086770
}
}
```
**响应字段说明**:
- `code`: 200表示成功
- `message`: SUCCESS表示成功
- `data.id`: PPT任务ID(数字类型,用于查询生成状态)
**注意**:返回的是`id`字段,用于后续查询任务状态
---
## 5. 查询PPT生成结果接口
### 请求
```http
POST https://ai.mingyangtek.com/aippt/api/c=15113
Content-Type: application/json
X-Userid: {sender_id}
X-Sender: {sender}
X-Chatid: {chat_id}
X-Channel: {channel}
{
"pptId": 2086770,
"userId": "1234"
}
```
**参数说明**:
- `pptId`:PPT任务ID(数字类型,从提交任务接口返回的`data.id`)
- `userId`:用户ID
**注意**:
- PPT生成需要一定时间,需要轮询查询
- 建议5-10秒查询一次
- 最多查询60次(约10分钟)
### 响应示例
**生成中**:
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"fileurl": "",
"status": 0
}
}
```
**生成完成**:
```json
{
"code": 200,
"message": "SUCCESS",
"data": {
"fileurl": "https://aipptus.oss-us-west-1.aliyuncs.com/worksdate/20260310/16198169/T2031281446055038976/1773130263007.pptx",
"status": 1
}
}
```
**响应字段说明**:
- `code`: 200表示成功
- `message`: SUCCESS表示成功
- `data.fileurl`: PPT文件下载链接(OSS直链)
- `data.status`: 任务状态
- `0`: 生成中
- `1`: 生成完成
**使用说明**:
1. 提交PPT任务后,获取`id`
2. 使用该`id`轮询查询状态
3. 当`status=1`时,返回`fileurl`下载链接
4. 用户可直接点击下载链接获取PPT
---
## 用户信息字段说明
### sender_id (X-Userid)
- **来源**: 从消息上下文的 `sender_id` 字段获取
- **格式**: 字符串,例如 `openclaw-control-ui`
- **用途**: 用户唯一标识,用于限流和用户追踪
- **传递方式**: 通过Header `X-Userid` 传递
### sender (X-Sender)
- **来源**: 从消息上下文的 `Sender.label` 字段获取
- **格式**: 字符串,例如 `openclaw-control-ui`
- **用途**: 用户显示名称
- **传递方式**: 通过Header `X-Sender` 传递
### chat_id (X-Chatid)
- **来源**: 从消息上下文的 `chat_id` 字段获取
- **格式**: 字符串,例如 `wecom:JiaJunHao`
- **用途**: 会话标识,区分不同对话
- **传递方式**: 通过Header `X-Chatid` 传递
### channel (X-Channel)
- **来源**: 从消息上下文的 `channel` 字段获取
- **格式**: 字符串,枚举值:`wecom`, `feishu`, `telegram`, `discord`, `slack` 等
- **用途**: 标识用户来自哪个渠道
- **传递方式**: 通过Header `X-Channel` 传递
**重要**:所有字段名使用小写,不使用驼峰
---
## 错误响应格式
所有接口在出错时返回统一的错误格式:
```json
{
"timestamp": 1773975110447,
"status": 404,
"error": "Not Found",
"path": "/aippt/api/c=15109"
}
```
### 常见错误码:
- `200` - 成功
- `400 Bad Request` - 参数错误
- `404 Not Found` - 资源不存在
- `429 Too Many Requests` - 请求频率超限
- `500 Internal Server Error` - 服务器内部错误
---
## 接口调用示例
### 示例:生成大纲
```bash
curl -X POST https://ai.mingyangtek.com/aippt/api/c=15109 \
-H "Content-Type: application/json" \
-H "X-Userid: openclaw-control-ui" \
-H "X-Sender: openclaw-control-ui" \
-H "X-Chatid: wecom:JiaJunHao" \
-H "X-Channel: wecom" \
-d '{"mainIdea":"沁园春"}'
```
---
## 待确认事项
以下事项需要后端开发时确认:
1. **修改大纲接口** - 请求参数格式
2. **获取模板列表接口** - 请求参数格式
3. **生成PPT接口** - 请求参数格式
4. **认证方式** - 是否需要额外的API Key或Token?
5. **异步处理** - PPT生成是否需要异步处理?
6. **OSS配置**:
- Bucket名称和地址
- 链接有效期(建议24小时)
- 是否需要CDN加速?
7. **限流策略**:具体的限流规则
8. **预览页面**:模板预览页面的实现方式
---
此文档会根据后端实现情况持续更新。