Spaces:
Sleeping
Sleeping
| """ | |
| Arabic Fashion Marketing AI - Hugging Face Space | |
| mT5 + LoRA โ 3 Arabic marketing post variants | |
| """ | |
| import gradio as gr | |
| import torch | |
| import re | |
| import random | |
| from transformers import MT5ForConditionalGeneration, MT5Tokenizer | |
| from peft import PeftModel | |
| # โโโ Load Model โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| device = DEVICE | |
| print("===== Application Startup =====") | |
| print("Loading model from Space files...") | |
| base_model_name = "google/mt5-base" | |
| tokenizer = MT5Tokenizer.from_pretrained(".") | |
| base_model = MT5ForConditionalGeneration.from_pretrained(base_model_name) | |
| model = PeftModel.from_pretrained(base_model, ".").to(DEVICE) | |
| model.eval() | |
| print(f"โ Model loaded on: {DEVICE}") | |
| # โโโ Config โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| EMOJIS = ['โจ', '๐ซ', '๐', '๐', '๐ฅ', '๐ค', '๐', '๐', '๐'] | |
| PLATFORM_CONFIG = { | |
| 'Instagram': {'hashtags': 8, 'style': 'formal_marketing', 'max_sentences': 4}, | |
| 'TikTok': {'hashtags': 5, 'style': 'formal_marketing', 'max_sentences': 3}, | |
| 'Facebook': {'hashtags': 4, 'style': 'formal_marketing', 'max_sentences': 4}, | |
| 'Website/Ads': {'hashtags': 3, 'style': 'formal_marketing', 'max_sentences': 4}, | |
| } | |
| AR_MAP = { | |
| 'platform': {'Instagram': 'ุฅูุณุชุฌุฑุงู ', 'TikTok': 'ุชูู ุชูู', 'Facebook': 'ููุณุจูู', 'Website/Ads': 'ุงูู ููุน ูุงูุฅุนูุงูุงุช'}, | |
| 'subcategory': {'Dresses': 'ูุณุชุงู', 'Bags': 'ุญููุจุฉ', 'Jewelry': 'ู ุฌููุฑุงุช', 'Loungewear': 'ุทูู ู ูุฒูู', | |
| 'Outerwear': 'ู ุนุทู', 'Sandals': 'ุตูุฏู', 'Heels': 'ุญุฐุงุก ุจูุนุจ', 'Pants': 'ุจูุทุงู', | |
| 'Abayas': 'ุนุจุงูุฉ', 'Sneakers': 'ุญุฐุงุก ุฑูุงุถู', 'Tops': 'ุจููุฒุฉ', 'Boots': 'ุจูุช', | |
| 'Scarves': 'ูุดุงุญ', 'Flats': 'ุญุฐุงุก ู ุณุทุญ', 'Activewear': 'ู ูุงุจุณ ุฑูุงุถูุฉ', | |
| 'Sunglasses': 'ูุธุงุฑุฉ ุดู ุณูุฉ', 'Belts/Others': 'ุญุฒุงู ', 'Belts / Others': 'ุญุฒุงู '}, | |
| 'material': {'Leather': 'ุงูุฌูุฏ', 'Genuine Leather': 'ุงูุฌูุฏ ุงูุทุจูุนู', 'Cotton': 'ุงููุทู', | |
| 'Silk': 'ุงูุญุฑูุฑ', 'Viscose': 'ุงูููุณููุฒ', 'Chiffon': 'ุงูุดูููู', 'Denim': 'ุงูุฏููู ', | |
| 'Suede': 'ุงูุดู ูุงู', 'Jersey': 'ุงูุฌูุฑุณู', 'Polyester': 'ุงูุจูููุณุชุฑ', 'Linen': 'ุงููุชุงู', | |
| 'Crepe': 'ุงููุฑูุจ', 'Canvas': 'ุงููุงููุงุณ', 'Mesh': 'ุงูุดุจู', 'Rubber': 'ุงูู ุทุงุท', | |
| 'Synthetic': 'ุงูุฎุงู ุงุช ุงูุตูุงุนูุฉ', 'Modal': 'ุงูู ูุฏุงู', 'Bamboo': 'ุฃููุงู ุงูุฎูุฒุฑุงู', | |
| 'Gold-plated': 'ุงูู ุนุฏู ุงูู ุทูู ุจุงูุฐูุจ'}, | |
| 'season': {'Summer': 'ุงูุตูู', 'Winter': 'ุงูุดุชุงุก', 'Spring': 'ุงูุฑุจูุน', 'Autumn': 'ุงูุฎุฑูู', | |
| 'All-Season': 'ูู ุงูู ูุงุณู ', 'All Seasons': 'ูู ุงูู ูุงุณู '}, | |
| 'style': {'Casual': 'ูุงุฌูุงู', 'Trendy': 'ุนุตุฑู', 'Formal': 'ุฑุณู ู', 'Sporty': 'ุฑูุงุถู', | |
| 'Modest': 'ู ุญุชุดู ', 'Luxury': 'ูุงุฎุฑ'}, | |
| 'occasion': {'Daily': 'ุงูุฅุทูุงูุงุช ุงูููู ูุฉ', 'Work': 'ุฃูุงู ุงูุนู ู', 'Party': 'ุงูุณูุฑุงุช ูุงูู ูุงุณุจุงุช', | |
| 'Travel': 'ุงูุณูุฑ', 'Ramadan/Eid': 'ุฑู ุถุงู ูุงูุนูุฏ', 'Gym/Beach': 'ุงูุฑูุงุถุฉ ูุงูุดุงุทุฆ', | |
| 'Wedding': 'ุงูุฃูุฑุงุญ', 'Sports': 'ุงูุฑูุงุถุฉ'}, | |
| 'goal': {'Sales': 'ุงูู ุจูุนุงุช', 'Brand Awareness': 'ุงููุนู ุจุงูุนูุงู ุฉ', 'Engagement': 'ุงูุชูุงุนู', 'Launch': 'ุงูุฅุทูุงู'}, | |
| 'tone': {'Friendly': 'ูุฏูุฉ', 'Bold': 'ุฌุฑูุฆุฉ', 'Luxury': 'ูุงุฎุฑุฉ', 'Minimal': 'ุฑุงููุฉ', | |
| 'Casual': 'ุฎูููุฉ', 'Formal': 'ุฑุณู ูุฉ', 'Confidence': 'ูุงุซูุฉ'}, | |
| 'emotion': {'Comfort': 'ุงูุฑุงุญุฉ', 'Confidence': 'ุงูุซูุฉ', 'Excitement': 'ุงูุญู ุงุณ', | |
| 'Trust': 'ุงูุซูุฉ', 'Elegance': 'ุงูุฃูุงูุฉ', 'Desire': 'ุงูุฑุบุจุฉ', 'Joy': 'ุงูุจูุฌุฉ', 'Pride': 'ุงููุฎุฑ'}, | |
| 'cta': {'Discover More': 'ุงุณุชูุดูู ุงูู ุฒูุฏ', 'Shop Now': 'ุงุทูุจูู ุงูุขู', | |
| 'Limited Offer': 'ุงุณุชููุฏู ู ู ุงูุนุฑุถ ุงูุขู', 'Explore Collection': 'ุงุณุชูุดูู ุงูู ุฌู ูุนุฉ', | |
| 'Buy Today': 'ุงุทูุจูู ุงูููู '}, | |
| 'usp': {'Premium embroidery': 'ุงูุชุทุฑูุฒ ุงููุงุฎุฑ', 'Performance meets style': 'ุงูุฃุฏุงุก ุงูุฐู ูุฌู ุน ุจูู ุงูุนู ููุฉ ูุงูุฃูุงูุฉ', | |
| 'Timeless design': 'ุงูุชุตู ูู ุงูุฎุงูุฏ', 'Breathable fabric': 'ุงูุฎุงู ุฉ ุงูู ุฑูุญุฉ ููุงุฑุชุฏุงุก'}, | |
| } | |
| HOOK_BANK = { | |
| 'Instagram': { | |
| 'product': [('luxury_statement', 'ุงููุฎุงู ุฉ ุงูุญููููุฉ ุชุธูุฑ ูู ุงูุชูุงุตูู ุงูุชู ุชุตูุน ุญุถูุฑุงู ู ู ูุฒุงู'), | |
| ('value_statement', 'ุงูุฃูุงูุฉ ุงูุฑุงููุฉ ุชุจุฏุฃ ู ู ุงุฎุชูุงุฑ ุงููุทุนุฉ ุงูุชู ุชุญู ู ููู ุฉ ูุงุถุญุฉ')], | |
| 'lifestyle': [('emotional', 'ุญูู ุชููุฏ ุงูุฃูุงูุฉ ุชูุงุตูู ุงูุฅุทูุงูุฉ ูุตุจุญ ุงูุญุถูุฑ ุฃูุซุฑ ุชุฃุซูุฑุงู'), | |
| ('question', 'ูู ุชุจุญุซูู ุนู ุฅุทูุงูุฉ ุชูุณุฌู ู ุน ู ูุงุณุจุงุชู ุจุฃุณููุจ ูููุช ุงูุฃูุธุงุฑ')], | |
| 'campaign': [('cta_lead', 'ูุฏู ู ุฅุทูุงูุชู ุจุฃุณููุจ ูู ูุญ ุงูู ุญุชูู ุฌุงุฐุจูุฉ ุฃูุถุญ'), | |
| ('value_statement', 'ุงุฌุนูู ุญุถูุฑู ุงูุฑูู ู ุฃูุซุฑ ุฃูุงูุฉ ู ู ุฎูุงู ูุทุนุฉ ุชุนูุณ ุงูุฐูู ุงูุฑููุน')], | |
| }, | |
| 'TikTok': { | |
| 'product': [('direct_benefit', 'ุงุฎุชุงุฑู ูุทุนุฉ ุชู ูุญ ุฅุทูุงูุชู ุชูุงุฒูุงู ุจูู ุงูุนู ููุฉ ูุงูุฃูุงูุฉ'), | |
| ('value_statement','ูุทุนุฉ ู ุตู ู ุฉ ูุชู ูุญ ุญุถูุฑู ุงูููู ู ูู ุณุฉ ุฃูุซุฑ ุชุฑุชูุจุงู')], | |
| 'lifestyle': [('question', 'ูู ุชุจุญุซูู ุนู ุชุตู ูู ููุงูุจ ููู ู ุจุฃุณููุจ ุฃููู'), | |
| ('emotional','ุญูู ุชูุชูู ุงูุซูุฉ ู ุน ุงูุจุณุงุทุฉ ุชุตุจุญ ุงูุฅุทูุงูุฉ ุฃูุซุฑ ุญุถูุฑุงู')], | |
| 'campaign': [('cta_lead', 'ุงู ูุญู ู ุญุชูุงู ุจุฏุงูุฉ ุฃููู ุชุจุฑุฒ ููู ุฉ ุงูุฅุทูุงูุฉ'), | |
| ('direct_benefit', 'ูุฏู ู ู ุญุชูู ุฃุณุฑุน ุฌุฐุจุงู ู ู ุฎูุงู ูุทุนุฉ ุชุนุจูุฑ ุนู ุฃุณููุจ ูุงุถุญ')], | |
| }, | |
| 'Facebook': { | |
| 'product': [('value_statement', 'ุงุฎุชูุงุฑ ุนู ูู ูุนูุณ ุฃูุงูุฉ ูุงุถุญุฉ ูููู ุฉ ู ุณุชู ุฑุฉ'), | |
| ('luxury_statement', 'ุงููุทุน ุงูู ู ูุฒุฉ ูู ุงูุชู ุชุฌู ุน ุจูู ุงูุฌูุฏุฉ ูุฃูุงูุฉ ุงูุชูุงุตูู')], | |
| 'lifestyle': [('question', 'ูู ุชุจุญุซูู ุนู ูุทุนุฉ ุชุฌู ุน ุจูู ุงูููู ุฉ ูุงูุฃูุงูุฉ'), | |
| ('emotional','ุงูุซูุฉ ุชุจุฏุฃ ู ู ุงุฎุชูุงุฑ ููุณุฌู ู ุน ุญุถูุฑู ุงูููู ู')], | |
| 'campaign': [('cta_lead', 'ูุฏู ู ู ุญุชูู ูุจุฑุฒ ุฌูุฏุฉ ุงูู ูุชุฌ ุจุฃุณููุจ ุงุญุชุฑุงูู'), | |
| ('value_statement', 'ุงุฌุนูู ุฑุณุงูุชู ุฃูุซุฑ ูุถูุญุงู ู ุน ูุทุนุฉ ุชุฏุนู ุญุถูุฑ ุงูุนูุงู ุฉ')], | |
| }, | |
| 'Website/Ads': { | |
| 'product': [('value_statement', 'ุชูุงุตูู ุงูู ูุชุฌ ุงููุงุถุญุฉ ุชู ูุญ ุงูุงุฎุชูุงุฑ ููู ุฉ ุฃูุจุฑ')], | |
| 'lifestyle': [('question', 'ูู ุชุจุญุซูู ุนู ูุทุนุฉ ุชูุฏู ูุงุฆุฏุฉ ูุงุถุญุฉ ูุฃูุงูุฉ ุนู ููุฉ')], | |
| 'campaign': [('cta_lead', 'ูุฏู ู ู ูุชุฌู ุจุฃุณููุจ ูุจุฑุฒ ููู ุชู ุจุตูุฑุฉ ู ุจุงุดุฑุฉ')], | |
| }, | |
| } | |
| REALISTIC_HASHTAGS = { | |
| 'Instagram': ['#ุฃูุงูุฉ_ูุณุงุฆูุฉ', '#ุฅุทูุงูุฉ_ุฑุงููุฉ', '#ู ูุถุฉ_ุฑุงููุฉ', '#ุฅุทูุงูุฉ_ุงูุณูุฑุงุช', '#ุชูุงุตูู_ูุงุฎุฑุฉ'], | |
| 'TikTok': ['#ุณุชุงูู_ุนู ูู', '#ุฅุทูุงูุฉ_ุนุตุฑูุฉ', '#ู ูุถุฉ_ููู ูุฉ', '#ููู_ููู ู', '#ุณูููุฑุฒ_ูุณุงุฆู'], | |
| 'Facebook': ['#ุฅุทูุงูุฉ_ุงูุนู ู', '#ุฃูุงูุฉ_ุนู ููุฉ', '#ุซูุฉ_ูู_ุงูุงุฎุชูุงุฑ', '#ุฌูุฏุฉ_ุฑุงููุฉ', '#ุชูุงุตูู_ุฃูููุฉ'], | |
| 'Website/Ads': ['#ุชุตู ูู _ู ู ูุฒ', '#ุฎุงู ุฉ_ุฑุงููุฉ', '#ุงุฎุชูุงุฑ_ุฃููู'], | |
| } | |
| BANNED_PHRASES = [ | |
| 'ุฅุญุณุงุณุงู ู ู', 'ุฃุณููุจ ูุงุฎุฑุฉ', 'ุฃุณููุจ ุฌุฑูุฆุฉ', 'ุญุถูุฑุงู ุฑุณู ูุฉ', 'ุฑุณุงูุฉ ุจุตุฑูุฉ', | |
| 'ุญุถูุฑ ุฑูู ู', 'ูู ูุญ ุงูู ุญุชูู', 'ุชูุตูู ูุนูุณ', 'ุจุฏุงูุฉ ุชุจุฑุฒ ููู ุฉ ุงูู ูุชุฌ', | |
| 'ูู ุง ูู ูุญ', 'ุฑุณุงูุฉ ุชุณููููุฉ', 'ุฌุงุฐุจูุฉ ุงูู ุญุชูู', | |
| 'ุญูุซ ูุฑูุฒ ุจุดูู ุฃุณุงุณู', 'ููุถู ู ุฃู ุงูุฌูุฏุฉ' | |
| ] | |
| CTA_PHRASES = list(AR_MAP['cta'].values()) | |
| # โโโ Helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def ar(category, value): | |
| return AR_MAP.get(category, {}).get(str(value).strip(), str(value).strip()) | |
| def normalize_spaces(text): | |
| return re.sub(r'\s+', ' ', str(text)).strip() | |
| def clean_terminal_punctuation(text): | |
| text = normalize_spaces(text) | |
| text = re.sub(r'[.ุ!]+$', '', text) | |
| return text.strip('ุ ') | |
| def to_sentence(text, emoji=None, ending='.'): | |
| text = clean_terminal_punctuation(text) | |
| if not text: return '' | |
| return f"{text}{emoji}{ending}" if emoji else f"{text}{ending}" | |
| def hook_to_sentence(text, hook_type): | |
| return to_sentence(text, random.choice(EMOJIS), ending='ุ' if hook_type == 'question' else '.') | |
| def apply_grammar_fixes(text): | |
| fixes = { | |
| 'ุฅุญุณุงุณุงู ู ู ุงูุฃูุงูุฉ': 'ุฅุญุณุงุณุงู ุจุงูุฃูุงูุฉ', | |
| 'ุฅุญุณุงุณุงู ู ู ุงูุซูุฉ': 'ุฅุญุณุงุณุงู ุจุงูุซูุฉ', | |
| 'ุฃุณููุจ ูุงุฎุฑุฉ': 'ุฃุณููุจ ูุงุฎุฑ', | |
| 'ุฃุณููุจ ุฌุฑูุฆุฉ': 'ุฃุณููุจ ุฌุฑูุก', | |
| 'ุญุถูุฑุงู ุฑุณู ูุฉ': 'ุญุถูุฑุงู ุฑุณู ูุงู', | |
| 'ุชูุตูู ูุนูุณ': 'ุชูุงุตูู ุชุนูุณ', | |
| 'ูุฐู ุงููุทุนุฉ ูู': 'ูุฐู ุงููุทุนุฉ ูู', | |
| 'ูุฐู ุงูุญููุจุฉ ูู': 'ูุฐู ุงูุญููุจุฉ ูู', | |
| 'ูุฐู ุงูุญููุจุฉ ุงูุฐู': 'ูุฐู ุงูุญููุจุฉ ุงูุชู', | |
| 'ูุฐู ุงููุทุนุฉ ุงูุฐู': 'ูุฐู ุงููุทุนุฉ ุงูุชู', | |
| 'ูู ูุฐู': 'ููุฐู', | |
| 'ูููู ุจููู': 'ูููู ุจูู', | |
| 'ูุนุฒุฒ ุฌุงุฐุจูุฉ ุงูุนุฑุถ ูุชุฏุนู ': 'ูุนุฒุฒ ุฌุงุฐุจูุฉ ุงูุนุฑุถ ููุฏุนู ', | |
| 'ุฃุณููุจ ูุงุฎุฑุฉ ูุฏุนู ': 'ุฃุณููุจ ูุงุฎุฑ ูุฏุนู ', | |
| 'ุฃุณููุจ ุฌุฑูุฆุฉ ูููุช': 'ุฃุณููุจ ุฌุฑูุก ูููุช', | |
| } | |
| for bad, good in fixes.items(): | |
| text = text.replace(bad, good) | |
| text = re.sub(r'\s+([๐ฅโจ๐ซ๐๐๐ค๐๐๐])', r'\1', text) | |
| return normalize_spaces(text) | |
| def contains_non_arabic_wording(text, product_name=''): | |
| tmp = text.replace(product_name, '') | |
| tmp = re.sub(r'[#_]', ' ', tmp) | |
| return bool(re.search(r'[A-Za-z]{2,}', tmp)) | |
| def strip_cta_phrases(text): | |
| t = str(text) | |
| for cta in CTA_PHRASES: | |
| if cta in t and normalize_spaces(t) != cta: | |
| t = t.replace(f'ุ {cta}', '').replace(f'ุ{cta}', '').replace(cta, '') | |
| return normalize_spaces(t.strip(' ุ.')) | |
| def clean_generated_line(text, product_name=''): | |
| if not text: return '' | |
| text = str(text).split('ุงููุงุดุชุงุฌุงุช:')[0] | |
| text = re.sub(r'[#].*', '', text) | |
| text = normalize_spaces(text) | |
| text = strip_cta_phrases(text) | |
| if contains_non_arabic_wording(text, product_name): return '' | |
| for bad in BANNED_PHRASES: | |
| if bad in text: return '' | |
| text = apply_grammar_fixes(text) | |
| if len(text.split()) < 5: return '' | |
| return text | |
| def choose_hook(platform, focus): | |
| bank = HOOK_BANK.get(platform, HOOK_BANK['Instagram']) | |
| return random.choice(bank.get(focus, bank['product'])) | |
| def cta_line(data): | |
| platform = data.get('Platform', 'Instagram') | |
| base = ar('cta', data.get('CTA', 'Explore Collection')) | |
| return to_sentence(base, random.choice(EMOJIS) if platform != 'TikTok' else None) | |
| def parse_product_details(text): | |
| data = {} | |
| for line in str(text).splitlines(): | |
| if ':' in line: | |
| k, v = line.split(':', 1) | |
| data[k.strip()] = v.strip() | |
| if 'Category' in data and ' - ' in data['Category']: | |
| main, sub = data['Category'].split(' - ', 1) | |
| data['_main_category'] = main.strip() | |
| data['_subcategory'] = sub.strip() | |
| return data | |
| def format_model_input(data): | |
| return f"""Generate polished formal Arabic marketing copy and Arabic hashtags. | |
| Strict rules: | |
| - Keep ONLY the product name in English. | |
| - Write all other text in polished formal Arabic with correct grammar. | |
| - Avoid literal repetition, weak generic phrases, and repeated CTAs. | |
| - Product-focused MUST mention product name, material, and USP clearly. | |
| - Lifestyle-focused MUST mention occasion or season and fit the target audience naturally. | |
| - Campaign-focused MUST mention the platform in Arabic once, then continue naturally. | |
| - Use realistic fashion-marketing hashtags in Arabic only. | |
| Category: {data.get('Category', '')} | |
| Product: {data.get('Product', '')} | |
| Description: {data.get('Description', '')} | |
| Material: {data.get('Material', '')} | |
| Season: {data.get('Season', '')} | |
| Style: {data.get('Style', '')} | |
| Occasion: {data.get('Occasion', '')} | |
| Target: {data.get('Target', '')} | |
| Goal: {data.get('Goal', '')} | |
| Platform: {data.get('Platform', '')} | |
| Tone: {data.get('Tone', '')} | |
| Emotion: {data.get('Emotion', '')} | |
| CTA: {data.get('CTA', '')} | |
| USP: {data.get('USP', '')}""" | |
| # โโโ Generation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def generate_candidates(product_details, num_return_sequences=6, max_len=220): | |
| data = parse_product_details(product_details) | |
| inputs = tokenizer(format_model_input(data), return_tensors="pt", max_length=512, truncation=True).to(device) | |
| with torch.no_grad(): | |
| outputs = model.generate( | |
| **inputs, do_sample=True, temperature=0.9, top_p=0.92, top_k=50, | |
| num_beams=1, early_stopping=False, no_repeat_ngram_size=3, | |
| repetition_penalty=1.15, max_length=max_len, num_return_sequences=num_return_sequences, | |
| ) | |
| decoded = [tokenizer.decode(o, skip_special_tokens=True) for o in outputs] | |
| return list(dict.fromkeys(decoded)) | |
| def product_core_line(data): | |
| p = data.get('Product', 'ุงูู ูุชุฌ') | |
| material = ar('material', data.get('Material', '')) | |
| usp = ar('usp', data.get('USP', '')) | |
| return f"ูุชุฃูู {p} ุจุฎุงู ุฉ {material} ูุชูุงุตูู ุชุนูุณ {usp} ุจุฃุณููุจ ูุจุฑุฒ ููู ุฉ ุงูุฅุทูุงูุฉ." | |
| def product_support_line(data): | |
| material = ar('material', data.get('Material', '')) | |
| usp = ar('usp', data.get('USP', '')) | |
| platform = data.get('Platform', 'Instagram') | |
| options = { | |
| 'Instagram': [ | |
| f"ูุชู ูุญ ุฎุงู ุฉ {material} ู ุน {usp} ุงูุชุตู ูู ุญุถูุฑุงู ุฑุงููุงู ูููุช ุงูุฃูุธุงุฑ ุจุฃุณููุจ ู ุชูุงุฒู.", | |
| f"ูุชุจุฑุฒ ุชูุงุตูู {usp} ู ุน ุฎุงู ุฉ {material} ููู ุฉ ุงูุชุตู ูู ูุชู ูุญู ุทุงุจุนุงู ุฃูุซุฑ ูุฎุงู ุฉ." | |
| ], | |
| 'TikTok': [ | |
| f"ูุชุถูู ุฎุงู ุฉ {material} ู ุน {usp} ูู ุณุฉ ุนู ููุฉ ุชุฌุนู ุงูุฅุทูุงูุฉ ุฃูุซุฑ ุชุฑุชูุจุงู ูุณูููุฉ ูู ุงูุชูุณูู.", | |
| f"ููู ูุญ ุงูุฌู ุน ุจูู {material} ู{usp} ุงูุชุตู ูู ุญุถูุฑุงู ูุงุถุญุงู ููุงุณุจ ุงูุฅุทูุงูุฉ ุงูููู ูุฉ." | |
| ], | |
| 'Facebook': [ | |
| f"ูุชุคูุฏ ุฎุงู ุฉ {material} ู ุน {usp} ููู ุฉ ุงูุชุตู ูู ูุชู ูุญู ุญุถูุฑุงู ุฃูููุงู ูุฏูู ุฃุซุฑู.", | |
| f"ูู ุง ุชุนูุณ ุชูุงุตูู {usp} ู ุน ุฎุงู ุฉ {material} ุฌูุฏุฉ ุงูุงุฎุชูุงุฑ ูุชุจุฑุฒ ุฃูุงูุฉ ุงูุชุตู ูู ." | |
| ], | |
| } | |
| return random.choice(options.get(platform, options['Instagram'])) | |
| def lifestyle_core_line(data): | |
| p = data.get('Product', 'ุงูู ูุชุฌ') | |
| occ = ar('occasion', data.get('Occasion', '')) | |
| season = ar('season', data.get('Season', '')) | |
| style = ar('style', data.get('Style', '')) | |
| target = str(data.get('Target', 'ุงููุฆุฉ ุงูู ุณุชูุฏูุฉ')).split('(')[0].strip() | |
| target_ar = 'ุงูู ุฑุฃุฉ' if 'women' in target.lower() or 'ูุณุงุก' in target else target | |
| return f"ููุงุฆู {p} {occ} ูู {season} ุจุฃุณููุจ {style} ููุงุณุจ {target_ar}." | |
| def lifestyle_support_line(data): | |
| occ = ar('occasion', data.get('Occasion', '')) | |
| season = ar('season', data.get('Season', '')) | |
| style = ar('style', data.get('Style', '')) | |
| target = str(data.get('Target', 'ุงููุฆุฉ ุงูู ุณุชูุฏูุฉ')).split('(')[0].strip() | |
| target_ar = 'ุงูู ุฑุฃุฉ' if 'women' in target.lower() or 'ูุณุงุก' in target else target | |
| return random.choice([ | |
| f"ููู ูุญ {occ} ุญุถูุฑุงู ููุณุฌู ู ุน {season} ููุนูุณ ุฃุณููุจุงู {style} ูููู ุจู {target_ar}.", | |
| f"ููุฌุนู {occ} ูู {season} ุฃูุซุฑ ุงูุณุฌุงู ุงู ู ุน ุฅุทูุงูุฉ {style} ุชุนุจุฑ ุนู {target_ar}.", | |
| ]) | |
| def campaign_core_line(data): | |
| p = data.get('Product', 'ุงูู ูุชุฌ') | |
| platform = ar('platform', data.get('Platform', 'Instagram')) | |
| usp = ar('usp', data.get('USP', '')) | |
| openers = { | |
| 'ุฅูุณุชุฌุฑุงู ': f"ุนูุฏ ุธููุฑู ุนูู {platform}ุ ูู ูุญ {p} ุงูุธููุฑ ุทุงุจุนุงู ุฃูุซุฑ ููุชุงู ููุงูุชุจุงู ุนุจุฑ ุฅุจุฑุงุฒ {usp}.", | |
| 'ุชูู ุชูู': f"ุนูุฏ ุธููุฑู ุนูู {platform}ุ ูู ูุญ {p} ุงูู ุญุชูู ุญุถูุฑุงู ุฃุณุฑุน ููุชุงู ููุงูุชุจุงู ู ู ุฎูุงู ุฅุจุฑุงุฒ {usp}.", | |
| 'ููุณุจูู': f"ุนูุฏ ุธููุฑู ุนูู {platform}ุ ูู ูุญ {p} ุงูุญุถูุฑ ุทุงุจุนุงู ุฃูุซุฑ ูุถูุญุงู ุนุจุฑ ุฅุจุฑุงุฒ {usp}.", | |
| 'ุงูู ููุน ูุงูุฅุนูุงูุงุช': f"ุนูุฏ ุชูุฏูู ู ุนุจุฑ {platform}ุ ูู ูุญ {p} ุงูุฑุณุงูุฉ ุญุถูุฑุงู ุฃูุถุญ ู ู ุฎูุงู ุฅุจุฑุงุฒ {usp}.", | |
| } | |
| return openers.get(platform, openers['ุฅูุณุชุฌุฑุงู ']) | |
| def campaign_support_line(data): | |
| tone = ar('tone', data.get('Tone', 'ุฑุณู ูุฉ')) | |
| goal = data.get('Goal', '') | |
| goal_map = {'Sales': 'ูุนุฒุฒ ุฌุงุฐุจูุฉ ุงูุนุฑุถ ุจุตูุฑุฉ ุฃูุซุฑ ุฅููุงุนุงู', 'Engagement': 'ูุฏุนู ุงูุชูุงุนู ุจุตูุฑุฉ ุฃูุถุญ', | |
| 'Brand Awareness': 'ูููู ุญุถูุฑ ุงูุนูุงู ุฉ ุจุตูุฑุฉ ุฃูุซุฑ ุงุญุชุฑุงููุฉ', 'Launch': 'ูุจุฑุฒ ุงูุทูุงูุฉ ุงูู ูุชุฌ ุจุตูุฑุฉ ูุงูุชุฉ'} | |
| tone_map = {'ูุงุฎุฑุฉ': 'ุจุฃุณููุจ ูุงุฎุฑ', 'ุฌุฑูุฆุฉ': 'ุจุฃุณููุจ ุฌุฑูุก', 'ุฑุณู ูุฉ': 'ุจุฃุณููุจ ุฑุณู ู'} | |
| return f"ููุณุงุนุฏ ุนูู ุฅุจุฑุงุฒ ุงูุฑุณุงูุฉ ุจุตูุงุบุฉ ู ุชูุงุฒูุฉ {goal_map.get(goal, 'ูุนุฒุฒ ุญุถูุฑ ุงูุญู ูุฉ ุจุตูุฑุฉ ุฃูุถุญ')} {tone_map.get(tone, 'ุจุฃุณููุจ ุฃููู')}." | |
| def dedupe_lines(lines): | |
| out, seen = [], [] | |
| for ln in lines: | |
| if not ln: continue | |
| key = re.sub(r'[^\wุก-ู]+', ' ', ln) | |
| if any(key[:25] == re.sub(r'[^\wุก-ู]+', ' ', x)[:25] for x in seen): continue | |
| out.append(ln); seen.append(ln) | |
| return out | |
| def hashtagify(text): | |
| text = re.sub(r'\s+', '_', str(text).strip()) | |
| text = re.sub(r'[^ุก-ู0-9_]+', '', text) | |
| text = re.sub(r'_+', '_', text).strip('_') | |
| return f"#{text}" if text else '' | |
| def hashtags_for_variant(data, focus): | |
| platform = data.get('Platform', 'Instagram') | |
| count = PLATFORM_CONFIG.get(platform, PLATFORM_CONFIG['Instagram'])['hashtags'] | |
| product_tags = [ar('subcategory', data.get('_subcategory', '')), ar('material', data.get('Material', '')), ar('usp', data.get('USP', ''))] | |
| lifestyle_tags = [ar('occasion', data.get('Occasion', '')), ar('season', data.get('Season', '')), | |
| ar('style', data.get('Style', '')), | |
| 'ุฃูุงูุฉ_ูุณุงุฆูุฉ' if platform == 'Instagram' else 'ุณุชุงูู_ุนู ูู' if platform == 'TikTok' else 'ุฃูุงูุฉ_ุนู ููุฉ'] | |
| campaign_tags = REALISTIC_HASHTAGS.get(platform, []) | |
| seeds = product_tags if focus == 'product' else lifestyle_tags if focus == 'lifestyle' else campaign_tags | |
| tags = [] | |
| for t in seeds: | |
| h = hashtagify(t) | |
| if h and h not in tags and len(h) <= 28: tags.append(h) | |
| for t in REALISTIC_HASHTAGS.get(platform, []): | |
| h = hashtagify(t.replace('#', '')) | |
| if h and h not in tags and len(h) <= 28 and len(tags) < count: tags.append(h) | |
| return ' '.join(tags[:count]) | |
| def build_variant(data, focus_key, candidates): | |
| platform = data.get('Platform', 'Instagram') | |
| hook_type, hook_text = choose_hook(platform, focus_key) | |
| if focus_key == 'product': | |
| line2, line3 = product_core_line(data), product_support_line(data) | |
| elif focus_key == 'lifestyle': | |
| line2, line3 = lifestyle_core_line(data), lifestyle_support_line(data) | |
| else: | |
| line2, line3 = campaign_core_line(data), campaign_support_line(data) | |
| hook_line = apply_grammar_fixes(hook_to_sentence(hook_text, hook_type)) | |
| line2 = apply_grammar_fixes(to_sentence(strip_cta_phrases(line2), random.choice(EMOJIS))) | |
| line3 = apply_grammar_fixes(to_sentence(strip_cta_phrases(line3), random.choice(EMOJIS))) | |
| cta = apply_grammar_fixes(cta_line(data)) | |
| final_lines = dedupe_lines([hook_line, line2, line3, cta]) | |
| cleaned_lines = [] | |
| for idx, ln in enumerate(final_lines): | |
| ln = normalize_spaces(ln) | |
| if idx < len(final_lines) - 1: | |
| ln = strip_cta_phrases(ln) | |
| ln = apply_grammar_fixes(ln) | |
| if not ln: continue | |
| ending = 'ุ' if idx == 0 and hook_type == 'question' else '.' | |
| ln = re.sub(r'[.ุ!]+$', '', ln).strip() + ending | |
| cleaned_lines.append(ln) | |
| if not cleaned_lines or not any(cta_p in cleaned_lines[-1] for cta_p in CTA_PHRASES): | |
| cleaned_lines.append(cta) | |
| caption = '\n'.join(cleaned_lines[:PLATFORM_CONFIG.get(platform, PLATFORM_CONFIG['Instagram'])['max_sentences']]) | |
| return caption, hashtags_for_variant(data, focus_key) | |
| def generate_3_variants(product_details): | |
| data = parse_product_details(product_details) | |
| candidates = generate_candidates(product_details) | |
| results = [] | |
| for focus in ['product', 'lifestyle', 'campaign']: | |
| cap, tags = build_variant(data, focus, candidates) | |
| results.extend([cap, tags]) | |
| return results | |
| # โโโ Gradio UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| CATEGORY_MAP = { | |
| "Apparel": ["Dresses", "Abayas", "Tops", "Pants", "Outerwear", "Activewear", "Loungewear"], | |
| "Footwear": ["Sneakers", "Sandals", "Heels", "Flats", "Boots"], | |
| "Accessories": ["Bags", "Jewelry", "Scarves", "Sunglasses", "Belts / Others"], | |
| } | |
| ALL_SUBCATEGORIES = [s for subs in CATEGORY_MAP.values() for s in subs] | |
| def update_subcategory(cat): | |
| choices = CATEGORY_MAP.get(cat, []) | |
| return gr.Dropdown(choices=choices, value=choices[0] if choices else None) | |
| def generate_posts(category, subcategory, product_name, description, material, | |
| season, style, occasion, gender, target_audience, | |
| campaign_goal, platform, tone, emotion, cta, usp): | |
| product_details = f"""Category: {category} - {subcategory} | |
| Product: {product_name} | |
| Description: {description} | |
| Material: {material} | |
| Season: {season} | |
| Style: {style} | |
| Occasion: {occasion} | |
| Target: {target_audience} ({gender}) | |
| Goal: {campaign_goal} | |
| Platform: {platform} | |
| Tone: {tone} | |
| Emotion: {emotion} | |
| CTA: {cta} | |
| USP: {usp}""" | |
| return generate_3_variants(product_details) | |
| with gr.Blocks(title="Arabic Fashion Marketing AI") as demo: | |
| gr.Markdown(""" | |
| # ๐ Arabic Fashion Marketing AI | |
| ### ุชูููุฏ 3 ุจูุณุชุงุช ุชุณููููุฉ ู ุฎุชููุฉ ุจุงููุบุฉ ุงูุนุฑุจูุฉ | |
| """) | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### ๐ฆ ู ุนููู ุงุช ุงูู ูุชุฌ") | |
| category = gr.Dropdown(label="Category", choices=list(CATEGORY_MAP.keys()), value="Apparel") | |
| subcategory = gr.Dropdown(label="Sub-Category", choices=ALL_SUBCATEGORIES, value="Dresses") | |
| category.change(fn=update_subcategory, inputs=category, outputs=subcategory) | |
| product_name = gr.Textbox(label="Product Name", placeholder="e.g. Flowy Midi Dress") | |
| description = gr.Textbox(label="Description", placeholder="e.g. Lightweight chiffon dress") | |
| material = gr.Textbox(label="Material", placeholder="e.g. Chiffon, Cotton, Leather...") | |
| season = gr.Dropdown(label="Season", choices=["Summer","Winter","Spring","Autumn","All-Season"], value="Summer") | |
| style = gr.Dropdown(label="Style", choices=["Casual","Formal","Sporty","Luxury","Modest","Trendy"], value="Casual") | |
| occasion = gr.Dropdown(label="Occasion", choices=["Daily","Work","Party","Wedding","Travel","Sports","Ramadan/Eid","Gym/Beach"], value="Daily") | |
| with gr.Column(): | |
| gr.Markdown("### ๐ฏ ุชูุงุตูู ุงูุญู ูุฉ") | |
| gender = gr.Dropdown(label="Gender", choices=["Women","Men","Unisex"], value="Women") | |
| target_audience = gr.Textbox(label="Target Audience", placeholder="e.g. Modern women", value="Modern women") | |
| campaign_goal = gr.Dropdown(label="Campaign Goal", choices=["Sales","Engagement","Brand Awareness","Launch"], value="Sales") | |
| platform = gr.Dropdown(label="Platform", choices=["Instagram","Facebook","TikTok","Website/Ads"], value="Instagram") | |
| tone = gr.Dropdown(label="Tone", choices=["Friendly","Luxury","Bold","Formal","Casual","Minimal","Confidence"], value="Friendly") | |
| emotion = gr.Dropdown(label="Emotion", choices=["Comfort","Confidence","Elegance","Joy","Excitement","Trust","Desire","Pride"], value="Comfort") | |
| cta = gr.Dropdown(label="Call to Action", choices=["Shop Now","Discover More","Explore Collection","Buy Today","Limited Offer"], value="Shop Now") | |
| usp = gr.Textbox(label="USP", placeholder="e.g. Breathable fabric", value="Breathable fabric") | |
| generate_btn = gr.Button("โจ Generate 3 Arabic Posts", variant="primary", size="lg") | |
| gr.Markdown("---") | |
| gr.Markdown("### ๐ ุงูุจูุณุช ุงูุฃูู โ ุชุฑููุฒ ุนูู ุงูู ูุชุฌ") | |
| with gr.Row(): | |
| cap1 = gr.Textbox(label="ุงููุต", lines=5, rtl=True) | |
| hash1 = gr.Textbox(label="ุงููุงุดุชุงุฌุงุช", lines=2, rtl=True) | |
| gr.Markdown("### ๐ฟ ุงูุจูุณุช ุงูุซุงูู โ ุชุฑููุฒ ุนูู ุงููุงูู ุณุชุงูู") | |
| with gr.Row(): | |
| cap2 = gr.Textbox(label="ุงููุต", lines=5, rtl=True) | |
| hash2 = gr.Textbox(label="ุงููุงุดุชุงุฌุงุช", lines=2, rtl=True) | |
| gr.Markdown("### ๐ฏ ุงูุจูุณุช ุงูุซุงูุซ โ ุชุฑููุฒ ุนูู ุงูุญู ูุฉ") | |
| with gr.Row(): | |
| cap3 = gr.Textbox(label="ุงููุต", lines=5, rtl=True) | |
| hash3 = gr.Textbox(label="ุงููุงุดุชุงุฌุงุช", lines=2, rtl=True) | |
| generate_btn.click( | |
| fn=generate_posts, | |
| inputs=[category, subcategory, product_name, description, material, | |
| season, style, occasion, gender, target_audience, | |
| campaign_goal, platform, tone, emotion, cta, usp], | |
| outputs=[cap1, hash1, cap2, hash2, cap3, hash3] | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| ["Apparel","Dresses","Flowy Midi Dress","Lightweight chiffon dress","Chiffon","Summer","Casual","Daily","Women","Modern women","Sales","Instagram","Friendly","Comfort","Shop Now","Breathable fabric"], | |
| ["Footwear","Sneakers","Classic Sneakers","Leather sneakers","Leather","All-Season","Sporty","Daily","Women","Active women","Engagement","TikTok","Bold","Confidence","Discover More","Performance meets style"], | |
| ["Accessories","Bags","Shoulder Bag","Leather bag with compartments","Leather","Winter","Luxury","Work","Women","Working women","Brand Awareness","Facebook","Luxury","Elegance","Explore Collection","Timeless design"], | |
| ], | |
| inputs=[category, subcategory, product_name, description, material, season, style, occasion, | |
| gender, target_audience, campaign_goal, platform, tone, emotion, cta, usp], | |
| run_on_click=False, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Soft()) | |