import gradio as gr import numpy as np import os from huggingface_hub import login from sentence_transformers import SentenceTransformer, util # --- 1. CONFIGURATION --- # Centralized place for all settings and constants. 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') # --- 2. COLOR DATA --- # The color palette data is kept separate for clarity and easy modification. 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": "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": "Gold", "hex": "#FFD700", "description": "A bright, metallic yellow associated with wealth and luxury." }, { "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": "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": "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": "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": "Ivory", "hex": "#FFFFF0", "description": "An off-white color that resembles the material from tusks and teeth." }, { "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": "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": "Onyx", "hex": "#353839", "description": "A deep, rich black, often with a subtle hint of dark blue." }, { "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": "Amethyst", "hex": "#9966CC", "description": "A moderate, violet-purple color, like the quartz gemstone." }, { "name": "Peridot", "hex": "#E6E200", "description": "A light olive-green or yellowish-green, named for the gem." }, { "name": "Turquoise", "hex": "#40E0D0", "description": "A greenish-blue color, often associated with tropical waters." }, { "name": "Silver", "hex": "#C0C0C0", "description": "A metallic gray color that resembles polished silver." }, { "name": "Bronze", "hex": "#CD7F32", "description": "A metallic brown color that resembles the alloy of copper and tin." }, { "name": "Obsidian", "hex": "#000000", "description": "A pure, deep black, like the volcanic glass formed from cooled lava." } ] # --- 3. CORE LOGIC --- # Encapsulated in a class to manage state (model, embeddings) cleanly. class MoodPaletteGenerator: """Handles model loading, embedding generation, and palette creation.""" def __init__(self, config: Config, color_data: list[dict[str, any]]): """Initializes the generator, logs in, and loads necessary assets.""" self.config = config self.color_data = color_data self._login_to_hf() self.embedding_model = self._load_model() self.color_embeddings = self._precompute_color_embeddings() 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.") print("Note: This may fail if the model is gated.") 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 ] embeddings = self.embedding_model.encode( color_texts, prompt_name=self.config.PROMPT_NAME ) print("Embeddings computed successfully.") return embeddings def _get_text_color_for_bg(self, hex_color: str) -> str: """ Calculates the luminance of a hex color and returns black ('#000000') or white ('#FFFFFF') for the best text contrast. """ 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' # Default to black on invalid hex def _format_palette_as_html(self, top_hits: list[dict[str, any]]) -> str: """Formats the top color hits into a displayable HTML string.""" if not top_hits: return "

Could not generate a palette. Please try another mood.

" cards_html = "" for hit in top_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) cards_html += f"""
{name} | {hex_code} | Score: {score:.2f}
""" return f"
{cards_html}
" def _create_dynamic_theme_css(self, top_hits: list[dict[str, any]]) -> str: """Generates a """ return css def generate_palette_and_theme(self, mood_text: str) -> tuple[str, str]: """ Generates a color palette HTML and a dynamic theme CSS string. """ if not mood_text or not mood_text.strip(): return "

Please enter a mood or a description.

", "" mood_embedding = self.embedding_model.encode( mood_text, prompt_name=self.config.PROMPT_NAME ) top_hits = util.semantic_search( mood_embedding, self.color_embeddings, top_k=self.config.TOP_K )[0] palette_html = self._format_palette_as_html(top_hits) theme_css = self._create_dynamic_theme_css(top_hits) return palette_html, theme_css def clear_theme(self) -> tuple[str, str]: return "", "" # --- 4. GRADIO UI --- # Defines and launches the web interface. def create_ui(generator: MoodPaletteGenerator): """Creates the Gradio web interface.""" with gr.Blocks(theme=gr.themes.Soft()) as demo: # This invisible component will hold our dynamic CSS dynamic_css_output = gr.HTML() gr.Markdown(""" # 🎨 Mood Palette Generator Describe a mood, a scene, or a feeling, and get a matching color palette.
**The UI theme will update to match your mood!** """) with gr.Row(): with gr.Column(scale=4): mood_input = gr.Textbox( value="Strawberry ice cream", label="Enter Your Mood or Scene", info="Be as descriptive as you like!" ) with gr.Column(scale=1, min_width=150): submit_button = gr.Button("Generate Palette", variant="primary") clear_button = gr.Button("Clear", variant="secondary") palette_output = gr.HTML(label="Your Generated Palette") # Define CSS for palette cards here once, instead of in every update gr.HTML(""" """) # Define the function to be called by events event_handler = generator.generate_palette_and_theme outputs_list = [palette_output, dynamic_css_output] gr.Examples( [ "A futuristic city at night, slick with rain", "The feeling of a cozy cabin during a blizzard", "Joyful chaos at a summer music festival", "Beach sunset with the palm tree", "A calm and lonely winter morning", "Vintage romance in a dusty library", "Cyberpunk alleyway with neon signs", ], inputs=mood_input, outputs=outputs_list, fn=event_handler, run_on_click=True, ) submit_button.click( fn=event_handler, inputs=mood_input, outputs=outputs_list, ) clear_button.click( fn=generator.clear_theme, outputs=outputs_list, ) # Also allow submitting by pressing Enter in the textbox mood_input.submit( fn=event_handler, inputs=mood_input, outputs=outputs_list, ) gr.Markdown(""" ---- ## What is this? This interactive application, the **Mood Palette Generator**, transforms your words into a vibrant color palette. Simply describe a mood, a scene, or a feeling and the app will generate a set of matching colors. As a unique touch, the entire user interface dynamically updates its theme to reflect the generated palette, immersing you in your chosen mood. ## How It Works? At its core, this tool is powered by [**EmbeddingGemma**](http://huggingface.co/google/embeddinggemma-300M), a state-of-the-art text embedding model. The process works in a few simple steps: 1. **Text to Vector**: When you enter a description, EmbeddingGemma converts your text into a numerical representation called an **embedding**. This embedding captures the semantic essence, or the "vibe" of your words. 2. **Semantic Color Search**: The application has a pre-defined library of colors, where each color is associated with its own descriptive text and a pre-computed embedding. 3. **Finding the Match**: Your input embedding is compared against the entire library of color embeddings to find the closest matches based on a similarity score. 4. **Palette Creation**: The colors with the highest similarity scores are selected and presented to you as a complete palette. The Mood Palette Generator is a perfect example of how embeddings can be used for creative applications beyond simple text search. """) return demo if __name__ == "__main__": # Initialize application components generator = MoodPaletteGenerator(config=Config(), color_data=COLOR_DATA) demo = create_ui(generator) demo.launch()