AI Analysis and Content Strategy fixes. Enhanced Strategy Routes refactoring.

This commit is contained in:
ajaysi
2026-01-10 19:32:50 +05:30
parent 0b63ae7fc1
commit 8193cdba67
298 changed files with 45678 additions and 10952 deletions

View File

@@ -1,23 +1,15 @@
"""Product Marketing Suite service package."""
"""Product Marketing Suite service package - Product asset creation only."""
from .orchestrator import ProductMarketingOrchestrator
from .brand_dna_sync import BrandDNASyncService
from .prompt_builder import ProductMarketingPromptBuilder
from .asset_audit import AssetAuditService
from .channel_pack import ChannelPackService
from .campaign_storage import CampaignStorageService
from .product_image_service import ProductImageService
from .product_animation_service import ProductAnimationService, ProductAnimationRequest
from .product_video_service import ProductVideoService, ProductVideoRequest
from .product_avatar_service import ProductAvatarService, ProductAvatarRequest
from .intelligent_prompt_builder import IntelligentPromptBuilder
from .personalization_service import PersonalizationService
__all__ = [
"ProductMarketingOrchestrator",
"BrandDNASyncService",
"ProductMarketingPromptBuilder",
"AssetAuditService",
"ChannelPackService",
"CampaignStorageService",
"ProductImageService",
"ProductAnimationService",
"ProductAnimationRequest",
@@ -25,5 +17,7 @@ __all__ = [
"ProductVideoRequest",
"ProductAvatarService",
"ProductAvatarRequest",
"IntelligentPromptBuilder",
"PersonalizationService",
]

View File

@@ -0,0 +1,454 @@
"""
Intelligent Prompt Builder
Infers complete requirements from minimal user input using onboarding data.
"""
from typing import Dict, Any, Optional, List
from loguru import logger
import json
from services.onboarding.database_service import OnboardingDatabaseService
from services.database import SessionLocal
from services.llm_providers.main_text_generation import llm_text_gen
from .product_marketing_templates import (
ProductMarketingTemplates,
TemplateCategory,
ProductImageTemplate,
ProductVideoTemplate,
ProductAvatarTemplate,
)
class IntelligentPromptBuilder:
"""
Intelligent prompt builder that infers requirements from minimal user input.
Example:
Input: "iPhone case for my store"
Output: Complete configuration with all fields pre-filled
"""
def __init__(self):
"""Initialize Intelligent Prompt Builder."""
self.logger = logger
logger.info("[Intelligent Prompt Builder] Initialized")
def infer_requirements(
self,
user_input: str,
user_id: str,
asset_type: Optional[str] = None
) -> Dict[str, Any]:
"""
Infer complete requirements from minimal user input.
Args:
user_input: Minimal user input (e.g., "iPhone case for my store")
user_id: User ID to fetch onboarding data
asset_type: Optional asset type hint (image, video, animation, avatar)
Returns:
Complete configuration dictionary with all fields pre-filled
"""
try:
# 1. Parse user input
parsed_input = self._parse_user_input(user_input, asset_type)
# 2. Get onboarding data
onboarding_data = self._get_onboarding_data(user_id)
# 3. Infer requirements from context
requirements = self._infer_from_context(parsed_input, onboarding_data, asset_type)
# 4. Match template
template = self._match_template(requirements, asset_type)
# 5. Generate smart defaults
defaults = self._generate_defaults(requirements, template, onboarding_data)
logger.info(f"[Intelligent Prompt Builder] Inferred requirements: {defaults.get('product_name', 'Unknown')}")
return defaults
except Exception as e:
logger.error(f"[Intelligent Prompt Builder] Error inferring requirements: {str(e)}", exc_info=True)
# Return basic defaults on error
return self._get_basic_defaults(user_input, asset_type)
def _parse_user_input(
self,
user_input: str,
asset_type: Optional[str] = None
) -> Dict[str, Any]:
"""
Parse minimal user input to extract entities.
Uses LLM with few-shot prompting to extract:
- Product name
- Product type
- Use case (e-commerce, marketing, social media, etc.)
- Platform hints (store, Instagram, Shopify, Amazon, etc.)
- Style preferences
"""
try:
# Build system prompt for entity extraction
system_prompt = """You are an expert at parsing product marketing requests.
Extract key information from user input and return structured JSON.
Extract:
- product_name: The product name or description
- product_type: Type of product (phone_case, clothing, electronics, food, etc.)
- use_case: Primary use case (ecommerce, social_media, marketing_campaign, documentation, etc.)
- platform_hints: Platforms mentioned (shopify, amazon, instagram, facebook, etc.)
- style_hints: Style preferences mentioned (professional, casual, luxury, minimalist, etc.)
- asset_type_hint: Type of asset needed (image, video, animation, avatar) if mentioned
Return JSON only, no explanations."""
# Few-shot examples
examples = """
Examples:
Input: "iPhone case for my store"
Output: {"product_name": "iPhone case", "product_type": "phone_case", "use_case": "ecommerce", "platform_hints": ["shopify"], "style_hints": [], "asset_type_hint": "image"}
Input: "Create a video for my new product launch on Instagram"
Output: {"product_name": "new product", "product_type": "unknown", "use_case": "social_media", "platform_hints": ["instagram"], "style_hints": [], "asset_type_hint": "video"}
Input: "Luxury watch photoshoot"
Output: {"product_name": "luxury watch", "product_type": "watch", "use_case": "marketing_campaign", "platform_hints": [], "style_hints": ["luxury"], "asset_type_hint": "image"}
"""
prompt = f"{examples}\n\nInput: {user_input}\nOutput:"
# Call LLM for parsing
json_struct = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"product_type": {"type": "string"},
"use_case": {"type": "string"},
"platform_hints": {"type": "array", "items": {"type": "string"}},
"style_hints": {"type": "array", "items": {"type": "string"}},
"asset_type_hint": {"type": "string"}
},
"required": ["product_name", "use_case"]
}
# Call LLM synchronously (llm_text_gen is synchronous)
result_text = llm_text_gen(
prompt=prompt,
system_prompt=system_prompt,
json_struct=json_struct,
user_id=None # No user_id needed for parsing
)
# Parse JSON response
try:
parsed = json.loads(result_text) if isinstance(result_text, str) else result_text
except json.JSONDecodeError:
# Fallback: try to extract JSON from text
import re
json_match = re.search(r'\{[^}]+\}', result_text)
if json_match:
parsed = json.loads(json_match.group())
else:
# Ultimate fallback: basic extraction
parsed = {
"product_name": user_input,
"product_type": "unknown",
"use_case": "marketing_campaign",
"platform_hints": [],
"style_hints": [],
"asset_type_hint": asset_type or "image"
}
# Override asset_type_hint if provided
if asset_type:
parsed["asset_type_hint"] = asset_type
logger.info(f"[Intelligent Prompt Builder] Parsed input: {parsed}")
return parsed
except Exception as e:
logger.error(f"[Intelligent Prompt Builder] Error parsing input: {str(e)}")
# Fallback: basic extraction
return {
"product_name": user_input,
"product_type": "unknown",
"use_case": "marketing_campaign",
"platform_hints": [],
"style_hints": [],
"asset_type_hint": asset_type or "image"
}
def _get_onboarding_data(self, user_id: str) -> Dict[str, Any]:
"""
Get all onboarding data for user.
Returns:
Dictionary with website_analysis, persona_data, competitor_analyses
"""
db = SessionLocal()
try:
onboarding_db = OnboardingDatabaseService(db)
website_analysis = onboarding_db.get_website_analysis(user_id, db)
persona_data = onboarding_db.get_persona_data(user_id, db)
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
return {
"website_analysis": website_analysis or {},
"persona_data": persona_data or {},
"competitor_analyses": competitor_analyses or [],
}
except Exception as e:
logger.error(f"[Intelligent Prompt Builder] Error getting onboarding data: {str(e)}")
return {
"website_analysis": {},
"persona_data": {},
"competitor_analyses": [],
}
finally:
db.close()
def _infer_from_context(
self,
parsed_input: Dict[str, Any],
onboarding_data: Dict[str, Any],
asset_type: Optional[str] = None
) -> Dict[str, Any]:
"""
Infer requirements from parsed input and onboarding context.
Uses onboarding data to fill in missing information:
- Platform from onboarding (if user has e-commerce setup)
- Style from brand DNA
- Target audience from onboarding
"""
requirements = parsed_input.copy()
website_analysis = onboarding_data.get("website_analysis", {})
persona_data = onboarding_data.get("persona_data", {})
# Infer platform from onboarding
if not requirements.get("platform_hints"):
# Check if user has e-commerce setup (from website analysis)
brand_analysis = website_analysis.get("brand_analysis", {})
# Try to infer platform from website URL or other hints
# For now, default to e-commerce if no hints
if requirements.get("use_case") == "ecommerce":
requirements["platform_hints"] = ["shopify"] # Default e-commerce platform
# Infer style from brand DNA
if not requirements.get("style_hints"):
if brand_analysis:
style_guidelines = brand_analysis.get("style_guidelines", {})
aesthetic = style_guidelines.get("aesthetic", "")
if aesthetic:
requirements["style_hints"] = [aesthetic.lower()]
# Infer target audience from onboarding
target_audience = website_analysis.get("target_audience", {})
if target_audience:
requirements["target_audience"] = target_audience
# Infer brand colors
if brand_analysis:
color_palette = brand_analysis.get("color_palette", [])
if color_palette:
requirements["brand_colors"] = color_palette[:5] # Top 5 colors
# Infer writing style
writing_style = website_analysis.get("writing_style", {})
if writing_style:
requirements["tone"] = writing_style.get("tone", "professional")
requirements["voice"] = writing_style.get("voice", "authoritative")
return requirements
def _match_template(
self,
requirements: Dict[str, Any],
asset_type: Optional[str] = None
) -> Optional[Dict[str, Any]]:
"""
Match requirements to appropriate template.
Returns:
Template dictionary or None
"""
asset_type_hint = asset_type or requirements.get("asset_type_hint", "image")
use_case = requirements.get("use_case", "marketing_campaign")
style_hints = requirements.get("style_hints", [])
if asset_type_hint == "image":
templates = ProductMarketingTemplates.get_product_image_templates()
# Match by use case
if use_case == "ecommerce":
# Match e-commerce template
for template in templates:
if "ecommerce" in template.id.lower() or "e-commerce" in template.name.lower():
return {
"id": template.id,
"name": template.name,
"category": template.category.value,
"environment": template.environment,
"background_style": template.background_style,
"lighting": template.lighting,
"style": template.style,
"angle": template.angle,
"recommended_resolution": template.recommended_resolution,
}
# Match by style
if style_hints:
style_lower = style_hints[0].lower()
for template in templates:
if style_lower in template.style.lower() or style_lower in template.name.lower():
return {
"id": template.id,
"name": template.name,
"category": template.category.value,
"environment": template.environment,
"background_style": template.background_style,
"lighting": template.lighting,
"style": template.style,
"angle": template.angle,
"recommended_resolution": template.recommended_resolution,
}
# Default: e-commerce product shot
default_template = templates[0] # ecommerce_product_shot
return {
"id": default_template.id,
"name": default_template.name,
"category": default_template.category.value,
"environment": default_template.environment,
"background_style": default_template.background_style,
"lighting": default_template.lighting,
"style": default_template.style,
"angle": default_template.angle,
"recommended_resolution": default_template.recommended_resolution,
}
elif asset_type_hint == "video":
templates = ProductMarketingTemplates.get_product_video_templates()
# Default: product demo video
default_template = templates[0]
return {
"id": default_template.id,
"name": default_template.name,
"category": default_template.category.value,
"video_type": default_template.video_type,
"resolution": default_template.resolution,
"duration": default_template.duration,
}
elif asset_type_hint == "avatar":
templates = ProductMarketingTemplates.get_product_avatar_templates()
# Default: product overview
default_template = templates[0]
return {
"id": default_template.id,
"name": default_template.name,
"category": default_template.category.value,
"explainer_type": default_template.explainer_type,
"resolution": default_template.resolution,
}
return None
def _generate_defaults(
self,
requirements: Dict[str, Any],
template: Optional[Dict[str, Any]],
onboarding_data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate complete configuration with smart defaults.
Combines:
- Parsed requirements
- Matched template
- Onboarding data
"""
defaults = {}
# Product information
defaults["product_name"] = requirements.get("product_name", "Product")
defaults["product_description"] = requirements.get("product_description", f"Professional {requirements.get('product_name', 'product')}")
# Asset type
asset_type = requirements.get("asset_type_hint", "image")
defaults["asset_type"] = asset_type
# Template information
if template:
defaults["template_id"] = template.get("id")
defaults["template_name"] = template.get("name")
# Image-specific defaults
if asset_type == "image" and template:
defaults["environment"] = template.get("environment", "studio")
defaults["background_style"] = template.get("background_style", "white")
defaults["lighting"] = template.get("lighting", "studio")
defaults["style"] = template.get("style", "photorealistic")
defaults["angle"] = template.get("angle", "front")
defaults["resolution"] = template.get("recommended_resolution", "1024x1024")
defaults["num_variations"] = 1
# Override with style hints if available
if requirements.get("style_hints"):
style_hint = requirements["style_hints"][0].lower()
if "luxury" in style_hint:
defaults["style"] = "luxury"
defaults["lighting"] = "dramatic"
elif "minimalist" in style_hint:
defaults["style"] = "minimalist"
defaults["background_style"] = "white"
elif "lifestyle" in style_hint:
defaults["environment"] = "lifestyle"
defaults["background_style"] = "lifestyle"
# Video-specific defaults
elif asset_type == "video" and template:
defaults["video_type"] = template.get("video_type", "demo")
defaults["resolution"] = template.get("resolution", "720p")
defaults["duration"] = template.get("duration", 10)
# Avatar-specific defaults
elif asset_type == "avatar" and template:
defaults["explainer_type"] = template.get("explainer_type", "product_overview")
defaults["resolution"] = template.get("resolution", "720p")
# Brand colors from onboarding
if requirements.get("brand_colors"):
defaults["brand_colors"] = requirements["brand_colors"]
# Additional context
defaults["additional_context"] = requirements.get("additional_context", "")
# Confidence score (how well we matched)
defaults["confidence"] = 0.8 if template else 0.5
defaults["inferred_fields"] = list(defaults.keys())
return defaults
def _get_basic_defaults(
self,
user_input: str,
asset_type: Optional[str] = None
) -> Dict[str, Any]:
"""Get basic defaults when parsing fails."""
return {
"product_name": user_input,
"product_description": f"Professional {user_input}",
"asset_type": asset_type or "image",
"environment": "studio",
"background_style": "white",
"lighting": "studio",
"style": "photorealistic",
"resolution": "1024x1024",
"num_variations": 1,
"confidence": 0.3,
"inferred_fields": ["product_name", "product_description"],
}

View File

@@ -0,0 +1,413 @@
"""
Personalization Service
Extracts ALL onboarding data and provides personalized defaults for forms and recommendations.
"""
from typing import Dict, Any, Optional, List
from loguru import logger
from services.onboarding.database_service import OnboardingDatabaseService
from services.database import SessionLocal
class PersonalizationService:
"""
Service for extracting user preferences from onboarding data
and providing personalized defaults and recommendations.
"""
def __init__(self):
"""Initialize Personalization Service."""
self.logger = logger
logger.info("[Personalization Service] Initialized")
def get_user_preferences(self, user_id: str) -> Dict[str, Any]:
"""
Get comprehensive user preferences from ALL onboarding data.
Returns:
Dictionary with personalized preferences:
- industry: User's industry
- target_audience: Demographics, expertise level
- platform_preferences: Preferred platforms from persona data
- content_preferences: Preferred content types
- style_preferences: Visual style, tone, voice
- brand_colors: Brand color palette
- templates: Recommended templates for user's industry
- channels: Recommended channels based on platform personas
"""
db = SessionLocal()
try:
onboarding_db = OnboardingDatabaseService(db)
website_analysis = onboarding_db.get_website_analysis(user_id, db)
persona_data = onboarding_db.get_persona_data(user_id, db)
competitor_analyses = onboarding_db.get_competitor_analysis(user_id, db)
preferences = {
"industry": None,
"target_audience": {},
"platform_preferences": [],
"content_preferences": [],
"style_preferences": {},
"brand_colors": [],
"recommended_templates": [],
"recommended_channels": [],
"writing_style": {},
"brand_values": [],
}
# Extract from website_analysis
if website_analysis:
# Industry
target_audience = website_analysis.get("target_audience", {})
preferences["industry"] = target_audience.get("industry_focus")
# Target audience
preferences["target_audience"] = {
"demographics": target_audience.get("demographics", []),
"expertise_level": target_audience.get("expertise_level", "intermediate"),
"industry_focus": target_audience.get("industry_focus"),
}
# Writing style
writing_style = website_analysis.get("writing_style", {})
preferences["writing_style"] = {
"tone": writing_style.get("tone", "professional"),
"voice": writing_style.get("voice", "authoritative"),
"complexity": writing_style.get("complexity", "intermediate"),
"engagement_level": writing_style.get("engagement_level", "moderate"),
}
# Brand colors
brand_analysis = website_analysis.get("brand_analysis", {})
if brand_analysis:
preferences["brand_colors"] = brand_analysis.get("color_palette", [])
preferences["brand_values"] = brand_analysis.get("brand_values", [])
# Style preferences
style_guidelines = website_analysis.get("style_guidelines", {})
if style_guidelines:
preferences["style_preferences"] = {
"aesthetic": style_guidelines.get("aesthetic", "modern"),
"visual_style": style_guidelines.get("visual_style", "clean"),
}
# Extract from persona_data
if persona_data:
core_persona = persona_data.get("corePersona", {})
platform_personas = persona_data.get("platformPersonas", {})
selected_platforms = persona_data.get("selectedPlatforms", [])
# Platform preferences from selected platforms
if selected_platforms:
preferences["platform_preferences"] = selected_platforms
elif platform_personas:
# Extract platforms from platform personas
preferences["platform_preferences"] = list(platform_personas.keys())
# Recommended channels based on platform personas
if platform_personas:
# Prioritize platforms with active personas
preferences["recommended_channels"] = list(platform_personas.keys())[:5] # Top 5
# Content preferences from persona
if core_persona:
content_format_rules = core_persona.get("content_format_rules", {})
if content_format_rules:
preferred_formats = content_format_rules.get("preferred_formats", [])
preferences["content_preferences"] = preferred_formats
# Infer content preferences from industry
if preferences["industry"]:
industry_content_map = {
"ecommerce": ["product_images", "product_videos", "lifestyle_content"],
"saas": ["feature_highlights", "tutorials", "demo_videos"],
"education": ["tutorials", "educational_content", "explainer_videos"],
"healthcare": ["informational_content", "patient_stories", "educational_videos"],
"finance": ["informational_content", "trust_building", "expert_content"],
"fashion": ["lifestyle_images", "fashion_shows", "style_guides"],
"food": ["food_photography", "recipe_videos", "lifestyle_content"],
}
industry_lower = preferences["industry"].lower()
for key, content_types in industry_content_map.items():
if key in industry_lower:
preferences["content_preferences"] = content_types
break
# Recommend templates based on industry
preferences["recommended_templates"] = self._get_recommended_templates(
preferences.get("industry"),
preferences.get("style_preferences", {}).get("aesthetic")
)
# Recommend channels if not already set
if not preferences["recommended_channels"]:
preferences["recommended_channels"] = self._get_recommended_channels(
preferences.get("industry"),
preferences.get("target_audience", {}).get("demographics", [])
)
logger.info(f"[Personalization] Extracted preferences for user {user_id}: industry={preferences.get('industry')}")
return preferences
except Exception as e:
logger.error(f"[Personalization] Error getting user preferences: {str(e)}", exc_info=True)
return self._get_default_preferences()
finally:
db.close()
def get_personalized_defaults(
self,
user_id: str,
form_type: str = "product_photoshoot"
) -> Dict[str, Any]:
"""
Get personalized defaults for a specific form.
Args:
user_id: User ID
form_type: Type of form (product_photoshoot, campaign_creator, product_video, etc.)
Returns:
Dictionary with pre-filled form values
"""
preferences = self.get_user_preferences(user_id)
defaults = {}
if form_type == "product_photoshoot":
defaults = {
"environment": self._infer_environment(preferences),
"background_style": self._infer_background_style(preferences),
"lighting": self._infer_lighting(preferences),
"style": self._infer_style(preferences),
"resolution": "1024x1024",
"num_variations": 1,
"brand_colors": preferences.get("brand_colors", []),
}
elif form_type == "campaign_creator":
defaults = {
"channels": preferences.get("recommended_channels", ["instagram", "linkedin"]),
"goal": self._infer_campaign_goal(preferences),
}
elif form_type == "product_video":
defaults = {
"video_type": self._infer_video_type(preferences),
"resolution": "720p",
"duration": 10,
}
elif form_type == "product_avatar":
defaults = {
"explainer_type": self._infer_explainer_type(preferences),
"resolution": "720p",
}
return defaults
def get_recommendations(self, user_id: str) -> Dict[str, Any]:
"""
Get personalized recommendations for user.
Returns:
Dictionary with:
- recommended_templates: Templates matching user's industry
- recommended_channels: Channels matching user's platform personas
- recommended_asset_types: Asset types matching user's content preferences
"""
preferences = self.get_user_preferences(user_id)
return {
"templates": preferences.get("recommended_templates", []),
"channels": preferences.get("recommended_channels", []),
"asset_types": preferences.get("content_preferences", []),
"industry": preferences.get("industry"),
"reasoning": self._generate_recommendation_reasoning(preferences),
}
def _get_recommended_templates(
self,
industry: Optional[str],
aesthetic: Optional[str] = None
) -> List[str]:
"""Get recommended template IDs based on industry and aesthetic."""
templates = []
if not industry:
return ["ecommerce_product_shot", "lifestyle_product"]
industry_lower = industry.lower() if industry else ""
# Industry-based template recommendations
if "ecommerce" in industry_lower or "retail" in industry_lower:
templates.extend(["ecommerce_product_shot", "lifestyle_product"])
elif "saas" in industry_lower or "tech" in industry_lower:
templates.extend(["technical_product_detail", "lifestyle_product"])
elif "luxury" in industry_lower or "premium" in industry_lower:
templates.extend(["luxury_product_showcase", "lifestyle_product"])
else:
templates.extend(["ecommerce_product_shot", "lifestyle_product"])
# Aesthetic-based adjustments
if aesthetic:
aesthetic_lower = aesthetic.lower()
if "luxury" in aesthetic_lower or "premium" in aesthetic_lower:
templates.insert(0, "luxury_product_showcase")
elif "minimalist" in aesthetic_lower or "clean" in aesthetic_lower:
templates.insert(0, "ecommerce_product_shot")
return templates[:3] # Return top 3
def _get_recommended_channels(
self,
industry: Optional[str],
demographics: List[str]
) -> List[str]:
"""Get recommended channels based on industry and demographics."""
channels = []
if not industry:
return ["instagram", "linkedin"]
industry_lower = industry.lower() if industry else ""
# Industry-based channel recommendations
if "b2b" in industry_lower or "saas" in industry_lower or "enterprise" in industry_lower:
channels.extend(["linkedin", "twitter", "youtube"])
elif "b2c" in industry_lower or "ecommerce" in industry_lower or "retail" in industry_lower:
channels.extend(["instagram", "facebook", "pinterest", "tiktok"])
elif "fashion" in industry_lower or "lifestyle" in industry_lower:
channels.extend(["instagram", "pinterest", "tiktok"])
elif "education" in industry_lower:
channels.extend(["youtube", "linkedin", "facebook"])
else:
channels.extend(["instagram", "linkedin", "facebook"])
# Demographics-based adjustments
if demographics:
demographics_str = " ".join(demographics).lower()
if "young" in demographics_str or "millennial" in demographics_str or "gen z" in demographics_str:
if "tiktok" not in channels:
channels.insert(0, "tiktok")
if "professional" in demographics_str or "business" in demographics_str:
if "linkedin" not in channels:
channels.insert(0, "linkedin")
return channels[:5] # Return top 5
def _infer_environment(self, preferences: Dict[str, Any]) -> str:
"""Infer environment setting from preferences."""
industry = preferences.get("industry", "").lower() if preferences.get("industry") else ""
aesthetic = preferences.get("style_preferences", {}).get("aesthetic", "").lower()
if "luxury" in aesthetic or "premium" in industry:
return "studio"
elif "ecommerce" in industry or "retail" in industry:
return "studio"
elif "lifestyle" in aesthetic:
return "lifestyle"
else:
return "studio"
def _infer_background_style(self, preferences: Dict[str, Any]) -> str:
"""Infer background style from preferences."""
industry = preferences.get("industry", "").lower() if preferences.get("industry") else ""
aesthetic = preferences.get("style_preferences", {}).get("aesthetic", "").lower()
if "ecommerce" in industry or "retail" in industry:
return "white"
elif "luxury" in aesthetic:
return "minimalist"
elif "lifestyle" in aesthetic:
return "lifestyle"
else:
return "white"
def _infer_lighting(self, preferences: Dict[str, Any]) -> str:
"""Infer lighting style from preferences."""
aesthetic = preferences.get("style_preferences", {}).get("aesthetic", "").lower()
if "luxury" in aesthetic or "dramatic" in aesthetic:
return "dramatic"
elif "natural" in aesthetic:
return "natural"
else:
return "studio"
def _infer_style(self, preferences: Dict[str, Any]) -> str:
"""Infer image style from preferences."""
aesthetic = preferences.get("style_preferences", {}).get("aesthetic", "").lower()
industry = preferences.get("industry", "").lower() if preferences.get("industry") else ""
if "luxury" in aesthetic or "premium" in industry:
return "luxury"
elif "minimalist" in aesthetic:
return "minimalist"
elif "technical" in industry or "saas" in industry:
return "technical"
else:
return "photorealistic"
def _infer_campaign_goal(self, preferences: Dict[str, Any]) -> str:
"""Infer campaign goal from preferences."""
industry = preferences.get("industry", "").lower() if preferences.get("industry") else ""
if "saas" in industry or "tech" in industry:
return "conversion"
elif "ecommerce" in industry or "retail" in industry:
return "conversion"
else:
return "awareness"
def _infer_video_type(self, preferences: Dict[str, Any]) -> str:
"""Infer video type from preferences."""
content_prefs = preferences.get("content_preferences", [])
if "demo" in str(content_prefs).lower():
return "demo"
elif "tutorial" in str(content_prefs).lower():
return "feature_highlight"
else:
return "demo"
def _infer_explainer_type(self, preferences: Dict[str, Any]) -> str:
"""Infer explainer type from preferences."""
content_prefs = preferences.get("content_preferences", [])
if "tutorial" in str(content_prefs).lower():
return "tutorial"
elif "feature" in str(content_prefs).lower():
return "feature_explainer"
else:
return "product_overview"
def _generate_recommendation_reasoning(self, preferences: Dict[str, Any]) -> str:
"""Generate human-readable reasoning for recommendations."""
industry = preferences.get("industry", "your industry")
channels = preferences.get("recommended_channels", [])
reasoning = f"Based on your {industry} industry"
if channels:
reasoning += f" and platform preferences, we recommend focusing on {', '.join(channels[:3])}"
reasoning += "."
return reasoning
def _get_default_preferences(self) -> Dict[str, Any]:
"""Get default preferences when onboarding data is unavailable."""
return {
"industry": None,
"target_audience": {},
"platform_preferences": ["instagram", "linkedin"],
"content_preferences": [],
"style_preferences": {},
"brand_colors": [],
"recommended_templates": ["ecommerce_product_shot", "lifestyle_product"],
"recommended_channels": ["instagram", "linkedin", "facebook"],
"writing_style": {
"tone": "professional",
"voice": "authoritative",
},
"brand_values": [],
}

View File

@@ -10,6 +10,8 @@ from dataclasses import dataclass
from services.image_studio.transform_service import TransformStudioService, TransformImageToVideoRequest
from services.image_studio.studio_manager import ImageStudioManager
from utils.logger_utils import get_service_logger
from utils.asset_tracker import save_asset_to_library
from services.database import SessionLocal
logger = get_service_logger("product_marketing.animation")
@@ -141,6 +143,63 @@ class ProductAnimationService:
result["animation_type"] = request.animation_type
result["source_module"] = "product_marketing"
# Save to Asset Library
if result.get("file_url") and result.get("filename"):
db = SessionLocal()
try:
# Build animation prompt for metadata
animation_prompt = self._build_animation_prompt(
animation_type=request.animation_type,
product_name=request.product_name,
product_description=request.product_description,
brand_context=request.brand_context,
additional_context=request.additional_context
)
asset_id = save_asset_to_library(
db=db,
user_id=user_id,
asset_type="video",
source_module="product_marketing",
filename=result.get("filename"),
file_url=result.get("file_url"),
file_path=result.get("file_path"),
file_size=result.get("file_size"),
mime_type="video/mp4",
title=f"{request.product_name} - {request.animation_type.title()} Animation",
description=f"Product animation: {request.product_description or request.product_name}",
prompt=animation_prompt,
tags=["product_marketing", "product_animation", request.animation_type, request.resolution],
provider=result.get("provider", "wavespeed"),
model=result.get("model_name", "alibaba/wan-2.5/image-to-video"),
cost=result.get("cost", 0.0),
generation_time=result.get("generation_time"),
asset_metadata={
"product_name": request.product_name,
"product_description": request.product_description,
"animation_type": request.animation_type,
"resolution": request.resolution,
"duration": request.duration,
"width": result.get("width"),
"height": result.get("height"),
},
)
if asset_id:
logger.info(f"[Product Animation] ✅ Saved animation to Asset Library: ID={asset_id}")
else:
logger.warning(f"[Product Animation] ⚠️ Asset Library save returned None")
except Exception as db_error:
logger.error(f"[Product Animation] Database error saving to Asset Library: {str(db_error)}", exc_info=True)
# Video is saved, but database tracking failed - not critical
finally:
if db:
try:
db.close()
except Exception:
pass
logger.info(
f"[Product Animation] ✅ Product animation completed: "
f"cost=${result.get('cost', 0):.2f}, video_url={result.get('video_url', 'N/A')}"

View File

@@ -14,6 +14,8 @@ import base64
from services.image_studio.infinitetalk_adapter import InfiniteTalkService
from services.story_writer.audio_generation_service import StoryAudioGenerationService
from utils.logger_utils import get_service_logger
from utils.asset_tracker import save_asset_to_library
from services.database import SessionLocal
logger = get_service_logger("product_marketing.avatar")
@@ -271,6 +273,65 @@ class ProductAvatarService:
result["file_size"] = file_size
result["duration"] = result.get("duration", 0.0)
# Save to Asset Library
db = SessionLocal()
try:
# Build avatar prompt for metadata
avatar_prompt = request.prompt
if not avatar_prompt:
avatar_prompt = self._build_avatar_prompt(
explainer_type=request.explainer_type,
product_name=request.product_name,
product_description=request.product_description,
brand_context=request.brand_context,
additional_context=request.additional_context
)
asset_id = save_asset_to_library(
db=db,
user_id=user_id,
asset_type="video",
source_module="product_marketing",
filename=filename,
file_url=file_url,
file_path=str(file_path),
file_size=file_size,
mime_type="video/mp4",
title=f"{request.product_name} - {request.explainer_type.replace('_', ' ').title()} Explainer",
description=f"Product explainer: {request.product_description or request.product_name}",
prompt=avatar_prompt,
tags=["product_marketing", "product_avatar", "explainer", request.explainer_type, request.resolution],
provider=result.get("provider", "infinitetalk"),
model=result.get("model_name", "infinitetalk"),
cost=result.get("cost", 0.0),
generation_time=result.get("generation_time"),
asset_metadata={
"product_name": request.product_name,
"product_description": request.product_description,
"explainer_type": request.explainer_type,
"resolution": request.resolution,
"duration": result.get("duration", 0.0),
"script_text": request.script_text,
"width": result.get("width"),
"height": result.get("height"),
},
)
if asset_id:
logger.info(f"[Product Avatar] ✅ Saved explainer video to Asset Library: ID={asset_id}")
else:
logger.warning(f"[Product Avatar] ⚠️ Asset Library save returned None")
except Exception as db_error:
logger.error(f"[Product Avatar] Database error saving to Asset Library: {str(db_error)}", exc_info=True)
# Video is saved, but database tracking failed - not critical
finally:
if db:
try:
db.close()
except Exception:
pass
logger.info(
f"[Product Avatar] ✅ Product explainer video generated successfully: "
f"cost=${result.get('cost', 0):.2f}, duration={result.get('duration', 0):.1f}s, "

View File

@@ -0,0 +1,390 @@
"""
Product Marketing Templates Library
Pre-built templates for common product marketing use cases.
"""
from typing import Dict, Any, List, Optional
from dataclasses import dataclass
from enum import Enum
class TemplateCategory(str, Enum):
"""Template categories."""
PRODUCT_IMAGE = "product_image"
PRODUCT_VIDEO = "product_video"
PRODUCT_AVATAR = "product_avatar"
@dataclass
class ProductImageTemplate:
"""Product image generation template."""
id: str
name: str
category: TemplateCategory
description: str
environment: str # studio, lifestyle, outdoor, minimalist
background_style: str # white, transparent, lifestyle, branded
lighting: str # natural, studio, dramatic, soft
style: str # photorealistic, minimalist, luxury, technical
angle: str # front, side, top, 45_degree, 360
use_cases: List[str]
prompt_template: Optional[str] = None
recommended_resolution: str = "1024x1024"
@dataclass
class ProductVideoTemplate:
"""Product video generation template."""
id: str
name: str
category: TemplateCategory
description: str
video_type: str # demo, storytelling, feature_highlight, launch
resolution: str # 480p, 720p, 1080p
duration: int # 5 or 10 seconds
use_cases: List[str]
prompt_template: Optional[str] = None
@dataclass
class ProductAvatarTemplate:
"""Product avatar/explainer video template."""
id: str
name: str
category: TemplateCategory
description: str
explainer_type: str # product_overview, feature_explainer, tutorial, brand_message
resolution: str # 480p, 720p
use_cases: List[str]
script_template: Optional[str] = None
prompt_template: Optional[str] = None
class ProductMarketingTemplates:
"""Product Marketing template definitions."""
@classmethod
def get_product_image_templates(cls) -> List[ProductImageTemplate]:
"""Get all product image templates."""
return [
ProductImageTemplate(
id="ecommerce_product_shot",
name="E-commerce Product Shot",
category=TemplateCategory.PRODUCT_IMAGE,
description="Professional product photography for e-commerce listings. Clean white background, studio lighting, front angle.",
environment="studio",
background_style="white",
lighting="studio",
style="photorealistic",
angle="front",
use_cases=["E-commerce listings", "Product catalogs", "Amazon/Shopify"],
prompt_template="{product_name} on white background, professional product photography, studio lighting, clean and minimalist, high quality, e-commerce style",
recommended_resolution="1024x1024",
),
ProductImageTemplate(
id="lifestyle_product",
name="Lifestyle Product Image",
category=TemplateCategory.PRODUCT_IMAGE,
description="Product in realistic lifestyle setting. Natural environment, authentic use case.",
environment="lifestyle",
background_style="lifestyle",
lighting="natural",
style="photorealistic",
angle="45_degree",
use_cases=["Social media", "Marketing campaigns", "Brand storytelling"],
prompt_template="{product_name} in realistic lifestyle setting, natural environment, authentic use case, relatable scenario, professional photography",
recommended_resolution="1024x1024",
),
ProductImageTemplate(
id="luxury_product_showcase",
name="Luxury Product Showcase",
category=TemplateCategory.PRODUCT_IMAGE,
description="Premium product presentation. Dramatic lighting, elegant composition, luxury aesthetic.",
environment="studio",
background_style="minimalist",
lighting="dramatic",
style="luxury",
angle="45_degree",
use_cases=["Premium brands", "Luxury products", "High-end marketing"],
prompt_template="{product_name} luxury product showcase, dramatic lighting, elegant composition, premium aesthetic, sophisticated, high-end",
recommended_resolution="1024x1024",
),
ProductImageTemplate(
id="technical_product_detail",
name="Technical Product Detail",
category=TemplateCategory.PRODUCT_IMAGE,
description="Technical product photography. Focus on details, specifications, features.",
environment="studio",
background_style="white",
lighting="studio",
style="technical",
angle="front",
use_cases=["Technical products", "Specification sheets", "Product documentation"],
prompt_template="{product_name} technical product photography, detailed features visible, clean background, professional technical documentation style",
recommended_resolution="1024x1024",
),
ProductImageTemplate(
id="social_media_product",
name="Social Media Product Post",
category=TemplateCategory.PRODUCT_IMAGE,
description="Product image optimized for social media. Eye-catching, shareable, engaging.",
environment="lifestyle",
background_style="lifestyle",
lighting="natural",
style="photorealistic",
angle="45_degree",
use_cases=["Instagram", "Facebook", "TikTok", "Pinterest"],
prompt_template="{product_name} social media product post, eye-catching, shareable, engaging, modern aesthetic, social media optimized",
recommended_resolution="1024x1024",
),
]
@classmethod
def get_product_video_templates(cls) -> List[ProductVideoTemplate]:
"""Get all product video templates."""
return [
ProductVideoTemplate(
id="product_demo_video",
name="Product Demo Video",
category=TemplateCategory.PRODUCT_VIDEO,
description="Product demonstration video showing product in use, showcasing key features and benefits.",
video_type="demo",
resolution="720p",
duration=10,
use_cases=["Product launches", "Feature showcases", "Marketing campaigns"],
prompt_template="{product_name} being demonstrated in use, showcasing key features and benefits, professional product demonstration, dynamic camera movement, engaging presentation",
),
ProductVideoTemplate(
id="product_storytelling",
name="Product Storytelling Video",
category=TemplateCategory.PRODUCT_VIDEO,
description="Narrative-driven product showcase. Emotional connection, compelling visual story.",
video_type="storytelling",
resolution="1080p",
duration=10,
use_cases=["Brand storytelling", "Emotional marketing", "Campaign videos"],
prompt_template="Story of {product_name}, narrative-driven product showcase, emotional connection, cinematic storytelling, compelling visual narrative",
),
ProductVideoTemplate(
id="feature_highlight_video",
name="Feature Highlight Video",
category=TemplateCategory.PRODUCT_VIDEO,
description="Close-up shots highlighting key product features. Feature-focused presentation.",
video_type="feature_highlight",
resolution="720p",
duration=10,
use_cases=["Feature announcements", "Product updates", "Technical showcases"],
prompt_template="{product_name} highlighting key features, close-up shots of important details, feature-focused presentation, professional product photography",
),
ProductVideoTemplate(
id="product_launch_video",
name="Product Launch Video",
category=TemplateCategory.PRODUCT_VIDEO,
description="Exciting product launch reveal. Dynamic presentation, launch event aesthetic.",
video_type="launch",
resolution="1080p",
duration=10,
use_cases=["Product launches", "Announcements", "Launch events"],
prompt_template="{product_name} product launch reveal, exciting unveiling, dynamic presentation, professional product showcase, launch event aesthetic",
),
]
@classmethod
def get_product_avatar_templates(cls) -> List[ProductAvatarTemplate]:
"""Get all product avatar/explainer templates."""
return [
ProductAvatarTemplate(
id="product_overview_explainer",
name="Product Overview Explainer",
category=TemplateCategory.PRODUCT_AVATAR,
description="Comprehensive product overview. Engaging and informative presentation.",
explainer_type="product_overview",
resolution="720p",
use_cases=["Product introductions", "Landing pages", "Sales presentations"],
script_template="Welcome! Today I'm excited to introduce {product_name}. {product_description}. This innovative product offers [key benefits]. Let me show you what makes it special...",
prompt_template="Professional product presentation of {product_name}, engaging and informative, clear communication, confident expression",
),
ProductAvatarTemplate(
id="feature_explainer",
name="Feature Explainer Video",
category=TemplateCategory.PRODUCT_AVATAR,
description="Detailed feature explanation. Pointing gestures, clear visual communication.",
explainer_type="feature_explainer",
resolution="720p",
use_cases=["Feature announcements", "Product tutorials", "How-to guides"],
script_template="Let me show you the key features of {product_name}. First, [feature 1] - this allows you to [benefit]. Next, [feature 2] - which enables [benefit]. Finally, [feature 3] - giving you [benefit]...",
prompt_template="Demonstrating features of {product_name}, detailed explanation, pointing gestures, clear visual communication",
),
ProductAvatarTemplate(
id="product_tutorial",
name="Product Tutorial Video",
category=TemplateCategory.PRODUCT_AVATAR,
description="Step-by-step product tutorial. Instructional and clear, friendly approach.",
explainer_type="tutorial",
resolution="720p",
use_cases=["User guides", "Onboarding", "Training materials"],
script_template="Welcome to this tutorial on {product_name}. Today I'll walk you through how to use it. Step 1: [instruction]. Step 2: [instruction]. Step 3: [instruction]...",
prompt_template="Tutorial presentation for {product_name}, step-by-step explanation, instructional and clear, friendly and approachable",
),
ProductAvatarTemplate(
id="brand_message_video",
name="Brand Message Video",
category=TemplateCategory.PRODUCT_AVATAR,
description="Brand message delivery. Authentic and compelling brand storytelling.",
explainer_type="brand_message",
resolution="720p",
use_cases=["Brand campaigns", "Mission statements", "Company values"],
script_template="At [Brand Name], we believe in {product_name} because [brand values]. Our mission is [mission statement]. This product represents [brand message]...",
prompt_template="Brand message delivery for {product_name}, authentic and compelling, brand storytelling, emotional connection",
),
]
@classmethod
def get_template_by_id(cls, template_id: str) -> Optional[Dict[str, Any]]:
"""Get a specific template by ID."""
# Search in all template types
for template in cls.get_product_image_templates():
if template.id == template_id:
return {
"id": template.id,
"name": template.name,
"category": template.category.value,
"description": template.description,
"template_data": {
"environment": template.environment,
"background_style": template.background_style,
"lighting": template.lighting,
"style": template.style,
"angle": template.angle,
"recommended_resolution": template.recommended_resolution,
},
"use_cases": template.use_cases,
"prompt_template": template.prompt_template,
}
for template in cls.get_product_video_templates():
if template.id == template_id:
return {
"id": template.id,
"name": template.name,
"category": template.category.value,
"description": template.description,
"template_data": {
"video_type": template.video_type,
"resolution": template.resolution,
"duration": template.duration,
},
"use_cases": template.use_cases,
"prompt_template": template.prompt_template,
}
for template in cls.get_product_avatar_templates():
if template.id == template_id:
return {
"id": template.id,
"name": template.name,
"category": template.category.value,
"description": template.description,
"template_data": {
"explainer_type": template.explainer_type,
"resolution": template.resolution,
},
"use_cases": template.use_cases,
"script_template": template.script_template,
"prompt_template": template.prompt_template,
}
return None
@classmethod
def get_templates_by_category(cls, category: TemplateCategory) -> List[Dict[str, Any]]:
"""Get all templates for a specific category."""
if category == TemplateCategory.PRODUCT_IMAGE:
return [
{
"id": t.id,
"name": t.name,
"description": t.description,
"environment": t.environment,
"background_style": t.background_style,
"lighting": t.lighting,
"style": t.style,
"angle": t.angle,
"use_cases": t.use_cases,
"prompt_template": t.prompt_template,
"recommended_resolution": t.recommended_resolution,
}
for t in cls.get_product_image_templates()
]
elif category == TemplateCategory.PRODUCT_VIDEO:
return [
{
"id": t.id,
"name": t.name,
"description": t.description,
"video_type": t.video_type,
"resolution": t.resolution,
"duration": t.duration,
"use_cases": t.use_cases,
"prompt_template": t.prompt_template,
}
for t in cls.get_product_video_templates()
]
elif category == TemplateCategory.PRODUCT_AVATAR:
return [
{
"id": t.id,
"name": t.name,
"description": t.description,
"explainer_type": t.explainer_type,
"resolution": t.resolution,
"use_cases": t.use_cases,
"script_template": t.script_template,
"prompt_template": t.prompt_template,
}
for t in cls.get_product_avatar_templates()
]
return []
@classmethod
def apply_template(
cls,
template_id: str,
product_name: str,
product_description: Optional[str] = None,
**kwargs
) -> Dict[str, Any]:
"""
Apply a template to product data.
Args:
template_id: Template ID to apply
product_name: Product name
product_description: Product description (optional)
**kwargs: Additional template-specific parameters
Returns:
Template configuration ready for use
"""
template = cls.get_template_by_id(template_id)
if not template:
raise ValueError(f"Template not found: {template_id}")
# Format prompt/script templates with product data
result = template.copy()
if result.get("prompt_template"):
result["prompt"] = result["prompt_template"].format(
product_name=product_name,
product_description=product_description or product_name,
**kwargs
)
if result.get("script_template"):
result["script"] = result["script_template"].format(
product_name=product_name,
product_description=product_description or product_name,
**kwargs
)
return result

View File

@@ -9,6 +9,8 @@ from dataclasses import dataclass
from services.llm_providers.main_video_generation import ai_video_generate
from utils.logger_utils import get_service_logger
from utils.asset_tracker import save_asset_to_library
from services.database import SessionLocal
logger = get_service_logger("product_marketing.video")
@@ -212,6 +214,62 @@ class ProductVideoService:
result["file_url"] = file_url
result["file_size"] = len(video_bytes)
# Save to Asset Library
db = SessionLocal()
try:
# Build video prompt for metadata
video_prompt = self._build_video_prompt(
video_type=request.video_type,
product_name=request.product_name,
product_description=request.product_description,
brand_context=request.brand_context,
additional_context=request.additional_context
)
asset_id = save_asset_to_library(
db=db,
user_id=user_id,
asset_type="video",
source_module="product_marketing",
filename=filename,
file_url=file_url,
file_path=str(file_path),
file_size=len(video_bytes),
mime_type="video/mp4",
title=f"{request.product_name} - {request.video_type.replace('_', ' ').title()} Video",
description=f"Product video: {request.product_description or request.product_name}",
prompt=video_prompt,
tags=["product_marketing", "product_video", request.video_type, request.resolution],
provider=result.get("provider", "wavespeed"),
model=result.get("model_name", "alibaba/wan-2.5/text-to-video"),
cost=result.get("cost", 0.0),
generation_time=result.get("generation_time"),
asset_metadata={
"product_name": request.product_name,
"product_description": request.product_description,
"video_type": request.video_type,
"resolution": request.resolution,
"duration": request.duration,
"width": result.get("width"),
"height": result.get("height"),
},
)
if asset_id:
logger.info(f"[Product Video] ✅ Saved video to Asset Library: ID={asset_id}")
else:
logger.warning(f"[Product Video] ⚠️ Asset Library save returned None")
except Exception as db_error:
logger.error(f"[Product Video] Database error saving to Asset Library: {str(db_error)}", exc_info=True)
# Video is saved, but database tracking failed - not critical
finally:
if db:
try:
db.close()
except Exception:
pass
logger.info(
f"[Product Video] ✅ Product video generated successfully: "
f"cost=${result.get('cost', 0):.2f}, video_url={file_url}"