feat: add ppt generation feat

This commit is contained in:
He Tao 2025-04-21 16:43:06 +08:00
parent d81eb40a80
commit 0d2f93c773
10 changed files with 256 additions and 0 deletions

11
.vscode/launch.json vendored
View File

@ -45,5 +45,16 @@
"PYTHONPATH": "${workspaceFolder}"
}
},
{
"name": "Python: graph.py",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/ppt/graph/builder.py",
"console": "integratedTerminal",
"justMyCode": false,
"env": {
"PYTHONPATH": "${workspaceFolder}"
}
},
]
}

View File

@ -29,6 +29,10 @@ cp .env.example .env
# Gemini: https://ai.google.dev/gemini-api/docs/openai
cp conf.yaml.example conf.yaml
# Install marp for ppt generation
# https://github.com/marp-team/marp-cli?tab=readme-ov-file#use-package-manager
brew install marp-cli
# Run the project
uv run main.py
```

View File

@ -14,4 +14,5 @@ AGENT_LLM_MAP: dict[str, LLMType] = {
"coder": "basic",
"reporter": "basic",
"podcast_script_writer": "basic",
"ppt_composer": "basic",
}

30
src/ppt/graph/builder.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
from langgraph.graph import END, START, StateGraph
from src.ppt.graph.ppt_composer_node import ppt_composer_node
from src.ppt.graph.ppt_generator_node import ppt_generator_node
from src.ppt.graph.state import PPTState
def build_graph():
"""Build and return the ppt workflow graph."""
# build state graph
builder = StateGraph(PPTState)
builder.add_node("ppt_composer", ppt_composer_node)
builder.add_node("ppt_generator", ppt_generator_node)
builder.add_edge(START, "ppt_composer")
builder.add_edge("ppt_composer", "ppt_generator")
builder.add_edge("ppt_generator", END)
return builder.compile()
if __name__ == "__main__":
from dotenv import load_dotenv
load_dotenv()
report_content = open("examples/nanjing_tangbao.md").read()
workflow = build_graph()
final_state = workflow.invoke({"input": report_content})

View File

@ -0,0 +1,33 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
import logging
import os
import uuid
from langchain.schema import HumanMessage, SystemMessage
from src.config.agents import AGENT_LLM_MAP
from src.llms.llm import get_llm_by_type
from src.prompts.template import get_prompt_template
from .state import PPTState
logger = logging.getLogger(__name__)
def ppt_composer_node(state: PPTState):
logger.info("Generating ppt content...")
model = get_llm_by_type(AGENT_LLM_MAP["ppt_composer"])
ppt_content = model.invoke(
[
SystemMessage(content=get_prompt_template("ppt_composer")),
HumanMessage(content=state["input"]),
],
)
logger.info(f"ppt_content: {ppt_content}")
# save the ppt content in a temp file
temp_ppt_file_path = os.path.join(os.getcwd(), f"ppt_content_{uuid.uuid4()}.md")
with open(temp_ppt_file_path, "w") as f:
f.write(ppt_content.content)
return {"ppt_content": ppt_content, "ppt_file_path": temp_ppt_file_path}

View File

@ -0,0 +1,25 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
import logging
import os
import subprocess
import uuid
from src.ppt.graph.state import PPTState
logger = logging.getLogger(__name__)
def ppt_generator_node(state: PPTState):
logger.info("Generating ppt file...")
# use marp cli to generate ppt file
# https://github.com/marp-team/marp-cli?tab=readme-ov-file
generated_file_path = os.path.join(
os.getcwd(), f"generated_ppt_{uuid.uuid4()}.pptx"
)
subprocess.run(["marp", state["ppt_file_path"], "-o", generated_file_path])
# remove the temp file
os.remove(state["ppt_file_path"])
logger.info(f"generated_file_path: {generated_file_path}")
return {"generated_file_path": generated_file_path}

20
src/ppt/graph/state.py Normal file
View File

@ -0,0 +1,20 @@
# Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
# SPDX-License-Identifier: MIT
from typing import Optional
from langgraph.graph import MessagesState
class PPTState(MessagesState):
"""State for the ppt generation."""
# Input
input: str = ""
# Output
generated_file_path: str = ""
# Assets
ppt_content: str = ""
ppt_file_path: str = ""

107
src/prompts/ppt_composer.md Normal file
View File

@ -0,0 +1,107 @@
# Professional Presentation (PPT) Markdown Assistant
## Purpose
You are a professional PPT presentation creation assistant who transforms user requirements into a clear, focused Markdown-formatted presentation text. Your output should start directly with the presentation content, without any introductory phrases or explanations.
## Markdown PPT Formatting Guidelines
### Title and Structure
- Use `#` for the title slide (typically one slide)
- Use `##` for slide titles
- Use `###` for subtitles (if needed)
- Use horizontal rule `---` to separate slides
### Content Formatting
- Use unordered lists (`*` or `-`) for key points
- Use ordered lists (`1.`, `2.`) for sequential steps
- Separate paragraphs with blank lines
- Use code blocks with triple backticks
- IMPORTANT: When including images, ONLY use the actual image URLs from the source content. DO NOT create fictional image URLs or placeholders like 'example.com'
## Processing Workflow
### 1. Understand User Requirements
- Carefully read all provided information
- Note:
* Presentation topic
* Target audience
* Key messages
* Presentation duration
* Specific style or format requirements
### 2. Extract Core Content
- Identify the most important points
- Remember: PPT supports the speech, not replaces it
### 3. Organize Content Structure
Typical structure includes:
- Title Slide
- Introduction/Agenda
- Body (multiple sections)
- Summary/Conclusion
- Optional Q&A section
### 4. Create Markdown Presentation
- Ensure each slide focuses on one main point
- Use concise, powerful language
- Emphasize points with bullet points
- Use appropriate title hierarchy
### 5. Review and Optimize
- Check for completeness
- Refine text formatting
- Ensure readability
## Important Guidelines
- Do not guess or add information not provided
- Ask clarifying questions if needed
- Simplify detailed or lengthy information
- Highlight Markdown advantages (easy editing, version control)
- ONLY use images that are explicitly provided in the source content
- NEVER create fictional image URLs or placeholders
- If you include an image, use the exact URL from the source content
## Input Processing Rules
- Carefully analyze user input
- Extract key presentation elements
- Transform input into structured Markdown format
- Maintain clarity and logical flow
## Example User Input
"Help me create a presentation about 'How to Improve Team Collaboration Efficiency' for project managers. Cover: defining team goals, establishing communication mechanisms, using collaboration tools like Slack and Microsoft Teams, and regular reviews and feedback. Presentation length is about 15 minutes."
## Expected Output Format
// IMPORTANT: Your response should start directly with the content below, with no introductory text
# Presentation Title
---
## Agenda
- Key Point 1
- Key Point 2
- Key Point 3
---
## Detailed Slide Content
- Specific bullet points
- Explanatory details
- Key takeaways
![Image Title](https://actual-source-url.com/image.jpg)
---
## Response Guidelines
- Provide a complete, ready-to-use Markdown presentation
- Ensure professional and clear formatting
- Adapt to user's specific context and requirements
- IMPORTANT: Start your response directly with the presentation content. DO NOT include any introductory phrases like "Here's a presentation about..." or "Here's a professional Markdown-formatted presentation..."
- Begin your response with the title using a single # heading
- For images, ONLY use the exact image URLs found in the source content. DO NOT invent or create fictional image URLs
- If the source content contains images, incorporate them in your presentation using the exact same URLs

View File

@ -16,10 +16,12 @@ from langgraph.types import Command
from src.graph.builder import build_graph
from src.podcast.graph.builder import build_graph as build_podcast_graph
from src.ppt.graph.builder import build_graph as build_ppt_graph
from src.server.chat_request import (
ChatMessage,
ChatRequest,
GeneratePodcastRequest,
GeneratePPTRequest,
TTSRequest,
)
from src.tools import VolcengineTTS
@ -216,3 +218,22 @@ async def generate_podcast(request: GeneratePodcastRequest):
except Exception as e:
logger.exception(f"Error occurred during podcast generation: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/ppt/generate")
async def generate_ppt(request: GeneratePPTRequest):
try:
report_content = request.content
print(report_content)
workflow = build_ppt_graph()
final_state = workflow.invoke({"input": report_content})
generated_file_path = final_state["generated_file_path"]
with open(generated_file_path, "rb") as f:
ppt_bytes = f.read()
return Response(
content=ppt_bytes,
media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation",
)
except Exception as e:
logger.exception(f"Error occurred during ppt generation: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

View File

@ -64,3 +64,7 @@ class TTSRequest(BaseModel):
class GeneratePodcastRequest(BaseModel):
content: str = Field(..., description="The content of the podcast")
class GeneratePPTRequest(BaseModel):
content: str = Field(..., description="The content of the ppt")