htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/dashboard.html
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>HtmlGraph Dashboard</title>
|
|
7
|
-
<!--
|
|
8
|
-
<script src="https://
|
|
9
|
-
<
|
|
10
|
-
<script src="https://d3js.org/d3-timer.v3.min.js"></script>
|
|
11
|
-
<script src="https://d3js.org/d3-force.v3.min.js"></script>
|
|
7
|
+
<!-- Vis.js for graph visualization -->
|
|
8
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
9
|
+
<link href="https://unpkg.com/vis-network/styles/vis-network.min.css" rel="stylesheet" type="text/css" />
|
|
12
10
|
<!-- Typography: JetBrains Mono + Outfit -->
|
|
13
11
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
14
12
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
@@ -106,9 +104,11 @@
|
|
|
106
104
|
background: var(--bg-primary);
|
|
107
105
|
color: var(--text-primary);
|
|
108
106
|
line-height: 1.5;
|
|
109
|
-
|
|
107
|
+
height: 100vh;
|
|
110
108
|
position: relative;
|
|
111
|
-
overflow
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/* Subtle grain texture overlay */
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
max-width: 1440px;
|
|
127
127
|
margin: 0 auto;
|
|
128
128
|
padding: 2rem;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
flex: 1;
|
|
132
|
+
min-height: 0;
|
|
133
|
+
width: 100%;
|
|
134
|
+
overflow: hidden;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
/* ================================================================
|
|
@@ -211,6 +217,7 @@
|
|
|
211
217
|
margin-bottom: 1rem;
|
|
212
218
|
border: 2px solid var(--border-strong);
|
|
213
219
|
width: fit-content;
|
|
220
|
+
flex-shrink: 0;
|
|
214
221
|
}
|
|
215
222
|
|
|
216
223
|
.view-btn {
|
|
@@ -252,8 +259,11 @@
|
|
|
252
259
|
}
|
|
253
260
|
|
|
254
261
|
.kanban.active {
|
|
255
|
-
display:
|
|
262
|
+
display: grid;
|
|
256
263
|
width: 100%;
|
|
264
|
+
flex: 1;
|
|
265
|
+
min-height: 0;
|
|
266
|
+
overflow: auto;
|
|
257
267
|
}
|
|
258
268
|
|
|
259
269
|
/* Dynamic grid: expanded columns grow, collapsed stay fixed */
|
|
@@ -871,6 +881,53 @@
|
|
|
871
881
|
.badge.priority-high { background: var(--priority-high); color: white; border-color: var(--priority-high); }
|
|
872
882
|
.badge.type { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
|
|
873
883
|
|
|
884
|
+
/* Agent attribution badges - color-coded by agent type */
|
|
885
|
+
.badge.agent {
|
|
886
|
+
padding: 0.375rem 0.625rem;
|
|
887
|
+
font-weight: 600;
|
|
888
|
+
display: inline-flex;
|
|
889
|
+
align-items: center;
|
|
890
|
+
gap: 0.3rem;
|
|
891
|
+
position: relative;
|
|
892
|
+
transition: all 0.2s ease;
|
|
893
|
+
cursor: help;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.badge.agent::before {
|
|
897
|
+
content: '';
|
|
898
|
+
display: inline-block;
|
|
899
|
+
width: 0.5rem;
|
|
900
|
+
height: 0.5rem;
|
|
901
|
+
border-radius: 50%;
|
|
902
|
+
background: currentColor;
|
|
903
|
+
opacity: 0.8;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.badge.agent:hover {
|
|
907
|
+
transform: translateY(-2px);
|
|
908
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/* Primary agents - Requested color system */
|
|
912
|
+
.badge.agent-claude { background: #2979FF; color: white; border-color: #2979FF; }
|
|
913
|
+
.badge.agent-codex { background: #00C853; color: white; border-color: #00C853; }
|
|
914
|
+
.badge.agent-orchestrator { background: #7C4DFF; color: white; border-color: #7C4DFF; }
|
|
915
|
+
.badge.agent-gemini { background: #FBC02D; color: #000; border-color: #FBC02D; }
|
|
916
|
+
.badge.agent-gemini-2 { background: #FF9100; color: white; border-color: #FF9100; }
|
|
917
|
+
|
|
918
|
+
/* Secondary agents - Backward compatibility */
|
|
919
|
+
.badge.agent-analyst { background: #7C3AED; color: white; border-color: #7C3AED; }
|
|
920
|
+
.badge.agent-developer { background: #00C853; color: white; border-color: #00C853; }
|
|
921
|
+
.badge.agent-researcher { background: #FF6D00; color: white; border-color: #FF6D00; }
|
|
922
|
+
.badge.agent-debugger { background: #E91E63; color: white; border-color: #E91E63; }
|
|
923
|
+
.badge.agent-default { background: #78909C; color: white; border-color: #78909C; }
|
|
924
|
+
|
|
925
|
+
/* Delegation badges */
|
|
926
|
+
.badge.delegation { padding: 0.25rem 0.5rem; font-size: 0.55rem; }
|
|
927
|
+
.badge.delegation-external { background: #00C853; color: white; }
|
|
928
|
+
.badge.delegation-fallback { background: #FF9100; color: white; }
|
|
929
|
+
.badge.delegation-direct { background: #2979FF; color: white; }
|
|
930
|
+
|
|
874
931
|
.card-path {
|
|
875
932
|
font-family: 'JetBrains Mono', monospace;
|
|
876
933
|
font-size: 0.625rem;
|
|
@@ -895,18 +952,127 @@
|
|
|
895
952
|
background: var(--bg-secondary);
|
|
896
953
|
border: 2px solid var(--border-strong);
|
|
897
954
|
box-shadow: var(--shadow-md);
|
|
955
|
+
flex-direction: column;
|
|
956
|
+
flex: 1;
|
|
957
|
+
min-height: 0;
|
|
958
|
+
overflow: hidden;
|
|
898
959
|
}
|
|
899
960
|
|
|
900
961
|
.graph-container.active {
|
|
901
|
-
display:
|
|
962
|
+
display: flex;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/* Graph Controls */
|
|
966
|
+
.graph-controls {
|
|
967
|
+
display: flex;
|
|
968
|
+
gap: 1.5rem;
|
|
969
|
+
align-items: center;
|
|
970
|
+
padding: 1rem;
|
|
971
|
+
border-bottom: 2px solid var(--border-strong);
|
|
972
|
+
background: var(--bg-tertiary);
|
|
973
|
+
flex-wrap: wrap;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
.graph-control-group {
|
|
977
|
+
display: flex;
|
|
978
|
+
align-items: center;
|
|
979
|
+
gap: 0.75rem;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
.graph-filter-label {
|
|
983
|
+
font-family: 'JetBrains Mono', monospace;
|
|
984
|
+
font-size: 0.75rem;
|
|
985
|
+
text-transform: uppercase;
|
|
986
|
+
letter-spacing: 0.1em;
|
|
987
|
+
color: var(--text-muted);
|
|
988
|
+
font-weight: 600;
|
|
989
|
+
white-space: nowrap;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.graph-filters {
|
|
993
|
+
display: flex;
|
|
994
|
+
gap: 1rem;
|
|
995
|
+
flex-wrap: wrap;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
.graph-filter-checkbox {
|
|
999
|
+
display: flex;
|
|
1000
|
+
align-items: center;
|
|
1001
|
+
gap: 0.4rem;
|
|
1002
|
+
cursor: pointer;
|
|
1003
|
+
font-size: 0.875rem;
|
|
1004
|
+
user-select: none;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.graph-filter-checkbox input {
|
|
1008
|
+
cursor: pointer;
|
|
1009
|
+
width: 16px;
|
|
1010
|
+
height: 16px;
|
|
1011
|
+
accent-color: var(--accent);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
.graph-search {
|
|
1015
|
+
padding: 0.5rem 0.75rem;
|
|
1016
|
+
border: 2px solid var(--border-strong);
|
|
1017
|
+
background: var(--bg-secondary);
|
|
1018
|
+
color: var(--text-primary);
|
|
1019
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1020
|
+
font-size: 0.875rem;
|
|
1021
|
+
border-radius: 0;
|
|
1022
|
+
min-width: 150px;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.graph-search::placeholder {
|
|
1026
|
+
color: var(--text-muted);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.graph-search:focus {
|
|
1030
|
+
outline: none;
|
|
1031
|
+
box-shadow: inset 0 0 0 2px var(--accent);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
.graph-control-buttons {
|
|
1035
|
+
margin-left: auto;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/* Graph Viewport */
|
|
1039
|
+
.graph-viewport {
|
|
1040
|
+
flex: 1;
|
|
1041
|
+
min-height: 0;
|
|
1042
|
+
position: relative;
|
|
1043
|
+
overflow: hidden;
|
|
1044
|
+
background: var(--bg-secondary);
|
|
1045
|
+
width: 100%;
|
|
902
1046
|
}
|
|
903
1047
|
|
|
904
1048
|
.graph-svg {
|
|
905
1049
|
width: 100%;
|
|
906
|
-
height:
|
|
1050
|
+
height: 100%;
|
|
907
1051
|
display: block;
|
|
908
1052
|
}
|
|
909
1053
|
|
|
1054
|
+
/* Vis.js Network Container */
|
|
1055
|
+
.graph-network {
|
|
1056
|
+
width: 100%;
|
|
1057
|
+
height: 100%;
|
|
1058
|
+
border: none;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/* Vis.js Node Styling */
|
|
1062
|
+
.vis-network {
|
|
1063
|
+
border: none;
|
|
1064
|
+
background: var(--bg-secondary);
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/* Vis.js Label Styling */
|
|
1068
|
+
.vis-label {
|
|
1069
|
+
color: white;
|
|
1070
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1071
|
+
font-size: 12px;
|
|
1072
|
+
font-weight: 500;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/* Node Status Classes */
|
|
910
1076
|
.graph-node {
|
|
911
1077
|
cursor: pointer;
|
|
912
1078
|
}
|
|
@@ -915,10 +1081,39 @@
|
|
|
915
1081
|
stroke: var(--border-strong);
|
|
916
1082
|
stroke-width: 2;
|
|
917
1083
|
transition: all 0.2s var(--ease-out-expo);
|
|
1084
|
+
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
|
918
1085
|
}
|
|
919
1086
|
|
|
920
1087
|
.graph-node:hover circle {
|
|
921
1088
|
stroke-width: 4;
|
|
1089
|
+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2));
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.graph-node.node-hidden {
|
|
1093
|
+
display: none;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.graph-node.node-search-highlight circle {
|
|
1097
|
+
stroke: var(--accent);
|
|
1098
|
+
stroke-width: 3;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/* Status-based sizing */
|
|
1102
|
+
.graph-node.node-done circle {
|
|
1103
|
+
opacity: 0.6;
|
|
1104
|
+
r: 15;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.graph-node.node-in-progress circle {
|
|
1108
|
+
r: 25;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.graph-node.node-todo circle {
|
|
1112
|
+
r: 22;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.graph-node.node-blocked circle {
|
|
1116
|
+
r: 22;
|
|
922
1117
|
}
|
|
923
1118
|
|
|
924
1119
|
.graph-node text {
|
|
@@ -932,8 +1127,9 @@
|
|
|
932
1127
|
|
|
933
1128
|
.graph-edge {
|
|
934
1129
|
stroke: var(--border);
|
|
935
|
-
stroke-width:
|
|
1130
|
+
stroke-width: 1.5;
|
|
936
1131
|
fill: none;
|
|
1132
|
+
transition: stroke-width 0.2s var(--ease-out-expo);
|
|
937
1133
|
}
|
|
938
1134
|
|
|
939
1135
|
.graph-edge.blocked_by {
|
|
@@ -945,17 +1141,47 @@
|
|
|
945
1141
|
stroke: var(--status-active);
|
|
946
1142
|
}
|
|
947
1143
|
|
|
1144
|
+
.graph-edge.hidden {
|
|
1145
|
+
display: none;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
948
1148
|
.graph-arrowhead {
|
|
949
1149
|
fill: var(--border);
|
|
950
1150
|
}
|
|
951
1151
|
|
|
952
|
-
.graph-
|
|
1152
|
+
.graph-edge.blocked_by .graph-arrowhead {
|
|
1153
|
+
fill: var(--status-blocked);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.graph-edge.related .graph-arrowhead {
|
|
1157
|
+
fill: var(--status-active);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/* Graph Footer */
|
|
1161
|
+
.graph-footer {
|
|
953
1162
|
display: flex;
|
|
954
|
-
|
|
955
|
-
|
|
1163
|
+
justify-content: space-between;
|
|
1164
|
+
align-items: center;
|
|
956
1165
|
padding: 1rem;
|
|
957
1166
|
border-top: 2px solid var(--border-strong);
|
|
958
1167
|
background: var(--bg-tertiary);
|
|
1168
|
+
flex-wrap: wrap;
|
|
1169
|
+
gap: 1rem;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.graph-stats {
|
|
1173
|
+
display: flex;
|
|
1174
|
+
gap: 1.5rem;
|
|
1175
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1176
|
+
font-size: 0.875rem;
|
|
1177
|
+
color: var(--text-secondary);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.graph-legend {
|
|
1181
|
+
display: flex;
|
|
1182
|
+
gap: 1.5rem;
|
|
1183
|
+
justify-content: center;
|
|
1184
|
+
flex-wrap: wrap;
|
|
959
1185
|
}
|
|
960
1186
|
|
|
961
1187
|
.graph-legend-item {
|
|
@@ -963,20 +1189,29 @@
|
|
|
963
1189
|
align-items: center;
|
|
964
1190
|
gap: 0.5rem;
|
|
965
1191
|
font-family: 'JetBrains Mono', monospace;
|
|
966
|
-
font-size: 0.
|
|
1192
|
+
font-size: 0.75rem;
|
|
967
1193
|
text-transform: uppercase;
|
|
968
1194
|
letter-spacing: 0.1em;
|
|
969
1195
|
color: var(--text-muted);
|
|
970
1196
|
}
|
|
971
1197
|
|
|
972
1198
|
.graph-legend-item span {
|
|
973
|
-
width:
|
|
1199
|
+
width: 20px;
|
|
974
1200
|
height: 3px;
|
|
975
1201
|
}
|
|
976
1202
|
|
|
1203
|
+
.graph-legend-item span.legend-color {
|
|
1204
|
+
width: 12px;
|
|
1205
|
+
height: 12px;
|
|
1206
|
+
border-radius: 2px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.legend-done { background: var(--status-done); }
|
|
1210
|
+
.legend-active { background: var(--status-active); }
|
|
1211
|
+
.legend-todo { background: var(--status-todo); }
|
|
977
1212
|
.legend-blocked { background: var(--status-blocked); }
|
|
978
1213
|
.legend-related { background: var(--status-active); }
|
|
979
|
-
.legend-
|
|
1214
|
+
.legend-blocked-edge { background: var(--status-blocked); }
|
|
980
1215
|
|
|
981
1216
|
/* ================================================================
|
|
982
1217
|
ANALYTICS VIEW
|
|
@@ -987,7 +1222,11 @@
|
|
|
987
1222
|
}
|
|
988
1223
|
|
|
989
1224
|
.analytics.active {
|
|
990
|
-
display:
|
|
1225
|
+
display: flex;
|
|
1226
|
+
flex-direction: column;
|
|
1227
|
+
flex: 1;
|
|
1228
|
+
min-height: 0;
|
|
1229
|
+
overflow: auto;
|
|
991
1230
|
}
|
|
992
1231
|
|
|
993
1232
|
/* ================================================================
|
|
@@ -999,84 +1238,805 @@
|
|
|
999
1238
|
}
|
|
1000
1239
|
|
|
1001
1240
|
.sessions.active {
|
|
1002
|
-
display:
|
|
1241
|
+
display: flex;
|
|
1242
|
+
flex-direction: column;
|
|
1243
|
+
flex: 1;
|
|
1244
|
+
min-height: 0;
|
|
1245
|
+
overflow: auto;
|
|
1003
1246
|
}
|
|
1004
1247
|
|
|
1005
1248
|
/* ================================================================
|
|
1006
|
-
|
|
1249
|
+
AGENTS VIEW - Multi-Agent Work Attribution
|
|
1007
1250
|
================================================================ */
|
|
1008
|
-
.
|
|
1251
|
+
.agents {
|
|
1009
1252
|
display: none;
|
|
1253
|
+
margin-top: 1.5rem;
|
|
1254
|
+
padding: 0 1.5rem 1.5rem 1.5rem;
|
|
1010
1255
|
}
|
|
1011
1256
|
|
|
1012
|
-
.
|
|
1013
|
-
display:
|
|
1257
|
+
.agents.active {
|
|
1258
|
+
display: flex;
|
|
1259
|
+
flex-direction: column;
|
|
1260
|
+
flex: 1;
|
|
1261
|
+
min-height: 0;
|
|
1262
|
+
overflow: auto;
|
|
1014
1263
|
}
|
|
1015
1264
|
|
|
1016
|
-
.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
background: var(--bg-secondary);
|
|
1022
|
-
border: 2px solid var(--border-strong);
|
|
1023
|
-
box-shadow: var(--shadow-md);
|
|
1265
|
+
.agent-stats-grid {
|
|
1266
|
+
display: grid;
|
|
1267
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
1268
|
+
gap: 1rem;
|
|
1269
|
+
margin: 1rem 0;
|
|
1024
1270
|
}
|
|
1025
1271
|
|
|
1026
|
-
.
|
|
1027
|
-
|
|
1028
|
-
border
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1272
|
+
.agent-stat-card {
|
|
1273
|
+
background: var(--bg-secondary);
|
|
1274
|
+
border: 2px solid var(--border);
|
|
1275
|
+
border-radius: 8px;
|
|
1276
|
+
padding: 1.5rem;
|
|
1277
|
+
text-align: center;
|
|
1278
|
+
box-shadow: var(--shadow-sm);
|
|
1032
1279
|
}
|
|
1033
1280
|
|
|
1034
|
-
.
|
|
1281
|
+
.agent-stat-card h4 {
|
|
1035
1282
|
color: var(--text-muted);
|
|
1036
|
-
font-
|
|
1283
|
+
font-size: 0.75rem;
|
|
1037
1284
|
text-transform: uppercase;
|
|
1038
1285
|
letter-spacing: 0.08em;
|
|
1039
|
-
font-
|
|
1040
|
-
|
|
1041
|
-
border-bottom: 2px solid var(--border-strong);
|
|
1286
|
+
font-weight: 600;
|
|
1287
|
+
margin-bottom: 0.5rem;
|
|
1042
1288
|
}
|
|
1043
1289
|
|
|
1044
|
-
.
|
|
1045
|
-
|
|
1290
|
+
.agent-stat-value {
|
|
1291
|
+
font-size: 1.75rem;
|
|
1292
|
+
font-weight: 700;
|
|
1293
|
+
color: var(--text-primary);
|
|
1294
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1046
1295
|
}
|
|
1047
1296
|
|
|
1048
|
-
.
|
|
1297
|
+
.agent-stat-unit {
|
|
1298
|
+
font-size: 0.75rem;
|
|
1299
|
+
color: var(--text-muted);
|
|
1300
|
+
margin-top: 0.25rem;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/* ================================================================
|
|
1304
|
+
WORKLOAD DISTRIBUTION CHART
|
|
1305
|
+
================================================================ */
|
|
1306
|
+
.workload-chart-container {
|
|
1307
|
+
background: var(--bg-secondary);
|
|
1308
|
+
border: 2px solid var(--border);
|
|
1309
|
+
border-radius: 8px;
|
|
1310
|
+
padding: 1.5rem;
|
|
1311
|
+
box-shadow: var(--shadow-sm);
|
|
1312
|
+
margin-top: 1rem;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.workload-chart-header {
|
|
1316
|
+
margin-bottom: 1.5rem;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
.workload-chart-header h3 {
|
|
1320
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1321
|
+
font-size: 0.875rem;
|
|
1322
|
+
text-transform: uppercase;
|
|
1323
|
+
letter-spacing: 0.08em;
|
|
1324
|
+
color: var(--text-muted);
|
|
1325
|
+
margin-bottom: 0.25rem;
|
|
1049
1326
|
font-weight: 600;
|
|
1050
|
-
color: var(--status-active);
|
|
1051
|
-
cursor: pointer;
|
|
1052
|
-
text-decoration: underline;
|
|
1053
|
-
text-underline-offset: 2px;
|
|
1054
1327
|
}
|
|
1055
1328
|
|
|
1056
|
-
.
|
|
1057
|
-
color: var(--
|
|
1329
|
+
.workload-chart-header p {
|
|
1330
|
+
color: var(--text-secondary);
|
|
1331
|
+
font-size: 0.875rem;
|
|
1332
|
+
margin: 0;
|
|
1058
1333
|
}
|
|
1059
1334
|
|
|
1060
|
-
|
|
1061
|
-
.session-filters {
|
|
1335
|
+
.workload-bars {
|
|
1062
1336
|
display: flex;
|
|
1337
|
+
flex-direction: column;
|
|
1063
1338
|
gap: 1rem;
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
border: 2px solid var(--border-strong);
|
|
1067
|
-
box-shadow: var(--shadow-md);
|
|
1068
|
-
margin-bottom: 1rem;
|
|
1069
|
-
flex-wrap: wrap;
|
|
1070
|
-
align-items: flex-end;
|
|
1339
|
+
max-height: 600px;
|
|
1340
|
+
overflow-y: auto;
|
|
1071
1341
|
}
|
|
1072
1342
|
|
|
1073
|
-
.
|
|
1343
|
+
.workload-bar-group {
|
|
1074
1344
|
display: flex;
|
|
1075
1345
|
flex-direction: column;
|
|
1076
1346
|
gap: 0.375rem;
|
|
1077
1347
|
}
|
|
1078
1348
|
|
|
1079
|
-
.
|
|
1349
|
+
.workload-bar-label {
|
|
1350
|
+
display: flex;
|
|
1351
|
+
justify-content: space-between;
|
|
1352
|
+
align-items: center;
|
|
1353
|
+
font-size: 0.875rem;
|
|
1354
|
+
font-weight: 500;
|
|
1355
|
+
color: var(--text-primary);
|
|
1356
|
+
margin-bottom: 0.25rem;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.workload-bar-label-name {
|
|
1360
|
+
display: flex;
|
|
1361
|
+
align-items: center;
|
|
1362
|
+
gap: 0.5rem;
|
|
1363
|
+
flex: 1;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.workload-agent-badge {
|
|
1367
|
+
display: inline-flex;
|
|
1368
|
+
align-items: center;
|
|
1369
|
+
justify-content: center;
|
|
1370
|
+
width: 24px;
|
|
1371
|
+
height: 24px;
|
|
1372
|
+
border-radius: 50%;
|
|
1373
|
+
font-size: 0.7rem;
|
|
1374
|
+
font-weight: 600;
|
|
1375
|
+
color: white;
|
|
1376
|
+
flex-shrink: 0;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
.workload-bar-value {
|
|
1380
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1381
|
+
font-size: 0.8rem;
|
|
1382
|
+
color: var(--text-muted);
|
|
1383
|
+
white-space: nowrap;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.workload-bar-container {
|
|
1387
|
+
position: relative;
|
|
1388
|
+
width: 100%;
|
|
1389
|
+
height: 32px;
|
|
1390
|
+
background: var(--bg-tertiary);
|
|
1391
|
+
border: 1px solid var(--border);
|
|
1392
|
+
border-radius: 4px;
|
|
1393
|
+
overflow: hidden;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
.workload-bar-fill {
|
|
1397
|
+
height: 100%;
|
|
1398
|
+
display: flex;
|
|
1399
|
+
align-items: center;
|
|
1400
|
+
padding: 0 0.75rem;
|
|
1401
|
+
transition: all 0.3s var(--ease-out-expo);
|
|
1402
|
+
position: relative;
|
|
1403
|
+
justify-content: flex-start;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.workload-bar-fill::after {
|
|
1407
|
+
content: '';
|
|
1408
|
+
position: absolute;
|
|
1409
|
+
inset: 0;
|
|
1410
|
+
background: linear-gradient(90deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 100%);
|
|
1411
|
+
pointer-events: none;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
.workload-bar-text {
|
|
1415
|
+
position: absolute;
|
|
1416
|
+
right: 0.75rem;
|
|
1417
|
+
top: 50%;
|
|
1418
|
+
transform: translateY(-50%);
|
|
1419
|
+
color: white;
|
|
1420
|
+
font-size: 0.75rem;
|
|
1421
|
+
font-weight: 600;
|
|
1422
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1423
|
+
white-space: nowrap;
|
|
1424
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
1425
|
+
pointer-events: none;
|
|
1426
|
+
z-index: 2;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
.workload-bar-hover-info {
|
|
1430
|
+
position: absolute;
|
|
1431
|
+
bottom: 100%;
|
|
1432
|
+
left: 50%;
|
|
1433
|
+
transform: translateX(-50%);
|
|
1434
|
+
background: var(--bg-tertiary);
|
|
1435
|
+
border: 1px solid var(--border-strong);
|
|
1436
|
+
border-radius: 4px;
|
|
1437
|
+
padding: 0.75rem;
|
|
1438
|
+
font-size: 0.75rem;
|
|
1439
|
+
white-space: nowrap;
|
|
1440
|
+
pointer-events: none;
|
|
1441
|
+
opacity: 0;
|
|
1442
|
+
visibility: hidden;
|
|
1443
|
+
transition: all 0.2s;
|
|
1444
|
+
z-index: 100;
|
|
1445
|
+
margin-bottom: 0.5rem;
|
|
1446
|
+
box-shadow: var(--shadow-md);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.workload-bar-container:hover .workload-bar-hover-info {
|
|
1450
|
+
opacity: 1;
|
|
1451
|
+
visibility: visible;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
.workload-bar-hover-info::after {
|
|
1455
|
+
content: '';
|
|
1456
|
+
position: absolute;
|
|
1457
|
+
top: 100%;
|
|
1458
|
+
left: 50%;
|
|
1459
|
+
transform: translateX(-50%);
|
|
1460
|
+
border: 6px solid transparent;
|
|
1461
|
+
border-top-color: var(--border-strong);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
.workload-chart-legend {
|
|
1465
|
+
display: flex;
|
|
1466
|
+
flex-wrap: wrap;
|
|
1467
|
+
gap: 1.5rem;
|
|
1468
|
+
margin-top: 1.5rem;
|
|
1469
|
+
padding-top: 1.5rem;
|
|
1470
|
+
border-top: 1px solid var(--border);
|
|
1471
|
+
font-size: 0.875rem;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
.workload-legend-item {
|
|
1475
|
+
display: flex;
|
|
1476
|
+
align-items: center;
|
|
1477
|
+
gap: 0.5rem;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.workload-legend-color {
|
|
1481
|
+
width: 16px;
|
|
1482
|
+
height: 16px;
|
|
1483
|
+
border-radius: 3px;
|
|
1484
|
+
flex-shrink: 0;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.workload-legend-label {
|
|
1488
|
+
color: var(--text-secondary);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
/* Agent Color System */
|
|
1492
|
+
.agent-claude { background: linear-gradient(135deg, #6366f1, #818cf8); }
|
|
1493
|
+
.agent-codex { background: linear-gradient(135deg, #10b981, #34d399); }
|
|
1494
|
+
.agent-orchestrator { background: linear-gradient(135deg, #f59e0b, #fbbf24); }
|
|
1495
|
+
.agent-gemini-2 { background: linear-gradient(135deg, #8b5cf6, #a78bfa); }
|
|
1496
|
+
.agent-gemini { background: linear-gradient(135deg, #ec4899, #f472b6); }
|
|
1497
|
+
.agent-analyst { background: linear-gradient(135deg, #0ea5e9, #38bdf8); }
|
|
1498
|
+
.agent-developer { background: linear-gradient(135deg, #06b6d4, #22d3ee); }
|
|
1499
|
+
|
|
1500
|
+
/* ================================================================
|
|
1501
|
+
AGENT COST VISUALIZATION
|
|
1502
|
+
================================================================ */
|
|
1503
|
+
.cost-breakdown-container {
|
|
1504
|
+
background: var(--bg-secondary);
|
|
1505
|
+
border: 2px solid var(--border);
|
|
1506
|
+
border-radius: 8px;
|
|
1507
|
+
padding: 1.5rem;
|
|
1508
|
+
box-shadow: var(--shadow-sm);
|
|
1509
|
+
margin-top: 1rem;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
.cost-breakdown-header {
|
|
1513
|
+
margin-bottom: 1.5rem;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
.cost-breakdown-header h3 {
|
|
1517
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1518
|
+
font-size: 0.875rem;
|
|
1519
|
+
text-transform: uppercase;
|
|
1520
|
+
letter-spacing: 0.08em;
|
|
1521
|
+
color: var(--text-muted);
|
|
1522
|
+
margin-bottom: 0.25rem;
|
|
1523
|
+
font-weight: 600;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.cost-breakdown-header p {
|
|
1527
|
+
color: var(--text-secondary);
|
|
1528
|
+
font-size: 0.875rem;
|
|
1529
|
+
margin: 0;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.cost-summary-metrics {
|
|
1533
|
+
display: grid;
|
|
1534
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
1535
|
+
gap: 1rem;
|
|
1536
|
+
margin-bottom: 1.5rem;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.cost-metric {
|
|
1540
|
+
background: var(--bg-tertiary);
|
|
1541
|
+
border: 1px solid var(--border);
|
|
1542
|
+
border-radius: 4px;
|
|
1543
|
+
padding: 1rem;
|
|
1544
|
+
text-align: center;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
.cost-metric-label {
|
|
1548
|
+
font-size: 0.7rem;
|
|
1549
|
+
text-transform: uppercase;
|
|
1550
|
+
letter-spacing: 0.08em;
|
|
1551
|
+
color: var(--text-muted);
|
|
1552
|
+
margin-bottom: 0.5rem;
|
|
1553
|
+
font-weight: 600;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.cost-metric-value {
|
|
1557
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1558
|
+
font-size: 1.5rem;
|
|
1559
|
+
font-weight: 700;
|
|
1560
|
+
color: var(--text-primary);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
.cost-metric-unit {
|
|
1564
|
+
font-size: 0.65rem;
|
|
1565
|
+
color: var(--text-muted);
|
|
1566
|
+
margin-top: 0.25rem;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
.cost-bars {
|
|
1570
|
+
display: flex;
|
|
1571
|
+
flex-direction: column;
|
|
1572
|
+
gap: 1.25rem;
|
|
1573
|
+
max-height: 600px;
|
|
1574
|
+
overflow-y: auto;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
.cost-bar-group {
|
|
1578
|
+
display: flex;
|
|
1579
|
+
flex-direction: column;
|
|
1580
|
+
gap: 0.375rem;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.cost-bar-label {
|
|
1584
|
+
display: flex;
|
|
1585
|
+
justify-content: space-between;
|
|
1586
|
+
align-items: center;
|
|
1587
|
+
font-size: 0.875rem;
|
|
1588
|
+
font-weight: 500;
|
|
1589
|
+
color: var(--text-primary);
|
|
1590
|
+
margin-bottom: 0.375rem;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.cost-bar-label-name {
|
|
1594
|
+
display: flex;
|
|
1595
|
+
align-items: center;
|
|
1596
|
+
gap: 0.5rem;
|
|
1597
|
+
flex: 1;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.cost-agent-badge {
|
|
1601
|
+
display: inline-flex;
|
|
1602
|
+
align-items: center;
|
|
1603
|
+
justify-content: center;
|
|
1604
|
+
width: 24px;
|
|
1605
|
+
height: 24px;
|
|
1606
|
+
border-radius: 50%;
|
|
1607
|
+
font-size: 0.7rem;
|
|
1608
|
+
font-weight: 600;
|
|
1609
|
+
color: white;
|
|
1610
|
+
flex-shrink: 0;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
.cost-bar-stats {
|
|
1614
|
+
display: flex;
|
|
1615
|
+
gap: 1rem;
|
|
1616
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1617
|
+
font-size: 0.75rem;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
.cost-bar-stat {
|
|
1621
|
+
display: flex;
|
|
1622
|
+
flex-direction: column;
|
|
1623
|
+
gap: 0.125rem;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.cost-bar-stat-label {
|
|
1627
|
+
color: var(--text-muted);
|
|
1628
|
+
font-size: 0.65rem;
|
|
1629
|
+
text-transform: uppercase;
|
|
1630
|
+
letter-spacing: 0.05em;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
.cost-bar-stat-value {
|
|
1634
|
+
color: var(--text-primary);
|
|
1635
|
+
font-weight: 600;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.cost-bar-container {
|
|
1639
|
+
position: relative;
|
|
1640
|
+
width: 100%;
|
|
1641
|
+
height: 40px;
|
|
1642
|
+
background: var(--bg-tertiary);
|
|
1643
|
+
border: 1px solid var(--border);
|
|
1644
|
+
border-radius: 4px;
|
|
1645
|
+
overflow: hidden;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
.cost-bar-stacked {
|
|
1649
|
+
display: flex;
|
|
1650
|
+
height: 100%;
|
|
1651
|
+
width: 100%;
|
|
1652
|
+
position: relative;
|
|
1653
|
+
overflow: hidden;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
.cost-bar-segment {
|
|
1657
|
+
height: 100%;
|
|
1658
|
+
display: flex;
|
|
1659
|
+
align-items: center;
|
|
1660
|
+
justify-content: center;
|
|
1661
|
+
position: relative;
|
|
1662
|
+
transition: all 0.3s var(--ease-out-expo);
|
|
1663
|
+
flex-grow: 1;
|
|
1664
|
+
min-width: 2px;
|
|
1665
|
+
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
.cost-bar-segment:last-child {
|
|
1669
|
+
border-right: none;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
.cost-bar-segment::after {
|
|
1673
|
+
content: '';
|
|
1674
|
+
position: absolute;
|
|
1675
|
+
inset: 0;
|
|
1676
|
+
background: linear-gradient(90deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 100%);
|
|
1677
|
+
pointer-events: none;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
.cost-bar-segment-label {
|
|
1681
|
+
position: relative;
|
|
1682
|
+
z-index: 2;
|
|
1683
|
+
font-size: 0.65rem;
|
|
1684
|
+
font-weight: 600;
|
|
1685
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1686
|
+
color: white;
|
|
1687
|
+
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
|
1688
|
+
white-space: nowrap;
|
|
1689
|
+
padding: 0 0.3rem;
|
|
1690
|
+
pointer-events: none;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
.cost-bar-tooltip {
|
|
1694
|
+
position: absolute;
|
|
1695
|
+
bottom: 100%;
|
|
1696
|
+
left: 50%;
|
|
1697
|
+
transform: translateX(-50%);
|
|
1698
|
+
background: var(--bg-tertiary);
|
|
1699
|
+
border: 1px solid var(--border-strong);
|
|
1700
|
+
border-radius: 4px;
|
|
1701
|
+
padding: 0.75rem;
|
|
1702
|
+
font-size: 0.75rem;
|
|
1703
|
+
pointer-events: none;
|
|
1704
|
+
opacity: 0;
|
|
1705
|
+
visibility: hidden;
|
|
1706
|
+
transition: all 0.2s;
|
|
1707
|
+
z-index: 100;
|
|
1708
|
+
margin-bottom: 0.5rem;
|
|
1709
|
+
box-shadow: var(--shadow-md);
|
|
1710
|
+
white-space: nowrap;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
.cost-bar-segment:hover ~ .cost-bar-tooltip,
|
|
1714
|
+
.cost-bar-container:hover .cost-bar-tooltip {
|
|
1715
|
+
opacity: 1;
|
|
1716
|
+
visibility: visible;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
.cost-bar-tooltip::after {
|
|
1720
|
+
content: '';
|
|
1721
|
+
position: absolute;
|
|
1722
|
+
top: 100%;
|
|
1723
|
+
left: 50%;
|
|
1724
|
+
transform: translateX(-50%);
|
|
1725
|
+
border: 6px solid transparent;
|
|
1726
|
+
border-top-color: var(--border-strong);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
.cost-range-indicator {
|
|
1730
|
+
display: flex;
|
|
1731
|
+
align-items: center;
|
|
1732
|
+
gap: 0.5rem;
|
|
1733
|
+
font-size: 0.7rem;
|
|
1734
|
+
color: var(--text-muted);
|
|
1735
|
+
margin-top: 0.25rem;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
.cost-range-dot {
|
|
1739
|
+
width: 8px;
|
|
1740
|
+
height: 8px;
|
|
1741
|
+
border-radius: 50%;
|
|
1742
|
+
flex-shrink: 0;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
.cost-range-dot.low { background: #10b981; }
|
|
1746
|
+
.cost-range-dot.medium { background: #f59e0b; }
|
|
1747
|
+
.cost-range-dot.high { background: #ef4444; }
|
|
1748
|
+
|
|
1749
|
+
.cost-breakdown-legend {
|
|
1750
|
+
display: flex;
|
|
1751
|
+
flex-wrap: wrap;
|
|
1752
|
+
gap: 1.5rem;
|
|
1753
|
+
margin-top: 1.5rem;
|
|
1754
|
+
padding-top: 1.5rem;
|
|
1755
|
+
border-top: 1px solid var(--border);
|
|
1756
|
+
font-size: 0.875rem;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.cost-legend-item {
|
|
1760
|
+
display: flex;
|
|
1761
|
+
align-items: center;
|
|
1762
|
+
gap: 0.5rem;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.cost-legend-color {
|
|
1766
|
+
width: 16px;
|
|
1767
|
+
height: 16px;
|
|
1768
|
+
border-radius: 3px;
|
|
1769
|
+
flex-shrink: 0;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
.cost-legend-label {
|
|
1773
|
+
color: var(--text-secondary);
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
/* Agent cost colors - match agent system */
|
|
1777
|
+
.cost-claude { background: #2979FF; }
|
|
1778
|
+
.cost-codex { background: #00C853; }
|
|
1779
|
+
.cost-orchestrator { background: #7C4DFF; }
|
|
1780
|
+
.cost-gemini { background: #FBC02D; }
|
|
1781
|
+
.cost-gemini-2 { background: #FF9100; }
|
|
1782
|
+
.agent-researcher { background: linear-gradient(135deg, #d946ef, #e879f9); }
|
|
1783
|
+
.agent-debugger { background: linear-gradient(135deg, #ef4444, #f87171); }
|
|
1784
|
+
.agent-default { background: linear-gradient(135deg, #6b7280, #9ca3af); }
|
|
1785
|
+
|
|
1786
|
+
.workload-empty-state {
|
|
1787
|
+
text-align: center;
|
|
1788
|
+
padding: 2rem;
|
|
1789
|
+
color: var(--text-muted);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
.workload-empty-state svg {
|
|
1793
|
+
width: 48px;
|
|
1794
|
+
height: 48px;
|
|
1795
|
+
margin: 0 auto 1rem;
|
|
1796
|
+
opacity: 0.5;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.workload-chart-responsive {
|
|
1800
|
+
max-height: 500px;
|
|
1801
|
+
overflow-y: auto;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
@media (max-width: 768px) {
|
|
1805
|
+
.workload-bar-group {
|
|
1806
|
+
gap: 0.25rem;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
.workload-bar-label {
|
|
1810
|
+
font-size: 0.8rem;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
.workload-bar-text {
|
|
1814
|
+
font-size: 0.65rem;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.workload-chart-legend {
|
|
1818
|
+
gap: 1rem;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
.workload-bars {
|
|
1822
|
+
max-height: 400px;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/* Skills Matrix Styles */
|
|
1827
|
+
.skills-matrix-container {
|
|
1828
|
+
background: var(--bg-secondary);
|
|
1829
|
+
border: 2px solid var(--border);
|
|
1830
|
+
border-radius: 8px;
|
|
1831
|
+
padding: 1.5rem;
|
|
1832
|
+
box-shadow: var(--shadow-sm);
|
|
1833
|
+
overflow-x: auto;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
.skills-matrix {
|
|
1837
|
+
display: grid;
|
|
1838
|
+
grid-template-columns: 150px repeat(auto-fit, minmax(100px, 1fr));
|
|
1839
|
+
gap: 0;
|
|
1840
|
+
min-width: 600px;
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.skills-matrix-cell {
|
|
1844
|
+
display: flex;
|
|
1845
|
+
align-items: center;
|
|
1846
|
+
justify-content: center;
|
|
1847
|
+
padding: 0.75rem;
|
|
1848
|
+
border: 1px solid var(--border);
|
|
1849
|
+
font-size: 0.85rem;
|
|
1850
|
+
min-height: 50px;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
.skills-matrix-header-row {
|
|
1854
|
+
position: sticky;
|
|
1855
|
+
top: 0;
|
|
1856
|
+
background: var(--bg-tertiary);
|
|
1857
|
+
font-weight: 600;
|
|
1858
|
+
text-transform: uppercase;
|
|
1859
|
+
letter-spacing: 0.05em;
|
|
1860
|
+
color: var(--text-muted);
|
|
1861
|
+
font-size: 0.75rem;
|
|
1862
|
+
z-index: 10;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
.skills-matrix-agent-name {
|
|
1866
|
+
position: sticky;
|
|
1867
|
+
left: 0;
|
|
1868
|
+
background: var(--bg-tertiary);
|
|
1869
|
+
font-weight: 600;
|
|
1870
|
+
color: var(--text-primary);
|
|
1871
|
+
text-align: left;
|
|
1872
|
+
z-index: 11;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
.skills-matrix-skill-label {
|
|
1876
|
+
writing-mode: horizontal-tb;
|
|
1877
|
+
white-space: nowrap;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
.proficiency-dot {
|
|
1881
|
+
display: inline-flex;
|
|
1882
|
+
align-items: center;
|
|
1883
|
+
justify-content: center;
|
|
1884
|
+
width: 28px;
|
|
1885
|
+
height: 28px;
|
|
1886
|
+
border-radius: 50%;
|
|
1887
|
+
font-size: 0.7rem;
|
|
1888
|
+
font-weight: 600;
|
|
1889
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
1890
|
+
cursor: help;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.proficiency-dot:hover {
|
|
1894
|
+
transform: scale(1.15);
|
|
1895
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
.proficiency-1 {
|
|
1899
|
+
background: #fee5e5;
|
|
1900
|
+
color: #8b0000;
|
|
1901
|
+
border: 1px solid #d4a5a5;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
.proficiency-2 {
|
|
1905
|
+
background: #ffcccb;
|
|
1906
|
+
color: #660000;
|
|
1907
|
+
border: 1px solid #c97c7c;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
.proficiency-3 {
|
|
1911
|
+
background: #ffb366;
|
|
1912
|
+
color: #5a3a00;
|
|
1913
|
+
border: 1px solid #cc8844;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.proficiency-4 {
|
|
1917
|
+
background: #99ff99;
|
|
1918
|
+
color: #1a4d1a;
|
|
1919
|
+
border: 1px solid #66cc66;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
.proficiency-5 {
|
|
1923
|
+
background: #00cc00;
|
|
1924
|
+
color: #ffffff;
|
|
1925
|
+
border: 1px solid #009900;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
.skill-category-legend {
|
|
1929
|
+
display: flex;
|
|
1930
|
+
gap: 1.5rem;
|
|
1931
|
+
flex-wrap: wrap;
|
|
1932
|
+
margin-top: 1.5rem;
|
|
1933
|
+
padding-top: 1.5rem;
|
|
1934
|
+
border-top: 1px solid var(--border);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
.skill-category-item {
|
|
1938
|
+
display: flex;
|
|
1939
|
+
align-items: center;
|
|
1940
|
+
gap: 0.5rem;
|
|
1941
|
+
font-size: 0.875rem;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
.skill-category-icon {
|
|
1945
|
+
display: inline-block;
|
|
1946
|
+
width: 16px;
|
|
1947
|
+
height: 16px;
|
|
1948
|
+
border-radius: 3px;
|
|
1949
|
+
background: var(--accent);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
.skill-tooltip {
|
|
1953
|
+
position: absolute;
|
|
1954
|
+
background: var(--bg-tertiary);
|
|
1955
|
+
border: 1px solid var(--border-strong);
|
|
1956
|
+
border-radius: 4px;
|
|
1957
|
+
padding: 0.5rem 0.75rem;
|
|
1958
|
+
font-size: 0.75rem;
|
|
1959
|
+
white-space: nowrap;
|
|
1960
|
+
pointer-events: none;
|
|
1961
|
+
z-index: 1000;
|
|
1962
|
+
box-shadow: var(--shadow-md);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
/* ================================================================
|
|
1966
|
+
TRACKS VIEW
|
|
1967
|
+
================================================================ */
|
|
1968
|
+
.tracks {
|
|
1969
|
+
display: none;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
.tracks.active {
|
|
1973
|
+
display: block;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
.sessions-table {
|
|
1977
|
+
width: 100%;
|
|
1978
|
+
border-collapse: collapse;
|
|
1979
|
+
font-family: 'JetBrains Mono', monospace;
|
|
1980
|
+
font-size: 0.875rem;
|
|
1981
|
+
background: var(--bg-secondary);
|
|
1982
|
+
border: 2px solid var(--border-strong);
|
|
1983
|
+
box-shadow: var(--shadow-md);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
.sessions-table th,
|
|
1987
|
+
.sessions-table td {
|
|
1988
|
+
border-bottom: 1px solid var(--border);
|
|
1989
|
+
padding: 0.875rem 1rem;
|
|
1990
|
+
text-align: left;
|
|
1991
|
+
vertical-align: middle;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
.sessions-table th {
|
|
1995
|
+
color: var(--text-muted);
|
|
1996
|
+
font-weight: 600;
|
|
1997
|
+
text-transform: uppercase;
|
|
1998
|
+
letter-spacing: 0.08em;
|
|
1999
|
+
font-size: 0.625rem;
|
|
2000
|
+
background: var(--bg-tertiary);
|
|
2001
|
+
border-bottom: 2px solid var(--border-strong);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
.sessions-table tr:hover td {
|
|
2005
|
+
background: var(--bg-tertiary);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
.sessions-table .session-id {
|
|
2009
|
+
font-weight: 600;
|
|
2010
|
+
color: var(--status-active);
|
|
2011
|
+
cursor: pointer;
|
|
2012
|
+
text-decoration: underline;
|
|
2013
|
+
text-underline-offset: 2px;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
.sessions-table .session-id:hover {
|
|
2017
|
+
color: var(--accent);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
/* Session Filters */
|
|
2021
|
+
.session-filters {
|
|
2022
|
+
display: flex;
|
|
2023
|
+
gap: 1rem;
|
|
2024
|
+
padding: 1rem 1.5rem;
|
|
2025
|
+
background: var(--bg-secondary);
|
|
2026
|
+
border: 2px solid var(--border-strong);
|
|
2027
|
+
box-shadow: var(--shadow-md);
|
|
2028
|
+
margin-bottom: 1rem;
|
|
2029
|
+
flex-wrap: wrap;
|
|
2030
|
+
align-items: flex-end;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.filter-group {
|
|
2034
|
+
display: flex;
|
|
2035
|
+
flex-direction: column;
|
|
2036
|
+
gap: 0.375rem;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
.filter-group label {
|
|
1080
2040
|
font-size: 0.75rem;
|
|
1081
2041
|
font-weight: 600;
|
|
1082
2042
|
color: var(--text-muted);
|
|
@@ -1592,6 +2552,47 @@
|
|
|
1592
2552
|
font-family: 'JetBrains Mono', monospace;
|
|
1593
2553
|
}
|
|
1594
2554
|
|
|
2555
|
+
/* Delegations */
|
|
2556
|
+
.delegations-list {
|
|
2557
|
+
display: flex;
|
|
2558
|
+
flex-direction: column;
|
|
2559
|
+
gap: 0.75rem;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
.delegation-item {
|
|
2563
|
+
padding: 0.75rem;
|
|
2564
|
+
border: 1px solid var(--border);
|
|
2565
|
+
background: var(--bg-tertiary);
|
|
2566
|
+
border-radius: 2px;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
.delegation-meta {
|
|
2570
|
+
display: flex;
|
|
2571
|
+
gap: 0.5rem;
|
|
2572
|
+
flex-wrap: wrap;
|
|
2573
|
+
align-items: center;
|
|
2574
|
+
margin-bottom: 0.5rem;
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
.delegation-task {
|
|
2578
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2579
|
+
font-size: 0.75rem;
|
|
2580
|
+
color: var(--text-secondary);
|
|
2581
|
+
margin-bottom: 0.375rem;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
.delegation-time {
|
|
2585
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2586
|
+
font-size: 0.65rem;
|
|
2587
|
+
color: var(--text-muted);
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
.mono {
|
|
2591
|
+
font-family: 'JetBrains Mono', monospace;
|
|
2592
|
+
font-size: 0.75rem;
|
|
2593
|
+
color: var(--text-secondary);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
1595
2596
|
/* Session Activity Preview */
|
|
1596
2597
|
.session-preview {
|
|
1597
2598
|
background: var(--bg-tertiary);
|
|
@@ -2007,6 +3008,7 @@
|
|
|
2007
3008
|
<button class="view-btn active" data-view="kanban">Work</button>
|
|
2008
3009
|
<button class="view-btn" data-view="graph">Graph</button>
|
|
2009
3010
|
<button class="view-btn" data-view="analytics">Analytics</button>
|
|
3011
|
+
<button class="view-btn" data-view="agents">Agents</button>
|
|
2010
3012
|
<button class="view-btn" data-view="sessions">Sessions</button>
|
|
2011
3013
|
</div>
|
|
2012
3014
|
|
|
@@ -2015,19 +3017,58 @@
|
|
|
2015
3017
|
|
|
2016
3018
|
<!-- Graph View -->
|
|
2017
3019
|
<div class="graph-container" id="graph-container">
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
3020
|
+
<!-- Graph Controls -->
|
|
3021
|
+
<div class="graph-controls">
|
|
3022
|
+
<div class="graph-control-group">
|
|
3023
|
+
<label class="graph-filter-label">Filter by Status:</label>
|
|
3024
|
+
<div class="graph-filters">
|
|
3025
|
+
<label class="graph-filter-checkbox">
|
|
3026
|
+
<input type="checkbox" data-status="todo" checked>
|
|
3027
|
+
<span>To Do</span>
|
|
3028
|
+
</label>
|
|
3029
|
+
<label class="graph-filter-checkbox">
|
|
3030
|
+
<input type="checkbox" data-status="in-progress" checked>
|
|
3031
|
+
<span>In Progress</span>
|
|
3032
|
+
</label>
|
|
3033
|
+
<label class="graph-filter-checkbox">
|
|
3034
|
+
<input type="checkbox" data-status="blocked" checked>
|
|
3035
|
+
<span>Blocked</span>
|
|
3036
|
+
</label>
|
|
3037
|
+
<label class="graph-filter-checkbox">
|
|
3038
|
+
<input type="checkbox" data-status="done">
|
|
3039
|
+
<span>Done</span>
|
|
3040
|
+
</label>
|
|
3041
|
+
</div>
|
|
3042
|
+
</div>
|
|
3043
|
+
|
|
3044
|
+
<div class="graph-control-group">
|
|
3045
|
+
<input type="text" id="graph-search" class="graph-search" placeholder="Search nodes..." />
|
|
3046
|
+
</div>
|
|
3047
|
+
|
|
3048
|
+
<div class="graph-control-group graph-control-buttons">
|
|
3049
|
+
<button id="graph-reset" class="btn btn-secondary">Reset View</button>
|
|
3050
|
+
<button id="graph-show-all" class="btn btn-secondary">Show All</button>
|
|
3051
|
+
</div>
|
|
3052
|
+
</div>
|
|
3053
|
+
|
|
3054
|
+
<!-- Graph Container for Vis.js -->
|
|
3055
|
+
<div class="graph-viewport">
|
|
3056
|
+
<div id="graph-network" class="graph-network"></div>
|
|
3057
|
+
</div>
|
|
3058
|
+
|
|
3059
|
+
<div class="graph-footer">
|
|
3060
|
+
<div class="graph-stats">
|
|
3061
|
+
<span id="graph-node-count">0 nodes</span>
|
|
3062
|
+
<span id="graph-edge-count">0 edges</span>
|
|
3063
|
+
</div>
|
|
3064
|
+
<div class="graph-legend">
|
|
3065
|
+
<div class="graph-legend-item"><span class="legend-done"></span> Done</div>
|
|
3066
|
+
<div class="graph-legend-item"><span class="legend-active"></span> In Progress</div>
|
|
3067
|
+
<div class="graph-legend-item"><span class="legend-todo"></span> To Do</div>
|
|
3068
|
+
<div class="graph-legend-item"><span class="legend-blocked"></span> Blocked</div>
|
|
3069
|
+
<div class="graph-legend-item"><span class="legend-related"></span> Related edge</div>
|
|
3070
|
+
<div class="graph-legend-item"><span class="legend-blocked-edge"></span> Blocked by</div>
|
|
3071
|
+
</div>
|
|
2031
3072
|
</div>
|
|
2032
3073
|
</div>
|
|
2033
3074
|
|
|
@@ -2115,6 +3156,78 @@
|
|
|
2115
3156
|
<div id="sessions-list" class="loading">Loading sessions...</div>
|
|
2116
3157
|
</div>
|
|
2117
3158
|
|
|
3159
|
+
<!-- Agents View - Multi-Agent Work Attribution -->
|
|
3160
|
+
<div class="agents" id="agents">
|
|
3161
|
+
<div class="analytics-header">
|
|
3162
|
+
<div>
|
|
3163
|
+
<h2>Multi-Agent Work Attribution</h2>
|
|
3164
|
+
<p>Track which agents completed work items and monitor delegation performance.</p>
|
|
3165
|
+
</div>
|
|
3166
|
+
<div style="display:flex; gap:0.5rem; align-items:center;">
|
|
3167
|
+
<button class="btn btn-primary" id="agents-refresh">Refresh</button>
|
|
3168
|
+
</div>
|
|
3169
|
+
</div>
|
|
3170
|
+
|
|
3171
|
+
<!-- Agent Skills Matrix -->
|
|
3172
|
+
<div class="analytics-card analytics-card-wide" id="agent-skills-matrix">
|
|
3173
|
+
<h3>Agent Specializations & Skills Matrix</h3>
|
|
3174
|
+
<p style="color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.5rem;">
|
|
3175
|
+
Proficiency levels based on work history analysis. Color intensity indicates expertise level (1=novice, 5=expert).
|
|
3176
|
+
</p>
|
|
3177
|
+
<div class="skills-matrix-container" id="skills-matrix-content">
|
|
3178
|
+
<div class="loading">Analyzing agent work history...</div>
|
|
3179
|
+
</div>
|
|
3180
|
+
</div>
|
|
3181
|
+
|
|
3182
|
+
<!-- Agent Stats Summary -->
|
|
3183
|
+
<div class="analytics-card" id="agent-summary">
|
|
3184
|
+
<div class="loading">Loading agent statistics...</div>
|
|
3185
|
+
</div>
|
|
3186
|
+
|
|
3187
|
+
<!-- Workload Distribution Chart -->
|
|
3188
|
+
<div class="analytics-card analytics-card-wide">
|
|
3189
|
+
<div class="workload-chart-container" id="workload-chart">
|
|
3190
|
+
<div class="workload-chart-header">
|
|
3191
|
+
<h3>Agent Workload Distribution</h3>
|
|
3192
|
+
<p>Horizontal bar chart showing work completion by agent</p>
|
|
3193
|
+
</div>
|
|
3194
|
+
<div id="workload-chart-content" class="loading">Loading workload data...</div>
|
|
3195
|
+
</div>
|
|
3196
|
+
</div>
|
|
3197
|
+
|
|
3198
|
+
<!-- Agent Work Table -->
|
|
3199
|
+
<div class="analytics-card analytics-card-wide" id="agent-work-table">
|
|
3200
|
+
<div class="loading">Loading agent work data...</div>
|
|
3201
|
+
</div>
|
|
3202
|
+
|
|
3203
|
+
<!-- Agent Performance Metrics -->
|
|
3204
|
+
<div class="analytics-card" id="agent-performance">
|
|
3205
|
+
<div class="loading">Loading agent performance metrics...</div>
|
|
3206
|
+
</div>
|
|
3207
|
+
|
|
3208
|
+
<!-- Agent Cost Breakdown -->
|
|
3209
|
+
<div class="analytics-card analytics-card-wide" id="agent-costs">
|
|
3210
|
+
<div class="cost-breakdown-container">
|
|
3211
|
+
<div class="cost-breakdown-header">
|
|
3212
|
+
<h3>Agent Cost Breakdown</h3>
|
|
3213
|
+
<p>Token costs aggregated by agent type with visual distribution</p>
|
|
3214
|
+
</div>
|
|
3215
|
+
|
|
3216
|
+
<div class="cost-summary-metrics" id="cost-metrics">
|
|
3217
|
+
<!-- Populated by JavaScript -->
|
|
3218
|
+
</div>
|
|
3219
|
+
|
|
3220
|
+
<div class="cost-bars" id="cost-bars-container">
|
|
3221
|
+
<!-- Populated by JavaScript -->
|
|
3222
|
+
</div>
|
|
3223
|
+
|
|
3224
|
+
<div class="cost-breakdown-legend" id="cost-legend">
|
|
3225
|
+
<!-- Populated by JavaScript -->
|
|
3226
|
+
</div>
|
|
3227
|
+
</div>
|
|
3228
|
+
</div>
|
|
3229
|
+
</div>
|
|
3230
|
+
|
|
2118
3231
|
</div>
|
|
2119
3232
|
|
|
2120
3233
|
<!-- Detail Panel -->
|
|
@@ -2732,11 +3845,233 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2732
3845
|
]);
|
|
2733
3846
|
}
|
|
2734
3847
|
|
|
3848
|
+
// =====================================================================
|
|
3849
|
+
// Agent Cost Visualization
|
|
3850
|
+
// =====================================================================
|
|
3851
|
+
|
|
3852
|
+
const AGENT_COLORS = {
|
|
3853
|
+
'claude': '#2979FF',
|
|
3854
|
+
'codex': '#00C853',
|
|
3855
|
+
'orchestrator': '#7C4DFF',
|
|
3856
|
+
'gemini': '#FBC02D',
|
|
3857
|
+
'gemini-2': '#FF9100',
|
|
3858
|
+
'analyst': '#0ea5e9',
|
|
3859
|
+
'developer': '#06b6d4',
|
|
3860
|
+
'researcher': '#d946ef',
|
|
3861
|
+
'debugger': '#ef4444',
|
|
3862
|
+
'default': '#78909C'
|
|
3863
|
+
};
|
|
3864
|
+
|
|
3865
|
+
function getAgentColor(agent) {
|
|
3866
|
+
const normalized = (agent || 'default').toLowerCase();
|
|
3867
|
+
return AGENT_COLORS[normalized] || AGENT_COLORS['default'];
|
|
3868
|
+
}
|
|
3869
|
+
|
|
3870
|
+
function formatCost(tokens) {
|
|
3871
|
+
// Approximate cost: $0.003 per 1K input tokens
|
|
3872
|
+
const cost = (tokens / 1000) * 0.003;
|
|
3873
|
+
return cost.toFixed(4);
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
function formatCostDisplay(tokens) {
|
|
3877
|
+
const cost = parseFloat(formatCost(tokens));
|
|
3878
|
+
if (cost === 0) return '$0.00';
|
|
3879
|
+
return `$${cost.toFixed(2)}`;
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
function getCostRange(cost) {
|
|
3883
|
+
// Define ranges: low (< $0.01), medium ($0.01-$0.05), high (> $0.05)
|
|
3884
|
+
const numCost = parseFloat(formatCost(cost));
|
|
3885
|
+
if (numCost < 0.01) return 'low';
|
|
3886
|
+
if (numCost < 0.05) return 'medium';
|
|
3887
|
+
return 'high';
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
function aggregateAgentCosts(sessions) {
|
|
3891
|
+
const costsByAgent = {};
|
|
3892
|
+
let totalCost = 0;
|
|
3893
|
+
|
|
3894
|
+
sessions.forEach(session => {
|
|
3895
|
+
const agent = session.properties?.agent || 'unknown';
|
|
3896
|
+
const tokens = parseInt(session.properties?.total_tokens) || 0;
|
|
3897
|
+
|
|
3898
|
+
if (!costsByAgent[agent]) {
|
|
3899
|
+
costsByAgent[agent] = {
|
|
3900
|
+
agent,
|
|
3901
|
+
totalTokens: 0,
|
|
3902
|
+
operationCount: 0,
|
|
3903
|
+
sessionCount: 0
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
costsByAgent[agent].totalTokens += tokens;
|
|
3908
|
+
costsByAgent[agent].operationCount += parseInt(session.properties?.event_count) || 0;
|
|
3909
|
+
costsByAgent[agent].sessionCount += 1;
|
|
3910
|
+
totalCost += tokens;
|
|
3911
|
+
});
|
|
3912
|
+
|
|
3913
|
+
return {
|
|
3914
|
+
byAgent: Object.values(costsByAgent).sort((a, b) => b.totalTokens - a.totalTokens),
|
|
3915
|
+
totalTokens: totalCost
|
|
3916
|
+
};
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
function renderAgentCostMetrics(costs) {
|
|
3920
|
+
const metricsEl = document.getElementById('cost-metrics');
|
|
3921
|
+
if (!metricsEl || costs.byAgent.length === 0) return;
|
|
3922
|
+
|
|
3923
|
+
const avgCostPerAgent = costs.totalTokens / costs.byAgent.length;
|
|
3924
|
+
const totalCostUSD = formatCostDisplay(costs.totalTokens);
|
|
3925
|
+
const avgCostUSD = formatCostDisplay(avgCostPerAgent);
|
|
3926
|
+
|
|
3927
|
+
const topAgent = costs.byAgent[0];
|
|
3928
|
+
const topAgentPercent = ((topAgent.totalTokens / costs.totalTokens) * 100).toFixed(1);
|
|
3929
|
+
|
|
3930
|
+
metricsEl.innerHTML = `
|
|
3931
|
+
<div class="cost-metric">
|
|
3932
|
+
<div class="cost-metric-label">Total Cost</div>
|
|
3933
|
+
<div class="cost-metric-value">${totalCostUSD}</div>
|
|
3934
|
+
<div class="cost-metric-unit">${costs.totalTokens.toLocaleString()} tokens</div>
|
|
3935
|
+
</div>
|
|
3936
|
+
<div class="cost-metric">
|
|
3937
|
+
<div class="cost-metric-label">Average Per Agent</div>
|
|
3938
|
+
<div class="cost-metric-value">${avgCostUSD}</div>
|
|
3939
|
+
<div class="cost-metric-unit">~${Math.round(avgCostPerAgent).toLocaleString()} tokens</div>
|
|
3940
|
+
</div>
|
|
3941
|
+
<div class="cost-metric">
|
|
3942
|
+
<div class="cost-metric-label">Top Agent</div>
|
|
3943
|
+
<div class="cost-metric-value">${topAgent.agent}</div>
|
|
3944
|
+
<div class="cost-metric-unit">${topAgentPercent}% of total</div>
|
|
3945
|
+
</div>
|
|
3946
|
+
<div class="cost-metric">
|
|
3947
|
+
<div class="cost-metric-label">Agent Count</div>
|
|
3948
|
+
<div class="cost-metric-value">${costs.byAgent.length}</div>
|
|
3949
|
+
<div class="cost-metric-unit">unique agents</div>
|
|
3950
|
+
</div>
|
|
3951
|
+
`;
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
function renderAgentCostBars(costs) {
|
|
3955
|
+
const containerEl = document.getElementById('cost-bars-container');
|
|
3956
|
+
if (!containerEl || costs.byAgent.length === 0) {
|
|
3957
|
+
if (containerEl) containerEl.innerHTML = '<div class="loading">No cost data available</div>';
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
const maxCost = Math.max(...costs.byAgent.map(a => a.totalTokens));
|
|
3962
|
+
|
|
3963
|
+
const barsHTML = costs.byAgent.map(agent => {
|
|
3964
|
+
const percentOfTotal = (agent.totalTokens / costs.totalTokens) * 100;
|
|
3965
|
+
const costUSD = formatCostDisplay(agent.totalTokens);
|
|
3966
|
+
const costRange = getCostRange(agent.totalTokens);
|
|
3967
|
+
const color = getAgentColor(agent.agent);
|
|
3968
|
+
const avgPerSession = Math.round(agent.totalTokens / agent.sessionCount);
|
|
3969
|
+
|
|
3970
|
+
return `
|
|
3971
|
+
<div class="cost-bar-group">
|
|
3972
|
+
<div class="cost-bar-label">
|
|
3973
|
+
<div class="cost-bar-label-name">
|
|
3974
|
+
<div class="cost-agent-badge" style="background: ${color};">
|
|
3975
|
+
${agent.agent.substring(0, 1).toUpperCase()}
|
|
3976
|
+
</div>
|
|
3977
|
+
<span>${agent.agent}</span>
|
|
3978
|
+
</div>
|
|
3979
|
+
<div class="cost-bar-stats">
|
|
3980
|
+
<div class="cost-bar-stat">
|
|
3981
|
+
<div class="cost-bar-stat-label">Cost</div>
|
|
3982
|
+
<div class="cost-bar-stat-value">${costUSD}</div>
|
|
3983
|
+
</div>
|
|
3984
|
+
<div class="cost-bar-stat">
|
|
3985
|
+
<div class="cost-bar-stat-label">%</div>
|
|
3986
|
+
<div class="cost-bar-stat-value">${percentOfTotal.toFixed(1)}%</div>
|
|
3987
|
+
</div>
|
|
3988
|
+
<div class="cost-bar-stat">
|
|
3989
|
+
<div class="cost-bar-stat-label">Tokens</div>
|
|
3990
|
+
<div class="cost-bar-stat-value">${agent.totalTokens.toLocaleString()}</div>
|
|
3991
|
+
</div>
|
|
3992
|
+
</div>
|
|
3993
|
+
</div>
|
|
3994
|
+
|
|
3995
|
+
<div class="cost-bar-container">
|
|
3996
|
+
<div class="cost-bar-stacked">
|
|
3997
|
+
<div class="cost-bar-segment" style="
|
|
3998
|
+
width: 100%;
|
|
3999
|
+
background: ${color};
|
|
4000
|
+
opacity: 0.85;
|
|
4001
|
+
" title="${agent.agent}: ${costUSD}">
|
|
4002
|
+
<span class="cost-bar-segment-label">
|
|
4003
|
+
${percentOfTotal.toFixed(0)}%
|
|
4004
|
+
</span>
|
|
4005
|
+
</div>
|
|
4006
|
+
</div>
|
|
4007
|
+
<div class="cost-bar-tooltip">
|
|
4008
|
+
Sessions: ${agent.sessionCount} | Avg/Session: ${avgPerSession.toLocaleString()} tokens
|
|
4009
|
+
</div>
|
|
4010
|
+
</div>
|
|
4011
|
+
|
|
4012
|
+
<div class="cost-range-indicator">
|
|
4013
|
+
<div class="cost-range-dot ${costRange}"></div>
|
|
4014
|
+
<span>${costRange === 'low' ? 'Low' : costRange === 'medium' ? 'Medium' : 'High'} cost</span>
|
|
4015
|
+
</div>
|
|
4016
|
+
</div>
|
|
4017
|
+
`;
|
|
4018
|
+
}).join('');
|
|
4019
|
+
|
|
4020
|
+
containerEl.innerHTML = barsHTML;
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
function renderAgentCostLegend(costs) {
|
|
4024
|
+
const legendEl = document.getElementById('cost-legend');
|
|
4025
|
+
if (!legendEl || costs.byAgent.length === 0) return;
|
|
4026
|
+
|
|
4027
|
+
const legendItems = costs.byAgent.map(agent => {
|
|
4028
|
+
const color = getAgentColor(agent.agent);
|
|
4029
|
+
return `
|
|
4030
|
+
<div class="cost-legend-item">
|
|
4031
|
+
<div class="cost-legend-color" style="background: ${color};"></div>
|
|
4032
|
+
<span class="cost-legend-label">${agent.agent}</span>
|
|
4033
|
+
</div>
|
|
4034
|
+
`;
|
|
4035
|
+
}).join('');
|
|
4036
|
+
|
|
4037
|
+
legendEl.innerHTML = legendItems;
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
async function loadAndRenderAgentCosts() {
|
|
4041
|
+
const container = document.getElementById('agent-costs');
|
|
4042
|
+
if (!container) return;
|
|
4043
|
+
|
|
4044
|
+
try {
|
|
4045
|
+
// Fetch all sessions to aggregate costs
|
|
4046
|
+
if (allSessions.length === 0) {
|
|
4047
|
+
const response = await fetch(`${API}/sessions`);
|
|
4048
|
+
if (!response.ok) throw new Error('Failed to load sessions');
|
|
4049
|
+
const data = await response.json();
|
|
4050
|
+
allSessions = data.nodes || [];
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
if (allSessions.length === 0) {
|
|
4054
|
+
return;
|
|
4055
|
+
}
|
|
4056
|
+
|
|
4057
|
+
// Aggregate costs by agent
|
|
4058
|
+
const costs = aggregateAgentCosts(allSessions);
|
|
4059
|
+
|
|
4060
|
+
// Render visualization
|
|
4061
|
+
renderAgentCostMetrics(costs);
|
|
4062
|
+
renderAgentCostBars(costs);
|
|
4063
|
+
renderAgentCostLegend(costs);
|
|
4064
|
+
} catch (err) {
|
|
4065
|
+
console.error('Error loading agent costs:', err);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
|
|
2735
4069
|
async function ensureAnalyticsLoaded(force = false) {
|
|
2736
4070
|
const stale = (Date.now() - analyticsLoadedAt) > 60_000;
|
|
2737
4071
|
if (!force && analyticsCache.overview && !stale) return;
|
|
2738
4072
|
try {
|
|
2739
4073
|
await loadAndRenderAnalyticsBase();
|
|
4074
|
+
await loadAndRenderAgentCosts();
|
|
2740
4075
|
if (!analyticsCache.selectedFeatureId && analyticsCache.features && analyticsCache.features.length) {
|
|
2741
4076
|
await loadFeatureAnalytics(analyticsCache.features[0].feature_id);
|
|
2742
4077
|
}
|
|
@@ -3240,7 +4575,9 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3240
4575
|
// Sort tracks by feature completion (incomplete first), then by priority
|
|
3241
4576
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
3242
4577
|
const sortedTracks = Array.from(tracked.values())
|
|
3243
|
-
|
|
4578
|
+
// FIXED: Do NOT filter out tracks with null metadata - they still have features to show!
|
|
4579
|
+
// Only filter if there are no features for this track
|
|
4580
|
+
.filter(t => t.features && t.features.length > 0)
|
|
3244
4581
|
.sort((a, b) => {
|
|
3245
4582
|
// Calculate completion percentage for each track
|
|
3246
4583
|
const aTotal = a.features.length;
|
|
@@ -3257,7 +4594,10 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3257
4594
|
}
|
|
3258
4595
|
|
|
3259
4596
|
// Within same completion status, sort by priority
|
|
3260
|
-
|
|
4597
|
+
// Use track priority if available, otherwise default to 'medium'
|
|
4598
|
+
const aPriority = (a.track && a.track.priority) || 'medium';
|
|
4599
|
+
const bPriority = (b.track && b.track.priority) || 'medium';
|
|
4600
|
+
return (priorityOrder[aPriority] || 2) - (priorityOrder[bPriority] || 2);
|
|
3261
4601
|
});
|
|
3262
4602
|
|
|
3263
4603
|
// Render
|
|
@@ -3426,18 +4766,51 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3426
4766
|
<div class="track-column-cards">
|
|
3427
4767
|
${byStatus[status].length === 0
|
|
3428
4768
|
? '<div class="empty-column">No items</div>'
|
|
3429
|
-
: byStatus[status].map(f =>
|
|
4769
|
+
: byStatus[status].map(f => {
|
|
4770
|
+
// Determine agent badge color based on agent name
|
|
4771
|
+
let agentClass = 'agent-default';
|
|
4772
|
+
if (f.agent_assigned) {
|
|
4773
|
+
const agentName = f.agent_assigned.toLowerCase();
|
|
4774
|
+
// Primary agents
|
|
4775
|
+
if (agentName.includes('claude')) agentClass = 'agent-claude';
|
|
4776
|
+
else if (agentName.includes('codex')) agentClass = 'agent-codex';
|
|
4777
|
+
else if (agentName.includes('orchestrator')) agentClass = 'agent-orchestrator';
|
|
4778
|
+
else if (agentName.includes('gemini-2') || agentName.includes('gemini 2')) agentClass = 'agent-gemini-2';
|
|
4779
|
+
else if (agentName.includes('gemini')) agentClass = 'agent-gemini';
|
|
4780
|
+
// Secondary agents (backward compatibility)
|
|
4781
|
+
else if (agentName.includes('analyst')) agentClass = 'agent-analyst';
|
|
4782
|
+
else if (agentName.includes('developer')) agentClass = 'agent-developer';
|
|
4783
|
+
else if (agentName.includes('researcher')) agentClass = 'agent-researcher';
|
|
4784
|
+
else if (agentName.includes('debugger')) agentClass = 'agent-debugger';
|
|
4785
|
+
}
|
|
4786
|
+
// Check if feature has delegations
|
|
4787
|
+
const delegations = (f.properties && f.properties.delegations) || [];
|
|
4788
|
+
const delegationBadges = delegations.length > 0
|
|
4789
|
+
? `<span class="badge delegation" title="Delegated to ${delegations.length} spawner(s)">Delegated: ${delegations.length}</span>`
|
|
4790
|
+
: '';
|
|
4791
|
+
return `
|
|
3430
4792
|
<div class="card priority-${f.priority}"
|
|
3431
4793
|
data-collection="${f._collection}"
|
|
3432
|
-
data-id="${f.id}"
|
|
4794
|
+
data-id="${f.id}"
|
|
4795
|
+
data-agent="${f.agent_assigned || ''}"
|
|
4796
|
+
onclick="toggleCardTimeline(event)">
|
|
4797
|
+
<button class="card-expand-btn" onclick="toggleCardTimeline(event)" title="Toggle agent timeline">▼</button>
|
|
3433
4798
|
<div class="card-title">${f.title}</div>
|
|
3434
4799
|
<div class="card-meta">
|
|
3435
4800
|
<span class="badge priority-${f.priority}">${f.priority}</span>
|
|
3436
4801
|
${f.type !== 'feature' ? `<span class="badge type">${f.type}</span>` : ''}
|
|
4802
|
+
${f.agent_assigned ? `<span class="badge agent ${agentClass}">${f.agent_assigned}</span>` : ''}
|
|
4803
|
+
${delegationBadges}
|
|
3437
4804
|
<span class="card-path">${f._collection}/${f.id}</span>
|
|
3438
4805
|
</div>
|
|
4806
|
+
<div class="card-timeline" data-feature-id="${f.id}">
|
|
4807
|
+
<div class="timeline-header">Agent Timeline</div>
|
|
4808
|
+
<div class="timeline-list" data-loading="true">
|
|
4809
|
+
<div class="timeline-empty">Loading timeline...</div>
|
|
4810
|
+
</div>
|
|
4811
|
+
</div>
|
|
3439
4812
|
</div>
|
|
3440
|
-
`).join('')}
|
|
4813
|
+
`}).join('')}
|
|
3441
4814
|
</div>
|
|
3442
4815
|
</div>
|
|
3443
4816
|
`).join('')}
|
|
@@ -3731,6 +5104,24 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3731
5104
|
|
|
3732
5105
|
let bodyHtml = '';
|
|
3733
5106
|
|
|
5107
|
+
// Helper function to get agent badge class
|
|
5108
|
+
function getAgentClass(agentName) {
|
|
5109
|
+
if (!agentName) return 'agent-default';
|
|
5110
|
+
const name = agentName.toLowerCase();
|
|
5111
|
+
// Primary agents
|
|
5112
|
+
if (name.includes('claude')) return 'agent-claude';
|
|
5113
|
+
if (name.includes('codex')) return 'agent-codex';
|
|
5114
|
+
if (name.includes('orchestrator')) return 'agent-orchestrator';
|
|
5115
|
+
if (name.includes('gemini-2') || name.includes('gemini 2')) return 'agent-gemini-2';
|
|
5116
|
+
if (name.includes('gemini')) return 'agent-gemini';
|
|
5117
|
+
// Secondary agents (backward compatibility)
|
|
5118
|
+
if (name.includes('analyst')) return 'agent-analyst';
|
|
5119
|
+
if (name.includes('developer')) return 'agent-developer';
|
|
5120
|
+
if (name.includes('researcher')) return 'agent-researcher';
|
|
5121
|
+
if (name.includes('debugger')) return 'agent-debugger';
|
|
5122
|
+
return 'agent-default';
|
|
5123
|
+
}
|
|
5124
|
+
|
|
3734
5125
|
// Meta section
|
|
3735
5126
|
bodyHtml += `
|
|
3736
5127
|
<div class="panel-section">
|
|
@@ -3739,7 +5130,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3739
5130
|
<span class="badge priority-${node.priority}">${node.priority}</span>
|
|
3740
5131
|
<span class="badge type">${node.type}</span>
|
|
3741
5132
|
<span class="badge">${node.status}</span>
|
|
3742
|
-
${node.agent_assigned ? `<span class="badge">Agent: ${node.agent_assigned}</span>` : ''}
|
|
5133
|
+
${node.agent_assigned ? `<span class="badge agent ${getAgentClass(node.agent_assigned)}">Agent: ${node.agent_assigned}</span>` : ''}
|
|
3743
5134
|
</div>
|
|
3744
5135
|
</div>
|
|
3745
5136
|
`;
|
|
@@ -3768,6 +5159,43 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3768
5159
|
`;
|
|
3769
5160
|
}
|
|
3770
5161
|
|
|
5162
|
+
// Delegation information section (if delegation data exists in properties)
|
|
5163
|
+
if (node.properties && (node.properties.delegated_tasks || node.properties.delegations)) {
|
|
5164
|
+
const delegations = node.properties.delegations || node.properties.delegated_tasks || [];
|
|
5165
|
+
if (delegations && delegations.length > 0) {
|
|
5166
|
+
bodyHtml += `
|
|
5167
|
+
<div class="panel-section">
|
|
5168
|
+
<h3>Delegations (${delegations.length})</h3>
|
|
5169
|
+
<div class="delegations-list">
|
|
5170
|
+
${delegations.map((d, idx) => {
|
|
5171
|
+
const spawner = d.spawner || d.executor || 'unknown';
|
|
5172
|
+
const executorType = d.executor_type || 'direct';
|
|
5173
|
+
let executorBadge = 'delegation-direct';
|
|
5174
|
+
if (executorType === 'external_cli') executorBadge = 'delegation-external';
|
|
5175
|
+
else if (executorType === 'fallback') executorBadge = 'delegation-fallback';
|
|
5176
|
+
|
|
5177
|
+
const tokens = d.tokens_used ? ` (${d.tokens_used} tokens)` : '';
|
|
5178
|
+
const cost = d.cost ? ` - $${d.cost.toFixed(2)}` : '';
|
|
5179
|
+
|
|
5180
|
+
return `
|
|
5181
|
+
<div class="delegation-item">
|
|
5182
|
+
<div class="delegation-meta">
|
|
5183
|
+
<span class="badge delegation ${executorBadge}">${spawner}</span>
|
|
5184
|
+
<span class="badge delegation">${executorType}</span>
|
|
5185
|
+
${tokens ? `<span class="mono">${tokens}</span>` : ''}
|
|
5186
|
+
${cost ? `<span class="mono">${cost}</span>` : ''}
|
|
5187
|
+
</div>
|
|
5188
|
+
${d.task_id ? `<div class="delegation-task">Task: ${d.task_id}</div>` : ''}
|
|
5189
|
+
${d.timestamp ? `<div class="delegation-time">${new Date(d.timestamp).toLocaleString()}</div>` : ''}
|
|
5190
|
+
</div>
|
|
5191
|
+
`;
|
|
5192
|
+
}).join('')}
|
|
5193
|
+
</div>
|
|
5194
|
+
</div>
|
|
5195
|
+
`;
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5198
|
+
|
|
3771
5199
|
// Edges section (excluding implemented-in which gets special handling)
|
|
3772
5200
|
const edgeTypes = Object.keys(node.edges || {}).filter(t => t !== 'implemented-in');
|
|
3773
5201
|
if (edgeTypes.length > 0) {
|
|
@@ -4083,219 +5511,771 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4083
5511
|
return div.innerHTML;
|
|
4084
5512
|
}
|
|
4085
5513
|
|
|
4086
|
-
async function refreshDashboard() {
|
|
4087
|
-
const { status, nodes } = await loadData();
|
|
4088
|
-
await renderKanban(nodes);
|
|
4089
|
-
updateKanbanGrid();
|
|
5514
|
+
async function refreshDashboard() {
|
|
5515
|
+
const { status, nodes } = await loadData();
|
|
5516
|
+
await renderKanban(nodes);
|
|
5517
|
+
updateKanbanGrid();
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
// =====================================================================
|
|
5521
|
+
// Graph Visualization
|
|
5522
|
+
// =====================================================================
|
|
5523
|
+
|
|
5524
|
+
let visNetwork = null; // Vis.js network instance
|
|
5525
|
+
|
|
5526
|
+
function getNodeColor(node) {
|
|
5527
|
+
const colors = {
|
|
5528
|
+
'done': getComputedStyle(document.documentElement).getPropertyValue('--status-done').trim(),
|
|
5529
|
+
'in-progress': getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(),
|
|
5530
|
+
'blocked': getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(),
|
|
5531
|
+
'todo': getComputedStyle(document.documentElement).getPropertyValue('--status-todo').trim()
|
|
5532
|
+
};
|
|
5533
|
+
return colors[node.status] || colors['todo'];
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
function getNodeRadius(node) {
|
|
5537
|
+
const statusSizes = {
|
|
5538
|
+
'done': 20,
|
|
5539
|
+
'in-progress': 35,
|
|
5540
|
+
'blocked': 30,
|
|
5541
|
+
'todo': 28
|
|
5542
|
+
};
|
|
5543
|
+
return statusSizes[node.status] || 25;
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
function wrapText(text, maxCharsPerLine = 10) {
|
|
5547
|
+
const words = text.split(/\s+/);
|
|
5548
|
+
const lines = [];
|
|
5549
|
+
let currentLine = '';
|
|
5550
|
+
|
|
5551
|
+
for (const word of words) {
|
|
5552
|
+
const testLine = currentLine ? currentLine + ' ' + word : word;
|
|
5553
|
+
if (testLine.length <= maxCharsPerLine) {
|
|
5554
|
+
currentLine = testLine;
|
|
5555
|
+
} else {
|
|
5556
|
+
if (currentLine) lines.push(currentLine);
|
|
5557
|
+
currentLine = word.length > maxCharsPerLine
|
|
5558
|
+
? word.substring(0, maxCharsPerLine - 1) + '…'
|
|
5559
|
+
: word;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
if (currentLine) lines.push(currentLine);
|
|
5563
|
+
|
|
5564
|
+
if (lines.length > 3) {
|
|
5565
|
+
lines.length = 3;
|
|
5566
|
+
lines[2] = lines[2].substring(0, lines[2].length - 1) + '…';
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
return lines.join('\n');
|
|
5570
|
+
}
|
|
5571
|
+
|
|
5572
|
+
function buildGraphData(nodes) {
|
|
5573
|
+
const graphNodes = nodes.map(n => ({
|
|
5574
|
+
id: n.id,
|
|
5575
|
+
title: n.title,
|
|
5576
|
+
status: n.status,
|
|
5577
|
+
type: n.type,
|
|
5578
|
+
priority: n.priority,
|
|
5579
|
+
edges: n.edges || {},
|
|
5580
|
+
_collection: n._collection
|
|
5581
|
+
}));
|
|
5582
|
+
|
|
5583
|
+
const nodeIds = new Set(nodes.map(n => n.id));
|
|
5584
|
+
const graphEdges = [];
|
|
5585
|
+
|
|
5586
|
+
nodes.forEach(node => {
|
|
5587
|
+
Object.entries(node.edges || {}).forEach(([edgeType, edges]) => {
|
|
5588
|
+
edges.forEach(edge => {
|
|
5589
|
+
if (nodeIds.has(edge.target_id)) {
|
|
5590
|
+
graphEdges.push({
|
|
5591
|
+
from: node.id,
|
|
5592
|
+
to: edge.target_id,
|
|
5593
|
+
type: edgeType
|
|
5594
|
+
});
|
|
5595
|
+
}
|
|
5596
|
+
});
|
|
5597
|
+
});
|
|
5598
|
+
});
|
|
5599
|
+
|
|
5600
|
+
return { nodes: graphNodes, edges: graphEdges };
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
// Graph State Management
|
|
5604
|
+
let graphState = {
|
|
5605
|
+
allNodes: [],
|
|
5606
|
+
allEdges: [],
|
|
5607
|
+
visibleNodeIds: new Set(),
|
|
5608
|
+
searchQuery: '',
|
|
5609
|
+
filters: {
|
|
5610
|
+
todo: true,
|
|
5611
|
+
'in-progress': true,
|
|
5612
|
+
blocked: true,
|
|
5613
|
+
done: false
|
|
5614
|
+
}
|
|
5615
|
+
};
|
|
5616
|
+
|
|
5617
|
+
function applyGraphFilters() {
|
|
5618
|
+
const filters = {};
|
|
5619
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
5620
|
+
filters[cb.dataset.status] = cb.checked;
|
|
5621
|
+
});
|
|
5622
|
+
|
|
5623
|
+
graphState.filters = filters;
|
|
5624
|
+
graphState.searchQuery = (document.getElementById('graph-search')?.value || '').toLowerCase();
|
|
5625
|
+
|
|
5626
|
+
// Determine visible nodes
|
|
5627
|
+
graphState.visibleNodeIds = new Set();
|
|
5628
|
+
graphState.allNodes.forEach(node => {
|
|
5629
|
+
const statusMatch = filters[node.status] || false;
|
|
5630
|
+
const searchMatch = !graphState.searchQuery || node.title.toLowerCase().includes(graphState.searchQuery);
|
|
5631
|
+
if (statusMatch && searchMatch) {
|
|
5632
|
+
graphState.visibleNodeIds.add(node.id);
|
|
5633
|
+
}
|
|
5634
|
+
});
|
|
5635
|
+
|
|
5636
|
+
// Update Vis.js network with visible nodes and edges
|
|
5637
|
+
if (visNetwork) {
|
|
5638
|
+
const visibleNodes = graphState.allNodes.filter(n => graphState.visibleNodeIds.has(n.id));
|
|
5639
|
+
const visibleEdges = graphState.allEdges.filter(e =>
|
|
5640
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
5641
|
+
);
|
|
5642
|
+
|
|
5643
|
+
const nodesDataset = new vis.DataSet(visibleNodes.map(n => ({
|
|
5644
|
+
id: n.id,
|
|
5645
|
+
label: wrapText(n.title),
|
|
5646
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
5647
|
+
color: {
|
|
5648
|
+
background: getNodeColor(n),
|
|
5649
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
5650
|
+
highlight: {
|
|
5651
|
+
background: getNodeColor(n),
|
|
5652
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
5653
|
+
}
|
|
5654
|
+
},
|
|
5655
|
+
size: getNodeRadius(n),
|
|
5656
|
+
font: {
|
|
5657
|
+
size: 12,
|
|
5658
|
+
face: "'JetBrains Mono', monospace",
|
|
5659
|
+
color: 'white',
|
|
5660
|
+
strokeWidth: 0
|
|
5661
|
+
},
|
|
5662
|
+
physics: true,
|
|
5663
|
+
borderWidth: 2,
|
|
5664
|
+
status: n.status,
|
|
5665
|
+
_collection: n._collection,
|
|
5666
|
+
x: undefined, // Let physics handle positioning
|
|
5667
|
+
y: undefined
|
|
5668
|
+
})));
|
|
5669
|
+
|
|
5670
|
+
const edgesDataset = new vis.DataSet(visibleEdges.map(e => ({
|
|
5671
|
+
from: e.from,
|
|
5672
|
+
to: e.to,
|
|
5673
|
+
arrows: 'to',
|
|
5674
|
+
smooth: { type: 'continuous' },
|
|
5675
|
+
color: e.type === 'blocked_by'
|
|
5676
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
5677
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
5678
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
5679
|
+
width: 1.5
|
|
5680
|
+
})));
|
|
5681
|
+
|
|
5682
|
+
visNetwork.setData({ nodes: nodesDataset, edges: edgesDataset });
|
|
5683
|
+
}
|
|
5684
|
+
|
|
5685
|
+
updateGraphStats();
|
|
5686
|
+
localStorage.setItem('graphFilters', JSON.stringify(graphState.filters));
|
|
5687
|
+
}
|
|
5688
|
+
|
|
5689
|
+
function updateGraphStats() {
|
|
5690
|
+
const visibleNodeCount = graphState.visibleNodeIds.size;
|
|
5691
|
+
const visibleEdgeCount = graphState.allEdges.filter(e =>
|
|
5692
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
5693
|
+
).length;
|
|
5694
|
+
const nodeCountEl = document.getElementById('graph-node-count');
|
|
5695
|
+
const edgeCountEl = document.getElementById('graph-edge-count');
|
|
5696
|
+
if (nodeCountEl) nodeCountEl.textContent = `${visibleNodeCount} nodes`;
|
|
5697
|
+
if (edgeCountEl) edgeCountEl.textContent = `${visibleEdgeCount} edges`;
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5700
|
+
function resetGraphView() {
|
|
5701
|
+
const searchEl = document.getElementById('graph-search');
|
|
5702
|
+
if (searchEl) searchEl.value = '';
|
|
5703
|
+
graphState.searchQuery = '';
|
|
5704
|
+
applyGraphFilters();
|
|
5705
|
+
if (visNetwork) visNetwork.fit();
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5708
|
+
function showAllNodes() {
|
|
5709
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
5710
|
+
cb.checked = true;
|
|
5711
|
+
});
|
|
5712
|
+
graphState.filters = { todo: true, 'in-progress': true, blocked: true, done: true };
|
|
5713
|
+
applyGraphFilters();
|
|
5714
|
+
if (visNetwork) visNetwork.fit();
|
|
5715
|
+
}
|
|
5716
|
+
|
|
5717
|
+
function renderGraph(nodes) {
|
|
5718
|
+
if (nodes.length === 0) {
|
|
5719
|
+
if (visNetwork) visNetwork.destroy();
|
|
5720
|
+
visNetwork = null;
|
|
5721
|
+
return;
|
|
5722
|
+
}
|
|
5723
|
+
|
|
5724
|
+
const { nodes: graphNodes, edges: graphEdges } = buildGraphData(nodes);
|
|
5725
|
+
|
|
5726
|
+
graphState.allNodes = graphNodes;
|
|
5727
|
+
graphState.allEdges = graphEdges;
|
|
5728
|
+
|
|
5729
|
+
// FILTER FIRST: Apply default filters before rendering
|
|
5730
|
+
// Default: show todo, in-progress, blocked (exclude done items)
|
|
5731
|
+
graphState.visibleNodeIds = new Set();
|
|
5732
|
+
graphState.allNodes.forEach(node => {
|
|
5733
|
+
if (graphState.filters[node.status] !== false) {
|
|
5734
|
+
graphState.visibleNodeIds.add(node.id);
|
|
5735
|
+
}
|
|
5736
|
+
});
|
|
5737
|
+
|
|
5738
|
+
// Only render visible nodes and edges
|
|
5739
|
+
const visibleNodes = graphState.allNodes.filter(n => graphState.visibleNodeIds.has(n.id));
|
|
5740
|
+
const visibleEdges = graphState.allEdges.filter(e =>
|
|
5741
|
+
graphState.visibleNodeIds.has(e.from) && graphState.visibleNodeIds.has(e.to)
|
|
5742
|
+
);
|
|
5743
|
+
|
|
5744
|
+
// Create Vis.js nodes dataset with FILTERED nodes only
|
|
5745
|
+
const nodesData = new vis.DataSet(visibleNodes.map(n => ({
|
|
5746
|
+
id: n.id,
|
|
5747
|
+
label: wrapText(n.title),
|
|
5748
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
5749
|
+
color: {
|
|
5750
|
+
background: getNodeColor(n),
|
|
5751
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
5752
|
+
highlight: {
|
|
5753
|
+
background: getNodeColor(n),
|
|
5754
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
5755
|
+
}
|
|
5756
|
+
},
|
|
5757
|
+
size: getNodeRadius(n),
|
|
5758
|
+
font: {
|
|
5759
|
+
size: 12,
|
|
5760
|
+
face: "'JetBrains Mono', monospace",
|
|
5761
|
+
color: 'white',
|
|
5762
|
+
strokeWidth: 0
|
|
5763
|
+
},
|
|
5764
|
+
physics: true,
|
|
5765
|
+
borderWidth: 2,
|
|
5766
|
+
status: n.status,
|
|
5767
|
+
_collection: n._collection
|
|
5768
|
+
})));
|
|
5769
|
+
|
|
5770
|
+
// Create Vis.js edges dataset with FILTERED edges only
|
|
5771
|
+
const edgesData = new vis.DataSet(visibleEdges.map(e => ({
|
|
5772
|
+
from: e.from,
|
|
5773
|
+
to: e.to,
|
|
5774
|
+
arrows: 'to',
|
|
5775
|
+
smooth: { type: 'continuous' },
|
|
5776
|
+
color: e.type === 'blocked_by'
|
|
5777
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
5778
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
5779
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
5780
|
+
width: 1.5
|
|
5781
|
+
})));
|
|
5782
|
+
|
|
5783
|
+
// Destroy existing network if it exists
|
|
5784
|
+
if (visNetwork) {
|
|
5785
|
+
visNetwork.destroy();
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5788
|
+
// Create new Vis.js network
|
|
5789
|
+
const container = document.getElementById('graph-network');
|
|
5790
|
+
const data = {
|
|
5791
|
+
nodes: nodesData,
|
|
5792
|
+
edges: edgesData
|
|
5793
|
+
};
|
|
5794
|
+
|
|
5795
|
+
// Optimize physics based on node count
|
|
5796
|
+
const nodeCount = visibleNodes.length;
|
|
5797
|
+
const stabilizationIterations = nodeCount > 300 ? 100 : (nodeCount > 150 ? 150 : 200);
|
|
5798
|
+
|
|
5799
|
+
const options = {
|
|
5800
|
+
physics: {
|
|
5801
|
+
enabled: true,
|
|
5802
|
+
stabilization: {
|
|
5803
|
+
iterations: stabilizationIterations,
|
|
5804
|
+
fit: true
|
|
5805
|
+
},
|
|
5806
|
+
barnesHut: {
|
|
5807
|
+
gravitationalConstant: -30000,
|
|
5808
|
+
centralGravity: 0.3,
|
|
5809
|
+
springLength: 200,
|
|
5810
|
+
springConstant: 0.04
|
|
5811
|
+
},
|
|
5812
|
+
maxVelocity: 50
|
|
5813
|
+
},
|
|
5814
|
+
interaction: {
|
|
5815
|
+
navigationButtons: true,
|
|
5816
|
+
keyboard: true,
|
|
5817
|
+
zoomView: true,
|
|
5818
|
+
dragView: true
|
|
5819
|
+
},
|
|
5820
|
+
nodes: {
|
|
5821
|
+
shape: 'circle',
|
|
5822
|
+
scaling: {
|
|
5823
|
+
min: 10,
|
|
5824
|
+
max: 50
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
};
|
|
5828
|
+
|
|
5829
|
+
visNetwork = new vis.Network(container, data, options);
|
|
5830
|
+
|
|
5831
|
+
// Handle node clicks
|
|
5832
|
+
visNetwork.on('click', (params) => {
|
|
5833
|
+
if (params.nodes.length > 0) {
|
|
5834
|
+
const nodeId = params.nodes[0];
|
|
5835
|
+
const node = graphState.allNodes.find(n => n.id === nodeId);
|
|
5836
|
+
if (node) {
|
|
5837
|
+
openPanel(node._collection, node.id);
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
});
|
|
5841
|
+
|
|
5842
|
+
// Apply filters after network is initialized
|
|
5843
|
+
applyGraphFilters();
|
|
4090
5844
|
}
|
|
4091
5845
|
|
|
4092
5846
|
// =====================================================================
|
|
4093
|
-
//
|
|
5847
|
+
// Agent Skills Analysis
|
|
4094
5848
|
// =====================================================================
|
|
4095
5849
|
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
5850
|
+
function analyzeAgentSkills(sessions) {
|
|
5851
|
+
const skillProfiles = {};
|
|
5852
|
+
const agents = [...new Set(sessions.map(s => s.properties?.agent).filter(Boolean))];
|
|
5853
|
+
agents.forEach(agent => {
|
|
5854
|
+
skillProfiles[agent] = {Implementation: 0, Analysis: 0, Testing: 0, Documentation: 0, Coordination: 0};
|
|
5855
|
+
});
|
|
5856
|
+
sessions.forEach(session => {
|
|
5857
|
+
const agent = session.properties?.agent;
|
|
5858
|
+
if (!agent) return;
|
|
5859
|
+
const desc = (session.name || session.id || '').toLowerCase();
|
|
5860
|
+
const cnt = session.properties?.event_count || 0;
|
|
5861
|
+
if (desc.includes('test') || desc.includes('validate')) skillProfiles[agent].Testing += Math.min(cnt / 10, 2);
|
|
5862
|
+
if (desc.includes('implement') || desc.includes('code') || desc.includes('build')) skillProfiles[agent].Implementation += Math.min(cnt / 10, 2);
|
|
5863
|
+
if (desc.includes('analyze') || desc.includes('research')) skillProfiles[agent].Analysis += Math.min(cnt / 10, 2);
|
|
5864
|
+
if (desc.includes('document') || desc.includes('explain')) skillProfiles[agent].Documentation += Math.min(cnt / 10, 2);
|
|
5865
|
+
if (desc.includes('coordinate') || desc.includes('delegate')) skillProfiles[agent].Coordination += Math.min(cnt / 10, 2);
|
|
5866
|
+
if (agent.includes('Claude')) {
|
|
5867
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
5868
|
+
skillProfiles[agent].Documentation = Math.max(skillProfiles[agent].Documentation, 4);
|
|
5869
|
+
}
|
|
5870
|
+
if (agent.includes('Codex')) {
|
|
5871
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 5);
|
|
5872
|
+
skillProfiles[agent].Testing = Math.max(skillProfiles[agent].Testing, 4);
|
|
5873
|
+
}
|
|
5874
|
+
if (agent.includes('Orchestrator')) {
|
|
5875
|
+
skillProfiles[agent].Coordination = Math.max(skillProfiles[agent].Coordination, 5);
|
|
5876
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4);
|
|
5877
|
+
}
|
|
5878
|
+
if (agent.includes('Gemini')) {
|
|
5879
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
5880
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 3);
|
|
5881
|
+
}
|
|
5882
|
+
});
|
|
5883
|
+
agents.forEach(agent => {
|
|
5884
|
+
Object.keys(skillProfiles[agent]).forEach(skill => {
|
|
5885
|
+
skillProfiles[agent][skill] = Math.min(5, Math.max(1, skillProfiles[agent][skill]));
|
|
5886
|
+
});
|
|
5887
|
+
});
|
|
5888
|
+
return { agents, skillProfiles };
|
|
4106
5889
|
}
|
|
4107
5890
|
|
|
4108
|
-
function
|
|
4109
|
-
|
|
4110
|
-
.reduce((sum, edges) => sum + edges.length, 0);
|
|
4111
|
-
return Math.min(45 + edgeCount * 3, 60);
|
|
5891
|
+
function getProficiencyColor(level) {
|
|
5892
|
+
return `proficiency-${Math.round(level)}`;
|
|
4112
5893
|
}
|
|
4113
5894
|
|
|
4114
|
-
function
|
|
4115
|
-
const
|
|
4116
|
-
|
|
4117
|
-
|
|
5895
|
+
function getProficiencyLabel(level) {
|
|
5896
|
+
const labels = ['', 'Novice', 'Beginner', 'Intermediate', 'Advanced', 'Expert'];
|
|
5897
|
+
return labels[Math.round(level)] || 'Expert';
|
|
5898
|
+
}
|
|
4118
5899
|
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
5900
|
+
function renderSkillsMatrix(agents, skillProfiles) {
|
|
5901
|
+
const skills = ['Implementation', 'Analysis', 'Testing', 'Documentation', 'Coordination'];
|
|
5902
|
+
let html = '<div class="skills-matrix">';
|
|
5903
|
+
html += '<div class="skills-matrix-cell skills-matrix-header-row">AGENT</div>';
|
|
5904
|
+
skills.forEach(skill => html += `<div class="skills-matrix-cell skills-matrix-header-row">${skill}</div>`);
|
|
5905
|
+
agents.forEach(agent => {
|
|
5906
|
+
html += `<div class="skills-matrix-cell skills-matrix-agent-name">${agent}</div>`;
|
|
5907
|
+
skills.forEach(skill => {
|
|
5908
|
+
const level = skillProfiles[agent][skill];
|
|
5909
|
+
const rnd = Math.round(level);
|
|
5910
|
+
html += `<div class="skills-matrix-cell"><div class="proficiency-dot ${getProficiencyColor(level)}" title="${getProficiencyLabel(level)} (${rnd}/5)">${rnd}</div></div>`;
|
|
5911
|
+
});
|
|
5912
|
+
});
|
|
5913
|
+
html += '</div><div class="skill-category-legend"><div style="font-weight: 600; width: 100%; margin-bottom: 0.5rem;">Proficiency Scale:</div>';
|
|
5914
|
+
for (let i = 1; i <= 5; i++) {
|
|
5915
|
+
html += `<div class="skill-category-item"><span class="proficiency-dot proficiency-${i}">${i}</span> ${getProficiencyLabel(i)}</div>`;
|
|
4129
5916
|
}
|
|
4130
|
-
|
|
5917
|
+
html += '</div>';
|
|
5918
|
+
return html;
|
|
5919
|
+
}
|
|
4131
5920
|
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
5921
|
+
async function loadAndRenderAgents() {
|
|
5922
|
+
const el = document.getElementById('skills-matrix-content');
|
|
5923
|
+
try {
|
|
5924
|
+
let sessions = allSessions;
|
|
5925
|
+
if (!sessions.length) {
|
|
5926
|
+
const r = await fetch(`${API}/sessions`);
|
|
5927
|
+
if (!r.ok) throw new Error('Failed to load');
|
|
5928
|
+
sessions = (await r.json()).nodes || [];
|
|
5929
|
+
}
|
|
5930
|
+
if (!sessions.length) {
|
|
5931
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents found</div>';
|
|
5932
|
+
return;
|
|
5933
|
+
}
|
|
5934
|
+
const { agents, skillProfiles } = analyzeAgentSkills(sessions);
|
|
5935
|
+
if (!agents.length) {
|
|
5936
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents</div>';
|
|
5937
|
+
return;
|
|
5938
|
+
}
|
|
5939
|
+
el.innerHTML = renderSkillsMatrix(agents, skillProfiles);
|
|
5940
|
+
} catch (e) {
|
|
5941
|
+
el.innerHTML = `<div style="padding: 2rem; color: red;">Error: ${e.message}</div>`;
|
|
4135
5942
|
}
|
|
4136
|
-
|
|
4137
|
-
return lines;
|
|
4138
5943
|
}
|
|
4139
5944
|
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
title: n.title,
|
|
4144
|
-
status: n.status,
|
|
4145
|
-
type: n.type,
|
|
4146
|
-
priority: n.priority,
|
|
4147
|
-
edges: n.edges || {},
|
|
4148
|
-
_collection: n._collection,
|
|
4149
|
-
x: null,
|
|
4150
|
-
y: null
|
|
4151
|
-
}));
|
|
5945
|
+
// =====================================================================
|
|
5946
|
+
// View Toggle
|
|
5947
|
+
// =====================================================================
|
|
4152
5948
|
|
|
4153
|
-
|
|
4154
|
-
const
|
|
5949
|
+
function switchView(view) {
|
|
5950
|
+
const kanban = document.getElementById('kanban');
|
|
5951
|
+
const graph = document.getElementById('graph-container');
|
|
5952
|
+
const analytics = document.getElementById('analytics');
|
|
5953
|
+
const agents = document.getElementById('agents');
|
|
5954
|
+
const sessions = document.getElementById('sessions');
|
|
5955
|
+
const buttons = document.querySelectorAll('.view-btn');
|
|
4155
5956
|
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
edges.forEach(edge => {
|
|
4159
|
-
if (nodeIds.has(edge.target_id)) {
|
|
4160
|
-
graphEdges.push({
|
|
4161
|
-
source: node.id,
|
|
4162
|
-
target: edge.target_id,
|
|
4163
|
-
type: edgeType
|
|
4164
|
-
});
|
|
4165
|
-
}
|
|
4166
|
-
});
|
|
4167
|
-
});
|
|
5957
|
+
buttons.forEach(btn => {
|
|
5958
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
4168
5959
|
});
|
|
4169
5960
|
|
|
4170
|
-
|
|
5961
|
+
if (view === 'kanban') {
|
|
5962
|
+
kanban.classList.add('active');
|
|
5963
|
+
graph.classList.remove('active');
|
|
5964
|
+
analytics.classList.remove('active');
|
|
5965
|
+
agents.classList.remove('active');
|
|
5966
|
+
sessions.classList.remove('active');
|
|
5967
|
+
renderKanban(allNodes);
|
|
5968
|
+
} else if (view === 'graph') {
|
|
5969
|
+
kanban.classList.remove('active');
|
|
5970
|
+
graph.classList.add('active');
|
|
5971
|
+
analytics.classList.remove('active');
|
|
5972
|
+
agents.classList.remove('active');
|
|
5973
|
+
sessions.classList.remove('active');
|
|
5974
|
+
renderGraph(allNodes);
|
|
5975
|
+
} else if (view === 'analytics') {
|
|
5976
|
+
kanban.classList.remove('active');
|
|
5977
|
+
graph.classList.remove('active');
|
|
5978
|
+
analytics.classList.add('active');
|
|
5979
|
+
agents.classList.remove('active');
|
|
5980
|
+
sessions.classList.remove('active');
|
|
5981
|
+
ensureAnalyticsLoaded(false);
|
|
5982
|
+
} else if (view === 'agents') {
|
|
5983
|
+
kanban.classList.remove('active');
|
|
5984
|
+
graph.classList.remove('active');
|
|
5985
|
+
analytics.classList.remove('active');
|
|
5986
|
+
agents.classList.add('active');
|
|
5987
|
+
sessions.classList.remove('active');
|
|
5988
|
+
loadAndRenderAgents();
|
|
5989
|
+
} else if (view === 'sessions') {
|
|
5990
|
+
kanban.classList.remove('active');
|
|
5991
|
+
graph.classList.remove('active');
|
|
5992
|
+
analytics.classList.remove('active');
|
|
5993
|
+
agents.classList.remove('active');
|
|
5994
|
+
sessions.classList.add('active');
|
|
5995
|
+
loadAndRenderSessions();
|
|
5996
|
+
}
|
|
4171
5997
|
}
|
|
4172
5998
|
|
|
4173
|
-
function renderGraph(nodes) {
|
|
4174
|
-
const svg = document.getElementById('graph-svg');
|
|
4175
|
-
const edgesGroup = document.getElementById('graph-edges');
|
|
4176
|
-
const nodesGroup = document.getElementById('graph-nodes');
|
|
4177
5999
|
|
|
4178
|
-
|
|
4179
|
-
|
|
6000
|
+
// =====================================================================
|
|
6001
|
+
// Init
|
|
6002
|
+
// =====================================================================
|
|
4180
6003
|
|
|
4181
|
-
|
|
6004
|
+
document.getElementById('panel-close').addEventListener('click', closePanel);
|
|
6005
|
+
document.getElementById('panel-overlay').addEventListener('click', closePanel);
|
|
4182
6006
|
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
6007
|
+
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
6008
|
+
btn.addEventListener('click', () => switchView(btn.dataset.view));
|
|
6009
|
+
});
|
|
4186
6010
|
|
|
4187
|
-
|
|
4188
|
-
|
|
6011
|
+
document.getElementById('analytics-refresh').addEventListener('click', () => {
|
|
6012
|
+
ensureAnalyticsLoaded(true);
|
|
6013
|
+
});
|
|
4189
6014
|
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
6015
|
+
document.getElementById('analytics-features').addEventListener('click', (e) => {
|
|
6016
|
+
const btn = e.target.closest && e.target.closest('button[data-feature]');
|
|
6017
|
+
if (!btn) return;
|
|
6018
|
+
loadFeatureAnalytics(btn.dataset.feature).catch(err => renderAnalyticsError(err));
|
|
6019
|
+
});
|
|
4195
6020
|
|
|
4196
|
-
|
|
6021
|
+
// Session filter event listeners
|
|
6022
|
+
document.getElementById('filter-status').addEventListener('change', applySessionFilters);
|
|
6023
|
+
document.getElementById('filter-agent').addEventListener('change', applySessionFilters);
|
|
6024
|
+
document.getElementById('filter-search').addEventListener('input', applySessionFilters);
|
|
6025
|
+
document.getElementById('filter-date-from').addEventListener('change', applySessionFilters);
|
|
6026
|
+
document.getElementById('filter-date-to').addEventListener('change', applySessionFilters);
|
|
6027
|
+
document.getElementById('filter-clear').addEventListener('click', clearSessionFilters);
|
|
6028
|
+
document.getElementById('compare-sessions-btn').addEventListener('click', compareSessions);
|
|
4197
6029
|
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
.distance(120)
|
|
4202
|
-
.strength(0.5))
|
|
4203
|
-
.force('charge', d3.forceManyBody()
|
|
4204
|
-
.strength(-400))
|
|
4205
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
4206
|
-
.force('collision', d3.forceCollide().radius(70))
|
|
4207
|
-
.on('tick', updatePositions);
|
|
6030
|
+
// Session comparison modal
|
|
6031
|
+
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
6032
|
+
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4208
6033
|
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
line.dataset.source = edge.source.id || edge.source;
|
|
4214
|
-
line.dataset.target = edge.target.id || edge.target;
|
|
4215
|
-
edgesGroup.appendChild(line);
|
|
4216
|
-
});
|
|
6034
|
+
// Graph filter event listeners
|
|
6035
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6036
|
+
cb.addEventListener('change', applyGraphFilters);
|
|
6037
|
+
});
|
|
4217
6038
|
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
g.dataset.collection = node._collection;
|
|
6039
|
+
const graphSearchEl = document.getElementById('graph-search');
|
|
6040
|
+
if (graphSearchEl) {
|
|
6041
|
+
graphSearchEl.addEventListener('input', applyGraphFilters);
|
|
6042
|
+
}
|
|
4223
6043
|
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
6044
|
+
const graphResetBtn = document.getElementById('graph-reset');
|
|
6045
|
+
if (graphResetBtn) {
|
|
6046
|
+
graphResetBtn.addEventListener('click', resetGraphView);
|
|
6047
|
+
}
|
|
4228
6048
|
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
lines.forEach((line, i) => {
|
|
4235
|
-
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
|
4236
|
-
tspan.setAttribute('x', '0');
|
|
4237
|
-
tspan.setAttribute('dy', i === 0 ? `${startY}px` : `${lineHeight}px`);
|
|
4238
|
-
tspan.textContent = line;
|
|
4239
|
-
text.appendChild(tspan);
|
|
4240
|
-
});
|
|
6049
|
+
const graphShowAllBtn = document.getElementById('graph-show-all');
|
|
6050
|
+
if (graphShowAllBtn) {
|
|
6051
|
+
graphShowAllBtn.addEventListener('click', showAllNodes);
|
|
6052
|
+
}
|
|
4241
6053
|
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
6054
|
+
document.addEventListener('keydown', (e) => {
|
|
6055
|
+
if (e.key === 'Escape') closePanel();
|
|
6056
|
+
});
|
|
4245
6057
|
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
6058
|
+
loadData().then(async ({ status, nodes }) => {
|
|
6059
|
+
await renderKanban(nodes);
|
|
6060
|
+
updateKanbanGrid();
|
|
6061
|
+
}).catch(err => {
|
|
6062
|
+
console.error('Error loading dashboard data:', err);
|
|
6063
|
+
});
|
|
4249
6064
|
|
|
4250
|
-
|
|
4251
|
-
|
|
6065
|
+
function showAllNodes() {
|
|
6066
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6067
|
+
cb.checked = true;
|
|
6068
|
+
});
|
|
6069
|
+
graphState.filters = { todo: true, 'in-progress': true, blocked: true, done: true };
|
|
6070
|
+
applyGraphFilters();
|
|
6071
|
+
}
|
|
4252
6072
|
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
6073
|
+
function renderGraph(nodes) {
|
|
6074
|
+
if (nodes.length === 0) {
|
|
6075
|
+
if (visNetwork) visNetwork.destroy();
|
|
6076
|
+
visNetwork = null;
|
|
6077
|
+
return;
|
|
6078
|
+
}
|
|
4259
6079
|
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
6080
|
+
const { nodes: graphNodes, edges: graphEdges } = buildGraphData(nodes);
|
|
6081
|
+
|
|
6082
|
+
graphState.allNodes = graphNodes;
|
|
6083
|
+
graphState.allEdges = graphEdges;
|
|
6084
|
+
|
|
6085
|
+
// Create Vis.js nodes dataset
|
|
6086
|
+
const nodesData = new vis.DataSet(graphNodes.map(n => ({
|
|
6087
|
+
id: n.id,
|
|
6088
|
+
label: wrapText(n.title),
|
|
6089
|
+
title: n.title + '\nStatus: ' + n.status,
|
|
6090
|
+
color: {
|
|
6091
|
+
background: getNodeColor(n),
|
|
6092
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--border-strong').trim(),
|
|
6093
|
+
highlight: {
|
|
6094
|
+
background: getNodeColor(n),
|
|
6095
|
+
border: getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()
|
|
4264
6096
|
}
|
|
4265
|
-
}
|
|
6097
|
+
},
|
|
6098
|
+
size: getNodeRadius(n),
|
|
6099
|
+
font: {
|
|
6100
|
+
size: 12,
|
|
6101
|
+
face: "'JetBrains Mono', monospace",
|
|
6102
|
+
color: 'white',
|
|
6103
|
+
strokeWidth: 0
|
|
6104
|
+
},
|
|
6105
|
+
physics: true,
|
|
6106
|
+
borderWidth: 2,
|
|
6107
|
+
status: n.status,
|
|
6108
|
+
_collection: n._collection
|
|
6109
|
+
})));
|
|
6110
|
+
|
|
6111
|
+
// Create Vis.js edges dataset
|
|
6112
|
+
const edgesData = new vis.DataSet(graphEdges.map(e => ({
|
|
6113
|
+
from: e.from,
|
|
6114
|
+
to: e.to,
|
|
6115
|
+
arrows: 'to',
|
|
6116
|
+
smooth: { type: 'continuous' },
|
|
6117
|
+
color: e.type === 'blocked_by'
|
|
6118
|
+
? { color: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-blocked').trim() }
|
|
6119
|
+
: { color: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim(), highlight: getComputedStyle(document.documentElement).getPropertyValue('--status-active').trim() },
|
|
6120
|
+
dashes: e.type === 'blocked_by' ? [6, 4] : false,
|
|
6121
|
+
width: 1.5
|
|
6122
|
+
})));
|
|
6123
|
+
|
|
6124
|
+
// Destroy existing network if it exists
|
|
6125
|
+
if (visNetwork) {
|
|
6126
|
+
visNetwork.destroy();
|
|
6127
|
+
}
|
|
4266
6128
|
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
6129
|
+
// Create new Vis.js network
|
|
6130
|
+
const container = document.getElementById('graph-network');
|
|
6131
|
+
const data = {
|
|
6132
|
+
nodes: nodesData,
|
|
6133
|
+
edges: edgesData
|
|
6134
|
+
};
|
|
6135
|
+
|
|
6136
|
+
const options = {
|
|
6137
|
+
physics: {
|
|
6138
|
+
enabled: true,
|
|
6139
|
+
stabilization: {
|
|
6140
|
+
iterations: 200,
|
|
6141
|
+
fit: true
|
|
6142
|
+
},
|
|
6143
|
+
barnesHut: {
|
|
6144
|
+
gravitationalConstant: -30000,
|
|
6145
|
+
centralGravity: 0.3,
|
|
6146
|
+
springLength: 200,
|
|
6147
|
+
springConstant: 0.04
|
|
6148
|
+
},
|
|
6149
|
+
maxVelocity: 50
|
|
6150
|
+
},
|
|
6151
|
+
interaction: {
|
|
6152
|
+
navigationButtons: true,
|
|
6153
|
+
keyboard: true,
|
|
6154
|
+
zoomView: true,
|
|
6155
|
+
dragView: true
|
|
6156
|
+
},
|
|
6157
|
+
nodes: {
|
|
6158
|
+
shape: 'circle',
|
|
6159
|
+
scaling: {
|
|
6160
|
+
min: 10,
|
|
6161
|
+
max: 50
|
|
4273
6162
|
}
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
6163
|
+
}
|
|
6164
|
+
};
|
|
4276
6165
|
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
6166
|
+
visNetwork = new vis.Network(container, data, options);
|
|
6167
|
+
|
|
6168
|
+
// Handle node clicks
|
|
6169
|
+
visNetwork.on('click', (params) => {
|
|
6170
|
+
if (params.nodes.length > 0) {
|
|
6171
|
+
const nodeId = params.nodes[0];
|
|
6172
|
+
const node = graphState.allNodes.find(n => n.id === nodeId);
|
|
4281
6173
|
if (node) {
|
|
4282
|
-
node.
|
|
4283
|
-
node.y = Math.max(30, Math.min(height - 30, node.y));
|
|
4284
|
-
g.setAttribute('transform', `translate(${node.x}, ${node.y})`);
|
|
6174
|
+
openPanel(node._collection, node.id);
|
|
4285
6175
|
}
|
|
6176
|
+
}
|
|
6177
|
+
});
|
|
6178
|
+
|
|
6179
|
+
// Apply filters after network is initialized
|
|
6180
|
+
applyGraphFilters();
|
|
6181
|
+
}
|
|
6182
|
+
|
|
6183
|
+
// =====================================================================
|
|
6184
|
+
// Agent Skills Analysis
|
|
6185
|
+
// =====================================================================
|
|
6186
|
+
|
|
6187
|
+
function analyzeAgentSkills(sessions) {
|
|
6188
|
+
const skillProfiles = {};
|
|
6189
|
+
const agents = [...new Set(sessions.map(s => s.properties?.agent).filter(Boolean))];
|
|
6190
|
+
agents.forEach(agent => {
|
|
6191
|
+
skillProfiles[agent] = {Implementation: 0, Analysis: 0, Testing: 0, Documentation: 0, Coordination: 0};
|
|
6192
|
+
});
|
|
6193
|
+
sessions.forEach(session => {
|
|
6194
|
+
const agent = session.properties?.agent;
|
|
6195
|
+
if (!agent) return;
|
|
6196
|
+
const desc = (session.name || session.id || '').toLowerCase();
|
|
6197
|
+
const cnt = session.properties?.event_count || 0;
|
|
6198
|
+
if (desc.includes('test') || desc.includes('validate')) skillProfiles[agent].Testing += Math.min(cnt / 10, 2);
|
|
6199
|
+
if (desc.includes('implement') || desc.includes('code') || desc.includes('build')) skillProfiles[agent].Implementation += Math.min(cnt / 10, 2);
|
|
6200
|
+
if (desc.includes('analyze') || desc.includes('research')) skillProfiles[agent].Analysis += Math.min(cnt / 10, 2);
|
|
6201
|
+
if (desc.includes('document') || desc.includes('explain')) skillProfiles[agent].Documentation += Math.min(cnt / 10, 2);
|
|
6202
|
+
if (desc.includes('coordinate') || desc.includes('delegate')) skillProfiles[agent].Coordination += Math.min(cnt / 10, 2);
|
|
6203
|
+
if (agent.includes('Claude')) {
|
|
6204
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6205
|
+
skillProfiles[agent].Documentation = Math.max(skillProfiles[agent].Documentation, 4);
|
|
6206
|
+
}
|
|
6207
|
+
if (agent.includes('Codex')) {
|
|
6208
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 5);
|
|
6209
|
+
skillProfiles[agent].Testing = Math.max(skillProfiles[agent].Testing, 4);
|
|
6210
|
+
}
|
|
6211
|
+
if (agent.includes('Orchestrator')) {
|
|
6212
|
+
skillProfiles[agent].Coordination = Math.max(skillProfiles[agent].Coordination, 5);
|
|
6213
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4);
|
|
6214
|
+
}
|
|
6215
|
+
if (agent.includes('Gemini')) {
|
|
6216
|
+
skillProfiles[agent].Analysis = Math.max(skillProfiles[agent].Analysis, 4.5);
|
|
6217
|
+
skillProfiles[agent].Implementation = Math.max(skillProfiles[agent].Implementation, 3);
|
|
6218
|
+
}
|
|
6219
|
+
});
|
|
6220
|
+
agents.forEach(agent => {
|
|
6221
|
+
Object.keys(skillProfiles[agent]).forEach(skill => {
|
|
6222
|
+
skillProfiles[agent][skill] = Math.min(5, Math.max(1, skillProfiles[agent][skill]));
|
|
4286
6223
|
});
|
|
6224
|
+
});
|
|
6225
|
+
return { agents, skillProfiles };
|
|
6226
|
+
}
|
|
4287
6227
|
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
6228
|
+
function getProficiencyColor(level) {
|
|
6229
|
+
return `proficiency-${Math.round(level)}`;
|
|
6230
|
+
}
|
|
6231
|
+
|
|
6232
|
+
function getProficiencyLabel(level) {
|
|
6233
|
+
const labels = ['', 'Novice', 'Beginner', 'Intermediate', 'Advanced', 'Expert'];
|
|
6234
|
+
return labels[Math.round(level)] || 'Expert';
|
|
6235
|
+
}
|
|
6236
|
+
|
|
6237
|
+
function renderSkillsMatrix(agents, skillProfiles) {
|
|
6238
|
+
const skills = ['Implementation', 'Analysis', 'Testing', 'Documentation', 'Coordination'];
|
|
6239
|
+
let html = '<div class="skills-matrix">';
|
|
6240
|
+
html += '<div class="skills-matrix-cell skills-matrix-header-row">AGENT</div>';
|
|
6241
|
+
skills.forEach(skill => html += `<div class="skills-matrix-cell skills-matrix-header-row">${skill}</div>`);
|
|
6242
|
+
agents.forEach(agent => {
|
|
6243
|
+
html += `<div class="skills-matrix-cell skills-matrix-agent-name">${agent}</div>`;
|
|
6244
|
+
skills.forEach(skill => {
|
|
6245
|
+
const level = skillProfiles[agent][skill];
|
|
6246
|
+
const rnd = Math.round(level);
|
|
6247
|
+
html += `<div class="skills-matrix-cell"><div class="proficiency-dot ${getProficiencyColor(level)}" title="${getProficiencyLabel(level)} (${rnd}/5)">${rnd}</div></div>`;
|
|
4298
6248
|
});
|
|
6249
|
+
});
|
|
6250
|
+
html += '</div><div class="skill-category-legend"><div style="font-weight: 600; width: 100%; margin-bottom: 0.5rem;">Proficiency Scale:</div>';
|
|
6251
|
+
for (let i = 1; i <= 5; i++) {
|
|
6252
|
+
html += `<div class="skill-category-item"><span class="proficiency-dot proficiency-${i}">${i}</span> ${getProficiencyLabel(i)}</div>`;
|
|
6253
|
+
}
|
|
6254
|
+
html += '</div>';
|
|
6255
|
+
return html;
|
|
6256
|
+
}
|
|
6257
|
+
|
|
6258
|
+
async function loadAndRenderAgents() {
|
|
6259
|
+
const el = document.getElementById('skills-matrix-content');
|
|
6260
|
+
try {
|
|
6261
|
+
let sessions = allSessions;
|
|
6262
|
+
if (!sessions.length) {
|
|
6263
|
+
const r = await fetch(`${API}/sessions`);
|
|
6264
|
+
if (!r.ok) throw new Error('Failed to load');
|
|
6265
|
+
sessions = (await r.json()).nodes || [];
|
|
6266
|
+
}
|
|
6267
|
+
if (!sessions.length) {
|
|
6268
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents found</div>';
|
|
6269
|
+
return;
|
|
6270
|
+
}
|
|
6271
|
+
const { agents, skillProfiles } = analyzeAgentSkills(sessions);
|
|
6272
|
+
if (!agents.length) {
|
|
6273
|
+
el.innerHTML = '<div style="padding: 2rem; color: var(--text-muted);">No agents</div>';
|
|
6274
|
+
return;
|
|
6275
|
+
}
|
|
6276
|
+
el.innerHTML = renderSkillsMatrix(agents, skillProfiles);
|
|
6277
|
+
} catch (e) {
|
|
6278
|
+
el.innerHTML = `<div style="padding: 2rem; color: red;">Error: ${e.message}</div>`;
|
|
4299
6279
|
}
|
|
4300
6280
|
}
|
|
4301
6281
|
|
|
@@ -4307,6 +6287,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4307
6287
|
const kanban = document.getElementById('kanban');
|
|
4308
6288
|
const graph = document.getElementById('graph-container');
|
|
4309
6289
|
const analytics = document.getElementById('analytics');
|
|
6290
|
+
const agents = document.getElementById('agents');
|
|
4310
6291
|
const sessions = document.getElementById('sessions');
|
|
4311
6292
|
const buttons = document.querySelectorAll('.view-btn');
|
|
4312
6293
|
|
|
@@ -4318,29 +6299,41 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4318
6299
|
kanban.classList.add('active');
|
|
4319
6300
|
graph.classList.remove('active');
|
|
4320
6301
|
analytics.classList.remove('active');
|
|
6302
|
+
agents.classList.remove('active');
|
|
4321
6303
|
sessions.classList.remove('active');
|
|
4322
6304
|
renderKanban(allNodes);
|
|
4323
6305
|
} else if (view === 'graph') {
|
|
4324
6306
|
kanban.classList.remove('active');
|
|
4325
6307
|
graph.classList.add('active');
|
|
4326
6308
|
analytics.classList.remove('active');
|
|
6309
|
+
agents.classList.remove('active');
|
|
4327
6310
|
sessions.classList.remove('active');
|
|
4328
6311
|
renderGraph(allNodes);
|
|
4329
6312
|
} else if (view === 'analytics') {
|
|
4330
6313
|
kanban.classList.remove('active');
|
|
4331
6314
|
graph.classList.remove('active');
|
|
4332
6315
|
analytics.classList.add('active');
|
|
6316
|
+
agents.classList.remove('active');
|
|
4333
6317
|
sessions.classList.remove('active');
|
|
4334
6318
|
ensureAnalyticsLoaded(false);
|
|
6319
|
+
} else if (view === 'agents') {
|
|
6320
|
+
kanban.classList.remove('active');
|
|
6321
|
+
graph.classList.remove('active');
|
|
6322
|
+
analytics.classList.remove('active');
|
|
6323
|
+
agents.classList.add('active');
|
|
6324
|
+
sessions.classList.remove('active');
|
|
6325
|
+
loadAndRenderAgents();
|
|
4335
6326
|
} else if (view === 'sessions') {
|
|
4336
6327
|
kanban.classList.remove('active');
|
|
4337
6328
|
graph.classList.remove('active');
|
|
4338
6329
|
analytics.classList.remove('active');
|
|
6330
|
+
agents.classList.remove('active');
|
|
4339
6331
|
sessions.classList.add('active');
|
|
4340
6332
|
loadAndRenderSessions();
|
|
4341
6333
|
}
|
|
4342
6334
|
}
|
|
4343
6335
|
|
|
6336
|
+
|
|
4344
6337
|
// =====================================================================
|
|
4345
6338
|
// Init
|
|
4346
6339
|
// =====================================================================
|
|
@@ -4375,6 +6368,11 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4375
6368
|
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
4376
6369
|
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4377
6370
|
|
|
6371
|
+
// Graph filter event listeners
|
|
6372
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6373
|
+
cb.addEventListener('change', applyGraphFilters);
|
|
6374
|
+
});
|
|
6375
|
+
|
|
4378
6376
|
document.addEventListener('keydown', (e) => {
|
|
4379
6377
|
if (e.key === 'Escape') closePanel();
|
|
4380
6378
|
});
|