mood-theme / app.py
ysharma's picture
ysharma HF Staff
Update app.py
f629164 verified
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()