AI PPT 生成
AI PPT,基于 LLM 大模型,实现粘贴大纲或上传材料,一键生成演示文稿的功能。它的实现原理是:
- 先由对话模型,把大纲或材料拆分成每页的分镜(主题、标题、要点、生图提示词)
- 再由绘图模型,把每页画成一张 16:9 的整页图,标题、要点直接画在图里
生成完成后,可以预览、修改单页、切换历史版本,并导出 PPTX、PDF。导出的文件每页是一张整页图,没有可编辑的文本框。
方案的不足:
这种「LLM 拆分镜 + 逐页生图,文字直接画在图里」的思路,上手快、画面风格统一,但也有明显的限制:
- 文字由绘图模型画出,可能出现错字、乱码;改一个字也只能改提示词重新生图,构图可能整体变化
- 导出的 PPTX、PDF 每页只是一张图,不能二次编辑文字、调整排版,也不能复制文字
后续,我们计划基于 AI Agent 生成支持二次编辑的 PPT,文字、图片等元素可以单独修改。

整个功能,涉及到 3 个表:
- 【配置】
ai_api_key:API 密钥表 - 【配置】
ai_model:模型表 - 【PPT】
ai_ppt:PPT 表
补充说明:
ai_api_key、ai_model 已经在 《功能开启》 讲解,就不重复赘述。
AI PPT 需要导入对应的 SQL,包括:ai_ppt 表,[AI 大模型 -> AI PPT]、[AI 大模型 -> 控制台 -> PPT 管理] 菜单,ai_ppt_status 字典,以及「AI PPT 超时 Job」定时任务。
# 1. 前置准备
# 1.1 配置对话模型
生成分镜时,使用 [模型配置] 中排序最靠前、已开启的对话模型,并记录到 ai_ppt 表的 chat_model_id 字段,后续继续生成也沿用它。
对话模型的配置,可见 《功能开启》 的「4.2 模型表」小节。
# 1.2 配置绘图模型
在 [AI 大模型 -> 控制台 -> 模型配置] 菜单,新增一个模型:
- 所属平台:目前支持 OpenAI、硅基流动、通义千问;Stable Diffusion、Midjourney 等平台的绘图模型不支持
- 模型类型:选择「图像」,对应 AiModelTypeEnum 的 IMAGE(
type为 2) - API 秘钥:选择同一平台的密钥。密钥的平台与模型的平台不一致时,生图会报错
- 模型标识:填写平台的绘图模型标识,例如说 OpenAI 的
gpt-image-2、硅基流动的Qwen/Qwen-Image

每页按平台使用接近 16:9 的固定尺寸生图,选择的模型需要支持该尺寸:
| 平台 | 尺寸 | 接入文档 |
|---|---|---|
OpenAI(gpt-image 开头的模型) | 1536x1024 | 《【模型接入】OpenAI》 |
| OpenAI(其它模型) | 1792x1024 | 《【模型接入】OpenAI》 |
| 硅基流动 | 1664x928 | 《【模型接入】硅基流动》 |
| 通义千问 | 1280x720 | 《【模型接入】通义千问》 |
可以配置多个绘图模型,用户创建时在下拉框中选择。
# 1.3 开启定时任务
生成是在后端异步执行的。如果服务重启等原因中断了生成,任务会一直处于“生成中”,既不能继续生成,也不能删除。
所以需要定时执行 AiPptTimeoutJob 任务(JobHandler 名字为 aiPptTimeoutJob),把超过 30 分钟没有进展的任务结束掉。建议每分钟执行一次,即 Cron 表达式 0 * * * * ?。
具体怎么配置定时任务,可以参考 《定时任务》 文档。SQL 里已经有「AI PPT 超时 Job」任务,只是处于暂停状态,开启即可。
# 2. PPT 生成
前端对应 [AI 大模型 -> AI PPT] 菜单,对应 yudao-ui-admin-vue3 项目的 @/views/ai/ppt/index 目录,提供给普通用户使用。
左边是创建表单,右边是【我的 PPT】。有任务在生成中时,【我的 PPT】每 3 秒自动刷新。后端 HTTP 接口,由 yudao-module-ai 模块的 ppt 包的 AiPptController 实现。
# 2.1 创建 PPT
在左侧表单填写:
- 格式:「详细演示文稿」图片上的说明文字多一些,适合单独阅读;「演示用幻灯片」只放标题、少量关键词,配合演讲使用
- 选择语言:中文或 English,分镜和图片里的文字都使用该语言
- PPT 页数:3 ~ 30 页,生成分镜时作为页数约束。「时长」的短、默认,分别对应 6、10 页
- 绘图模型:只列出 OpenAI、硅基流动、通义千问平台的绘图模型
- 素材:「粘贴大纲」和「上传材料」二选一。粘贴大纲最多 25000 个字符,建议使用 Markdown 标题等分层级的大纲
- 风格与设计要求:选填,例如说“极简商务风”“卡通手绘风”
点击【生成】后,【我的 PPT】会新增一条“生成中”的记录,依次展示“正在生成分镜…”“正在生成第 N 页”。标题先取大纲的第一行(上传材料时取文件名),分镜生成后替换为模型给出的标题。
选择「上传材料」时,支持 PDF、Word、TXT、Markdown 格式,大小不超过 10MB:

