""" HITL-KG Styles Module Contains all styling constants for the visualization: - Cytoscape graph stylesheet - Layout configurations - Color schemes - Node descriptions for UI """ from typing import Dict, List, Any # Color palette (matching Tailwind dark theme) COLORS = { "bg_primary": "#0f172a", # slate-900 "bg_secondary": "#1e293b", # slate-800 "bg_tertiary": "#334155", # slate-700 "text_primary": "#f8fafc", # slate-50 "text_secondary": "#e2e8f0", # slate-200 "text_muted": "#94a3b8", # slate-400 "border": "#475569", # slate-600 # Node colors "query": "#38bdf8", # sky-400 "fact": "#4ade80", # green-400 "reasoning": "#818cf8", # indigo-400 "hypothesis": "#fbbf24", # amber-400 "conclusion": "#f472b6", # pink-400 "evidence": "#2dd4bf", # teal-400 "constraint": "#fb7185", # rose-400 "ghost": "#94a3b8", # slate-400 # Edge colors "edge_default": "#64748b", # slate-500 "edge_supports": "#4ade80", "edge_contradicts": "#f87171", "edge_alternative": "#fbbf24", } # Cytoscape stylesheet CYTOSCAPE_STYLESHEET = [ # Base node style { "selector": "node", "style": { "label": "data(label)", "background-color": "#818cf8", "color": COLORS["text_primary"], "font-size": "11px", "font-weight": "500", "text-wrap": "wrap", "text-max-width": "120px", "text-valign": "bottom", "text-halign": "center", "text-margin-y": "6px", "text-outline-color": COLORS["bg_primary"], "text-outline-width": "2px", "width": "35px", "height": "35px", "border-width": "2px", "border-color": "#ffffff33", } }, # Base edge style { "selector": "edge", "style": { "curve-style": "bezier", "target-arrow-shape": "triangle", "target-arrow-color": COLORS["edge_default"], "line-color": COLORS["edge_default"], "width": 2, "opacity": 0.7, } }, # Node type styles { "selector": ".query", "style": { "background-color": COLORS["query"], "shape": "round-rectangle", "width": "40px", "height": "40px", } }, { "selector": ".fact", "style": { "background-color": COLORS["fact"], "shape": "diamond", } }, { "selector": ".reasoning", "style": { "background-color": COLORS["reasoning"], "shape": "ellipse", } }, { "selector": ".hypothesis", "style": { "background-color": COLORS["hypothesis"], "shape": "hexagon", "width": "38px", "height": "38px", } }, { "selector": ".conclusion", "style": { "background-color": COLORS["conclusion"], "shape": "star", "width": "45px", "height": "45px", } }, { "selector": ".evidence", "style": { "background-color": COLORS["evidence"], "shape": "rectangle", } }, { "selector": ".constraint", "style": { "background-color": COLORS["constraint"], "shape": "triangle", } }, { "selector": ".ghost", "style": { "background-color": COLORS["ghost"], "opacity": 0.5, "border-style": "dashed", } }, # Edge type styles { "selector": "[type = 'supports']", "style": { "line-color": COLORS["edge_supports"], "target-arrow-color": COLORS["edge_supports"], } }, { "selector": "[type = 'contradicts']", "style": { "line-color": COLORS["edge_contradicts"], "target-arrow-color": COLORS["edge_contradicts"], "line-style": "dashed", } }, { "selector": "[type = 'alternative']", "style": { "line-color": COLORS["edge_alternative"], "target-arrow-color": COLORS["edge_alternative"], "line-style": "dotted", } }, # Selection state { "selector": ":selected", "style": { "border-width": "3px", "border-color": "#ffffff", "background-opacity": 1, } }, # Hover state (requires cytoscape extension) { "selector": "node:active", "style": { "overlay-opacity": 0.2, "overlay-color": "#ffffff", } }, ] # Layout configurations LAYOUT_CONFIGS = { "hierarchical": { "name": "dagre", "rankDir": "TB", "nodeSep": 50, "rankSep": 70, "edgeSep": 20, "animate": True, "animationDuration": 300, }, "force": { "name": "cose", "animate": True, "animationDuration": 500, "nodeRepulsion": 8000, "idealEdgeLength": 80, "edgeElasticity": 100, "gravity": 0.5, }, "radial": { "name": "concentric", "animate": True, "animationDuration": 300, "minNodeSpacing": 50, "levelWidth": 2, # Fixed value instead of lambda (lambdas can't be JSON serialized) }, } # Node descriptions for help/legend NODE_DESCRIPTIONS = { "query": { "icon": "❓", "name": "Query", "color": COLORS["query"], "description": "Your input question or symptom description", }, "fact": { "icon": "📋", "name": "Fact", "color": COLORS["fact"], "description": "Verified medical fact from knowledge base", }, "reasoning": { "icon": "🔍", "name": "Reasoning", "color": COLORS["reasoning"], "description": "Logical inference step connecting evidence", }, "hypothesis": { "icon": "💡", "name": "Hypothesis", "color": COLORS["hypothesis"], "description": "Potential diagnosis being considered", }, "conclusion": { "icon": "✅", "name": "Conclusion", "color": COLORS["conclusion"], "description": "Final diagnostic conclusion or recommendation", }, "evidence": { "icon": "📊", "name": "Evidence", "color": COLORS["evidence"], "description": "Supporting evidence from medical literature", }, "constraint": { "icon": "⚠️", "name": "Constraint", "color": COLORS["constraint"], "description": "Limitation or warning in the reasoning", }, "ghost": { "icon": "👻", "name": "Ghost", "color": COLORS["ghost"], "description": "Pruned reasoning path (can be resurrected)", }, } def create_legend_items() -> List[Dict[str, str]]: """Create legend items for UI.""" return [ {"label": desc["name"], "color": desc["color"], "description": desc["description"]} for desc in NODE_DESCRIPTIONS.values() ] # Custom CSS (injected into the page) CUSTOM_CSS = """ /* Chat container */ .chat-container { scrollbar-width: thin; scrollbar-color: #475569 #1e293b; } .chat-container::-webkit-scrollbar { width: 6px; } .chat-container::-webkit-scrollbar-track { background: #1e293b; } .chat-container::-webkit-scrollbar-thumb { background-color: #475569; border-radius: 3px; } /* Message styling */ .message { padding: 8px 12px; margin-bottom: 8px; border-radius: 8px; font-size: 0.9rem; line-height: 1.5; } .message-user { background-color: #1e3a5f; border-left: 3px solid #38bdf8; } .message-assistant { background-color: #1e293b; border-left: 3px solid #818cf8; } .message-error { background-color: #450a0a; border-left: 3px solid #f87171; } /* Status dot */ .status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; } .status-dot.active { background-color: #4ade80; } .status-dot.inactive { background-color: #f87171; } /* Quick action buttons */ .quick-action-btn { background-color: #334155; border: 1px solid #475569; color: #e2e8f0; border-radius: 16px; padding: 4px 12px; font-size: 0.8rem; transition: all 0.2s; } .quick-action-btn:hover { background-color: #475569; color: #f8fafc; } /* Node detail box */ .node-detail { background-color: #0f172a; border: 1px solid #475569; border-radius: 8px; padding: 12px; } /* Content preview */ .content-preview { background-color: #0f172a; border-radius: 4px; padding: 8px; margin-top: 8px; } /* Feedback buttons */ .feedback-btn { transition: all 0.2s; } .feedback-btn.correct:not(:disabled):hover { background-color: #22c55e; } .feedback-btn.incorrect:not(:disabled):hover { background-color: #ef4444; } /* Legend */ .legend-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; } .legend-item { display: flex; align-items: center; padding: 2px 8px; background-color: #1e293b; border-radius: 12px; cursor: help; } .legend-dot { width: 10px; height: 10px; border-radius: 50%; margin-right: 6px; } /* Card styling */ .card { border-radius: 8px; } .card-header { padding: 12px 16px; border-bottom: 1px solid #475569; } .card-body { padding: 16px; } /* Slider styling */ .rc-slider-rail { background-color: #334155; } .rc-slider-track { background-color: #818cf8; } .rc-slider-handle { border-color: #818cf8; } /* Dropdown styling */ .Select-control { background-color: #334155 !important; border-color: #475569 !important; } .Select-value-label { color: #f8fafc !important; } .Select-menu-outer { background-color: #1e293b !important; } """