Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import numpy as np | |
| import os | |
| import json | |
| import time | |
| from typing import Iterable | |
| from huggingface_hub import login | |
| from sentence_transformers import SentenceTransformer, util | |
| from gradio.themes.base import Base | |
| from gradio.themes.utils import colors, fonts, sizes | |
| # --- CONFIGURATION --- | |
| class Config: | |
| """Configuration settings for the application.""" | |
| EMBEDDING_MODEL_ID = "google/embeddinggemma-300M" | |
| PROMPT_NAME = "STS" | |
| TOP_K = 5 | |
| HF_TOKEN = os.getenv('HF_TOKEN') | |
| # --- COLOR DATA --- | |
| COLOR_DATA = [ | |
| {"name": "Crimson", "hex": "#DC143C", "description": "A deep, rich red color, leaning slightly towards purple."}, | |
| {"name": "Scarlet", "hex": "#FF2400", "description": "A brilliant, vivid red with a hint of orange."}, | |
| {"name": "Blood Red", "hex": "#8B0000", "description": "Dark, intense red color associated with danger and horror."}, | |
| {"name": "Coral", "hex": "#FF7F50", "description": "A vibrant pinkish-orange reminiscent of marine invertebrates."}, | |
| {"name": "Tangerine", "hex": "#F28500", "description": "A saturated, zesty orange, like the ripe citrus fruit."}, | |
| {"name": "Pumpkin", "hex": "#FF7518", "description": "Bright orange color associated with autumn and Halloween."}, | |
| {"name": "Gold", "hex": "#FFD700", "description": "A bright, metallic yellow associated with wealth and luxury."}, | |
| {"name": "Champagne", "hex": "#F7E7CE", "description": "Pale golden color associated with celebration and elegance."}, | |
| {"name": "Lemon Chiffon", "hex": "#FFFACD", "description": "A pale, light yellow, as soft and airy as the dessert."}, | |
| {"name": "Lime Green", "hex": "#32CD32", "description": "A bright green color, evoking freshness and zesty energy."}, | |
| {"name": "Forest Green", "hex": "#228B22", "description": "A dark, shaded green, like the canopy of a dense forest."}, | |
| {"name": "Mint", "hex": "#98FB98", "description": "Fresh, cool green color associated with tranquility and cleanliness."}, | |
| {"name": "Teal", "hex": "#008080", "description": "A medium blue-green color, often seen as sophisticated and calming."}, | |
| {"name": "Cyan", "hex": "#00FFFF", "description": "A vibrant greenish-blue, one of the primary subtractive colors."}, | |
| {"name": "Sky Blue", "hex": "#87CEEB", "description": "A light, pale blue, like the color of a clear daytime sky."}, | |
| {"name": "Royal Blue", "hex": "#4169E1", "description": "A deep, vivid blue that is both rich and bright."}, | |
| {"name": "Navy", "hex": "#000080", "description": "Dark blue color associated with professionalism and depth."}, | |
| {"name": "Indigo", "hex": "#4B0082", "description": "A deep, rich color between blue and violet in the spectrum."}, | |
| {"name": "Lavender", "hex": "#E6E6FA", "description": "A light, pale purple with a bluish hue, named after the flower."}, | |
| {"name": "Plum", "hex": "#DDA0DD", "description": "A reddish-purple color, like the ripe fruit it's named after."}, | |
| {"name": "Purple", "hex": "#800080", "description": "Deep purple color associated with royalty and mystery."}, | |
| {"name": "Magenta", "hex": "#FF00FF", "description": "A purplish-red color that lies between red and violet."}, | |
| {"name": "Hot Pink", "hex": "#FF69B4", "description": "A bright, vivid pink that is both bold and energetic."}, | |
| {"name": "Rose", "hex": "#FF007F", "description": "Bright pink color associated with romance and femininity."}, | |
| {"name": "Ivory", "hex": "#FFFFF0", "description": "An off-white color that resembles the material from tusks and teeth."}, | |
| {"name": "Cream", "hex": "#F5F5DC", "description": "Warm off-white color associated with comfort and elegance."}, | |
| {"name": "Beige", "hex": "#F5F5DC", "description": "A pale sandy fawn color, often used as a warm, neutral tone."}, | |
| {"name": "Taupe", "hex": "#483C32", "description": "A dark grayish-brown or brownish-gray color."}, | |
| {"name": "Coffee", "hex": "#6F4E37", "description": "Rich brown color associated with warmth and comfort."}, | |
| {"name": "Chocolate", "hex": "#7B3F00", "description": "Deep brown color associated with richness and indulgence."}, | |
| {"name": "Slate Gray", "hex": "#708090", "description": "A medium gray with a slight blue tinge, like the metamorphic rock."}, | |
| {"name": "Charcoal", "hex": "#36454F", "description": "A dark, almost black gray, like burnt wood."}, | |
| {"name": "Silver", "hex": "#C0C0C0", "description": "A metallic gray color that resembles polished silver."}, | |
| {"name": "Emerald", "hex": "#50C878", "description": "A brilliant green, named after the precious gemstone."}, | |
| {"name": "Sapphire", "hex": "#0F52BA", "description": "A deep, lustrous blue, reminiscent of the valuable gemstone."}, | |
| {"name": "Ruby", "hex": "#E0115F", "description": "A deep red color, inspired by the gemstone of the same name."}, | |
| {"name": "Turquoise", "hex": "#40E0D0", "description": "A greenish-blue color, often associated with tropical waters."}, | |
| {"name": "Bronze", "hex": "#CD7F32", "description": "A metallic brown color that resembles the alloy of copper and tin."}, | |
| {"name": "Neon Green", "hex": "#39FF14", "description": "Extremely bright green color associated with technology and cyberpunk."}, | |
| {"name": "Electric Blue", "hex": "#7DF9FF", "description": "Bright, vibrant blue color associated with energy and technology."}, | |
| {"name": "Sunset Orange", "hex": "#FF4500", "description": "Warm orange color reminiscent of beautiful sunsets."}, | |
| {"name": "Midnight Blue", "hex": "#191970", "description": "Very dark blue color associated with mystery and night."}, | |
| {"name": "Burgundy", "hex": "#800020", "description": "Dark red color associated with sophistication and luxury."}, | |
| {"name": "Peach", "hex": "#FFCBA4", "description": "Soft orange-pink color associated with warmth and gentleness."}, | |
| {"name": "Sage", "hex": "#9CAF88", "description": "Muted green color associated with wisdom and tranquility."}, | |
| {"name": "Dusty Rose", "hex": "#DCAE96", "description": "Muted pink color with vintage and romantic associations."}, | |
| {"name": "Steel Blue", "hex": "#4682B4", "description": "Blue-gray color associated with strength and industry."}, | |
| {"name": "Mauve", "hex": "#E0B0FF", "description": "Pale purple color associated with elegance and sophistication."}, | |
| {"name": "Rust", "hex": "#B7410E", "description": "Reddish-brown color associated with vintage and industrial themes."}, | |
| {"name": "Olive", "hex": "#808000", "description": "Yellow-green color associated with nature and earthiness."}, | |
| {"name": "Maroon", "hex": "#800000", "description": "Dark red color associated with depth and richness."}, | |
| ] | |
| # --- FONT DATA --- | |
| FONT_DATA = [ | |
| {"name": "Playfair Display", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&display=swap", "description": "Elegant, sophisticated, editorial, high-contrast serif with dramatic flair, perfect for luxury brands and fashion magazines"}, | |
| {"name": "Inter", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&display=swap", "description": "Modern, clean, professional, highly legible sans-serif designed for digital interfaces and contemporary design"}, | |
| {"name": "Amatic SC", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Amatic+SC:wght@400;700&display=swap", "description": "Playful, casual, handwritten, fun, child-like, informal font perfect for creative and whimsical projects"}, | |
| {"name": "Crimson Text", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Crimson+Text:wght@400;600&display=swap", "description": "Classical, scholarly, academic, readable serif inspired by old-style typefaces, ideal for books and literature"}, | |
| {"name": "Roboto", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap", "description": "Friendly, approachable, geometric sans-serif with a mechanical skeleton, widely used in digital applications"}, | |
| {"name": "Dancing Script", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Dancing+Script:wght@400;700&display=swap", "description": "Romantic, flowing, elegant script font perfect for wedding invitations, greeting cards, and feminine designs"}, | |
| {"name": "Oswald", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Oswald:wght@300;400;600&display=swap", "description": "Bold, condensed, impactful sans-serif with strong presence, ideal for headlines and masculine designs"}, | |
| {"name": "Lora", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Lora:wght@400;600&display=swap", "description": "Warm, friendly, contemporary serif with calligraphic roots, perfect for body text and storytelling"}, | |
| {"name": "Montserrat", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600&display=swap", "description": "Urban, modern, versatile sans-serif inspired by Buenos Aires signage, great for branding and corporate use"}, | |
| {"name": "Pacifico", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Pacifico&display=swap", "description": "Surfing, California, retro, casual script font with beach vibes and laid-back summer feeling"}, | |
| {"name": "Source Code Pro", "family": "monospace", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;600&display=swap", "description": "Technical, programming, coding, monospaced font designed for developers and technical documentation"}, | |
| {"name": "Merriweather", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap", "description": "Traditional, readable, pleasant serif designed for comfortable reading on screens and in print"}, | |
| {"name": "Raleway", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;600&display=swap", "description": "Sophisticated, thin, elegant sans-serif with distinctive 'W', perfect for fashion and high-end design"}, | |
| {"name": "Poppins", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap", "description": "Geometric, modern, friendly sans-serif with circular forms, popular for contemporary web design"}, | |
| {"name": "Open Sans", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600&display=swap", "description": "Neutral, friendly, optimistic sans-serif designed for legibility across interfaces, print, and web"}, | |
| {"name": "Creepster", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Creepster&display=swap", "description": "Horror, scary, Halloween, gothic font with dripping effect, perfect for spooky and thriller themes"}, | |
| {"name": "Orbitron", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap", "description": "Futuristic, sci-fi, technology, space-age font perfect for cyberpunk and futuristic designs"}, | |
| {"name": "Righteous", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Righteous&display=swap", "description": "Futuristic, sci-fi, technology, bold display font with unique character shapes for modern designs"}, | |
| {"name": "Satisfy", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Satisfy&display=swap", "description": "Casual, relaxed, handwritten script with natural flow, perfect for personal and informal communications"}, | |
| {"name": "Anton", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Anton&display=swap", "description": "Bold, condensed, impactful sans-serif perfect for headlines, posters, and attention-grabbing text"}, | |
| {"name": "Courgette", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Courgette&display=swap", "description": "French, bistro, café, elegant script font with continental European charm and sophistication"}, | |
| {"name": "Indie Flower", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Indie+Flower&display=swap", "description": "Indie, hipster, handwritten font with quirky personality, perfect for creative and artistic projects"}, | |
| {"name": "PT Serif", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=PT+Serif:wght@400;700&display=swap", "description": "Russian, Cyrillic, transitional serif with excellent readability for both Latin and Cyrillic scripts"}, | |
| {"name": "Questrial", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Questrial&display=swap", "description": "Simple, clean, minimal sans-serif with subtle quirks, perfect for modern and understated designs"}, | |
| {"name": "Bangers", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Bangers&display=swap", "description": "Comic book, superhero, pop art font inspired by mid-20th century comic books and advertisements"}, | |
| {"name": "Sacramento", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Sacramento&display=swap", "description": "Monoline, cursive script with vintage charm, perfect for elegant and sophisticated branding"}, | |
| {"name": "Bitter", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Bitter:wght@400;700&display=swap", "description": "Contemporary, slab serif with slight contrast, designed for comfortable reading in long texts"}, | |
| {"name": "Lobster", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Lobster&display=swap", "description": "Bold, retro, vintage script font with a 1950s diner feel, perfect for nostalgic and Americana designs"}, | |
| {"name": "Fredoka One", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap", "description": "Friendly, rounded, playful display font perfect for children's content, toys, and fun applications"}, | |
| {"name": "Abril Fatface", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap", "description": "Bold, dramatic, high-contrast display serif inspired by French and Italian typography, perfect for headlines"}, | |
| {"name": "Great Vibes", "family": "script", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap", "description": "Elegant, formal, calligraphic script with sophisticated curves, ideal for luxury and premium branding"}, | |
| {"name": "Shadows Into Light", "family": "handwriting", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Shadows+Into+Light&display=swap", "description": "Casual, handwritten, personal font that feels like natural handwriting with a marker or pen"}, | |
| {"name": "Libre Baskerville", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Libre+Baskerville:wght@400;700&display=swap", "description": "Classic, traditional, scholarly serif based on American Type Founder's Baskerville, perfect for academic texts"}, | |
| {"name": "Bungee", "family": "display", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Bungee&display=swap", "description": "Urban, street art, graffiti-inspired display font perfect for hip-hop culture and urban designs"}, | |
| {"name": "Philosopher", "family": "sans-serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Philosopher:wght@400;700&display=swap", "description": "Intellectual, academic, thoughtful sans-serif with classical proportions, perfect for philosophical content"}, | |
| {"name": "Cinzel", "family": "serif", "google_fonts_url": "https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&display=swap", "description": "Ancient, Roman, classical serif inspired by inscriptions, perfect for historical and luxury themes"}, | |
| ] | |
| # --- DYNAMIC MOOD THEME CLASS --- | |
| class MoodTheme(Base): | |
| """Dynamic theme class that can be configured with mood-based colors and fonts.""" | |
| def __init__( | |
| self, | |
| mood_colors: list = None, | |
| mood_fonts: list = None, | |
| theme_name: str = "MoodTheme", | |
| *, | |
| primary_hue: colors.Color | str = colors.blue, | |
| secondary_hue: colors.Color | str = colors.gray, | |
| neutral_hue: colors.Color | str = colors.gray, | |
| spacing_size: sizes.Size | str = sizes.spacing_md, | |
| radius_size: sizes.Size | str = sizes.radius_md, | |
| text_size: sizes.Size | str = sizes.text_md, | |
| font: fonts.Font | str | Iterable[fonts.Font | str] = ( | |
| fonts.GoogleFont("Inter"), | |
| "ui-sans-serif", | |
| "sans-serif", | |
| ), | |
| font_mono: fonts.Font | str | Iterable[fonts.Font | str] = ( | |
| fonts.GoogleFont("Source Code Pro"), | |
| "ui-monospace", | |
| "monospace", | |
| ), | |
| ): | |
| self.mood_colors = mood_colors or [] | |
| self.mood_fonts = mood_fonts or [] | |
| self.theme_name = theme_name | |
| # Map mood colors to existing Gradio colors | |
| if self.mood_colors: | |
| primary_color = self._map_hex_to_gradio_color(self.mood_colors[0]['hex']) | |
| if len(self.mood_colors) > 1: | |
| secondary_color = self._map_hex_to_gradio_color(self.mood_colors[1]['hex']) | |
| else: | |
| secondary_color = colors.gray | |
| if len(self.mood_colors) > 2: | |
| neutral_color = self._map_hex_to_gradio_color(self.mood_colors[2]['hex']) | |
| else: | |
| neutral_color = colors.blue | |
| else: | |
| primary_color = primary_hue | |
| secondary_color = secondary_hue | |
| neutral_color = neutral_hue | |
| # If mood fonts provided, use them | |
| if self.mood_fonts: | |
| primary_font = fonts.GoogleFont(self.mood_fonts[0]['name']) | |
| font = (primary_font, "ui-sans-serif", "sans-serif") | |
| if len(self.mood_fonts) > 1: | |
| mono_font = fonts.GoogleFont(self.mood_fonts[1]['name']) | |
| font_mono = (mono_font, "ui-monospace", "monospace") | |
| super().__init__( | |
| primary_hue=primary_color, | |
| secondary_hue=secondary_color, | |
| neutral_hue=neutral_color, | |
| spacing_size=spacing_size, | |
| radius_size=radius_size, | |
| text_size=text_size, | |
| font=font, | |
| font_mono=font_mono, | |
| ) | |
| # Apply additional mood-based styling that will persist in published theme | |
| self._apply_mood_styling() | |
| def _map_hex_to_gradio_color(self, hex_color: str): | |
| """Map hex colors to deliberately different Gradio colors.""" | |
| # Create a more aggressive mapping to ensure variety | |
| color_mapping = { | |
| # Reds - map to different red variants | |
| "#DC143C": colors.red, # Crimson | |
| "#FF2400": colors.rose, # Scarlet | |
| "#8B0000": colors.red, # Dark red | |
| # Oranges | |
| "#FF7F50": colors.orange, # Coral | |
| "#F28500": colors.amber, # Tangerine | |
| "#FF7518": colors.orange, # Pumpkin | |
| # Yellows/Golds | |
| "#FFD700": colors.yellow, # Gold | |
| "#F7E7CE": colors.amber, # Champagne | |
| "#FFFACD": colors.yellow, # Lemon Chiffon | |
| # Greens | |
| "#32CD32": colors.lime, # Lime Green | |
| "#228B22": colors.green, # Forest Green | |
| "#50C878": colors.emerald, # Emerald | |
| # Blues | |
| "#4169E1": colors.blue, # Royal Blue | |
| "#87CEEB": colors.sky, # Sky Blue | |
| "#0F52BA": colors.indigo, # Sapphire | |
| # Purples | |
| "#4B0082": colors.indigo, # Indigo | |
| "#800080": colors.purple, # Purple | |
| "#DDA0DD": colors.violet, # Plum | |
| # Pinks | |
| "#FF69B4": colors.pink, # Hot Pink | |
| "#FF00FF": colors.fuchsia, # Magenta | |
| # Teals/Cyans | |
| "#008080": colors.teal, # Teal | |
| "#40E0D0": colors.cyan, # Turquoise | |
| # Grays/Browns | |
| "#708090": colors.slate, # Slate Gray | |
| "#36454F": colors.gray, # Charcoal | |
| "#6F4E37": colors.stone, # Coffee | |
| } | |
| return color_mapping.get(hex_color, colors.blue) | |
| def _apply_mood_styling(self): | |
| """Apply additional styling based on mood colors and fonts.""" | |
| if self.mood_colors: | |
| # Use the actual hex colors for specific styling | |
| color1 = self.mood_colors[0]['hex'] | |
| color2 = self.mood_colors[1]['hex'] if len(self.mood_colors) > 1 else color1 | |
| color3 = self.mood_colors[2]['hex'] if len(self.mood_colors) > 2 else color2 | |
| # Apply styling that will be preserved in the published theme | |
| self.set( | |
| # Button styling | |
| button_primary_background_fill=color1, | |
| button_primary_background_fill_hover=color2, | |
| button_primary_text_color="white", | |
| button_secondary_background_fill=color3, | |
| button_secondary_text_color="white", | |
| # Block styling | |
| block_title_background_fill=color2, | |
| block_title_text_color="white", | |
| # Input styling | |
| input_border_color_focus=color1, | |
| slider_color=color1, | |
| ) | |
| # --- CORE LOGIC --- | |
| class MoodThemeGenerator: | |
| """Handles model loading, embedding generation, and theme creation.""" | |
| def __init__(self, config: Config, color_data: list, font_data: list): | |
| """Initializes the generator, logs in, and loads necessary assets.""" | |
| self.config = config | |
| self.color_data = color_data | |
| self.font_data = font_data | |
| self._login_to_hf() | |
| self.embedding_model = self._load_model() | |
| self.color_embeddings = self._precompute_color_embeddings() | |
| self.font_embeddings = self._precompute_font_embeddings() | |
| self.current_theme_data = None | |
| def _login_to_hf(self): | |
| """Logs into Hugging Face Hub if a token is provided.""" | |
| if self.config.HF_TOKEN: | |
| print("Logging into Hugging Face Hub...") | |
| login(token=self.config.HF_TOKEN) | |
| else: | |
| print("HF_TOKEN not found. Proceeding without login.") | |
| def _load_model(self) -> SentenceTransformer: | |
| """Loads the Sentence Transformer model.""" | |
| print(f"Initializing embedding model: {self.config.EMBEDDING_MODEL_ID}...") | |
| try: | |
| return SentenceTransformer(self.config.EMBEDDING_MODEL_ID) | |
| except Exception as e: | |
| print(f"Error loading model: {e}") | |
| raise | |
| def _precompute_color_embeddings(self) -> np.ndarray: | |
| """Generates and stores embeddings for the color descriptions.""" | |
| print("Pre-computing embeddings for color palette...") | |
| color_texts = [f"{color['name']}, {color['description']}" for color in self.color_data] | |
| return self.embedding_model.encode(color_texts, prompt_name=self.config.PROMPT_NAME) | |
| def _precompute_font_embeddings(self) -> np.ndarray: | |
| """Generates and stores embeddings for the font descriptions.""" | |
| print("Pre-computing embeddings for font palette...") | |
| font_texts = [f"{font['name']}, {font['description']}" for font in self.font_data] | |
| return self.embedding_model.encode(font_texts, prompt_name=self.config.PROMPT_NAME) | |
| def _get_text_color_for_bg(self, hex_color: str) -> str: | |
| """Calculate best text color for background.""" | |
| hex_color = hex_color.lstrip('#') | |
| try: | |
| r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) | |
| luminance = (0.299 * r + 0.587 * g + 0.114 * b) | |
| return '#000000' if luminance > 150 else '#FFFFFF' | |
| except (ValueError, IndexError): | |
| return '#000000' | |
| def _format_results_as_html(self, color_hits: list, font_hits: list) -> str: | |
| """Format both color and font results as HTML.""" | |
| html = "<div class='results-container'>" | |
| # Colors section | |
| html += "<h3 style='color: #2c3e50; margin-bottom: 15px;'>Color Palette</h3><div class='color-results'>" | |
| for hit in color_hits: | |
| color_info = self.color_data[hit['corpus_id']] | |
| hex_code = color_info['hex'] | |
| name = color_info['name'] | |
| score = hit['score'] | |
| text_color = self._get_text_color_for_bg(hex_code) | |
| html += f""" | |
| <div class="result-card color-card" style="background-color: {hex_code}; color: {text_color};"> | |
| <strong>{name}</strong><br> | |
| {hex_code}<br> | |
| Score: {score:.2f} | |
| </div> | |
| """ | |
| html += "</div>" | |
| # Fonts section | |
| html += "<h3 style='color: #2c3e50; margin: 20px 0 15px 0;'>Font Palette</h3><div class='font-results'>" | |
| for hit in font_hits: | |
| font_info = self.font_data[hit['corpus_id']] | |
| font_name = font_info['name'] | |
| score = hit['score'] | |
| html += f""" | |
| <div class="result-card font-card"> | |
| <div class="font-header"> | |
| <strong>{font_name}</strong> | |
| <span class="score">Score: {score:.2f}</span> | |
| </div> | |
| <div class="font-sample" style="font-family: '{font_name}', sans-serif;"> | |
| The Quick Brown Fox Jumps Over The Lazy Dog | |
| </div> | |
| </div> | |
| """ | |
| html += "</div></div>" | |
| return html | |
| def generate_mood_theme(self, mood_text: str) -> tuple[str, str, str]: | |
| """Generate a complete mood theme with colors and fonts.""" | |
| if not mood_text or not mood_text.strip(): | |
| return "<p>Please enter a mood or description.</p>", "", "" | |
| # Generate embeddings for the mood | |
| mood_embedding = self.embedding_model.encode(mood_text, prompt_name=self.config.PROMPT_NAME) | |
| # Find matching colors and fonts | |
| color_hits = util.semantic_search(mood_embedding, self.color_embeddings, top_k=self.config.TOP_K)[0] | |
| font_hits = util.semantic_search(mood_embedding, self.font_embeddings, top_k=self.config.TOP_K)[0] | |
| # Extract the actual color and font data | |
| selected_colors = [self.color_data[hit['corpus_id']] for hit in color_hits] | |
| selected_fonts = [self.font_data[hit['corpus_id']] for hit in font_hits] | |
| # Store current theme data for publishing | |
| self.current_theme_data = { | |
| "mood": mood_text, | |
| "colors": selected_colors, | |
| "fonts": selected_fonts, | |
| "color_scores": [hit['score'] for hit in color_hits], | |
| "font_scores": [hit['score'] for hit in font_hits] | |
| } | |
| # Format results for display | |
| results_html = self._format_results_as_html(color_hits, font_hits) | |
| # Create dynamic CSS for theme application | |
| theme_css = self._create_theme_css(selected_colors, selected_fonts) | |
| # Create theme name suggestion | |
| theme_name = f"mood-{mood_text.lower().replace(' ', '-')[:20]}" | |
| return results_html, theme_css, theme_name | |
| def _create_theme_css(self, colors: list, fonts: list) -> str: | |
| """Create CSS to apply the theme dynamically.""" | |
| # Import fonts | |
| font_imports = [] | |
| seen_urls = set() | |
| for font in fonts: | |
| if font['google_fonts_url'] not in seen_urls: | |
| font_imports.append(f"@import url('{font['google_fonts_url']}');") | |
| seen_urls.add(font['google_fonts_url']) | |
| # Apply colors and fonts to various elements | |
| color1 = colors[0]['hex'] if colors else "#4169E1" | |
| color2 = colors[1]['hex'] if len(colors) > 1 else color1 | |
| color3 = colors[2]['hex'] if len(colors) > 2 else color2 | |
| font1 = fonts[0]['name'] if fonts else "Inter" | |
| font2 = fonts[1]['name'] if len(fonts) > 1 else font1 | |
| css = f""" | |
| <style> | |
| {chr(10).join(font_imports)} | |
| :root {{ | |
| --primary-color: {color1}; | |
| --secondary-color: {color2}; | |
| --tertiary-color: {color3}; | |
| --primary-font: '{font1}'; | |
| --secondary-font: '{font2}'; | |
| }} | |
| /* Apply primary color and font to headers and primary buttons */ | |
| h1, h2, h3, .gr-button-primary {{ | |
| background: linear-gradient(135deg, {color1}, {color2}) !important; | |
| color: white !important; | |
| font-family: '{font1}', sans-serif !important; | |
| border: none !important; | |
| }} | |
| /* Apply secondary styling */ | |
| .gr-button-secondary {{ | |
| background: {color3} !important; | |
| color: white !important; | |
| font-family: '{font2}', sans-serif !important; | |
| border: 2px solid {color2} !important; | |
| }} | |
| /* Input styling */ | |
| .gr-textbox input, .gr-textbox textarea {{ | |
| border: 2px solid {color2} !important; | |
| font-family: '{font2}', sans-serif !important; | |
| }} | |
| .gr-textbox input:focus, .gr-textbox textarea:focus {{ | |
| border-color: {color1} !important; | |
| box-shadow: 0 0 0 3px {color1}33 !important; | |
| }} | |
| /* Slider styling */ | |
| .gr-slider input[type="range"] {{ | |
| accent-color: {color1} !important; | |
| }} | |
| /* Block styling */ | |
| .gr-block {{ | |
| border: 2px solid {color3}44 !important; | |
| border-radius: 12px !important; | |
| }} | |
| .gr-block .gr-block-title {{ | |
| background: {color2} !important; | |
| color: white !important; | |
| font-family: '{font1}', sans-serif !important; | |
| }} | |
| /* Background gradient */ | |
| .gradio-container {{ | |
| background: linear-gradient(45deg, {color3}22, {color1}11) !important; | |
| }} | |
| /* Results styling */ | |
| .results-container {{ | |
| font-family: '{font2}', sans-serif !important; | |
| }} | |
| .result-card {{ | |
| border: 2px solid {color2} !important; | |
| border-radius: 8px !important; | |
| margin: 8px 0 !important; | |
| padding: 12px !important; | |
| transition: transform 0.2s !important; | |
| }} | |
| .result-card:hover {{ | |
| transform: scale(1.02) !important; | |
| }} | |
| .color-card {{ | |
| min-height: 60px !important; | |
| display: flex !important; | |
| flex-direction: column !important; | |
| justify-content: center !important; | |
| text-align: center !important; | |
| font-weight: bold !important; | |
| }} | |
| .font-card {{ | |
| background: white !important; | |
| color: #333 !important; | |
| }} | |
| .font-sample {{ | |
| font-size: 18px !important; | |
| margin-top: 8px !important; | |
| padding: 8px !important; | |
| background: {color3}22 !important; | |
| border-radius: 4px !important; | |
| }} | |
| .score {{ | |
| background: {color1} !important; | |
| color: white !important; | |
| padding: 2px 6px !important; | |
| border-radius: 4px !important; | |
| font-size: 0.8em !important; | |
| }} | |
| </style> | |
| """ | |
| return css | |
| def publish_theme(self, theme_name: str, version: str = "1.0.0") -> str: | |
| """Publish the current theme to HuggingFace Hub.""" | |
| if not self.current_theme_data: | |
| return "No theme data available. Please generate a theme first." | |
| if not self.config.HF_TOKEN: | |
| return "HuggingFace token not found. Please set HF_TOKEN environment variable." | |
| # DEBUG: Print what we're actually working with | |
| print("=== DEBUG INFO ===") | |
| print(f"Colors: {[(c['name'], c['hex']) for c in self.current_theme_data['colors'][:3]]}") | |
| print(f"Fonts: {[f['name'] for f in self.current_theme_data['fonts'][:2]]}") | |
| try: | |
| # Create theme with more distinct differentiation | |
| mood_theme = MoodTheme( | |
| mood_colors=self.current_theme_data['colors'][:3], | |
| mood_fonts=self.current_theme_data['fonts'][:2], | |
| theme_name=theme_name.replace('-', '_').replace(' ', '_') | |
| ) | |
| # Additional forced differentiation based on first color | |
| first_color = self.current_theme_data['colors'][0]['hex'] | |
| if first_color in ['#DC143C', '#FF2400', '#8B0000']: # Red family | |
| mood_theme.set(button_primary_background_fill="#8B0000") | |
| elif first_color in ['#FFD700', '#F7E7CE', '#FFFACD']: # Yellow/Gold family | |
| mood_theme.set(button_primary_background_fill="#DAA520") | |
| elif first_color in ['#228B22', '#32CD32', '#50C878']: # Green family | |
| mood_theme.set(button_primary_background_fill="#006400") | |
| elif first_color in ['#4169E1', '#87CEEB', '#0F52BA']: # Blue family | |
| mood_theme.set(button_primary_background_fill="#000080") | |
| else: | |
| mood_theme.set(button_primary_background_fill=first_color) | |
| clean_name = theme_name.lower().replace(' ', '-').replace('_', '-') | |
| repo_name = f"{clean_name}-{int(time.time())}" | |
| mood_theme.push_to_hub( | |
| repo_name=repo_name, | |
| version=version, | |
| hf_token=self.config.HF_TOKEN | |
| ) | |
| return f"Theme published: {repo_name}\nFirst color: {first_color}\nFirst font: {self.current_theme_data['fonts'][0]['name']}" | |
| except Exception as e: | |
| return f"Error publishing theme: {str(e)}" | |
| def clear_theme(self) -> tuple[str, str, str]: | |
| """Clear the current theme.""" | |
| self.current_theme_data = None | |
| return "", "", "" | |
| # --- GRADIO UI --- | |
| def create_ui(generator: MoodThemeGenerator): | |
| """Creates the Gradio web interface.""" | |
| with gr.Blocks(title="Mood Theme Generator") as demo: | |
| # Dynamic CSS output | |
| dynamic_css_output = gr.HTML() | |
| gr.Markdown(""" | |
| # Mood Theme Generator | |
| Transform your words into a complete design system! Describe any mood, scene, or feeling, | |
| and get a matching **color palette** and **font selection** that's automatically applied | |
| as a **custom Gradio theme**. | |
| **Features:** | |
| - AI-powered color palette generation | |
| - Semantic font matching | |
| - Real-time theme application | |
| - One-click theme publishing to HuggingFace Hub | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| mood_input = gr.Textbox( | |
| value="Cozy autumn coffee shop with warm lighting", | |
| label="Describe Your Mood", | |
| info="Be as descriptive and creative as you like!", | |
| lines=2 | |
| ) | |
| with gr.Column(scale=1): | |
| generate_btn = gr.Button("Generate Theme", variant="primary", size="lg") | |
| clear_btn = gr.Button("Clear", variant="secondary") | |
| # Results display | |
| results_output = gr.HTML(label="Generated Theme Elements") | |
| # Theme management section | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| theme_name_input = gr.Textbox( | |
| label="Theme Name", | |
| placeholder="my-awesome-theme", | |
| info="Name for your theme (will be used for HuggingFace repo)" | |
| ) | |
| with gr.Column(scale=1): | |
| version_input = gr.Textbox( | |
| value="1.0.0", | |
| label="Version", | |
| info="Semantic version" | |
| ) | |
| with gr.Column(scale=1): | |
| publish_btn = gr.Button("Publish Theme", variant="secondary") | |
| publish_output = gr.Textbox( | |
| label="Publishing Status", | |
| interactive=False, | |
| lines=3 | |
| ) | |
| # Examples | |
| gr.Examples( | |
| examples=[ | |
| "Cozy autumn coffee shop with warm lighting and vintage books", | |
| "Futuristic cyberpunk city with neon lights and dark atmosphere", | |
| "Peaceful zen garden with natural stones and flowing water", | |
| "Vibrant music festival with colorful stage lights and energy", | |
| "Elegant luxury hotel lobby with marble and gold accents", | |
| "Retro 80s arcade with bright colors and pixel art aesthetics", | |
| "Minimalist Scandinavian office with clean lines and natural wood", | |
| "Romantic Paris café with soft pastels and vintage charm", | |
| "Bold modern art gallery with striking contrasts and geometry", | |
| "Serene beach sunset with warm oranges and calm blues" | |
| ], | |
| inputs=[mood_input], | |
| outputs=[results_output, dynamic_css_output, theme_name_input], | |
| fn=generator.generate_mood_theme, | |
| run_on_click=True, | |
| label="Try These Examples" | |
| ) | |
| # Event handlers | |
| generate_btn.click( | |
| fn=generator.generate_mood_theme, | |
| inputs=[mood_input], | |
| outputs=[results_output, dynamic_css_output, theme_name_input] | |
| ) | |
| clear_btn.click( | |
| fn=generator.clear_theme, | |
| outputs=[results_output, dynamic_css_output, theme_name_input] | |
| ) | |
| mood_input.submit( | |
| fn=generator.generate_mood_theme, | |
| inputs=[mood_input], | |
| outputs=[results_output, dynamic_css_output, theme_name_input] | |
| ) | |
| publish_btn.click( | |
| fn=generator.publish_theme, | |
| inputs=[theme_name_input, version_input], | |
| outputs=[publish_output] | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ## How It Works | |
| This application uses [**EmbeddingGemma**](https://huggingface.co/google/embeddinggemma-300M) | |
| to understand the semantic meaning of your mood description and match it with: | |
| 1. **Colors**: A curated palette of colors with rich descriptions | |
| 2. **Fonts**: A collection of Google Fonts with personality profiles | |
| 3. **Theme Generation**: Automatic creation of a cohesive Gradio theme | |
| 4. **Publishing**: One-click publishing to HuggingFace Hub for reuse | |
| The magic happens through **semantic similarity search** - your words are converted | |
| into mathematical representations (embeddings) that capture meaning, not just keywords. | |
| **Theme Application**: The generated theme is applied in real-time using: | |
| - Primary color → Headers, primary buttons, accents | |
| - Secondary color → Borders, secondary elements | |
| - Tertiary color → Backgrounds, subtle highlights | |
| - Primary font → Headers, important text | |
| - Secondary font → Body text, inputs | |
| **Publishing**: Published themes can be reused in any Gradio app with: | |
| ```python | |
| theme = gr.Theme.from_hub("your-username/your-theme-name") | |
| with gr.Blocks(theme=theme) as demo: | |
| # Your app here | |
| ``` | |
| """) | |
| return demo | |
| if __name__ == "__main__": | |
| # Initialize the generator | |
| generator = MoodThemeGenerator( | |
| config=Config(), | |
| color_data=COLOR_DATA, | |
| font_data=FONT_DATA | |
| ) | |
| # Create and launch the UI | |
| demo = create_ui(generator) | |
| demo.launch() |