empathy-framework 3.7.0__py3-none-any.whl → 3.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coach_wizards/code_reviewer_README.md +60 -0
- coach_wizards/code_reviewer_wizard.py +180 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/METADATA +148 -11
- empathy_framework-3.8.0.dist-info/RECORD +333 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/top_level.txt +5 -1
- empathy_healthcare_plugin/monitors/__init__.py +9 -0
- empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
- empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
- empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
- empathy_llm_toolkit/agent_factory/__init__.py +53 -0
- empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
- empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
- empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
- empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
- empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
- empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
- empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
- empathy_llm_toolkit/agent_factory/base.py +305 -0
- empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
- empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
- empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
- empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
- empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
- empathy_llm_toolkit/agent_factory/decorators.py +286 -0
- empathy_llm_toolkit/agent_factory/factory.py +558 -0
- empathy_llm_toolkit/agent_factory/framework.py +192 -0
- empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
- empathy_llm_toolkit/agent_factory/resilient.py +320 -0
- empathy_llm_toolkit/cli/__init__.py +8 -0
- empathy_llm_toolkit/cli/sync_claude.py +487 -0
- empathy_llm_toolkit/code_health.py +150 -3
- empathy_llm_toolkit/config/__init__.py +29 -0
- empathy_llm_toolkit/config/unified.py +295 -0
- empathy_llm_toolkit/routing/__init__.py +32 -0
- empathy_llm_toolkit/routing/model_router.py +362 -0
- empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
- empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
- empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
- empathy_llm_toolkit/security/README.md +262 -0
- empathy_llm_toolkit/security/__init__.py +62 -0
- empathy_llm_toolkit/security/audit_logger.py +929 -0
- empathy_llm_toolkit/security/audit_logger_example.py +152 -0
- empathy_llm_toolkit/security/pii_scrubber.py +640 -0
- empathy_llm_toolkit/security/secrets_detector.py +678 -0
- empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
- empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
- empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
- empathy_llm_toolkit/wizards/__init__.py +38 -0
- empathy_llm_toolkit/wizards/base_wizard.py +364 -0
- empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
- empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
- empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
- empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
- empathy_os/__init__.py +52 -52
- empathy_os/adaptive/__init__.py +13 -0
- empathy_os/adaptive/task_complexity.py +127 -0
- empathy_os/cache/__init__.py +117 -0
- empathy_os/cache/base.py +166 -0
- empathy_os/cache/dependency_manager.py +253 -0
- empathy_os/cache/hash_only.py +248 -0
- empathy_os/cache/hybrid.py +390 -0
- empathy_os/cache/storage.py +282 -0
- empathy_os/cli.py +118 -8
- empathy_os/cli_unified.py +121 -1
- empathy_os/config/__init__.py +63 -0
- empathy_os/config/xml_config.py +239 -0
- empathy_os/config.py +2 -1
- empathy_os/dashboard/__init__.py +15 -0
- empathy_os/dashboard/server.py +743 -0
- empathy_os/memory/__init__.py +195 -0
- empathy_os/memory/claude_memory.py +466 -0
- empathy_os/memory/config.py +224 -0
- empathy_os/memory/control_panel.py +1298 -0
- empathy_os/memory/edges.py +179 -0
- empathy_os/memory/graph.py +567 -0
- empathy_os/memory/long_term.py +1194 -0
- empathy_os/memory/nodes.py +179 -0
- empathy_os/memory/redis_bootstrap.py +540 -0
- empathy_os/memory/security/__init__.py +31 -0
- empathy_os/memory/security/audit_logger.py +930 -0
- empathy_os/memory/security/pii_scrubber.py +640 -0
- empathy_os/memory/security/secrets_detector.py +678 -0
- empathy_os/memory/short_term.py +2119 -0
- empathy_os/memory/storage/__init__.py +15 -0
- empathy_os/memory/summary_index.py +583 -0
- empathy_os/memory/unified.py +619 -0
- empathy_os/metrics/__init__.py +12 -0
- empathy_os/metrics/prompt_metrics.py +190 -0
- empathy_os/models/__init__.py +136 -0
- empathy_os/models/__main__.py +13 -0
- empathy_os/models/cli.py +655 -0
- empathy_os/models/empathy_executor.py +354 -0
- empathy_os/models/executor.py +252 -0
- empathy_os/models/fallback.py +671 -0
- empathy_os/models/provider_config.py +563 -0
- empathy_os/models/registry.py +382 -0
- empathy_os/models/tasks.py +302 -0
- empathy_os/models/telemetry.py +548 -0
- empathy_os/models/token_estimator.py +378 -0
- empathy_os/models/validation.py +274 -0
- empathy_os/monitoring/__init__.py +52 -0
- empathy_os/monitoring/alerts.py +23 -0
- empathy_os/monitoring/alerts_cli.py +268 -0
- empathy_os/monitoring/multi_backend.py +271 -0
- empathy_os/monitoring/otel_backend.py +363 -0
- empathy_os/optimization/__init__.py +19 -0
- empathy_os/optimization/context_optimizer.py +272 -0
- empathy_os/plugins/__init__.py +28 -0
- empathy_os/plugins/base.py +361 -0
- empathy_os/plugins/registry.py +268 -0
- empathy_os/project_index/__init__.py +30 -0
- empathy_os/project_index/cli.py +335 -0
- empathy_os/project_index/crew_integration.py +430 -0
- empathy_os/project_index/index.py +425 -0
- empathy_os/project_index/models.py +501 -0
- empathy_os/project_index/reports.py +473 -0
- empathy_os/project_index/scanner.py +538 -0
- empathy_os/prompts/__init__.py +61 -0
- empathy_os/prompts/config.py +77 -0
- empathy_os/prompts/context.py +177 -0
- empathy_os/prompts/parser.py +285 -0
- empathy_os/prompts/registry.py +313 -0
- empathy_os/prompts/templates.py +208 -0
- empathy_os/resilience/__init__.py +56 -0
- empathy_os/resilience/circuit_breaker.py +256 -0
- empathy_os/resilience/fallback.py +179 -0
- empathy_os/resilience/health.py +300 -0
- empathy_os/resilience/retry.py +209 -0
- empathy_os/resilience/timeout.py +135 -0
- empathy_os/routing/__init__.py +43 -0
- empathy_os/routing/chain_executor.py +433 -0
- empathy_os/routing/classifier.py +217 -0
- empathy_os/routing/smart_router.py +234 -0
- empathy_os/routing/wizard_registry.py +307 -0
- empathy_os/trust/__init__.py +28 -0
- empathy_os/trust/circuit_breaker.py +579 -0
- empathy_os/validation/__init__.py +19 -0
- empathy_os/validation/xml_validator.py +281 -0
- empathy_os/wizard_factory_cli.py +170 -0
- empathy_os/workflows/__init__.py +360 -0
- empathy_os/workflows/base.py +1660 -0
- empathy_os/workflows/bug_predict.py +962 -0
- empathy_os/workflows/code_review.py +960 -0
- empathy_os/workflows/code_review_adapters.py +310 -0
- empathy_os/workflows/code_review_pipeline.py +720 -0
- empathy_os/workflows/config.py +600 -0
- empathy_os/workflows/dependency_check.py +648 -0
- empathy_os/workflows/document_gen.py +1069 -0
- empathy_os/workflows/documentation_orchestrator.py +1205 -0
- empathy_os/workflows/health_check.py +679 -0
- empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
- empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
- empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
- empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
- empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
- empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
- empathy_os/workflows/manage_documentation.py +804 -0
- empathy_os/workflows/new_sample_workflow1.py +146 -0
- empathy_os/workflows/new_sample_workflow1_README.md +150 -0
- empathy_os/workflows/perf_audit.py +687 -0
- empathy_os/workflows/pr_review.py +748 -0
- empathy_os/workflows/progress.py +445 -0
- empathy_os/workflows/progress_server.py +322 -0
- empathy_os/workflows/refactor_plan.py +693 -0
- empathy_os/workflows/release_prep.py +808 -0
- empathy_os/workflows/research_synthesis.py +404 -0
- empathy_os/workflows/secure_release.py +585 -0
- empathy_os/workflows/security_adapters.py +297 -0
- empathy_os/workflows/security_audit.py +1046 -0
- empathy_os/workflows/step_config.py +234 -0
- empathy_os/workflows/test5.py +125 -0
- empathy_os/workflows/test5_README.md +158 -0
- empathy_os/workflows/test_gen.py +1855 -0
- empathy_os/workflows/test_lifecycle.py +526 -0
- empathy_os/workflows/test_maintenance.py +626 -0
- empathy_os/workflows/test_maintenance_cli.py +590 -0
- empathy_os/workflows/test_maintenance_crew.py +821 -0
- empathy_os/workflows/xml_enhanced_crew.py +285 -0
- empathy_software_plugin/cli/__init__.py +120 -0
- empathy_software_plugin/cli/inspect.py +362 -0
- empathy_software_plugin/cli.py +3 -1
- empathy_software_plugin/wizards/__init__.py +42 -0
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
- empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
- empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
- empathy_software_plugin/wizards/base_wizard.py +288 -0
- empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
- empathy_software_plugin/wizards/code_review_wizard.py +606 -0
- empathy_software_plugin/wizards/debugging/__init__.py +50 -0
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
- empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
- empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
- empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
- empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
- empathy_software_plugin/wizards/debugging/verification.py +369 -0
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
- empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
- empathy_software_plugin/wizards/performance/__init__.py +9 -0
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
- empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
- empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
- empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
- empathy_software_plugin/wizards/security/__init__.py +32 -0
- empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
- empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
- empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
- empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
- empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
- empathy_software_plugin/wizards/testing/__init__.py +27 -0
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
- empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
- empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
- empathy_software_plugin/wizards/testing_wizard.py +274 -0
- hot_reload/README.md +473 -0
- hot_reload/__init__.py +62 -0
- hot_reload/config.py +84 -0
- hot_reload/integration.py +228 -0
- hot_reload/reloader.py +298 -0
- hot_reload/watcher.py +179 -0
- hot_reload/websocket.py +176 -0
- scaffolding/README.md +589 -0
- scaffolding/__init__.py +35 -0
- scaffolding/__main__.py +14 -0
- scaffolding/cli.py +240 -0
- test_generator/__init__.py +38 -0
- test_generator/__main__.py +14 -0
- test_generator/cli.py +226 -0
- test_generator/generator.py +325 -0
- test_generator/risk_analyzer.py +216 -0
- workflow_patterns/__init__.py +33 -0
- workflow_patterns/behavior.py +249 -0
- workflow_patterns/core.py +76 -0
- workflow_patterns/output.py +99 -0
- workflow_patterns/registry.py +255 -0
- workflow_patterns/structural.py +288 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
- agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
- agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
- agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
- agents/compliance_anticipation_agent.py +0 -1422
- agents/compliance_db.py +0 -339
- agents/epic_integration_wizard.py +0 -530
- agents/notifications.py +0 -291
- agents/trust_building_behaviors.py +0 -872
- empathy_framework-3.7.0.dist-info/RECORD +0 -105
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/WHEEL +0 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/entry_points.txt +0 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/licenses/LICENSE +0 -0
- /empathy_os/{monitoring.py → agent_monitoring.py} +0 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
"""Dashboard Server for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Lightweight web server for viewing patterns, costs, and health.
|
|
4
|
+
Uses built-in http.server to avoid external dependencies.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import http.server
|
|
11
|
+
import json
|
|
12
|
+
import socketserver
|
|
13
|
+
import threading
|
|
14
|
+
import webbrowser
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
# Try to import optional dependencies
|
|
20
|
+
try:
|
|
21
|
+
from empathy_os.cost_tracker import CostTracker
|
|
22
|
+
|
|
23
|
+
HAS_COST_TRACKER = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
CostTracker = None # type: ignore[misc, assignment]
|
|
26
|
+
HAS_COST_TRACKER = False
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from empathy_os.discovery import DiscoveryEngine
|
|
30
|
+
|
|
31
|
+
HAS_DISCOVERY = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
DiscoveryEngine = None # type: ignore[misc, assignment]
|
|
34
|
+
HAS_DISCOVERY = False
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from empathy_os.workflows import get_workflow_stats, list_workflows
|
|
38
|
+
|
|
39
|
+
HAS_WORKFLOWS = True
|
|
40
|
+
except ImportError:
|
|
41
|
+
get_workflow_stats = None # type: ignore[assignment]
|
|
42
|
+
list_workflows = None # type: ignore[assignment]
|
|
43
|
+
HAS_WORKFLOWS = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DashboardHandler(http.server.BaseHTTPRequestHandler):
|
|
47
|
+
"""HTTP request handler for the dashboard."""
|
|
48
|
+
|
|
49
|
+
patterns_dir = "./patterns"
|
|
50
|
+
empathy_dir = ".empathy"
|
|
51
|
+
|
|
52
|
+
def log_message(self, format, *args):
|
|
53
|
+
"""Suppress default logging."""
|
|
54
|
+
|
|
55
|
+
def do_GET(self):
|
|
56
|
+
"""Handle GET requests."""
|
|
57
|
+
parsed = urlparse(self.path)
|
|
58
|
+
path = parsed.path
|
|
59
|
+
|
|
60
|
+
if path == "/" or path == "/index.html":
|
|
61
|
+
self._serve_dashboard()
|
|
62
|
+
elif path == "/api/patterns":
|
|
63
|
+
self._serve_patterns()
|
|
64
|
+
elif path == "/api/costs":
|
|
65
|
+
self._serve_costs()
|
|
66
|
+
elif path == "/api/stats":
|
|
67
|
+
self._serve_stats()
|
|
68
|
+
elif path == "/api/health":
|
|
69
|
+
self._serve_health()
|
|
70
|
+
elif path == "/api/workflows":
|
|
71
|
+
self._serve_workflows()
|
|
72
|
+
else:
|
|
73
|
+
self.send_error(404, "Not Found")
|
|
74
|
+
|
|
75
|
+
def _serve_dashboard(self):
|
|
76
|
+
"""Serve the main dashboard HTML."""
|
|
77
|
+
html = self._generate_dashboard_html()
|
|
78
|
+
self.send_response(200)
|
|
79
|
+
self.send_header("Content-type", "text/html")
|
|
80
|
+
self.send_header("Content-Length", len(html))
|
|
81
|
+
self.end_headers()
|
|
82
|
+
self.wfile.write(html.encode())
|
|
83
|
+
|
|
84
|
+
def _serve_patterns(self):
|
|
85
|
+
"""Serve patterns as JSON."""
|
|
86
|
+
patterns = self._load_patterns()
|
|
87
|
+
self._send_json(patterns)
|
|
88
|
+
|
|
89
|
+
def _serve_costs(self):
|
|
90
|
+
"""Serve cost data as JSON."""
|
|
91
|
+
if HAS_COST_TRACKER and CostTracker is not None:
|
|
92
|
+
tracker = CostTracker(self.empathy_dir)
|
|
93
|
+
data = tracker.get_summary(30)
|
|
94
|
+
else:
|
|
95
|
+
data = {"error": "Cost tracking not available"}
|
|
96
|
+
self._send_json(data)
|
|
97
|
+
|
|
98
|
+
def _serve_stats(self):
|
|
99
|
+
"""Serve discovery stats as JSON."""
|
|
100
|
+
if HAS_DISCOVERY and DiscoveryEngine is not None:
|
|
101
|
+
engine = DiscoveryEngine(self.empathy_dir)
|
|
102
|
+
data = engine.get_stats()
|
|
103
|
+
else:
|
|
104
|
+
data = {"error": "Discovery not available"}
|
|
105
|
+
self._send_json(data)
|
|
106
|
+
|
|
107
|
+
def _serve_health(self):
|
|
108
|
+
"""Serve health check."""
|
|
109
|
+
self._send_json({"status": "healthy", "timestamp": datetime.now().isoformat()})
|
|
110
|
+
|
|
111
|
+
def _serve_workflows(self):
|
|
112
|
+
"""Serve workflow stats as JSON."""
|
|
113
|
+
if HAS_WORKFLOWS and get_workflow_stats is not None:
|
|
114
|
+
data = get_workflow_stats()
|
|
115
|
+
# Add available workflows
|
|
116
|
+
if list_workflows is not None:
|
|
117
|
+
data["available_workflows"] = list_workflows()
|
|
118
|
+
else:
|
|
119
|
+
data = {"error": "Workflows not available"}
|
|
120
|
+
self._send_json(data)
|
|
121
|
+
|
|
122
|
+
def _send_json(self, data):
|
|
123
|
+
"""Send JSON response."""
|
|
124
|
+
content = json.dumps(data, indent=2, default=str)
|
|
125
|
+
self.send_response(200)
|
|
126
|
+
self.send_header("Content-type", "application/json")
|
|
127
|
+
self.send_header("Content-Length", len(content))
|
|
128
|
+
self.end_headers()
|
|
129
|
+
self.wfile.write(content.encode())
|
|
130
|
+
|
|
131
|
+
def _load_patterns(self) -> dict:
|
|
132
|
+
"""Load patterns from disk."""
|
|
133
|
+
patterns = {
|
|
134
|
+
"debugging": [],
|
|
135
|
+
"security": [],
|
|
136
|
+
"tech_debt": {"snapshots": []},
|
|
137
|
+
"inspection": [],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
patterns_path = Path(self.patterns_dir)
|
|
141
|
+
if not patterns_path.exists():
|
|
142
|
+
return patterns
|
|
143
|
+
|
|
144
|
+
for name in ["debugging", "security", "tech_debt", "inspection"]:
|
|
145
|
+
file_path = patterns_path / f"{name}.json"
|
|
146
|
+
if file_path.exists():
|
|
147
|
+
try:
|
|
148
|
+
with open(file_path) as f:
|
|
149
|
+
data = json.load(f)
|
|
150
|
+
patterns[name] = data
|
|
151
|
+
except (OSError, json.JSONDecodeError):
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
return patterns
|
|
155
|
+
|
|
156
|
+
def _generate_dashboard_html(self) -> str:
|
|
157
|
+
"""Generate the dashboard HTML page."""
|
|
158
|
+
patterns = self._load_patterns()
|
|
159
|
+
|
|
160
|
+
# Count patterns (handle both dict and list formats)
|
|
161
|
+
debugging_data = patterns.get("debugging", {})
|
|
162
|
+
if isinstance(debugging_data, dict):
|
|
163
|
+
bug_count = len(debugging_data.get("patterns", []))
|
|
164
|
+
else:
|
|
165
|
+
bug_count = len(debugging_data) if isinstance(debugging_data, list) else 0
|
|
166
|
+
|
|
167
|
+
security_data = patterns.get("security", {})
|
|
168
|
+
if isinstance(security_data, dict):
|
|
169
|
+
security_count = len(security_data.get("decisions", []))
|
|
170
|
+
else:
|
|
171
|
+
security_count = len(security_data) if isinstance(security_data, list) else 0
|
|
172
|
+
|
|
173
|
+
debt_items = 0
|
|
174
|
+
tech_debt_data = patterns.get("tech_debt", {})
|
|
175
|
+
if isinstance(tech_debt_data, dict):
|
|
176
|
+
snapshots = tech_debt_data.get("snapshots", [])
|
|
177
|
+
if snapshots:
|
|
178
|
+
debt_items = snapshots[-1].get("total_items", 0)
|
|
179
|
+
|
|
180
|
+
# Get cost summary
|
|
181
|
+
cost_summary = {"savings": 0, "savings_percent": 0, "requests": 0}
|
|
182
|
+
if CostTracker is not None:
|
|
183
|
+
try:
|
|
184
|
+
tracker = CostTracker(self.empathy_dir)
|
|
185
|
+
cost_summary = tracker.get_summary(30) # noqa: F841
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
# Get workflow stats
|
|
190
|
+
workflow_stats = {
|
|
191
|
+
"total_runs": 0,
|
|
192
|
+
"by_workflow": {},
|
|
193
|
+
"by_tier": {"cheap": 0, "capable": 0, "premium": 0},
|
|
194
|
+
"recent_runs": [],
|
|
195
|
+
"total_savings": 0.0,
|
|
196
|
+
"avg_savings_percent": 0.0,
|
|
197
|
+
}
|
|
198
|
+
if HAS_WORKFLOWS and get_workflow_stats is not None:
|
|
199
|
+
try:
|
|
200
|
+
workflow_stats = get_workflow_stats()
|
|
201
|
+
except Exception:
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
return f"""<!DOCTYPE html>
|
|
205
|
+
<html lang="en">
|
|
206
|
+
<head>
|
|
207
|
+
<meta charset="UTF-8">
|
|
208
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
209
|
+
<title>Empathy Framework Dashboard</title>
|
|
210
|
+
<style>
|
|
211
|
+
:root {{
|
|
212
|
+
--primary: #4f46e5;
|
|
213
|
+
--success: #10b981;
|
|
214
|
+
--warning: #f59e0b;
|
|
215
|
+
--danger: #ef4444;
|
|
216
|
+
--bg: #f3f4f6;
|
|
217
|
+
--card-bg: #ffffff;
|
|
218
|
+
--text: #1f2937;
|
|
219
|
+
--text-muted: #6b7280;
|
|
220
|
+
}}
|
|
221
|
+
|
|
222
|
+
* {{
|
|
223
|
+
margin: 0;
|
|
224
|
+
padding: 0;
|
|
225
|
+
box-sizing: border-box;
|
|
226
|
+
}}
|
|
227
|
+
|
|
228
|
+
body {{
|
|
229
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
230
|
+
background: var(--bg);
|
|
231
|
+
color: var(--text);
|
|
232
|
+
line-height: 1.6;
|
|
233
|
+
}}
|
|
234
|
+
|
|
235
|
+
.container {{
|
|
236
|
+
max-width: 1200px;
|
|
237
|
+
margin: 0 auto;
|
|
238
|
+
padding: 2rem;
|
|
239
|
+
}}
|
|
240
|
+
|
|
241
|
+
header {{
|
|
242
|
+
text-align: center;
|
|
243
|
+
margin-bottom: 2rem;
|
|
244
|
+
}}
|
|
245
|
+
|
|
246
|
+
h1 {{
|
|
247
|
+
color: var(--primary);
|
|
248
|
+
font-size: 2rem;
|
|
249
|
+
margin-bottom: 0.5rem;
|
|
250
|
+
}}
|
|
251
|
+
|
|
252
|
+
.subtitle {{
|
|
253
|
+
color: var(--text-muted);
|
|
254
|
+
}}
|
|
255
|
+
|
|
256
|
+
.grid {{
|
|
257
|
+
display: grid;
|
|
258
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
259
|
+
gap: 1.5rem;
|
|
260
|
+
margin-bottom: 2rem;
|
|
261
|
+
}}
|
|
262
|
+
|
|
263
|
+
.card {{
|
|
264
|
+
background: var(--card-bg);
|
|
265
|
+
border-radius: 12px;
|
|
266
|
+
padding: 1.5rem;
|
|
267
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
268
|
+
}}
|
|
269
|
+
|
|
270
|
+
.card h2 {{
|
|
271
|
+
font-size: 0.875rem;
|
|
272
|
+
color: var(--text-muted);
|
|
273
|
+
text-transform: uppercase;
|
|
274
|
+
letter-spacing: 0.05em;
|
|
275
|
+
margin-bottom: 0.5rem;
|
|
276
|
+
}}
|
|
277
|
+
|
|
278
|
+
.card .value {{
|
|
279
|
+
font-size: 2.5rem;
|
|
280
|
+
font-weight: 700;
|
|
281
|
+
color: var(--text);
|
|
282
|
+
}}
|
|
283
|
+
|
|
284
|
+
.card .label {{
|
|
285
|
+
color: var(--text-muted);
|
|
286
|
+
font-size: 0.875rem;
|
|
287
|
+
}}
|
|
288
|
+
|
|
289
|
+
.card.success .value {{
|
|
290
|
+
color: var(--success);
|
|
291
|
+
}}
|
|
292
|
+
|
|
293
|
+
.card.warning .value {{
|
|
294
|
+
color: var(--warning);
|
|
295
|
+
}}
|
|
296
|
+
|
|
297
|
+
.section {{
|
|
298
|
+
background: var(--card-bg);
|
|
299
|
+
border-radius: 12px;
|
|
300
|
+
padding: 1.5rem;
|
|
301
|
+
margin-bottom: 1.5rem;
|
|
302
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
303
|
+
}}
|
|
304
|
+
|
|
305
|
+
.section h2 {{
|
|
306
|
+
font-size: 1.25rem;
|
|
307
|
+
margin-bottom: 1rem;
|
|
308
|
+
color: var(--text);
|
|
309
|
+
}}
|
|
310
|
+
|
|
311
|
+
table {{
|
|
312
|
+
width: 100%;
|
|
313
|
+
border-collapse: collapse;
|
|
314
|
+
}}
|
|
315
|
+
|
|
316
|
+
th, td {{
|
|
317
|
+
text-align: left;
|
|
318
|
+
padding: 0.75rem;
|
|
319
|
+
border-bottom: 1px solid var(--bg);
|
|
320
|
+
}}
|
|
321
|
+
|
|
322
|
+
th {{
|
|
323
|
+
color: var(--text-muted);
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
font-size: 0.875rem;
|
|
326
|
+
}}
|
|
327
|
+
|
|
328
|
+
.status {{
|
|
329
|
+
display: inline-block;
|
|
330
|
+
padding: 0.25rem 0.75rem;
|
|
331
|
+
border-radius: 9999px;
|
|
332
|
+
font-size: 0.75rem;
|
|
333
|
+
font-weight: 500;
|
|
334
|
+
}}
|
|
335
|
+
|
|
336
|
+
.status.resolved {{
|
|
337
|
+
background: #d1fae5;
|
|
338
|
+
color: #065f46;
|
|
339
|
+
}}
|
|
340
|
+
|
|
341
|
+
.status.investigating {{
|
|
342
|
+
background: #fef3c7;
|
|
343
|
+
color: #92400e;
|
|
344
|
+
}}
|
|
345
|
+
|
|
346
|
+
.commands {{
|
|
347
|
+
display: flex;
|
|
348
|
+
flex-wrap: wrap;
|
|
349
|
+
gap: 0.5rem;
|
|
350
|
+
margin-top: 1rem;
|
|
351
|
+
}}
|
|
352
|
+
|
|
353
|
+
.command {{
|
|
354
|
+
background: var(--bg);
|
|
355
|
+
padding: 0.5rem 1rem;
|
|
356
|
+
border-radius: 6px;
|
|
357
|
+
font-family: monospace;
|
|
358
|
+
font-size: 0.875rem;
|
|
359
|
+
}}
|
|
360
|
+
|
|
361
|
+
.workflow-grid {{
|
|
362
|
+
display: grid;
|
|
363
|
+
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
364
|
+
gap: 1rem;
|
|
365
|
+
margin-bottom: 1.5rem;
|
|
366
|
+
}}
|
|
367
|
+
|
|
368
|
+
.workflow-card {{
|
|
369
|
+
background: var(--bg);
|
|
370
|
+
border-radius: 8px;
|
|
371
|
+
padding: 1rem;
|
|
372
|
+
text-align: center;
|
|
373
|
+
}}
|
|
374
|
+
|
|
375
|
+
.workflow-card h3 {{
|
|
376
|
+
font-size: 0.875rem;
|
|
377
|
+
color: var(--text);
|
|
378
|
+
margin-bottom: 0.5rem;
|
|
379
|
+
}}
|
|
380
|
+
|
|
381
|
+
.workflow-card .runs {{
|
|
382
|
+
font-size: 1.5rem;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
color: var(--primary);
|
|
385
|
+
}}
|
|
386
|
+
|
|
387
|
+
.workflow-card .savings {{
|
|
388
|
+
font-size: 0.875rem;
|
|
389
|
+
color: var(--success);
|
|
390
|
+
}}
|
|
391
|
+
|
|
392
|
+
.tier-bar {{
|
|
393
|
+
display: flex;
|
|
394
|
+
height: 24px;
|
|
395
|
+
border-radius: 4px;
|
|
396
|
+
overflow: hidden;
|
|
397
|
+
margin: 1rem 0;
|
|
398
|
+
}}
|
|
399
|
+
|
|
400
|
+
.tier-bar .tier {{
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
justify-content: center;
|
|
404
|
+
font-size: 0.75rem;
|
|
405
|
+
font-weight: 500;
|
|
406
|
+
color: white;
|
|
407
|
+
}}
|
|
408
|
+
|
|
409
|
+
.tier-bar .cheap {{ background: #10b981; }}
|
|
410
|
+
.tier-bar .capable {{ background: #3b82f6; }}
|
|
411
|
+
.tier-bar .premium {{ background: #8b5cf6; }}
|
|
412
|
+
|
|
413
|
+
.recent-run {{
|
|
414
|
+
display: flex;
|
|
415
|
+
align-items: center;
|
|
416
|
+
gap: 1rem;
|
|
417
|
+
padding: 0.75rem 0;
|
|
418
|
+
border-bottom: 1px solid var(--bg);
|
|
419
|
+
}}
|
|
420
|
+
|
|
421
|
+
.recent-run:last-child {{
|
|
422
|
+
border-bottom: none;
|
|
423
|
+
}}
|
|
424
|
+
|
|
425
|
+
.recent-run .name {{
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
min-width: 100px;
|
|
428
|
+
}}
|
|
429
|
+
|
|
430
|
+
.recent-run .provider {{
|
|
431
|
+
font-size: 0.75rem;
|
|
432
|
+
color: var(--text-muted);
|
|
433
|
+
background: var(--bg);
|
|
434
|
+
padding: 0.125rem 0.5rem;
|
|
435
|
+
border-radius: 4px;
|
|
436
|
+
}}
|
|
437
|
+
|
|
438
|
+
.recent-run .result {{
|
|
439
|
+
margin-left: auto;
|
|
440
|
+
display: flex;
|
|
441
|
+
gap: 1rem;
|
|
442
|
+
align-items: center;
|
|
443
|
+
}}
|
|
444
|
+
|
|
445
|
+
.recent-run .savings-badge {{
|
|
446
|
+
font-size: 0.75rem;
|
|
447
|
+
color: var(--success);
|
|
448
|
+
font-weight: 500;
|
|
449
|
+
}}
|
|
450
|
+
|
|
451
|
+
.recent-run .time {{
|
|
452
|
+
font-size: 0.75rem;
|
|
453
|
+
color: var(--text-muted);
|
|
454
|
+
}}
|
|
455
|
+
|
|
456
|
+
footer {{
|
|
457
|
+
text-align: center;
|
|
458
|
+
color: var(--text-muted);
|
|
459
|
+
font-size: 0.875rem;
|
|
460
|
+
padding: 2rem 0;
|
|
461
|
+
}}
|
|
462
|
+
|
|
463
|
+
@media (prefers-color-scheme: dark) {{
|
|
464
|
+
:root {{
|
|
465
|
+
--bg: #111827;
|
|
466
|
+
--card-bg: #1f2937;
|
|
467
|
+
--text: #f9fafb;
|
|
468
|
+
--text-muted: #9ca3af;
|
|
469
|
+
}}
|
|
470
|
+
}}
|
|
471
|
+
</style>
|
|
472
|
+
</head>
|
|
473
|
+
<body>
|
|
474
|
+
<div class="container">
|
|
475
|
+
<header>
|
|
476
|
+
<h1>Empathy Framework Dashboard</h1>
|
|
477
|
+
<p class="subtitle">Pattern learning and cost optimization at a glance</p>
|
|
478
|
+
</header>
|
|
479
|
+
|
|
480
|
+
<div class="grid">
|
|
481
|
+
<div class="card">
|
|
482
|
+
<h2>Bug Patterns</h2>
|
|
483
|
+
<div class="value">{bug_count}</div>
|
|
484
|
+
<div class="label">patterns learned</div>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<div class="card">
|
|
488
|
+
<h2>Security Decisions</h2>
|
|
489
|
+
<div class="value">{security_count}</div>
|
|
490
|
+
<div class="label">documented</div>
|
|
491
|
+
</div>
|
|
492
|
+
|
|
493
|
+
<div class="card warning">
|
|
494
|
+
<h2>Tech Debt Items</h2>
|
|
495
|
+
<div class="value">{debt_items}</div>
|
|
496
|
+
<div class="label">tracked</div>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div class="card">
|
|
500
|
+
<h2>Workflow Runs</h2>
|
|
501
|
+
<div class="value">{workflow_stats.get("total_runs", 0)}</div>
|
|
502
|
+
<div class="label">{workflow_stats.get("avg_savings_percent", 0):.0f}% savings</div>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div class="card success">
|
|
506
|
+
<h2>Total Savings</h2>
|
|
507
|
+
<div class="value">${workflow_stats.get("total_savings", 0):.2f}</div>
|
|
508
|
+
<div class="label">workflows + API</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<div class="section">
|
|
513
|
+
<h2>Recent Bug Patterns</h2>
|
|
514
|
+
<table>
|
|
515
|
+
<thead>
|
|
516
|
+
<tr>
|
|
517
|
+
<th>Type</th>
|
|
518
|
+
<th>Root Cause</th>
|
|
519
|
+
<th>Status</th>
|
|
520
|
+
<th>Resolved</th>
|
|
521
|
+
</tr>
|
|
522
|
+
</thead>
|
|
523
|
+
<tbody>
|
|
524
|
+
{self._render_bug_table(patterns)}
|
|
525
|
+
</tbody>
|
|
526
|
+
</table>
|
|
527
|
+
</div>
|
|
528
|
+
|
|
529
|
+
<div class="section">
|
|
530
|
+
<h2>Multi-Model Workflows</h2>
|
|
531
|
+
<div class="workflow-grid">
|
|
532
|
+
{self._render_workflow_cards(workflow_stats)}
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<h3 style="margin-bottom: 0.5rem; font-size: 1rem;">Model Tier Usage</h3>
|
|
536
|
+
{self._render_tier_bar(workflow_stats)}
|
|
537
|
+
|
|
538
|
+
<h3 style="margin-top: 1.5rem; margin-bottom: 0.5rem; font-size: 1rem;">Recent Runs</h3>
|
|
539
|
+
{self._render_recent_runs(workflow_stats)}
|
|
540
|
+
</div>
|
|
541
|
+
|
|
542
|
+
<div class="section">
|
|
543
|
+
<h2>Quick Commands</h2>
|
|
544
|
+
<p>Run these commands for common tasks:</p>
|
|
545
|
+
<div class="commands">
|
|
546
|
+
<span class="command">empathy morning</span>
|
|
547
|
+
<span class="command">empathy ship</span>
|
|
548
|
+
<span class="command">empathy fix-all</span>
|
|
549
|
+
<span class="command">empathy learn</span>
|
|
550
|
+
<span class="command">empathy sync-claude</span>
|
|
551
|
+
<span class="command">empathy costs</span>
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
|
|
555
|
+
<footer>
|
|
556
|
+
<p>Empathy Framework Dashboard - Refresh to update data</p>
|
|
557
|
+
<p>Last updated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
|
|
558
|
+
</footer>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
<script>
|
|
562
|
+
// Auto-refresh every 30 seconds
|
|
563
|
+
setTimeout(() => location.reload(), 30000);
|
|
564
|
+
</script>
|
|
565
|
+
</body>
|
|
566
|
+
</html>"""
|
|
567
|
+
|
|
568
|
+
def _render_bug_table(self, patterns: dict) -> str:
|
|
569
|
+
"""Render bug patterns as table rows."""
|
|
570
|
+
debugging_data = patterns.get("debugging", {})
|
|
571
|
+
if isinstance(debugging_data, dict):
|
|
572
|
+
bugs = debugging_data.get("patterns", [])
|
|
573
|
+
elif isinstance(debugging_data, list):
|
|
574
|
+
bugs = debugging_data
|
|
575
|
+
else:
|
|
576
|
+
bugs = []
|
|
577
|
+
if not bugs:
|
|
578
|
+
return (
|
|
579
|
+
'<tr><td colspan="4">No patterns yet. Run "empathy learn" to get started.</td></tr>'
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
rows = []
|
|
583
|
+
for bug in bugs[-10:]: # Last 10
|
|
584
|
+
status_class = bug.get("status", "investigating")
|
|
585
|
+
root = bug.get("root_cause", "")
|
|
586
|
+
root_display = (root[:60] + "...") if len(root) > 60 else (root or "-")
|
|
587
|
+
resolved = bug.get("resolved_at") or bug.get("timestamp")
|
|
588
|
+
date_display = resolved[:10] if resolved else "-"
|
|
589
|
+
rows.append(
|
|
590
|
+
f"""
|
|
591
|
+
<tr>
|
|
592
|
+
<td>{bug.get("bug_type", "unknown")}</td>
|
|
593
|
+
<td>{root_display}</td>
|
|
594
|
+
<td><span class="status {status_class}">{status_class}</span></td>
|
|
595
|
+
<td>{date_display}</td>
|
|
596
|
+
</tr>
|
|
597
|
+
""",
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
return "".join(rows)
|
|
601
|
+
|
|
602
|
+
def _render_workflow_cards(self, workflow_stats: dict) -> str:
|
|
603
|
+
"""Render workflow stat cards."""
|
|
604
|
+
by_workflow = workflow_stats.get("by_workflow", {})
|
|
605
|
+
|
|
606
|
+
if not by_workflow:
|
|
607
|
+
return '<div class="workflow-card"><p>No workflow runs yet.</p></div>'
|
|
608
|
+
|
|
609
|
+
cards = []
|
|
610
|
+
for name, stats in by_workflow.items():
|
|
611
|
+
runs = stats.get("runs", 0)
|
|
612
|
+
savings = stats.get("savings", 0)
|
|
613
|
+
cards.append(
|
|
614
|
+
f"""
|
|
615
|
+
<div class="workflow-card">
|
|
616
|
+
<h3>{name}</h3>
|
|
617
|
+
<div class="runs">{runs}</div>
|
|
618
|
+
<div class="savings">${savings:.4f} saved</div>
|
|
619
|
+
</div>
|
|
620
|
+
""",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
return "".join(cards)
|
|
624
|
+
|
|
625
|
+
def _render_tier_bar(self, workflow_stats: dict) -> str:
|
|
626
|
+
"""Render model tier usage bar."""
|
|
627
|
+
by_tier = workflow_stats.get("by_tier", {})
|
|
628
|
+
total = sum(by_tier.values())
|
|
629
|
+
|
|
630
|
+
if total == 0:
|
|
631
|
+
return '<div class="tier-bar"><div class="tier">No data yet</div></div>'
|
|
632
|
+
|
|
633
|
+
cheap_pct = (by_tier.get("cheap", 0) / total) * 100 if total > 0 else 0
|
|
634
|
+
capable_pct = (by_tier.get("capable", 0) / total) * 100 if total > 0 else 0
|
|
635
|
+
premium_pct = (by_tier.get("premium", 0) / total) * 100 if total > 0 else 0
|
|
636
|
+
|
|
637
|
+
return f"""
|
|
638
|
+
<div class="tier-bar">
|
|
639
|
+
<div class="tier cheap" style="width: {cheap_pct}%;">{cheap_pct:.0f}% cheap</div>
|
|
640
|
+
<div class="tier capable" style="width: {capable_pct}%;">{capable_pct:.0f}% capable</div>
|
|
641
|
+
<div class="tier premium" style="width: {premium_pct}%;">{premium_pct:.0f}% premium</div>
|
|
642
|
+
</div>
|
|
643
|
+
<div style="display: flex; gap: 1rem; font-size: 0.75rem; color: var(--text-muted);">
|
|
644
|
+
<span>Cheap (Haiku/GPT-mini): ${by_tier.get("cheap", 0):.4f}</span>
|
|
645
|
+
<span>Capable (Sonnet/GPT-4o): ${by_tier.get("capable", 0):.4f}</span>
|
|
646
|
+
<span>Premium (Opus/GPT-5): ${by_tier.get("premium", 0):.4f}</span>
|
|
647
|
+
</div>
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
def _render_recent_runs(self, workflow_stats: dict) -> str:
|
|
651
|
+
"""Render recent workflow runs."""
|
|
652
|
+
recent_runs = workflow_stats.get("recent_runs", [])
|
|
653
|
+
|
|
654
|
+
if not recent_runs:
|
|
655
|
+
return '<p style="color: var(--text-muted);">No recent workflow runs.</p>'
|
|
656
|
+
|
|
657
|
+
runs_html = []
|
|
658
|
+
for run in recent_runs[:5]: # Show last 5
|
|
659
|
+
name = run.get("workflow", "unknown")
|
|
660
|
+
provider = run.get("provider", "unknown")
|
|
661
|
+
success = run.get("success", False)
|
|
662
|
+
savings_pct = run.get("savings_percent", 0)
|
|
663
|
+
started = run.get("started_at", "")
|
|
664
|
+
|
|
665
|
+
# Format time
|
|
666
|
+
time_str = started[:16].replace("T", " ") if started else "-"
|
|
667
|
+
|
|
668
|
+
status_icon = "✓" if success else "✗"
|
|
669
|
+
status_color = "var(--success)" if success else "var(--danger)"
|
|
670
|
+
|
|
671
|
+
runs_html.append(
|
|
672
|
+
f"""
|
|
673
|
+
<div class="recent-run">
|
|
674
|
+
<span style="color: {status_color};">{status_icon}</span>
|
|
675
|
+
<span class="name">{name}</span>
|
|
676
|
+
<span class="provider">{provider}</span>
|
|
677
|
+
<span class="result">
|
|
678
|
+
<span class="savings-badge">{savings_pct:.0f}% saved</span>
|
|
679
|
+
<span class="time">{time_str}</span>
|
|
680
|
+
</span>
|
|
681
|
+
</div>
|
|
682
|
+
""",
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
return "".join(runs_html)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class ThreadedHTTPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
|
689
|
+
"""Threaded HTTP server."""
|
|
690
|
+
|
|
691
|
+
allow_reuse_address = True
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def run_dashboard(
|
|
695
|
+
port: int = 8765,
|
|
696
|
+
patterns_dir: str = "./patterns",
|
|
697
|
+
empathy_dir: str = ".empathy",
|
|
698
|
+
open_browser: bool = True,
|
|
699
|
+
) -> None:
|
|
700
|
+
"""Run the dashboard server.
|
|
701
|
+
|
|
702
|
+
Args:
|
|
703
|
+
port: Port to run on (default: 8765)
|
|
704
|
+
patterns_dir: Path to patterns directory
|
|
705
|
+
empathy_dir: Path to empathy data directory
|
|
706
|
+
open_browser: Open browser automatically
|
|
707
|
+
|
|
708
|
+
"""
|
|
709
|
+
# Configure handler
|
|
710
|
+
DashboardHandler.patterns_dir = patterns_dir
|
|
711
|
+
DashboardHandler.empathy_dir = empathy_dir
|
|
712
|
+
|
|
713
|
+
url = f"http://localhost:{port}"
|
|
714
|
+
|
|
715
|
+
print("\n Empathy Dashboard")
|
|
716
|
+
print(f" Running at: {url}")
|
|
717
|
+
print(" Press Ctrl+C to stop\n")
|
|
718
|
+
|
|
719
|
+
if open_browser:
|
|
720
|
+
# Open browser in a separate thread to not block server start
|
|
721
|
+
threading.Timer(0.5, lambda: webbrowser.open(url)).start()
|
|
722
|
+
|
|
723
|
+
try:
|
|
724
|
+
with ThreadedHTTPServer(("", port), DashboardHandler) as httpd:
|
|
725
|
+
httpd.serve_forever()
|
|
726
|
+
except KeyboardInterrupt:
|
|
727
|
+
print("\n Dashboard stopped.")
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def cmd_dashboard(args):
|
|
731
|
+
"""CLI command handler for dashboard."""
|
|
732
|
+
port = getattr(args, "port", 8765)
|
|
733
|
+
patterns_dir = getattr(args, "patterns_dir", "./patterns")
|
|
734
|
+
empathy_dir = getattr(args, "empathy_dir", ".empathy")
|
|
735
|
+
no_browser = getattr(args, "no_browser", False)
|
|
736
|
+
|
|
737
|
+
run_dashboard(
|
|
738
|
+
port=port,
|
|
739
|
+
patterns_dir=patterns_dir,
|
|
740
|
+
empathy_dir=empathy_dir,
|
|
741
|
+
open_browser=not no_browser,
|
|
742
|
+
)
|
|
743
|
+
return 0
|