"""Avatar SVG renderer — an abstract circle face whose expression is mood-driven. A pure HTML-string port of the prototype's ``Avatar`` component (``ui/raw/shared.jsx``): mood selects the mouth path, eye state, and sweat drop; ``active`` adds a pulsing ring. The colour is derived from the agent hue via the design's OKLch helpers. No Gradio import — the returned ```` string is dropped straight into a ``gr.HTML`` block, and Unit 1's CSS animates the emitted ``av-*`` classes. """ from __future__ import annotations # Colour helpers live in the design-vocabulary layer (``adapter``) so the avatar, MindCard, # feed line, and split row all derive one phosphor from one hue. Re-exported here for the # renderers that have always reached for them via this module. from src.ui.fishbowl.adapter import agent_color, agent_color_dim __all__ = ["render_avatar", "agent_color", "agent_color_dim"] # mouth path per mood (panic is drawn as an open 'o' instead of a path) _MOUTHS: dict[str, str] = { "calm": "M37 62 Q50 70 63 62", "thinking": "M41 64 L59 64", "truth": "M35 60 Q50 75 65 60", "smug": "M40 66 Q50 64 60 59", "gossip": "M40 65 Q50 63 60 60", "lying": "M40 64 Q50 60 60 64", "panic": "", # drawn as 'o' } def render_avatar(hue: int, mood: str = "calm", size: int = 64, active: bool = False) -> str: """Return an inline ```` avatar string whose expression varies by mood. ``hue`` is an agent hue (0–360); ``mood`` selects the face; ``size`` is the pixel box; ``active`` draws the speaking ring. Emits ``av-*`` classes so Unit 1's CSS can animate the avatar (ring pulse, blink, sweat, gasp, etc.). """ col = agent_color(hue) dim = agent_color_dim(hue) sweating = mood in ("lying", "panic") big = mood == "panic" is_flat_eyes = mood in ("smug", "gossip") mouth_path = _MOUTHS.get(mood) or _MOUTHS["calm"] eye_ry = 6 if big else (1.6 if is_flat_eyes else 4) eye_rx = 5 if is_flat_eyes else 3.4 ring = "" if active: ring = ( f'' ) if is_flat_eyes: eyes = ( f'' f'' ) else: blink = " av-blink" if mood == "thinking" else "" eyes = ( f'' f'' ) if mood == "panic": mouth = f'' else: mouth = f'' sweat = "" if sweating: sweat = ( '' ) face = ( f'' ) # mood-specific animation class so Unit 1's CSS can target it (av-truth, av-smug, …) return ( f'
' f'' f"{ring}{face}{eyes}{mouth}{sweat}" f"
" )