- 选择文件后,前端先通过项目通用的文件上传,把文件保存到文件服务,提交时只传文件地址(
fileUrl) - 生成分镜时,后端按地址下载文件,使用 Tika 读取文本,超过 25000 字的部分截断。私有存储会使用预签名地址读取,可见 《文件存储(上传下载)》 的「7. 私有桶支持」小节
# 2.2 预览与修改
点击记录的封面或标题,打开预览:左边是当前页的大图,右边是每页的缩略图。生成中时,已经生成的页可以先看。点击【显示提示词】,可以查看当前页的生图提示词。

某一页不满意时,点击【修改】按钮,打开「修改第 N 页」:
- 生图提示词:默认是本页当前图的完整提示词,可以直接修改,例如说改掉画错的字、换配色。点击【重新生成】后,按修改后的提示词重新文生图(不参考当前图),成功后追加为新版本,并设为当前图;失败时当前图不变
- 历史版本:本页所有成功生成过的图,点击即可切换为当前图,不重新生成

重绘期间,任务回到“生成中”;重绘结束后(无论成败),每页都有图时回到“已完成”。
# 2.3 我的 PPT
在【我的 PPT】中,每条记录有如下操作:
- 查看提示词:创建时填写的风格与设计要求
- 查看大纲:素材(上传材料时为读取出的文本),以及每页的主题、标题、要点
- 继续生成:仅已失败的任务。没有分镜时重新生成分镜,否则只补齐没有图的页,已生成的图保留
- 导出:仅已完成的任务,可以导出 PPTX 或 PDF。页面为 16:9,每页一张整页图,等比缩放、居中
- 删除:仅已完成、已失败的任务。图片文件不随任务删除

# 3. PPT 管理
前端对应 [AI 大模型 -> 控制台 -> PPT 管理] 菜单,对应 yudao-ui-admin-vue3 项目的 @/views/ai/ppt/manager 目录,提供给管理员使用,查看所有用户的 PPT 记录,可以预览、查看大纲(只读)、导出、删除。
对应的按钮权限为 ai:ppt:query(查询、预览、导出)、ai:ppt:delete(删除)。

