Add File
This commit is contained in:
345
src/landppt/services/image/adapters/ppt_prompt_adapter.py
Normal file
345
src/landppt/services/image/adapters/ppt_prompt_adapter.py
Normal file
@@ -0,0 +1,345 @@
|
||||
"""
|
||||
PPT内容到图片生成提示词的适配器
|
||||
将PPT幻灯片内容转换为适合AI图片生成的提示词
|
||||
"""
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, Any, List, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ..models import ImageGenerationRequest, ImageProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PPTSlideContext:
|
||||
"""PPT幻灯片上下文信息"""
|
||||
title: str
|
||||
content: str
|
||||
scenario: str
|
||||
topic: str
|
||||
page_number: int
|
||||
total_pages: int
|
||||
slide_type: str = "content"
|
||||
language: str = "zh"
|
||||
|
||||
|
||||
class PPTPromptAdapter:
|
||||
"""PPT内容到图片生成提示词的适配器"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any] = None):
|
||||
self.config = config or {}
|
||||
|
||||
# 场景到视觉风格的映射
|
||||
self.scenario_styles = {
|
||||
"general": "professional, clean, modern",
|
||||
"business": "corporate, professional, sleek, business meeting",
|
||||
"education": "educational, academic, classroom, learning",
|
||||
"technology": "futuristic, digital, high-tech, innovation",
|
||||
"tourism": "scenic, beautiful, travel, destination",
|
||||
"analysis": "data visualization, charts, analytics, research",
|
||||
"history": "historical, vintage, cultural, heritage",
|
||||
"medical": "medical, healthcare, clinical, scientific",
|
||||
"finance": "financial, banking, investment, economic",
|
||||
"marketing": "creative, colorful, engaging, promotional"
|
||||
}
|
||||
|
||||
# 内容类型到视觉元素的映射
|
||||
self.content_type_visuals = {
|
||||
"title": "title slide, presentation cover, professional header",
|
||||
"overview": "overview diagram, roadmap, structure visualization",
|
||||
"content": "content slide, information display, bullet points",
|
||||
"conclusion": "conclusion slide, summary, final thoughts",
|
||||
"data": "data visualization, charts, graphs, statistics",
|
||||
"process": "process flow, workflow, step-by-step diagram",
|
||||
"comparison": "comparison chart, versus layout, side-by-side",
|
||||
"timeline": "timeline, chronological order, historical progression"
|
||||
}
|
||||
|
||||
# 语言特定的提示词模板
|
||||
self.prompt_templates = {
|
||||
"zh": {
|
||||
"base": "专业的PPT幻灯片背景图片,{style_desc},{content_desc},高质量,商务风格",
|
||||
"title": "PPT标题页背景,{topic}主题,{style_desc},专业设计",
|
||||
"content": "PPT内容页背景,支持{content_type}展示,{style_desc},简洁明了",
|
||||
"data": "数据可视化背景,适合图表展示,{style_desc},专业分析风格",
|
||||
"conclusion": "PPT结论页背景,总结性设计,{style_desc},完整收尾"
|
||||
},
|
||||
"en": {
|
||||
"base": "Professional PPT slide background, {style_desc}, {content_desc}, high quality, business style",
|
||||
"title": "PPT title slide background, {topic} theme, {style_desc}, professional design",
|
||||
"content": "PPT content slide background, supports {content_type} display, {style_desc}, clean and clear",
|
||||
"data": "Data visualization background, suitable for charts, {style_desc}, professional analysis style",
|
||||
"conclusion": "PPT conclusion slide background, summary design, {style_desc}, complete ending"
|
||||
}
|
||||
}
|
||||
|
||||
async def generate_image_prompt(self, slide_context: PPTSlideContext) -> str:
|
||||
"""为PPT幻灯片生成图片提示词"""
|
||||
try:
|
||||
# 分析幻灯片内容
|
||||
content_analysis = self._analyze_slide_content(slide_context)
|
||||
|
||||
# 获取场景风格描述
|
||||
style_desc = self._get_scenario_style(slide_context.scenario)
|
||||
|
||||
# 获取内容类型描述
|
||||
content_desc = self._get_content_description(slide_context, content_analysis)
|
||||
|
||||
# 选择合适的提示词模板
|
||||
template_key = self._select_template_key(slide_context, content_analysis)
|
||||
template = self.prompt_templates[slide_context.language][template_key]
|
||||
|
||||
# 构建提示词
|
||||
prompt = template.format(
|
||||
style_desc=style_desc,
|
||||
content_desc=content_desc,
|
||||
topic=slide_context.topic,
|
||||
content_type=content_analysis["type"]
|
||||
)
|
||||
|
||||
# 添加质量和风格修饰符
|
||||
prompt = self._enhance_prompt_quality(prompt, slide_context)
|
||||
|
||||
# 添加负面提示词建议
|
||||
negative_prompt = self._generate_negative_prompt(slide_context)
|
||||
|
||||
logger.info(f"Generated image prompt for slide {slide_context.page_number}: {prompt[:100]}...")
|
||||
|
||||
return prompt
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate image prompt: {e}")
|
||||
# 返回基础提示词
|
||||
return self._get_fallback_prompt(slide_context)
|
||||
|
||||
async def create_generation_request(self,
|
||||
slide_context: PPTSlideContext,
|
||||
provider: ImageProvider = ImageProvider.DALLE,
|
||||
**kwargs) -> ImageGenerationRequest:
|
||||
"""创建图片生成请求"""
|
||||
# 生成主提示词
|
||||
prompt = await self.generate_image_prompt(slide_context)
|
||||
|
||||
# 生成负面提示词
|
||||
negative_prompt = self._generate_negative_prompt(slide_context)
|
||||
|
||||
# 根据幻灯片类型调整参数
|
||||
generation_params = self._get_generation_params(slide_context, provider)
|
||||
generation_params.update(kwargs)
|
||||
|
||||
return ImageGenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
provider=provider,
|
||||
**generation_params
|
||||
)
|
||||
|
||||
def _analyze_slide_content(self, slide_context: PPTSlideContext) -> Dict[str, Any]:
|
||||
"""分析幻灯片内容"""
|
||||
content = slide_context.content.lower()
|
||||
title = slide_context.title.lower()
|
||||
|
||||
analysis = {
|
||||
"type": "content",
|
||||
"keywords": [],
|
||||
"themes": [],
|
||||
"visual_elements": [],
|
||||
"complexity": "medium"
|
||||
}
|
||||
|
||||
# 识别内容类型
|
||||
if slide_context.page_number == 1:
|
||||
analysis["type"] = "title"
|
||||
elif slide_context.page_number == slide_context.total_pages:
|
||||
analysis["type"] = "conclusion"
|
||||
elif any(word in content for word in ["数据", "统计", "图表", "分析", "data", "chart", "graph"]):
|
||||
analysis["type"] = "data"
|
||||
elif any(word in content for word in ["流程", "步骤", "过程", "process", "workflow", "step"]):
|
||||
analysis["type"] = "process"
|
||||
elif any(word in content for word in ["对比", "比较", "versus", "comparison", "vs"]):
|
||||
analysis["type"] = "comparison"
|
||||
elif any(word in content for word in ["时间", "历史", "发展", "timeline", "history", "evolution"]):
|
||||
analysis["type"] = "timeline"
|
||||
elif any(word in content for word in ["概述", "总览", "overview", "summary", "outline"]):
|
||||
analysis["type"] = "overview"
|
||||
|
||||
# 提取关键词
|
||||
keywords = self._extract_content_keywords(content + " " + title)
|
||||
analysis["keywords"] = keywords[:10] # 最多10个关键词
|
||||
|
||||
# 识别主题
|
||||
themes = self._identify_content_themes(content + " " + title)
|
||||
analysis["themes"] = themes
|
||||
|
||||
# 评估复杂度
|
||||
if len(content.split()) > 100:
|
||||
analysis["complexity"] = "high"
|
||||
elif len(content.split()) < 30:
|
||||
analysis["complexity"] = "low"
|
||||
|
||||
return analysis
|
||||
|
||||
def _extract_content_keywords(self, text: str) -> List[str]:
|
||||
"""从内容中提取关键词"""
|
||||
# 移除标点符号和特殊字符
|
||||
text = re.sub(r'[^\w\s]', ' ', text)
|
||||
words = text.split()
|
||||
|
||||
# 停用词列表
|
||||
stop_words = {
|
||||
'的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '上', '也', '很', '到',
|
||||
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'
|
||||
}
|
||||
|
||||
# 过滤停用词和短词
|
||||
keywords = [word for word in words if word not in stop_words and len(word) > 2]
|
||||
|
||||
# 统计词频并返回最常见的词
|
||||
from collections import Counter
|
||||
word_counts = Counter(keywords)
|
||||
|
||||
return [word for word, count in word_counts.most_common(15)]
|
||||
|
||||
def _identify_content_themes(self, text: str) -> List[str]:
|
||||
"""识别内容主题"""
|
||||
themes = []
|
||||
text_lower = text.lower()
|
||||
|
||||
theme_keywords = {
|
||||
"success": ["成功", "成就", "胜利", "优秀", "success", "achievement", "victory", "excellent"],
|
||||
"innovation": ["创新", "创意", "新颖", "突破", "innovation", "creative", "novel", "breakthrough"],
|
||||
"growth": ["增长", "发展", "提升", "进步", "growth", "development", "improvement", "progress"],
|
||||
"teamwork": ["团队", "合作", "协作", "配合", "team", "cooperation", "collaboration", "partnership"],
|
||||
"technology": ["技术", "科技", "数字", "智能", "technology", "digital", "smart", "tech"],
|
||||
"business": ["商业", "业务", "市场", "经济", "business", "market", "economic", "commercial"],
|
||||
"education": ["教育", "学习", "培训", "知识", "education", "learning", "training", "knowledge"],
|
||||
"future": ["未来", "前景", "展望", "趋势", "future", "prospect", "outlook", "trend"]
|
||||
}
|
||||
|
||||
for theme, keywords in theme_keywords.items():
|
||||
if any(keyword in text_lower for keyword in keywords):
|
||||
themes.append(theme)
|
||||
|
||||
return themes[:3] # 最多3个主题
|
||||
|
||||
def _get_scenario_style(self, scenario: str) -> str:
|
||||
"""获取场景对应的视觉风格"""
|
||||
return self.scenario_styles.get(scenario, self.scenario_styles["general"])
|
||||
|
||||
def _get_content_description(self, slide_context: PPTSlideContext, content_analysis: Dict[str, Any]) -> str:
|
||||
"""获取内容描述"""
|
||||
content_type = content_analysis["type"]
|
||||
base_desc = self.content_type_visuals.get(content_type, "professional presentation slide")
|
||||
|
||||
# 添加关键词增强
|
||||
if content_analysis["keywords"]:
|
||||
keywords_str = ", ".join(content_analysis["keywords"][:3])
|
||||
base_desc += f", related to {keywords_str}"
|
||||
|
||||
# 添加主题增强
|
||||
if content_analysis["themes"]:
|
||||
themes_str = ", ".join(content_analysis["themes"])
|
||||
base_desc += f", {themes_str} theme"
|
||||
|
||||
return base_desc
|
||||
|
||||
def _select_template_key(self, slide_context: PPTSlideContext, content_analysis: Dict[str, Any]) -> str:
|
||||
"""选择合适的提示词模板"""
|
||||
content_type = content_analysis["type"]
|
||||
|
||||
if content_type == "title":
|
||||
return "title"
|
||||
elif content_type == "data":
|
||||
return "data"
|
||||
elif content_type == "conclusion":
|
||||
return "conclusion"
|
||||
else:
|
||||
return "content"
|
||||
|
||||
def _enhance_prompt_quality(self, prompt: str, slide_context: PPTSlideContext) -> str:
|
||||
"""增强提示词质量"""
|
||||
# 添加质量修饰符
|
||||
quality_modifiers = [
|
||||
"high resolution",
|
||||
"professional quality",
|
||||
"clean design",
|
||||
"modern aesthetic",
|
||||
"suitable for presentation"
|
||||
]
|
||||
|
||||
# 根据场景添加特定修饰符
|
||||
if slide_context.scenario == "business":
|
||||
quality_modifiers.extend(["corporate style", "executive presentation"])
|
||||
elif slide_context.scenario == "technology":
|
||||
quality_modifiers.extend(["futuristic design", "digital aesthetic"])
|
||||
elif slide_context.scenario == "education":
|
||||
quality_modifiers.extend(["educational design", "academic style"])
|
||||
|
||||
enhanced_prompt = prompt + ", " + ", ".join(quality_modifiers[:3])
|
||||
|
||||
return enhanced_prompt
|
||||
|
||||
def _generate_negative_prompt(self, slide_context: PPTSlideContext) -> str:
|
||||
"""生成负面提示词"""
|
||||
negative_elements = [
|
||||
"cluttered",
|
||||
"messy",
|
||||
"unprofessional",
|
||||
"low quality",
|
||||
"blurry",
|
||||
"distorted",
|
||||
"inappropriate",
|
||||
"distracting elements",
|
||||
"poor composition",
|
||||
"amateur design"
|
||||
]
|
||||
|
||||
# 根据场景添加特定的负面元素
|
||||
if slide_context.scenario == "business":
|
||||
negative_elements.extend(["casual", "informal", "playful"])
|
||||
elif slide_context.scenario == "education":
|
||||
negative_elements.extend(["complex", "overwhelming", "confusing"])
|
||||
|
||||
return ", ".join(negative_elements[:8])
|
||||
|
||||
def _get_generation_params(self, slide_context: PPTSlideContext, provider: ImageProvider) -> Dict[str, Any]:
|
||||
"""获取生成参数"""
|
||||
params = {
|
||||
"width": 1920, # PPT标准宽度
|
||||
"height": 1080, # PPT标准高度
|
||||
"quality": "standard"
|
||||
}
|
||||
|
||||
# 根据提供者调整参数
|
||||
if provider == ImageProvider.DALLE:
|
||||
params.update({
|
||||
"size": "1792x1024", # DALL-E 3支持的最接近16:9的尺寸
|
||||
"quality": "standard",
|
||||
"style": "natural" # 更适合PPT背景
|
||||
})
|
||||
elif provider == ImageProvider.STABLE_DIFFUSION:
|
||||
params.update({
|
||||
"width": 1024,
|
||||
"height": 576, # 16:9比例
|
||||
"steps": 30,
|
||||
"cfg_scale": 7.0,
|
||||
"sampler": "K_DPM_2_ANCESTRAL"
|
||||
})
|
||||
|
||||
# 根据幻灯片类型调整
|
||||
if slide_context.page_number == 1: # 标题页
|
||||
params["quality"] = "hd" if provider == ImageProvider.DALLE else params.get("quality")
|
||||
|
||||
return params
|
||||
|
||||
def _get_fallback_prompt(self, slide_context: PPTSlideContext) -> str:
|
||||
"""获取回退提示词"""
|
||||
scenario_style = self._get_scenario_style(slide_context.scenario)
|
||||
|
||||
if slide_context.language == "zh":
|
||||
return f"专业的PPT幻灯片背景,{scenario_style}风格,适合{slide_context.topic}主题,高质量,商务设计"
|
||||
else:
|
||||
return f"Professional PPT slide background, {scenario_style} style, suitable for {slide_context.topic} theme, high quality, business design"
|
||||
Reference in New Issue
Block a user