博客5:Deepseek+Python-pptx生成ppt
1. 核心实现原理
通过Deepseek生成结构化PPT内容,再使用python-pptx库转换为实际PPT文件,主要分为两个关键步骤:
1.1 Deepseek生成PPT内容
- 设计专门的提示词模板,确保AI输出规范的JSON结构
- 控制生成内容的详细程度和格式
- 针对不同场景优化提示词
1.2 python-pptx构建PPT
- 解析AI生成的JSON内容
- 自动创建幻灯片并填充内容
- 应用统一的样式和布局
2、核心代码实现:
2.1 前端传送ppt描述给后端
主要包括内容、题目、页数、字体、主题颜色等。例如,用户可以指定PPT的风格为专业正式风格,颜色主题为蓝色系,字体为微软雅黑等,前端将这些信息封装在一个表单对象中,然后通过HTTP POST请求发送到后端指定的接口。
const formData = new FormData();
formData.append('content', this.inputContent.trim());
formData.append('title', this.pptOptions.title || '');
formData.append('slide_count', this.pptOptions.slide_count || '15');
formData.append('template_style', this.pptOptions.template_style || 'professional');
formData.append('color_theme', this.pptOptions.color_theme || 'blue');
formData.append('font_family', this.pptOptions.font_family || '微软雅黑');
formData.append('token', this.token);
formData.append('version', 'simple');
const response = await fetch('http://localhost:5300/api/ppt/simple', {
method: 'POST',
body: formData
});
2.2 后端处理
2.2.1 生成JSON
构造系统提示和用户输入消息 -> 调用AI模型生成PPT内容 -> 清理和解析生成内容 ->
验证内容结构和格式 -> 返回符合要求的PPT内容JSON
system_prompt = f"""你是一个专业PPT内容生成助手。请生成{slide_count}页PPT内容,每页内容需要包含以下信息:
1. 第一页(标题页):
- title: 主标题
- subtitle: 副标题
- description: 简要介绍PPT的主题和目的
2. 第二页(目录页):
- title: "目录"
- sections: 列出所有章节标题的列表
3. 后续内容页:
每页需要包含:
- title: 页面标题
- type: 页面类型(可选值:content, two_column, image_content)
- points: 主要内容点列表,每个点必须是一个包含main和details的对象:
{{
"main": "主要观点",
"details": ["详细说明1", "详细说明2"]
}}
- notes: 演讲者注释(可选)
4. 最后一页(结束页):
- title: "总结与问答"
- points: 总结要点列表(使用与内容页相同的point格式)
请特别注意:
- points字段中的每个元素必须是对象,不能是纯字符串
- 确保所有字符串使用双引号
- 不要包含注释或其他非JSON内容
- 段落文本(40-80字)
- 每个页面包含3个左右段落
- 段落内容使用无序列表形式
- 每段文字后最多配一张图片
- 表格需要完整呈现
- 所有标题层级严格使用#符号标记
- 避免使用其他格式元素(如加粗、斜体等)
请以严格JSON格式返回,确保格式如下:
{{
"slides": [
{{
"title": "主标题",
"subtitle": "副标题",
"description": "简介"
}},
{{
"title": "目录",
"sections": ["章节1", "章节2"]
}},
{{
"title": "章节标题",
"type": "content",
"points": [
{{
"main": "主要观点",
"details": ["详细说明1", "详细说明2"]
}}
],
"notes": "演讲者注释"
}}
]
}}
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"标题:{title}\n内容:{content}"}
]
try:
response = self.client.chat.completions.create(
model="deepseek-reasoner",
messages=messages,
temperature=0.1, # 降低温度,使生成更加确定性和详细
max_tokens=8192, # 增加最大token数,允许生成更多内容
top_p=0.95 # 调整top_p参数,保持一定的创造性同时确保质量
)
if not response.choices or len(response.choices) == 0:
raise ValueError("API响应中没有生成内容")
generated_content = response.choices[0].message.content
if not generated_content:
raise ValueError("API生成的内容为空")
# 清理和解析JSON内容
try:
content = generated_content.strip()
if content.startswith("```json"):
content = content[7:]
elif content.startswith("```"):
content = content[3:]
if content.endswith("```"):
content = content[:-3]
content = content.strip()
logger.info(f"清理后的内容: {content}")
ppt_content = json.loads(content)
# 验证内容结构
if not isinstance(ppt_content, dict) or 'slides' not in ppt_content:
raise ValueError("生成的内容缺少必要的'slides'字段")
if not isinstance(ppt_content['slides'], list):
raise ValueError("'slides'字段必须是列表类型")
# 验证points结构
for slide in ppt_content['slides']:
if 'points' in slide:
for point in slide['points']:
if not isinstance(point, dict) or 'main' not in point:
raise ValueError("points中的元素必须包含'main'字段的字典")
return ppt_content
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {str(e)}")
logger.error(f"原始内容: {generated_content}")
raise ValueError(f"生成的内容不是有效的JSON格式: {str(e)}")
except Exception as e:
logger.error(f"生成PPT内容失败: {str(e)}", exc_info=True)
raise
2.2.2 生成ppt
利用python-pptx库生成实际的PPT文件,过程包括创建一个新的演示文稿对象,设置幻灯片的尺寸为适合现代屏幕的16:9宽高比,然后根据JSON内容中的每页幻灯片数据,逐个创建幻灯片并填充内容。对于不同类型的幻灯片(如两栏布局),会应用相应的布局和样式。
file_id = uuid.uuid4().hex
base_name = f"{title}_{file_id}"
pptx_path = os.path.join(self.upload_folder, f"{base_name}.pptx")
try:
prs = Presentation()
prs.slide_width = Inches(16)
prs.slide_height = Inches(9)
for slide_data in ppt_content.get('slides', []):
if slide_data.get('type') == 'two_column':
slide = prs.slides.add_slide(prs.slide_layouts[6])
title_shape = slide.shapes.add_textbox(
Inches(1), Inches(0.5), Inches(14), Inches(1)
)
else:
slide = prs.slides.add_slide(prs.slide_layouts[1])
title_shape = slide.shapes.title
if title_shape:
title_shape.text = slide_data.get('title', '')
title_frame = title_shape.text_frame
title_paragraph = title_frame.paragraphs[0]
title_paragraph.alignment = PP_ALIGN.CENTER
title_run = title_paragraph.runs[0]
title_run.font.size = Pt(44)
title_run.font.bold = True
title_run.font.color.rgb = self.COLORS['primary']
if 'subtitle' in slide_data:
if len(slide.placeholders) > 1:
subtitle = slide.placeholders[1]
subtitle.text = slide_data['subtitle']
subtitle_frame = subtitle.text_frame
subtitle_paragraph = subtitle_frame.paragraphs[0]
subtitle_paragraph.alignment = PP_ALIGN.CENTER
subtitle_run = subtitle_paragraph.runs[0]
subtitle_run.font.size = Pt(24)
subtitle_run.font.italic = True
subtitle_run.font.color.rgb = self.COLORS['secondary']
elif 'sections' in slide_data:
if len(slide.placeholders) > 1:
content = slide.placeholders[1]
tf = content.text_frame
for section in slide_data['sections']:
p = tf.add_paragraph()
p.text = f"• {section}"
p.font.size = Pt(24)
p.font.color.rgb = self.COLORS['secondary']
p.space_after = Pt(12)
elif 'points' in slide_data:
if slide_data.get('type') == 'two_column':
left_content = slide.shapes.add_textbox(
Inches(1), Inches(2), Inches(6.5), Inches(5)
)
right_content = slide.shapes.add_textbox(
Inches(8.5), Inches(2), Inches(6.5), Inches(5)
)
mid_point = len(slide_data['points']) // 2
left_points = slide_data['points'][:mid_point]
right_points = slide_data['points'][mid_point:]
self._add_points_to_textbox(left_content.text_frame, left_points)
self._add_points_to_textbox(right_content.text_frame, right_points)
else:
if len(slide.placeholders) > 1:
content = slide.placeholders[1]
self._add_points_to_textbox(content.text_frame, slide_data['points'])
if 'notes' in slide_data:
notes_slide = slide.notes_slide
notes_slide.notes_text_frame.text = slide_data['notes']
prs.save(pptx_path)
logger.info(f"PPT文件已保存到:{pptx_path}")
return pptx_path
except Exception as e:
logger.error(f"生成PPT文件失败:{str(e)}", exc_info=True)
raise
def _add_points_to_textbox(self, text_frame, points: List[Dict[str, Any]]) -> None:
"""向文本框添加要点内容(增强容错处理)"""
for raw_point in points:
# 容错处理:如果point是字符串,转换为字典
if isinstance(raw_point, str):
point = {'main': raw_point, 'details': []}
else:
point = raw_point
# 添加主要观点
p = text_frame.add_paragraph()
p.text = f"• {point.get('main', '')}"
p.font.size = Pt(28)
p.font.bold = True
p.font.color.rgb = self.COLORS['primary']
p.space_after = Pt(12)
# 添加详细说明
for detail in point.get('details', []):
p = text_frame.add_paragraph()
p.text = f" - {detail}"
p.font.size = Pt(20)
p.font.color.rgb = self.COLORS['secondary']
p.space_after = Pt(8)
2.2.3 传送
返回包含文件路径和文件名的字典,之后便于前端客户端完成下载。
filename = os.path.basename(pptx_path)
return {
"file_path": pptx_path,
"filename": filename,
"status": "success"
}
2.3 接收
前端在接收到后端返回的响应后,会以blob格式接收存储文件,并触发下载操作。为了确保资源的合理使用,下载完成后需要注意释放Blob URL。
if (response.ok) {
// 直接处理文件响应
const blob = await response.blob();
// 从响应头获取文件名,或使用默认文件名
const filename = this.getFilenameFromResponse(response) || `${this.pptOptions.title || 'presentation'}.pptx`;
// 创建一个临时URL
const url = window.URL.createObjectURL(blob);
this.downloadUrl = url;
// 触发下载
this.downloadPPT(url, filename);
// 显示成功消息
this.$nextTick(() => {
this.errorMessage = '';
});
downloadPPT(url, filename) {
// 创建一个隐藏的a标签来触发下载
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename || `${this.pptOptions.title || 'presentation'}.pptx`;
document.body.appendChild(a);
a.click();
// 下载开始后移除元素
setTimeout(() => {
document.body.removeChild(a);
// 如果是Blob URL,需要释放
if (url.startsWith('blob:')) {
window.URL.revokeObjectURL(url);
}
}, 100);
// 显示成功消息
this.$nextTick(() => {
this.errorMessage = '';
// 设置下载URL以显示成功消息
if (!this.downloadUrl) {
this.downloadUrl = url;
}
});
}
3、优化方向
3.1 提示词优化
为了进一步提高生成内容的质量,计划增加few-shot prompt示例,这种示例提示词能够为模型提供更明确的输出方向和格式要求,从而生成更符合预期的内容。同时,优化内容长度控制参数,使生成的幻灯片内容既全面又不至于过于冗长。
3.2 python-pptx生成过程优化
在PPT生成过程中,计划预定义模板,用户可以根据自己的喜好和需求选择不同的模板,这将大大提高PPT的视觉吸引力和专业度;计划实现智能布局调整功能,根据内容的多少和类型自动优化幻灯片的布局,以确保内容的合理分布和最佳展示效果。
更多推荐



所有评论(0)