Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ node_modules/
output/
test_output/
.playwright-cli/
.vite/
temp_buid_note/

# 生成的文件
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NotebookLM's PPT feature is closer to a one-click result generator, with limited
- **Model control**: Configure separate OpenAI-compatible models for text planning, image generation, and image editing
- **Local-first config**: Manage model connections through local `config.yaml` or WebUI local API configuration; saved projects and exported files do not include API keys
- **Export-ready output**: Export generated decks to PDF/PPTX for presentation or further editing
- **Experimental high-fidelity editable PPTX export**: Rebuild slide images into editable PowerPoint text boxes, conservative native shapes, and positioned bitmap assets through a provider-gated generative editable export path

## ✨ Features

Expand All @@ -29,6 +30,7 @@ NotebookLM's PPT feature is closer to a one-click result generator, with limited
- 🔀 **Three model roles**: Configure `prompt_model`, `image_model`, and `edit_model` separately
- 🖼️ **Image result compatibility**: Accepts URLs, Markdown image links, data URLs, `b64_json`, and raw base64
- 💾 **Local multi-project persistence**: Save multiple PPT projects in the browser, including source content, outline, page designs, generated images, and per-slide edit history
- 📤 **Editable PPTX reconstruction**: Export a separate experimental high-fidelity editable PPTX mode after provider verification; see [Generative Editable PPTX Export](docs/generative-editable-pptx.md)

## 🚀 Quick Start

Expand Down Expand Up @@ -102,6 +104,8 @@ Notes:
6. **Preview & Edit**: Preview generated slides in the right panel and edit a single page when needed
7. **Export**: Export to PDF or PPTX

The export menu keeps the existing raster PPTX option and adds a separate experimental high-fidelity editable PPTX option. The editable mode uses OCR plus image editing/image generation providers and is quality-gated by default; it fails rather than silently returning a low-fidelity deck unless an explicit fallback policy is requested. Strict live provider verification is still pending for the currently tested provider set. See [Generative Editable PPTX Export](docs/generative-editable-pptx.md).

The built-in demo source is `doc/L9.md`. This is a repository-relative path, so a fresh clone can use it directly in the WebUI or CLI examples.

## 📁 Project Structure
Expand All @@ -123,28 +127,30 @@ OpenNotebookLM-AIPPT/

All configurations are managed in `config.yaml`, including:
- API configuration (`prompt_model`, `image_model`, `edit_model`)
- Generative editable PPTX provider roles and quality gates (`ocr_model`, cleanup, asset generation, repair, validation)
- PPT default settings (language, style, page count)
- Timeout and retry settings

See `config.example.yaml` for detailed configuration examples.

### Using OpenAI Compatible API

The current protocol is OpenAI-compatible `/chat/completions`: text profiles use
chat completions, and image/edit profiles use multimodal chat completions that
return an image URL, data URL, or base64 payload.

```yaml
api:
models:
prompt_model:
adapter: "openai_chat"
model: "gpt-4o"
base_url: "https://api.openai.com/v1"
api_key: "sk-xxx"
image_model:
adapter: "raw_chat_multimodal"
model: "gpt-image-2"
base_url: "https://api.example.com/v1"
api_key: "sk-xxx"
edit_model:
adapter: "raw_chat_multimodal"
model: "gpt-image-2"
base_url: "https://api.example.com/v1"
api_key: "sk-xxx"
Expand Down Expand Up @@ -186,14 +192,20 @@ npm run test
npm run build
```

GitHub Actions runs the same default checks on `main` and `dev` pull requests and pushes. Real model API calls are intentionally not part of the default CI because they require private keys and can be flaky.
GitHub Actions runs the same default checks on `main` and `dev` pull requests and pushes. Real model API calls and desktop PowerPoint/WPS rendering are intentionally not part of the default CI because they require private keys or host applications that are not available in GitHub runners. CI still validates package-level PPTX structure, object manifests, renderer contracts, and fake-provider reconstruction paths.

## 📋 TODO

- [ ] Upgrade generated PPT images into structured, editable PPT content
- [ ] Support region selection for partial slide editing
- [ ] Add more provider profile templates

## 🙏 Acknowledgements

