MT5_Model / app.py
Manaaal1234's picture
Update app.py
82bd62d
"""
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())