From 3a545952c0e99398564238b88448b7088b5db95b Mon Sep 17 00:00:00 2001 From: Joshua Zhang Date: Thu, 5 Mar 2026 18:03:00 +0800 Subject: [PATCH] update colors --- _colors.json | 17 +++-- step5_assign_colors.py | 163 +++++++++++++++++++++++++++-------------- 2 files changed, 115 insertions(+), 65 deletions(-) diff --git a/_colors.json b/_colors.json index c3a10a9..1163068 100644 --- a/_colors.json +++ b/_colors.json @@ -1,14 +1,15 @@ { "Malabar": "#000000", - "Venus": "#808080", + "Mars": "#FF0000", + "Venus": "#FFD700", + "Sun": "#FFA500", + "Earth": "#228B22", + "Atoms": "#008B8B", + "Narrator": "#708090", + "Volcanoes": "#8B0000", + "Asteroids": "#696969", "Jupiter": "#D2691E", "Moon": "#A9A9A9", - "Mars": "#FF4500", - "Narrator": "#808080", - "Asteroids": "#808080", - "Comet": "#808080", - "Volcanoes": "#8B0000", "Song": "#4682B4", - "Earth": "#228B22", - "Atoms": "#808080" + "Comet": "#DAA520" } \ No newline at end of file diff --git a/step5_assign_colors.py b/step5_assign_colors.py index b5a7ccb..ccff400 100644 --- a/step5_assign_colors.py +++ b/step5_assign_colors.py @@ -37,7 +37,11 @@ OUTPUT_FILE = Path("_colors.json") # Fixed color assignments FIXED_COLORS = { - "Malabar": "#000000" # Black + "Malabar": "#000000", # Black + "Mars": "#FF0000", # Red + "Venus": "#FFD700", # Gold + "Sun": "#FFA500", # Bright gold/orange + "Earth": "#228B22" # Forest green } # Default configurations for different providers @@ -97,43 +101,75 @@ def collect_speakers(input_dir: Path) -> Set[str]: return speakers -def assign_colors(speakers: Set[str], client: OpenAI, model: str) -> Dict[str, str]: - """Assign colors to speakers using LLM.""" - # Start with fixed colors - color_mapping = FIXED_COLORS.copy() +def get_unique_fallback_color(index: int) -> str: + """Generate a unique fallback color from a palette.""" + # Distinct color palette for fallback (dark enough for white background) + palette = [ + "#8B4513", # Saddle Brown + "#556B2F", # Dark Olive Green + "#483D8B", # Dark Slate Blue + "#2F4F4F", # Dark Slate Gray + "#8B008B", # Dark Magenta + "#4B0082", # Indigo + "#191970", # Midnight Blue + "#006400", # Dark Green + "#8B0000", # Dark Red + "#B8860B", # Dark Goldenrod + "#5F9EA0", # Cadet Blue + "#708090", # Slate Gray + "#CD853F", # Peru + "#BC8F8F", # Rosy Brown + "#4682B4", # Steel Blue + "#6B8E23", # Olive Drab + "#9370DB", # Medium Purple + "#8FBC8F", # Dark Sea Green + "#CD5C5C", # Indian Red + "#4169E1", # Royal Blue + ] + return palette[index % len(palette)] + + +def call_llm_for_colors(speakers: List[str], client: OpenAI, model: str, + existing_mapping: Dict[str, str], attempt: int = 1) -> Dict[str, str]: + """Call LLM to assign colors to speakers. Returns parsed color mapping.""" + speakers_list = ", ".join(speakers) - # Filter out speakers that already have fixed colors - remaining_speakers = [s for s in speakers if s not in color_mapping] - - if not remaining_speakers: - return color_mapping - - # Build prompt - speakers_list = ", ".join(remaining_speakers) + existing_info = "" + if existing_mapping: + existing_colors = [f" - {k} → {v}" for k, v in existing_mapping.items()] + existing_info = f"\nAlready assigned:\n" + "\n".join(existing_colors) prompt = f"""Assign CSS hex color codes to each speaker from "Little Malabar" based on their characteristics. Speakers to assign colors: -{speakers_list} +{speakers_list}{existing_info} Color assignment guidelines (use hex codes like #FF0000): -- Mars → #CD5C5C (red planet) or #FF4500 -- Earth → #228B22 (forest green) or #4169E1 (royal blue) - Moon → #A9A9A9 (dark gray) - avoid light colors -- Sun → #FFA500 (orange) - avoid light colors - Jupiter → #D2691E (chocolate/orange) - Galaxy → #9370DB (medium purple) or #FF69B4 (hot pink) - Star → #DAA520 (goldenrod) or #B8860B (dark goldenrod) - avoid white/light colors - Volcano → #8B0000 (dark red) or #FF4500 (orange red) - Kangaroo/Giraffe → #D2691E (chocolate) or #8B4513 (saddle brown) - Song → #4682B4 (steel blue) or #9370DB (medium purple) - avoid light colors +- Asteroids → #696969 (dim gray) or #A9A9A9 (dark gray) +- Atoms → #20B2AA (light sea green) or #008B8B (dark cyan) +- Comet → #FFD700 (gold) or #DAA520 (goldenrod) +- Narrator → #708090 (slate gray) or #778899 (light slate gray) -IMPORTANT: Do NOT use light colors like #FFFFFF (white), #FFFACD, #87CEEB, #C0C0C0. All colors must be dark enough to read on white backgrounds. +IMPORTANT: +- Do NOT use light colors like #FFFFFF (white), #FFFACD, #87CEEB, #C0C0C0 +- All colors must be dark enough to read on white backgrounds +- Each speaker should have a UNIQUE color (no duplicates!) -Fixed assignment: -- Malabar → #000000 (black, already set) +Fixed assignments (DO NOT change these): +- Malabar → #000000 (black) +- Mars → #FF0000 (red) +- Venus → #FFD700 (gold) +- Sun → #FFA500 (bright gold) +- Earth → #228B22 (green) -Reply with ONLY a JSON object mapping speaker names to hex color codes: +Reply with ONLY a JSON object mapping the remaining speaker names to hex color codes: {{"SpeakerName": "#RRGGBB", ...}} JSON:""" @@ -146,7 +182,8 @@ JSON:""" {"role": "user", "content": prompt} ], temperature=0.3, - max_tokens=500 + max_tokens=500, + extra_body={"thinking": {"type": "disabled"}} # Disable thinking ) message = response.choices[0].message @@ -161,46 +198,58 @@ JSON:""" if json_match: try: parsed = json.loads(json_match.group()) - for speaker, color in parsed.items(): - if speaker in remaining_speakers: - color_mapping[speaker] = color + return {k: v for k, v in parsed.items() if k in speakers} except json.JSONDecodeError: - print(f" Warning: Could not parse JSON response") + print(f" Warning: Could not parse JSON response on attempt {attempt}") - # Assign default hex colors for any remaining speakers - for speaker in remaining_speakers: - if speaker not in color_mapping: - # Simple fallback based on name (using hex codes) - name_lower = speaker.lower() - if 'mars' in name_lower: - color_mapping[speaker] = "#FF4500" # Orange red - elif 'earth' in name_lower: - color_mapping[speaker] = "#228B22" # Forest green - elif 'moon' in name_lower: - color_mapping[speaker] = "#A9A9A9" # Dark gray - elif 'sun' in name_lower: - color_mapping[speaker] = "#FFA500" # Orange - elif 'jupiter' in name_lower: - color_mapping[speaker] = "#D2691E" # Chocolate/orange - elif 'star' in name_lower: - color_mapping[speaker] = "#DAA520" # Goldenrod - elif 'galaxy' in name_lower: - color_mapping[speaker] = "#9370DB" # Medium purple - elif 'volcano' in name_lower: - color_mapping[speaker] = "#8B0000" # Dark red - elif 'song' in name_lower: - color_mapping[speaker] = "#4682B4" # Steel blue - else: - color_mapping[speaker] = "#808080" # Gray - - return color_mapping + return {} except Exception as e: - print(f" Error assigning colors: {e}") - # Return defaults for all remaining speakers - for speaker in remaining_speakers: - color_mapping[speaker] = "gray" + print(f" Error calling LLM on attempt {attempt}: {e}") + return {} + + +def assign_colors(speakers: Set[str], client: OpenAI, model: str) -> Dict[str, str]: + """Assign colors to speakers using LLM with retry logic.""" + # Start with fixed colors + color_mapping = FIXED_COLORS.copy() + + # Filter out speakers that already have fixed colors + remaining_speakers = [s for s in speakers if s not in color_mapping] + + if not remaining_speakers: return color_mapping + + max_retries = 3 + + for attempt in range(1, max_retries + 1): + # Get speakers that still need colors + still_need_colors = [s for s in remaining_speakers if s not in color_mapping] + + if not still_need_colors: + break # All speakers have colors + + if attempt > 1: + print(f" Retry {attempt-1}: {len(still_need_colors)} speakers still need colors...") + + # Call LLM to get colors + llm_result = call_llm_for_colors(still_need_colors, client, model, color_mapping, attempt) + + # Merge results + for speaker, color in llm_result.items(): + if speaker in still_need_colors: + color_mapping[speaker] = color + + # Check for any remaining speakers without colors + still_need_colors = [s for s in remaining_speakers if s not in color_mapping] + + if still_need_colors: + print(f" Using fallback colors for {len(still_need_colors)} speakers...") + # Assign unique fallback colors from palette + for idx, speaker in enumerate(still_need_colors): + color_mapping[speaker] = get_unique_fallback_color(idx) + + return color_mapping def main():