The editable PPTX reconstruction design references ideas from:
- [LRriver/slide-alchemy](https://github.com/LRriver/slide-alchemy)
- [ningzimu/image-to-editable-ppt-skill](https://github.com/ningzimu/image-to-editable-ppt-skill)

## 📄 License

Apache License 2.0
12 changes: 9 additions & 3 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ NotebookLM 的 PPT 能力更像“一键生成结果”,中间设计过程和
- **模型可控**:文本规划、生图、图片编辑可分别配置不同 OpenAI-compatible 模型
- **本地可跑**:可使用本地 `config.yaml` 或 WebUI 本地 API 配置管理模型连接,项目记录和导出文件不包含 API Key
- **结果可导出**:生成后可直接导出 PDF/PPTX,适合继续汇报或二次编辑
- **实验性高保真可编辑 PPTX 导出**:通过 provider 门禁的生成式可编辑导出链路,把幻灯片图片重建为 PowerPoint 可编辑文本框、保守原生形状和可移动图片资产

## ✨ 功能特性

Expand All @@ -29,6 +30,7 @@ NotebookLM 的 PPT 能力更像“一键生成结果”,中间设计过程和
- 🔀 **三模型角色**:支持 `prompt_model`、`image_model`、`edit_model` 分别配置
- 🖼️ **图像结果兼容**:兼容 URL、Markdown 图片链接、data URL、`b64_json` 和纯 base64
- 💾 **多项目本地留存**:支持在浏览器本地保存多个 PPT 项目,恢复资料、设计大纲、逐页设计、生成图片和单页编辑历史
- 📤 **可编辑 PPTX 重建**:新增独立的实验性高保真可编辑 PPTX 导出模式,需要通过 provider 验证后使用,详见 [Generative Editable PPTX Export](docs/generative-editable-pptx.md)

## 🚀 快速开始

Expand Down Expand Up @@ -102,6 +104,8 @@ AIPPT 会把项目内容和图片资源保存在当前浏览器 Profile 的 Inde
6. **预览编辑**:在右侧面板预览生成的幻灯片,点击可进行单页编辑
7. **导出文件**:选择 PDF 或 PPTX 格式导出

导出菜单会保留原有栅格 PPTX 选项,并新增独立的实验性高保真可编辑 PPTX 选项。可编辑模式依赖 OCR、图像编辑和图像生成 provider,默认优先保证质量:验证失败时返回错误,不会悄悄降级为低保真 PPTX,除非请求显式允许 fallback。当前测试过的 provider 组合仍未通过严格 live 验证。详见 [Generative Editable PPTX Export](docs/generative-editable-pptx.md)。

仓库内置演示资料为 `doc/L9.md`。该路径是仓库相对路径,clone 后可直接用于 WebUI 上传或命令行示例。

## 📁 项目结构
Expand All @@ -123,28 +127,30 @@ OpenNotebookLM-AIPPT/

所有配置统一在 `config.yaml` 中管理,包括:
- API 配置(文本 prompt、生图、编辑三角色模型)
- 生成式可编辑 PPTX provider 角色和质量门禁(OCR、清理、资产生成、修复、验证)
- PPT 默认配置(语言、风格、页数)
- 超时和重试配置

详细配置示例请参考 `config.example.yaml`。

### 使用 OpenAI 兼容 API

当前调用协议为 OpenAI-compatible `/chat/completions`:文本模型走 chat
completion,图像/编辑模型走多模态 chat completion,响应需返回图片 URL、data URL
或 base64。

```yaml
api:
models:
prompt_model:
adapter: "openai_chat"
model: "gpt-4o"
base_url: "https://api.openai.com/v1"
api_key: "sk-xxx"
image_model:
adapter: "raw_chat_multimodal"
model: "gpt-image-2"
base_url: "https://api.example.com/v1"
api_key: "sk-xxx"
edit_model:
adapter: "raw_chat_multimodal"
model: "gpt-image-2"
base_url: "https://api.example.com/v1"
api_key: "sk-xxx"
Expand Down
61 changes: 52 additions & 9 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ FastAPI 后端服务,为 WebUI 前端提供 PPT 生成、编辑和导出功能
- **文件上传**: 支持 `.md/.txt/.pdf/.docx/.pptx`,统一解析为 Markdown
- **PPT 生成**: 基于输入材料生成 PPT 幻灯片,支持流式返回进度
- **图生图编辑**: 对单页幻灯片进行修改
- **导出功能**: 支持导出为 PDF PPTX 格式
- **导出功能**: 支持导出为 PDF、栅格 PPTX 和生成式高保真可编辑 PPTX 格式
- **模型路由**: 支持 prompt/image/edit 三角色模型 profile

## API 端点
Expand Down Expand Up @@ -44,17 +44,15 @@ Content-Type: application/json
"content": "Markdown 内容",
"config": {
"model_profiles": {
"prompt_model": {
"text_model": {
"model": "DeepSeek-V4-Pro",
"base_url": "https://api.example.com/v1",
"api_key": "your-text-key",
"adapter": "openai_chat"
"api_key": "your-text-key"
},
"image_model": {
"model": "gpt-image-2",
"base_url": "https://api.example.com/v1",
"api_key": "your-image-key",
"adapter": "raw_chat_multimodal"
"api_key": "your-image-key"
}
},
"page_count": 10,
Expand All @@ -81,9 +79,9 @@ Content-Type: application/json
"instruction": "修改指令",
"config": {
"model_profiles": {
"prompt_model": {"model": "DeepSeek-V4-Pro", "base_url": "https://api.example.com/v1", "api_key": "key", "adapter": "openai_chat"},
"image_model": {"model": "gpt-image-2", "base_url": "https://api.example.com/v1", "api_key": "key", "adapter": "raw_chat_multimodal"},
"edit_model": {"model": "gpt-image-2", "base_url": "https://api.example.com/v1", "api_key": "key", "adapter": "raw_chat_multimodal"}
"text_model": {"model": "DeepSeek-V4-Pro", "base_url": "https://api.example.com/v1", "api_key": "key"},
"image_model": {"model": "gpt-image-2", "base_url": "https://api.example.com/v1", "api_key": "key"},
"edit_model": {"model": "gpt-image-2", "base_url": "https://api.example.com/v1", "api_key": "key"}
},
"quality": "1K",
"aspect_ratio": "16:9"
Expand Down Expand Up @@ -115,6 +113,51 @@ Content-Type: application/json
响应: 文件下载
```

#### 生成式高保真可编辑 PPTX

该模式与普通 `pptx` 分开:普通 `pptx` 仍是把每页图片作为整页背景放入 PowerPoint;`generative_editable_pptx` 会通过 OCR、图片清理、前景资产重建、原生形状拟合和验证流程重建可编辑元素。

```
POST /api/export
Content-Type: application/json

请求体:
{
"format": "generative_editable_pptx",
"aspect_ratio": "16:9",
"slide_order": ["slide-1", "slide-2"],
"editable_options": {
"fallback_policy": "fail"
},
"slides": [
{
"slide_id": "slide-1",
"image_base64": "base64编码的图片",
"text_metadata": [
{
"text": "Quarterly Plan",
"role": "title",
"order": 1,
"style_hint": {"font_size": 32, "bold": true}
}
]
}
]
}
```

`fallback_policy` 可选值:
- `fail`:默认。验证失败时返回错误,不返回低保真文件。
- `text_editable_background`:显式允许使用文本可编辑、背景清理后的 fallback。
- `raster_pptx`:显式允许降级到现有栅格 PPTX 导出。

成功响应仍是 `.pptx` 文件下载。若使用 fallback,响应 header 会包含:
- `X-Generative-Editable-Status`
- `X-Generative-Editable-Fallback-Policy`
- `X-Generative-Editable-Fallback-Used`

更多配置和限制见 `docs/generative-editable-pptx.md`。

## 启动服务器

### 方式 1: 使用启动脚本
Expand Down
36 changes: 31 additions & 5 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Pydantic 请求/响应模型定义
"""

from typing import List, Optional, Literal
from typing import Any, List, Optional, Literal
from pydantic import BaseModel, Field


Expand Down Expand Up @@ -51,18 +51,24 @@ class ModelProfileConfig(BaseModel):
model: str = Field(..., description="模型名称")
base_url: str = Field(..., description="OpenAI-compatible Base URL")
api_key: str = Field(..., description="API Key")
adapter: str = Field("openai_chat", description="适配器")
thinking: Optional[Literal["enabled", "disabled"]] = Field(
None, description="OpenAI-compatible thinking mode"
)


class ModelProfilesConfig(BaseModel):
"""三角色模型配置"""
"""模型 profile 配置"""

prompt_model: ModelProfileConfig
text_model: Optional[ModelProfileConfig] = None
prompt_model: Optional[ModelProfileConfig] = Field(None, description="旧字段,兼容 text_model")
image_model: ModelProfileConfig
edit_model: Optional[ModelProfileConfig] = None
VLM: Optional[ModelProfileConfig] = Field(
None, description="可选,多模态理解模型;图片转可编辑 PPTX 时使用"
)
ocr_model: Optional[ModelProfileConfig] = Field(
None, description="可选,OCR 模型;图片转可编辑 PPTX 时使用"
)


class GenerationConfig(BaseModel):
Expand Down Expand Up @@ -226,6 +232,7 @@ class SlideData(BaseModel):
page_number: int
image_base64: str
prompt: str
text_metadata: List["ExportTextMetadata"] = Field(default_factory=list)


class GenerationProgressEvent(BaseModel):
Expand Down Expand Up @@ -293,14 +300,33 @@ class ExportSlide(BaseModel):
"""导出的幻灯片"""

image_base64: str
slide_id: Optional[str] = None
text_metadata: List["ExportTextMetadata"] = Field(default_factory=list)


class ExportTextMetadata(BaseModel):
"""导出时附带的可编辑文本语义信息"""

text: str
role: str
order: int
style_hint: dict[str, Any] = Field(default_factory=dict)


class ExportEditableOptions(BaseModel):
"""可编辑导出的附加选项"""

fallback_policy: Literal["fail", "text_editable_background", "raster_pptx"] = "fail"


class ExportRequest(BaseModel):
"""导出请求"""

slides: List[ExportSlide]
format: Literal["pdf", "pptx"] = Field(..., description="导出格式")
format: Literal["pdf", "pptx", "generative_editable_pptx"] = Field(..., description="导出格式")
aspect_ratio: Literal["16:9", "4:3"] = Field("16:9", description="导出画幅比例")
slide_order: Optional[List[str]] = None
editable_options: ExportEditableOptions = Field(default_factory=ExportEditableOptions)


class ExportResponse(BaseModel):
Expand Down
19 changes: 14 additions & 5 deletions api/profile_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ def _has_complete_generation_profiles(config: Any) -> bool:
profiles = getattr(config, "model_profiles", None)
if not profiles:
return False
required = (profiles.prompt_model, profiles.image_model)
text_profile = (
getattr(profiles, "text_model", None)
or getattr(profiles, "prompt_model", None)
or getattr(profiles, "VLM", None)
)
image_profile = getattr(profiles, "image_model", None)
required = (text_profile, image_profile)
return all(
_has_value(profile.model) and _has_value(profile.base_url) and _has_value(profile.api_key)
profile
and _has_value(profile.model)
and _has_value(profile.base_url)
and _has_value(profile.api_key)
for profile in required
)

Expand All @@ -41,7 +50,7 @@ def profiles_from_generation_config(config: Any) -> ModelProfileSet:

if _has_complete_legacy_profiles(config):
data: Dict[str, Any] = {
"prompt_model": {
"text_model": {
"model": config.text.model,
"base_url": config.text.base_url,
"api_key": config.text.api_key,
Expand All @@ -68,7 +77,7 @@ def profiles_from_generation_config(config: Any) -> ModelProfileSet:

return resolve_model_profiles(
{
"prompt_model": {
"text_model": {
"model": config.get_text_model(),
"base_url": config.get_text_base_url(),
"api_key": config.get_text_api_key(),
Expand Down Expand Up @@ -98,7 +107,7 @@ def profiles_from_edit_config(config: Any) -> ModelProfileSet:

return resolve_model_profiles(
{
"prompt_model": {
"text_model": {
"model": config.model,
"base_url": config.base_url,
"api_key": config.api_key,
Expand Down
Loading
Loading