Update seo-multi-channel: remove image skill dependencies

This commit is contained in:
Kunthawat Greethong
2026-03-22 13:08:30 +07:00
parent fe48c4c294
commit 48595100a1
3 changed files with 279 additions and 477 deletions

View File

@@ -1,13 +1,13 @@
---
name: seo-multi-channel
description: Generate multi-channel marketing content (Facebook, Ads, Blog, X) with Thai language support, image generation, and website-creator integration. Use when user wants to create content for multiple channels from a single topic.
description: Generate multi-channel marketing content (Facebook, Ads, Blog, X) with Thai language support and website-creator integration. Use when user wants to create content for multiple channels from a single topic.
---
# 🎯 SEO Multi-Channel Content Generator
**Skill Name:** `seo-multi-channel`
**Category:** `deep`
**Load Skills:** `['image-generation', 'image-edit', 'website-creator']`
**Load Skills:** `['website-creator']`
---
@@ -17,8 +17,7 @@ Generate marketing content for multiple channels from a single topic with:
-**Priority Channels:** Facebook > Facebook Ads > Google Ads > Blog > X (Twitter)
-**Thai Language Support:** Full Thai text processing with PyThaiNLP
-**Image Generation:** Auto-generate images for social/ads, save to website repo for blog
-**Product Image Handling:** Browse website repo first, then ask user or enhance with image-edit
-**Image Handling:** Browse website repo for product images, or ask user to provide
-**Website-Creator Integration:** Auto-publish blog posts to Astro content collections
-**API-Ready Output:** Structured JSON for future ad platform API integration
-**Per-Project Context:** Context files in each website repo
@@ -169,13 +168,11 @@ Output:
2. If images found:
- Select best image (highest quality, product-focused)
- Call image-edit skill:
prompt: "Enhance product image for {channel}, professional lighting, clean background, {channel}-specific dimensions"
- Ask user: "Use this image or provide a new one?"
3. If no images found:
- Ask user: "No product images found in repo. Please provide image path or URL."
- Wait for user to provide
- Then call image-edit
```
#### **Non-Product Content:**
@@ -187,8 +184,8 @@ Output:
- Stats Infographic with charts
- News Clean, modern announcement style
2. Call image-generation skill:
prompt: "{content_type} illustration for {topic}, {style}, Thai-friendly aesthetic, {channel}-optimized dimensions"
2. Ask user to provide image:
- "For this content, please provide an image or use: [suggestion]"
3. Save images:
- Social/Ads seo-multi-channel/generated-images/{topic}/{channel}/
@@ -602,8 +599,8 @@ Each piece of content is scored before output:
2. **Formality Detection:** Auto-detects from brand voice context. Defaults to casual for social, normal for blog.
3. **Image Handling:**
- Product content → Browse repo first → Edit with image-edit
- Non-product → Generate fresh with image-generation
- Product content → Browse repo first → Ask user to confirm or provide
- Non-product → Ask user to provide image
- Blog images → Website repo
- Social/Ads images → Separate folder
@@ -617,8 +614,6 @@ Each piece of content is scored before output:
## 🔄 Integration with Other Skills
- **image-generation:** Called for fresh images (non-product content)
- **image-edit:** Called for product images (browse repo first)
- **website-creator:** Blog posts published to Astro content collections
- **seo-analyzers:** Quality scoring and Thai language analysis
- **seo-data:** Performance data for content optimization
@@ -630,8 +625,7 @@ Each piece of content is scored before output:
- ✅ Content generated for all selected channels
- ✅ Thai language processing accurate (word count, keyword density)
- ✅ Product images found/enhanced or user asked to provide
- ✅ Fresh images generated for non-product content
- ✅ Product images found or user asked to provide
- ✅ Blog posts published to Astro (if enabled)
- ✅ Git commit + push successful (triggers auto-deploy)
- ✅ Output structures API-ready for future integration

View File

@@ -19,12 +19,14 @@ import yaml
# Load environment variables
from dotenv import load_dotenv
load_dotenv()
# Thai language processing
try:
from pythainlp import word_tokenize, sent_tokenize
from pythainlp.util import normalize
THAI_SUPPORT = True
except ImportError:
THAI_SUPPORT = False
@@ -34,25 +36,25 @@ except ImportError:
class ThaiTextProcessor:
"""Thai language text processing utilities"""
@staticmethod
def count_words(text: str) -> int:
"""Count Thai words (no spaces between words)"""
if not THAI_SUPPORT:
return len(text.split())
tokens = word_tokenize(text, engine="newmm")
return len([t for t in tokens if t.strip() and not t.isspace()])
@staticmethod
def count_sentences(text: str) -> int:
"""Count Thai sentences"""
if not THAI_SUPPORT:
return len(text.split('.'))
return len(text.split("."))
sentences = sent_tokenize(text, engine="whitespace")
return len(sentences)
@staticmethod
def calculate_keyword_density(text: str, keyword: str) -> float:
"""Calculate keyword density for Thai text"""
@@ -60,124 +62,117 @@ class ThaiTextProcessor:
text_words = text.lower().split()
keyword_count = text.lower().count(keyword.lower())
return (keyword_count / len(text_words) * 100) if text_words else 0
text_normalized = normalize(text)
keyword_normalized = normalize(keyword)
count = text_normalized.count(keyword_normalized)
word_count = ThaiTextProcessor.count_words(text)
return (count / word_count * 100) if word_count > 0 else 0
@staticmethod
def detect_language(text: str) -> str:
"""Detect if content is Thai or English"""
thai_chars = sum(1 for c in text if '\u0E00' <= c <= '\u0E7F')
thai_chars = sum(1 for c in text if "\u0e00" <= c <= "\u0e7f")
total_chars = len(text)
thai_ratio = thai_chars / total_chars if total_chars > 0 else 0
return 'th' if thai_ratio > 0.3 else 'en'
return "th" if thai_ratio > 0.3 else "en"
class ChannelTemplate:
"""Load and manage channel templates"""
def __init__(self, channel_name: str, templates_dir: str):
self.channel_name = channel_name
self.template_path = os.path.join(templates_dir, f"{channel_name}.yaml")
self.template = self._load_template()
def _load_template(self) -> Dict:
"""Load YAML template"""
with open(self.template_path, 'r', encoding='utf-8') as f:
with open(self.template_path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def get_specs(self) -> Dict:
"""Get channel specifications"""
return self.template.get('fields', {})
return self.template.get("fields", {})
def get_quality_requirements(self) -> Dict:
"""Get quality requirements"""
return self.template.get('quality', {})
return self.template.get("quality", {})
class ImageHandler:
"""Handle image generation and editing"""
def __init__(self, chutes_api_token: str):
self.chutes_token = chutes_api_token
self.output_base = "output"
def find_product_images(self, product_name: str, website_repo: str) -> List[str]:
"""Find existing product images in website repo"""
import glob
extensions = ['.jpg', '.jpeg', '.png', '.webp']
extensions = [".jpg", ".jpeg", ".png", ".webp"]
found_images = []
search_patterns = [
f"**/*{product_name}*{{ext}}" for ext in extensions
] + [
search_patterns = [f"**/*{product_name}*{{ext}}" for ext in extensions] + [
"public/images/**/*{ext}",
"src/assets/**/*{ext}"
"src/assets/**/*{ext}",
]
for pattern in search_patterns:
matches = glob.glob(
os.path.join(website_repo, pattern.format(ext='*')),
recursive=True
os.path.join(website_repo, pattern.format(ext="*")), recursive=True
)
# Try specific extensions
for ext in extensions:
specific_matches = glob.glob(
os.path.join(website_repo, pattern.format(ext=ext)),
recursive=True
os.path.join(website_repo, pattern.format(ext=ext)), recursive=True
)
found_images.extend(specific_matches)
return list(set(found_images))[:10]
def generate_image_for_channel(self, topic: str, channel: str, content_type: str) -> str:
def generate_image_for_channel(
self, topic: str, channel: str, content_type: str
) -> str:
"""
Generate image for content.
For product: browse repo first, then ask user or use image-edit
For non-product: generate fresh with image-generation
Handle image for content.
For product: browse repo first, ask user to confirm/provide
For non-product: ask user to provide image
"""
# This would call the image-generation or image-edit skills
# For now, return placeholder
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_dir = os.path.join(
self.output_base,
self._slugify(topic),
channel,
"images"
self.output_base, self._slugify(topic), channel, "images"
)
os.makedirs(output_dir, exist_ok=True)
image_path = os.path.join(output_dir, f"generated_{timestamp}.png")
# Placeholder - in real implementation, would call image-generation skill
print(f" [Image Generation] Would generate image for {channel}")
print(f" [Image] Please provide image for: {channel}")
print(f" Topic: {topic}, Type: {content_type}")
return image_path
def _slugify(self, text: str) -> str:
"""Convert text to URL-friendly slug"""
import re
slug = re.sub(r'[^\w\s-]', '', text.lower())
slug = re.sub(r'[-\s]+', '-', slug)
return slug.strip('-_')
slug = re.sub(r"[^\w\s-]", "", text.lower())
slug = re.sub(r"[-\s]+", "-", slug)
return slug.strip("-_")
class ContentGenerator:
"""Main content generator class"""
def __init__(
self,
topic: str,
channels: List[str],
website_repo: Optional[str] = None,
auto_publish: bool = False,
language: Optional[str] = None
language: Optional[str] = None,
):
self.topic = topic
self.channels = channels
@@ -186,166 +181,172 @@ class ContentGenerator:
self.language = language
self.templates_dir = os.path.join(os.path.dirname(__file__), "templates")
self.output_base = "output"
# Initialize components
self.text_processor = ThaiTextProcessor()
self.image_handler = ImageHandler(os.getenv("CHUTES_API_TOKEN", ""))
# Load templates
self.templates = {}
for channel in channels:
template_name = self._get_template_name(channel)
if template_name:
self.templates[channel] = ChannelTemplate(template_name, self.templates_dir)
self.templates[channel] = ChannelTemplate(
template_name, self.templates_dir
)
def _get_template_name(self, channel: str) -> Optional[str]:
"""Map channel name to template file"""
mapping = {
'facebook': 'facebook',
'facebook_ads': 'facebook_ads',
'google_ads': 'google_ads',
'blog': 'blog',
'x': 'x_thread',
'twitter': 'x_thread'
"facebook": "facebook",
"facebook_ads": "facebook_ads",
"google_ads": "google_ads",
"blog": "blog",
"x": "x_thread",
"twitter": "x_thread",
}
return mapping.get(channel.lower())
def generate_all(self) -> Dict[str, Any]:
"""Generate content for all channels"""
results = {
'topic': self.topic,
'generated_at': datetime.now().isoformat(),
'channels': {},
'summary': {}
"topic": self.topic,
"generated_at": datetime.now().isoformat(),
"channels": {},
"summary": {},
}
print(f"\n🎯 Generating content for: {self.topic}")
print(f"📱 Channels: {', '.join(self.channels)}")
print(f"🌐 Language: {self.language or 'auto-detect'}\n")
for channel in self.channels:
if channel in self.templates:
print(f" Generating {channel}...")
channel_result = self._generate_for_channel(channel)
results['channels'][channel] = channel_result
results["channels"][channel] = channel_result
# Save results
self._save_results(results)
return results
def _generate_for_channel(self, channel: str) -> Dict:
"""Generate content for specific channel"""
template = self.templates[channel]
specs = template.get_specs()
# Detect language from topic
lang = self.language or self.text_processor.detect_language(self.topic)
# Generate variations (placeholder - real implementation would use LLM)
variations = []
num_variations = template.template.get('output', {}).get('variations', 5)
num_variations = template.template.get("output", {}).get("variations", 5)
for i in range(num_variations):
variation = self._create_variation(channel, i, lang, specs)
variations.append(variation)
return {
'channel': channel,
'language': lang,
'variations': variations,
'api_ready': template.template.get('api_ready', False)
"channel": channel,
"language": lang,
"variations": variations,
"api_ready": template.template.get("api_ready", False),
}
def _create_variation(
self,
channel: str,
variation_num: int,
language: str,
specs: Dict
self, channel: str, variation_num: int, language: str, specs: Dict
) -> Dict:
"""Create single content variation"""
# This is a placeholder - real implementation would call LLM
# with proper prompts based on channel template
base_variation = {
'id': f"{channel}_var_{variation_num + 1}",
'created_at': datetime.now().isoformat()
"id": f"{channel}_var_{variation_num + 1}",
"created_at": datetime.now().isoformat(),
}
# Channel-specific structure
if channel == 'facebook':
base_variation.update({
'primary_text': f"[Facebook Post {variation_num + 1}] {self.topic}...",
'headline': f"[Headline] {self.topic}",
'cta': "เรียนรู้เพิ่มเติม" if language == 'th' else "Learn More",
'hashtags': [f"#{self.topic.replace(' ', '')}"],
'image': {
'path': self.image_handler.generate_image_for_channel(
self.topic, channel, 'social'
)
if channel == "facebook":
base_variation.update(
{
"primary_text": f"[Facebook Post {variation_num + 1}] {self.topic}...",
"headline": f"[Headline] {self.topic}",
"cta": "เรียนรู้เพิ่มเติม" if language == "th" else "Learn More",
"hashtags": [f"#{self.topic.replace(' ', '')}"],
"image": {
"path": self.generate_image_for_channel(
self.topic, channel, "social"
)
},
}
})
elif channel == 'facebook_ads':
base_variation.update({
'primary_text': f"[FB Ad Primary Text] {self.topic}...",
'headline': f"[FB Ad Headline - 40 chars]",
'description': f"[FB Ad Description - 90 chars]",
'cta': "SHOP_NOW",
'api_ready': {
'platform': 'meta',
'api_version': 'v18.0',
'endpoint': '/act_{ad_account_id}/adcreatives'
)
elif channel == "facebook_ads":
base_variation.update(
{
"primary_text": f"[FB Ad Primary Text] {self.topic}...",
"headline": f"[FB Ad Headline - 40 chars]",
"description": f"[FB Ad Description - 90 chars]",
"cta": "SHOP_NOW",
"api_ready": {
"platform": "meta",
"api_version": "v18.0",
"endpoint": "/act_{ad_account_id}/adcreatives",
},
}
})
elif channel == 'google_ads':
base_variation.update({
'headlines': [
{'text': f"[Headline {i+1}] {self.topic}"}
for i in range(15)
],
'descriptions': [
{'text': f"[Description {i+1}] Learn more about {self.topic}"}
for i in range(4)
],
'keywords': [self.topic, f"บริการ {self.topic}"],
'api_ready': {
'platform': 'google',
'api_version': 'v15.0',
'endpoint': '/google.ads.googleads.v15.services/GoogleAdsService:Mutate'
)
elif channel == "google_ads":
base_variation.update(
{
"headlines": [
{"text": f"[Headline {i + 1}] {self.topic}"} for i in range(15)
],
"descriptions": [
{"text": f"[Description {i + 1}] Learn more about {self.topic}"}
for i in range(4)
],
"keywords": [self.topic, f"บริการ {self.topic}"],
"api_ready": {
"platform": "google",
"api_version": "v15.0",
"endpoint": "/google.ads.googleads.v15.services/GoogleAdsService:Mutate",
},
}
})
elif channel == 'blog':
base_variation.update({
'markdown': self._generate_blog_markdown(language),
'frontmatter': {
'title': f"{self.topic} - Complete Guide",
'description': f"Learn about {self.topic}",
'slug': self._slugify(self.topic),
'lang': language
},
'word_count': 2000 if language == 'en' else 1500,
'publish_status': 'draft'
})
elif channel in ['x', 'twitter']:
base_variation.update({
'tweets': [
f"[Tweet {i+1}/7] Content about {self.topic}..."
for i in range(7)
],
'thread_title': f"Everything about {self.topic} 🧵"
})
)
elif channel == "blog":
base_variation.update(
{
"markdown": self._generate_blog_markdown(language),
"frontmatter": {
"title": f"{self.topic} - Complete Guide",
"description": f"Learn about {self.topic}",
"slug": self._slugify(self.topic),
"lang": language,
},
"word_count": 2000 if language == "en" else 1500,
"publish_status": "draft",
}
)
elif channel in ["x", "twitter"]:
base_variation.update(
{
"tweets": [
f"[Tweet {i + 1}/7] Content about {self.topic}..."
for i in range(7)
],
"thread_title": f"Everything about {self.topic} 🧵",
}
)
return base_variation
def _generate_blog_markdown(self, language: str) -> str:
"""Generate blog post in Markdown format"""
slug = self._slugify(self.topic)
markdown = f"""---
title: "{self.topic} - Complete Guide"
description: "Learn everything about {self.topic} in this comprehensive guide"
@@ -354,7 +355,7 @@ slug: {slug}
lang: {language}
category: guides
tags: ["{self.topic}", "guide"]
created: {datetime.now().strftime('%Y-%m-%d')}
created: {datetime.now().strftime("%Y-%m-%d")}
---
# {self.topic}: Complete Guide
@@ -384,95 +385,91 @@ created: {datetime.now().strftime('%Y-%m-%d')}
[Summary and call-to-action...]
"""
return markdown
def _save_results(self, results: Dict):
"""Save results to output directory"""
output_dir = os.path.join(
self.output_base,
self._slugify(self.topic)
)
output_dir = os.path.join(self.output_base, self._slugify(self.topic))
os.makedirs(output_dir, exist_ok=True)
output_file = os.path.join(output_dir, "results.json")
with open(output_file, 'w', encoding='utf-8') as f:
with open(output_file, "w", encoding="utf-8") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\n✅ Results saved to: {output_file}")
def _slugify(self, text: str) -> str:
"""Convert text to URL-friendly slug"""
import re
slug = re.sub(r'[^\w\s-]', '', text.lower())
slug = re.sub(r'[-\s]+', '-', slug)
return slug.strip('-_')
slug = re.sub(r"[^\w\s-]", "", text.lower())
slug = re.sub(r"[-\s]+", "-", slug)
return slug.strip("-_")
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(
description='Generate multi-channel marketing content from a single topic'
description="Generate multi-channel marketing content from a single topic"
)
parser.add_argument(
'--topic', '-t',
required=True,
help='Topic to generate content about'
"--topic", "-t", required=True, help="Topic to generate content about"
)
parser.add_argument(
'--channels', '-c',
nargs='+',
default=['facebook', 'facebook_ads', 'google_ads', 'blog', 'x'],
choices=['facebook', 'facebook_ads', 'google_ads', 'blog', 'x', 'twitter'],
help='Channels to generate content for'
"--channels",
"-c",
nargs="+",
default=["facebook", "facebook_ads", "google_ads", "blog", "x"],
choices=["facebook", "facebook_ads", "google_ads", "blog", "x", "twitter"],
help="Channels to generate content for",
)
parser.add_argument(
'--website-repo', '-w',
help='Path to website repository (for blog auto-publish)'
"--website-repo",
"-w",
help="Path to website repository (for blog auto-publish)",
)
parser.add_argument(
'--auto-publish',
action='store_true',
help='Auto-publish blog posts to website'
"--auto-publish", action="store_true", help="Auto-publish blog posts to website"
)
parser.add_argument(
'--language', '-l',
choices=['th', 'en'],
help='Content language (default: auto-detect)'
"--language",
"-l",
choices=["th", "en"],
help="Content language (default: auto-detect)",
)
parser.add_argument(
'--product-name', '-p',
help='Product name (for product image handling)'
"--product-name", "-p", help="Product name (for product image handling)"
)
args = parser.parse_args()
# Create generator
generator = ContentGenerator(
topic=args.topic,
channels=args.channels,
website_repo=args.website_repo,
auto_publish=args.auto_publish,
language=args.language
language=args.language,
)
# Generate content
results = generator.generate_all()
# Print summary
print("\n📊 Summary:")
print(f" Topic: {results['topic']}")
print(f" Channels generated: {len(results['channels'])}")
for channel, data in results['channels'].items():
for channel, data in results["channels"].items():
print(f" - {channel}: {len(data['variations'])} variations")
print(f"\n✨ Done!")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -2,312 +2,123 @@
"""
Image Integration Module
Integrates with image-generation and image-edit skills.
Handles product vs non-product image workflows.
Since image-generation and image-edit skills are removed, this module
provides utilities to find existing images and ask user to provide new ones.
"""
import os
import sys
import subprocess
import glob
import argparse
from pathlib import Path
from typing import Optional, List
class ImageIntegration:
"""Integrate with image-generation and image-edit skills"""
def __init__(self, skills_base_path: str = None):
"""
Initialize image integration
Args:
skills_base_path: Base path to skills directory
"""
if skills_base_path is None:
# Default: assume we're in skills/seo-multi-channel/scripts/
base = Path(__file__).parent.parent.parent
self.skills_base = str(base)
else:
self.skills_base = skills_base
self.image_gen_script = os.path.join(self.skills_base, 'image-generation/scripts/image_gen.py')
self.image_edit_script = os.path.join(self.skills_base, 'image-edit/scripts/image_edit.py')
def generate_image(self, prompt: str, output_dir: str, width: int = 1024,
height: int = 1024, topic: str = None, channel: str = None) -> str:
"""
Generate image using image-generation skill
Args:
prompt: Image generation prompt
output_dir: Directory to save image
width: Image width
height: Image height
topic: Topic name (for filename)
channel: Channel name (for subfolder)
Returns:
Path to generated image
"""
# Create output directory
if topic and channel:
output_path = os.path.join(output_dir, topic, channel, 'images')
else:
output_path = output_dir
os.makedirs(output_path, exist_ok=True)
# Build command
cmd = [
sys.executable,
self.image_gen_script,
'generate',
prompt,
'--width', str(width),
'--height', str(height)
]
print(f"\n🎨 Generating image...")
print(f" Prompt: {prompt[:100]}...")
print(f" Size: {width}x{height}")
try:
# Run image generation
result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.path.dirname(self.image_gen_script))
if result.returncode == 0:
# Parse output (format: "filename.png [id]")
output_line = result.stdout.strip().split('\n')[-1]
image_path = output_line.split(' ')[0]
# Move to our output directory if needed
if image_path and os.path.exists(image_path):
dest_path = os.path.join(output_path, os.path.basename(image_path))
if image_path != dest_path:
import shutil
shutil.copy(image_path, dest_path)
print(f" ✓ Saved: {dest_path}")
return dest_path
print(f" ✗ Generation failed: {result.stderr}")
return None
except Exception as e:
print(f" ✗ Error: {e}")
return None
def edit_product_image(self, base_image_path: str, edit_prompt: str,
output_dir: str, topic: str = None, channel: str = None) -> str:
"""
Edit product image using image-edit skill
Args:
base_image_path: Path to existing product image
edit_prompt: Edit instructions
output_dir: Directory to save edited image
topic: Topic name
channel: Channel name
Returns:
Path to edited image
"""
if not os.path.exists(base_image_path):
print(f" ✗ Base image not found: {base_image_path}")
return None
# Create output directory
if topic and channel:
output_path = os.path.join(output_dir, topic, channel, 'images')
else:
output_path = output_dir
os.makedirs(output_path, exist_ok=True)
# Build command
cmd = [
sys.executable,
self.image_edit_script,
edit_prompt,
base_image_path
]
print(f"\n✏️ Editing product image...")
print(f" Base: {base_image_path}")
print(f" Edit: {edit_prompt[:100]}...")
try:
result = subprocess.run(cmd, capture_output=True, text=True, cwd=os.path.dirname(self.image_edit_script))
if result.returncode == 0:
output_line = result.stdout.strip().split('\n')[-1]
image_path = output_line.split(' ')[0]
if image_path and os.path.exists(image_path):
dest_path = os.path.join(output_path, os.path.basename(image_path))
if image_path != dest_path:
import shutil
shutil.copy(image_path, dest_path)
print(f" ✓ Saved: {dest_path}")
return dest_path
print(f" ✗ Edit failed: {result.stderr}")
return None
except Exception as e:
print(f" ✗ Error: {e}")
return None
def __init__(self, skills_base_path: str = ""):
pass
def find_product_images(self, product_name: str, website_repo: str) -> List[str]:
"""
Find existing product images in website repo
Args:
product_name: Product name to search for
website_repo: Path to website repository
Returns:
List of image paths
"""
import glob
extensions = ['.jpg', '.jpeg', '.png', '.webp']
if not website_repo or not os.path.exists(website_repo):
return []
extensions = [".jpg", ".jpeg", ".png", ".webp"]
found_images = []
# Search patterns
patterns = [
f"**/*{product_name}*{{ext}}",
f"public/images/**/*{{ext}}",
f"src/assets/**/*{{ext}}"
f"src/assets/**/*{{ext}}",
]
for pattern in patterns:
for ext in extensions:
search_pattern = pattern.format(ext=ext)
matches = glob.glob(os.path.join(website_repo, search_pattern), recursive=True)
found_images.extend(matches[:5]) # Limit per pattern
return list(set(found_images))[:10] # Return unique, max 10
def handle_product_content(self, product_name: str, website_repo: str,
edit_prompt: str, output_dir: str,
topic: str, channel: str) -> Optional[str]:
"""
Handle image for product content
Workflow:
1. Browse website repo for product images
2. If found: edit with image-edit
3. If not found: ask user to provide
Args:
product_name: Product name
website_repo: Path to website repo
edit_prompt: Edit instructions
output_dir: Output directory
topic: Topic name
channel: Channel name
Returns:
Path to image or None
"""
matches = glob.glob(
os.path.join(website_repo, search_pattern), recursive=True
)
found_images.extend(matches[:5])
return list(set(found_images))[:10]
def handle_product_content(
self, product_name: str, website_repo: str
) -> Optional[List[str]]:
"""Handle image for product content - returns found images for user to select"""
print(f"\n🔍 Looking for product images: {product_name}")
# Step 1: Find existing images
images = self.find_product_images(product_name, website_repo)
if images:
print(f" ✓ Found {len(images)} image(s)")
best_image = images[0] # Use first/best match
# Step 2: Edit image
return self.edit_product_image(
best_image,
edit_prompt,
output_dir,
topic,
channel
)
print(f" ✓ Found {len(images)} image(s):")
for i, img in enumerate(images[:5], 1):
print(f" {i}. {img}")
return images
else:
print(f" ✗ No product images found in repo")
print(f" Please provide product image manually")
return None
def handle_non_product_content(self, content_type: str, topic: str,
output_dir: str, channel: str) -> Optional[str]:
def suggest_non_product_image(self, content_type: str, topic: str) -> str:
"""
Generate fresh image for non-product content
Suggest image for non-product content
Args:
content_type: Type (service, stats, knowledge)
topic: Topic name
output_dir: Output directory
channel: Channel name
Returns:
Path to generated image
Suggestion message
"""
# Create prompt based on content type
prompts = {
'service': f"Professional illustration of {topic}, modern flat design, business context, Thai-friendly aesthetic",
'stats': f"Data visualization infographic for {topic}, clean charts, professional style",
'knowledge': f"Educational illustration for {topic}, clear visual metaphor, engaging style",
'default': f"Professional image for {topic}, modern design, high quality"
suggestions = {
"service": f"Please provide a professional service illustration for: {topic}",
"stats": f"Please provide a data visualization/infographic image for: {topic}",
"knowledge": f"Please provide an educational illustration for: {topic}",
"default": f"Please provide an image for: {topic}",
}
prompt = prompts.get(content_type, prompts['default'])
# Generate image
return self.generate_image(
prompt,
output_dir,
topic=topic,
channel=channel
)
return suggestions.get(content_type, suggestions["default"])
def main():
"""Test image integration"""
parser = argparse.ArgumentParser(description='Test Image Integration')
parser.add_argument('--action', choices=['generate', 'edit', 'find'], required=True)
parser.add_argument('--prompt', help='Image prompt or edit instructions')
parser.add_argument('--topic', help='Topic name')
parser.add_argument('--channel', help='Channel name')
parser.add_argument('--output-dir', default='./output', help='Output directory')
parser.add_argument('--product-name', help='Product name (for find action)')
parser.add_argument('--website-repo', help='Website repo path (for find action)')
parser = argparse.ArgumentParser(description="Test Image Integration")
parser.add_argument("--action", choices=["find", "suggest"], required=True)
parser.add_argument("--topic", help="Topic name")
parser.add_argument("--product-name", help="Product name (for find action)")
parser.add_argument("--website-repo", help="Website repo path (for find action)")
parser.add_argument(
"--content-type", default="default", help="Content type (for suggest action)"
)
args = parser.parse_args()
integration = ImageIntegration()
if args.action == 'generate':
result = integration.handle_non_product_content(
'service', args.topic, args.output_dir, args.channel
)
print(f"\nResult: {result}")
elif args.action == 'edit':
if not args.product_name or not args.website_repo:
print("Error: --product-name and --website-repo required for edit")
return
result = integration.handle_product_content(
args.product_name, args.website_repo, args.prompt,
args.output_dir, args.topic, args.channel
)
print(f"\nResult: {result}")
elif args.action == 'find':
if args.action == "find":
if not args.product_name or not args.website_repo:
print("Error: --product-name and --website-repo required for find")
return
images = integration.find_product_images(args.product_name, args.website_repo)
print(f"\nFound {len(images)} images:")
for img in images:
print(f" - {img}")
elif args.action == "suggest":
suggestion = integration.suggest_non_product_image(
args.content_type, args.topic or "your topic"
)
print(f"\n{suggestion}")
if __name__ == '__main__':
if __name__ == "__main__":
main()