"""HTML/CSS rendering for the FinAgent Gradio frontend. Provides pure rendering functions that generate HTML strings for: - Custom dark terminal theme CSS - Trading signal cards (BUY/SELL/HOLD) - Error cards for failed ticker analysis - Aggregate summary bar - Activity feed entries and container Full implementation is provided in task 4.x; this module currently contains function stubs. """ from datetime import datetime from typing import Any, List, Optional def build_css() -> str: """Generate custom CSS for the dark financial terminal theme. Returns: CSS string to be injected into the Gradio Blocks ``css`` parameter. """ return """ .gradio-container { font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace !important; background-color: #0d1117 !important; } .activity-feed { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 12px; max-height: 400px; overflow-y: auto; font-family: 'JetBrains Mono', monospace; font-size: 13px; } .activity-entry { padding: 4px 0; border-bottom: 1px solid #21262d; color: #c9d1d9; } .activity-timestamp { color: #8b949e; margin-right: 8px; } .activity-agent { color: #58a6ff; font-weight: bold; } .activity-spinner { color: #f0883e; } .signal-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; margin: 8px 0; border-left: 4px solid; } .signal-buy { border-left-color: #3fb950; } .signal-sell { border-left-color: #f85149; } .signal-hold { border-left-color: #d29922; } .signal-error { border-left-color: #f85149; background: #1c0c0c; } .signal-ticker { font-size: 18px; font-weight: bold; color: #f0f6fc; } .signal-action-buy { color: #3fb950; } .signal-action-sell { color: #f85149; } .signal-action-hold { color: #d29922; } .signal-confidence { font-size: 14px; color: #8b949e; } .signal-prices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; } .signal-price-item { text-align: center; padding: 8px; background: #0d1117; border-radius: 4px; } .signal-price-label { font-size: 11px; color: #8b949e; text-transform: uppercase; } .signal-price-value { font-size: 16px; color: #f0f6fc; font-weight: bold; } .summary-bar { display: flex; gap: 16px; padding: 12px; background: #161b22; border-radius: 6px; margin-top: 12px; border: 1px solid #30363d; } .summary-item { text-align: center; flex: 1; } """ def _format_price(value: Optional[float]) -> str: """Format an optional price as ``$X.XX`` or ``N/A`` when missing.""" if value is None: return "N/A" return f"${value:.2f}" def _action_text(action: Any) -> str: """Return the upper-case action label from an enum-like or string value.""" # Support both enum-like objects (with ``.value``) and plain strings. raw = getattr(action, "value", action) return str(raw).upper() def render_signal_card(signal: Any) -> str: """Render a TradingSignal as an HTML card. Args: signal: TradingSignal dataclass instance containing ticker, action, confidence, entry/stop-loss/target prices, and reasoning. Returns: HTML string for the signal card with action-specific color coding. """ action_text = _action_text(signal.action) action_lower = action_text.lower() action_class = f"signal-{action_lower}" action_color_class = f"signal-action-{action_lower}" entry_price = getattr(signal, "entry_price", None) stop_loss = getattr(signal, "stop_loss", None) target_price = getattr(signal, "target_price", None) prices_html = f"""