From 48595100a154d99358164c360d78e88debe0a642 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Sun, 22 Mar 2026 13:08:30 +0700 Subject: [PATCH] Update seo-multi-channel: remove image skill dependencies --- skills/seo-multi-channel/SKILL.md | 24 +- .../scripts/generate_content.py | 413 +++++++++--------- .../scripts/image_integration.py | 319 +++----------- 3 files changed, 279 insertions(+), 477 deletions(-) diff --git a/skills/seo-multi-channel/SKILL.md b/skills/seo-multi-channel/SKILL.md index d9d8b84..f210bc8 100644 --- a/skills/seo-multi-channel/SKILL.md +++ b/skills/seo-multi-channel/SKILL.md @@ -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 diff --git a/skills/seo-multi-channel/scripts/generate_content.py b/skills/seo-multi-channel/scripts/generate_content.py index e5f1080..b5a16b1 100644 --- a/skills/seo-multi-channel/scripts/generate_content.py +++ b/skills/seo-multi-channel/scripts/generate_content.py @@ -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() diff --git a/skills/seo-multi-channel/scripts/image_integration.py b/skills/seo-multi-channel/scripts/image_integration.py index 099ffd1..9b230d4 100644 --- a/skills/seo-multi-channel/scripts/image_integration.py +++ b/skills/seo-multi-channel/scripts/image_integration.py @@ -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()