# 4. PPT 表
ai_ppt:PPT 表,一套 PPT 一条记录。每页的分镜、当前图、历史版本,都放在 slides JSON 字段里。
省略 creator/create_time/updater/update_time/deleted/tenant_id 等通用字段
CREATE TABLE `ai_ppt` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`user_id` bigint NOT NULL COMMENT '用户编号',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题',
`source` tinyint NOT NULL COMMENT '素材来源:1 粘贴大纲 2 上传材料',
`content` longtext NULL COMMENT '大纲或材料文本',
`file_url` varchar(1024) NULL COMMENT '上传材料文件地址',
`prompt` varchar(1024) NOT NULL DEFAULT '' COMMENT '风格与设计要求',
`mode` tinyint NOT NULL COMMENT '格式:1 详细演示文稿 2 演示用幻灯片',
`language` varchar(16) NOT NULL DEFAULT 'zh-CN' COMMENT '语言',
`page_count` int NOT NULL COMMENT '请求页数',
`chat_model_id` bigint NOT NULL COMMENT '对话模型编号(生成分镜)',
`image_model_id` bigint NOT NULL COMMENT '绘图模型编号(整页图)',
`slides` longtext NULL COMMENT '分镜与配图(JSON 数组)',
`cover_url` varchar(1024) NULL COMMENT '封面图地址',
`slide_count` int NOT NULL DEFAULT 0 COMMENT '分镜页数',
`finish_count` int NOT NULL DEFAULT 0 COMMENT '已有配图页数',
`status` tinyint NOT NULL COMMENT '状态:10 生成中 20 已完成 30 已失败',
`attempt` int NOT NULL DEFAULT 0 COMMENT '生成轮次:每次进入生成中加 1,丢弃上一轮迟到的结果',
`error_message` varchar(1024) NULL COMMENT '失败原因',
`finish_time` datetime NULL COMMENT '完成时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status_update_time` (`status`, `update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI PPT 表';
① user_id 字段:对应 system_users 表的 id 字段,表示哪个用户的 PPT。
② title、source、content、file_url、prompt、mode、language、page_count 字段:创建参数。
source字段:素材来源,对应 AiPptSourceEnum 枚举。粘贴大纲时,content为大纲;上传材料时,记录file_url,生成分镜时再把读取出的文本写入contentmode字段:格式,对应 AiPptModeEnum 枚举,决定图片上文字的多少
③ chat_model_id、image_model_id 字段:生成分镜的对话模型、生成整页图的绘图模型,对应 ai_model 表的 id 字段。
④ slides 字段:分镜与配图,JSON 格式,对应 AiPptDO 的 Slide 内部类。每页包括:页码 index、主题 theme、标题 title、要点 points、生图提示词 imagePrompt、当前图 imageUrl、本页状态 status、失败原因 errorMessage,以及历史版本 images(每个版本记录图片地址 url、完整提示词 prompt、生成时间 createTime)。
cover_url、slide_count、finish_count 字段:冗余 slides 的第一张图、页数、有图的页数,用于列表展示封面和进度。
⑤ status 字段:PPT 状态,对应 AiPptStatusEnum 枚举、ai_ppt_status 字典,每页的 status 也使用它。整体流程是:
- 用户提交时,先插入一条 10 生成中的记录,此时
slides为空,表示还在分镜阶段 - 分镜生成后,按页码逐页串行生图,每页的图都保存到文件服务
- 每页都有图,更新为 20 已完成;分镜或任一页生图失败,更新为 30 已失败,已生成的图保留,失败原因记录在
error_message字段 - 继续生成、单页重绘时,回到 10 生成中,结束后再按每页是否有图,更新为已完成或已失败
- AiPptTimeoutJob 把超过 30 分钟没有进展(
update_time未变化)的生成中任务结束,失败原因为“生成超时”
attempt 字段:生成轮次。每次进入生成中(新建、继续生成、单页重绘),以及切换版本时加 1。异步生成只在状态、轮次与开始时一致时才写入,超时后迟到的结果会被丢弃,不会覆盖新一轮;基于旧数据的并发修改也会失败,提示刷新后重试。
finish_time 字段:完成时间,已完成、已失败时记录。
最最最关键的代码!!!大家可以重点看看!!!
AiPptServiceImpl 的实现,调用链路如下:
#generatePpt(...):校验绘图模型、获取默认对话模型,插入生成中的记录后,异步执行#executeGeneratePpt(...)#executeGeneratePpt(...):- 上传材料时,先通过 AiDocumentUtils 的
#readUrl(...)方法,使用 Tika 读取文件文本 - 调用 Spring AI 的 ChatModel 生成分镜(JSON)
- 逐页调用 Spring AI 的 ImageModel 文生图,生图参数由 AiImageUtils 的
#buildImageOptions(...)方法按平台构建,生成的图片通过 FileApi 保存到文件服务
- 上传材料时,先通过 AiDocumentUtils 的
#resumePptMy(...):继续生成,重新进入#executeGeneratePpt(...)#redrawSlideMy(...)、#updateSlideImageMy(...):单页重绘、切换历史版本#exportPpt(...):下载每页的当前图,通过 AiDocumentUtils 的#buildPptx(...)、#buildPdf(...)方法拼接成文件#timeoutPpt():由 AiPptTimeoutJob 定时调用,结束超时的任务