GenAI风起云涌之后,AI+办公、AI+提效的应用层出不穷。但是有几个真正好用到心坎里去的?
今天介绍的是自己手搓的小项目: 👉 ai-pptx 👈
一、背景
为什么不用市面上的AI-PPT呢?
- 很多打工人使用PPT,基本都是工作汇报相关,都得用公司模板啊!那些软件不支持呀!
- 我想精心整个PPT,但是觉得上面给的那些模板都很low,那咋办!
要是能支持自己提供的PPT模板,AI生成内容。
Beauty In Beauty Out,岂不美哉?
二、实现
我们先看效果:
模板内容
内容重填后
基于上面背景,我们需要将一个PPT模板”为自己所用“,必须要想办法把文本框都拉出来,并知道每个文本框应该填什么东西。
这里涉及几个点:
- PPTX模板的文本框需要是参数的形式,便于代码检出
- 结合主题使用LLM生成提纲
- 为了PPT出来更多样性,在对内容页做随机抽取
- 基于提纲和模板页参数,使用LLM生成内容
- 生成的内容需要重写回填到PPT上
模板参数化及抽取
PPT模板的文本框参数化是个麻烦事儿,暂时是手动重写成参数,并用'{param}'来重写,并且里面的参数必须带有语义化。例如,同个类型的文本框,需要列举多个内容,就需要参数带上编号,sub_title_1 \ sub_title_2 等等。
对应编写好后,使用代码检出参数:
def _extract_params_from_template(self):
"""生成PPT文件"""
# 注意提取参数时,需要把模板PPT中的<组合>都解锁,不然可能存在找不到文本框的情况
start_slide_idx = 0
catalogue_slide_idx = 1
title_slide_idx = 2
content_slide_idxs = [3, 4, 5, 6, 7, 8]
end_slide_idx = 9
template_params = {
"first_slide": {"nos": [start_slide_idx], "params": []},
"catalogue_slide": {"nos": [catalogue_slide_idx], "params": []},
"title_slide": {"nos": [title_slide_idx], "params": []},
"content_slide": {"nos": content_slide_idxs, "params": []},
"end_slide": {"nos": [end_slide_idx], "params": []}
}
# PPT中同一页的{params}定义必须不同,避免混淆,不同页面不做要求
for slide_name, slide_info in template_params.items():
nos = slide_info["nos"]
for n in nos:
slide = self.template_ppt.slides[n]
temp_params = []
for shape in slide.shapes:
if shape.has_text_frame:
for paragraph in shape.text_frame.paragraphs:
for run in paragraph.runs:
matches = [match.group(1) for match in re.finditer(self.PPT_PARAM_PATTERN, run.text)]
temp_params.extend(matches)
slide_info["params"].append(temp_params)
return template_params
这里我把幻灯片分为了五类:首页、目录页、标题页、内容页、结束页,需要分别填入对应页码id (from 0)。
实际上我们真正要做生成的是内容页(content_slide)。但是我都做了检出,方便回填时保持一致。
大纲生成
上述已实现code去检出所有参数。
接下来就是生成大纲,简单用LLM生成一个,并且考虑到多样性,prompt如下:
## Goals:
请根据《{topic}》主题,用{language}语言,列出紧扣主题的PPT大纲及内容,其中目录要有4个title、每个title中4个sub-title,,最后严格按照[Output Format]输出。
## Output Format:
{output_format}
output_format根据实际需要即可,title数量和sub-title数量都可以自定义。
内容生成
有了大纲,剩下就是怎么把标题、内容对应参数填入。
并且考虑到PPT的多样性,我们在content_slide中随机抽取模板参数,并使用prompt和目标提纲标题内容,基于参数生成内容。
def llm_generate_content_slide_content(self, topic: str, online_content: str):
"""根据大纲生成完整内容"""
logger.info(f"online_content: \n{online_content}")
online_content = json.loads(online_content)
current_online_content = online_content["pages"]
content_slide = self.template_params["content_slide"]
# 新增标题编号、子标题编号
for idx, c in enumerate(current_online_content):
c["no"] = idx + 1
for c in current_online_content:
for idx, s in enumerate(c["pages"]):
s["sub_no"] = idx + 1
title_count = len(current_online_content)
resorted_no_idxs = random.sample(range(len(content_slide["nos"])), k=title_count)
current_template_resort_nos = [content_slide["nos"][idx] for idx in resorted_no_idxs]
...
内容补充并填充的prompt如下:
## OnlineJson
```{oc}```
## TemplateParamsJson
```{tp}```
# Tasks
严格参照[Info.TemplateParamsJson],基于《{topic}》中的`{title}`标题的内容,对应填充[Info.OnlineJson],最后按照markdown的json格式输出。
注意:json的key值严格对应[Info.TemplateParamsJson],key对应的值不能存在列表或字典。
------
output:
生成后,二者基于映射关系,再进行回填即可。
这里代码太多,就不做展示了,具体内容可参考项目 ai-pptx 的 utils/pptx_generator.py
。
三、技术细节QA
首页、目录页咋操作
其实都可以模仿content_slide的方式操作即可(只不过我懒...),不然就是单纯写死填充。
能否自动参数化
已经在做了hhh,必须使用LLM,但是稳定性欠佳。
思路是:结合slides-shape中group层级+本身模板的文本内容,让LLM反推该文本框的内容类型和属性。
为什么用win32来实现slide的copy
因为python中的pptx库本身不支持copy slide,找到的方案在拷贝图片时会出现偶发的图片丢失属性的问题。使用下来,win32的pptx接口复制较为稳妥。但是这也限制了这套代码只能适用于Windows用户,后续我会补上新的拷贝方案,供多种选择。
That's All.
项目地址: ai-pptx 欢迎Star & Pr
申明: 项目中用到PPT模板归金山文档所有,仅用作测试使用,不得商用。