HITL-KG / src /styles.py
avojarot's picture
Upload 22 files
c5880fb verified
"""
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;
}
"""