mirror of
https://git.mirrors.martin98.com/https://github.com/bytedance/deer-flow
synced 2025-08-18 02:35:57 +08:00
feat: add ppt generation feat
This commit is contained in:
parent
d81eb40a80
commit
0d2f93c773
11
.vscode/launch.json
vendored
11
.vscode/launch.json
vendored
@ -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}"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
@ -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
|
||||
```
|
||||
|
@ -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
30
src/ppt/graph/builder.py
Normal 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})
|
33
src/ppt/graph/ppt_composer_node.py
Normal file
33
src/ppt/graph/ppt_composer_node.py
Normal 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}
|
25
src/ppt/graph/ppt_generator_node.py
Normal file
25
src/ppt/graph/ppt_generator_node.py
Normal 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
20
src/ppt/graph/state.py
Normal 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
107
src/prompts/ppt_composer.md
Normal 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
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
## 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
|
@ -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))
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user