htmlgraph 0.9.3__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 +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- 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 +192 -100
- 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 +190 -14
- htmlgraph/analytics_index.py +135 -51
- 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 +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- 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 +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- 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 +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- 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 +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- 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 +45 -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 +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- 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 +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- 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/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- 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/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- 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 +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -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 -->
|
|
@@ -2663,73 +3776,294 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2663
3776
|
}
|
|
2664
3777
|
}
|
|
2665
3778
|
|
|
2666
|
-
async function loadAndRenderCommitDag(featureId) {
|
|
2667
|
-
const data = await fetchAnalytics('commit-graph', { feature_id: featureId });
|
|
2668
|
-
renderCommitDag(featureId, data.graph || {});
|
|
2669
|
-
}
|
|
3779
|
+
async function loadAndRenderCommitDag(featureId) {
|
|
3780
|
+
const data = await fetchAnalytics('commit-graph', { feature_id: featureId });
|
|
3781
|
+
renderCommitDag(featureId, data.graph || {});
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
function renderAnalyticsError(err) {
|
|
3785
|
+
const msg = err && err.message ? err.message : String(err);
|
|
3786
|
+
const overview = document.getElementById('analytics-overview');
|
|
3787
|
+
const tools = document.getElementById('analytics-tools');
|
|
3788
|
+
const features = document.getElementById('analytics-features');
|
|
3789
|
+
const continuity = document.getElementById('analytics-continuity');
|
|
3790
|
+
const commits = document.getElementById('analytics-commits');
|
|
3791
|
+
const commitDag = document.getElementById('analytics-commit-dag');
|
|
3792
|
+
const hint = window.location.protocol === 'file:'
|
|
3793
|
+
? 'You opened this dashboard as a local file. Run <span class="mono">htmlgraph serve</span> and open <span class="mono">http://localhost:8080</span>, or pass <span class="mono">?api=http://localhost:8080/api</span>.'
|
|
3794
|
+
: 'Ensure <span class="mono">htmlgraph serve</span> is running and accessible from this page.';
|
|
3795
|
+
|
|
3796
|
+
const html = `
|
|
3797
|
+
<h3>Analytics Unavailable</h3>
|
|
3798
|
+
<p class="analytics-note">${msg}</p>
|
|
3799
|
+
<p class="analytics-note">${hint}</p>
|
|
3800
|
+
<p class="analytics-note">Build the index:</p>
|
|
3801
|
+
<pre class="mono">PYTHONPATH=src/python .venv/bin/python -m htmlgraph.cli events export-sessions -g .htmlgraph\nPYTHONPATH=src/python .venv/bin/python -m htmlgraph.cli index rebuild -g .htmlgraph</pre>
|
|
3802
|
+
`;
|
|
3803
|
+
overview.innerHTML = html;
|
|
3804
|
+
tools.innerHTML = html;
|
|
3805
|
+
features.innerHTML = html;
|
|
3806
|
+
continuity.innerHTML = html;
|
|
3807
|
+
commits.innerHTML = html;
|
|
3808
|
+
if (commitDag) commitDag.innerHTML = html;
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3811
|
+
async function loadAndRenderAnalyticsBase() {
|
|
3812
|
+
const [overview, features, transitions] = await Promise.all([
|
|
3813
|
+
fetchAnalytics('overview'),
|
|
3814
|
+
fetchAnalytics('features', { limit: 50 }).then(r => r.features),
|
|
3815
|
+
fetchAnalytics('transitions', { limit: 25 }).then(r => r.transitions),
|
|
3816
|
+
]);
|
|
3817
|
+
|
|
3818
|
+
analyticsCache.overview = overview;
|
|
3819
|
+
analyticsCache.features = features;
|
|
3820
|
+
analyticsCache.transitions = transitions;
|
|
3821
|
+
analyticsLoadedAt = Date.now();
|
|
3822
|
+
|
|
3823
|
+
renderAnalyticsOverview(overview);
|
|
3824
|
+
renderAnalyticsTools(transitions);
|
|
3825
|
+
renderAnalyticsFeatures(features);
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
async function loadAndRenderContinuity(featureId) {
|
|
3829
|
+
analyticsCache.selectedFeatureId = featureId;
|
|
3830
|
+
const data = await fetchAnalytics('continuity', { feature_id: featureId, limit: 200 });
|
|
3831
|
+
renderAnalyticsContinuity(featureId, data.sessions || []);
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
async function loadAndRenderCommits(featureId) {
|
|
3835
|
+
const data = await fetchAnalytics('commits', { feature_id: featureId, limit: 200 });
|
|
3836
|
+
renderAnalyticsCommits(featureId, data.commits || []);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
async function loadFeatureAnalytics(featureId) {
|
|
3840
|
+
analyticsCache.selectedFeatureId = featureId;
|
|
3841
|
+
await Promise.all([
|
|
3842
|
+
loadAndRenderContinuity(featureId),
|
|
3843
|
+
loadAndRenderCommits(featureId),
|
|
3844
|
+
loadAndRenderCommitDag(featureId)
|
|
3845
|
+
]);
|
|
3846
|
+
}
|
|
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>
|
|
2670
4011
|
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
const commitDag = document.getElementById('analytics-commit-dag');
|
|
2679
|
-
const hint = window.location.protocol === 'file:'
|
|
2680
|
-
? 'You opened this dashboard as a local file. Run <span class="mono">htmlgraph serve</span> and open <span class="mono">http://localhost:8080</span>, or pass <span class="mono">?api=http://localhost:8080/api</span>.'
|
|
2681
|
-
: 'Ensure <span class="mono">htmlgraph serve</span> is running and accessible from this page.';
|
|
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('');
|
|
2682
4019
|
|
|
2683
|
-
|
|
2684
|
-
<h3>Analytics Unavailable</h3>
|
|
2685
|
-
<p class="analytics-note">${msg}</p>
|
|
2686
|
-
<p class="analytics-note">${hint}</p>
|
|
2687
|
-
<p class="analytics-note">Build the index:</p>
|
|
2688
|
-
<pre class="mono">PYTHONPATH=src/python .venv/bin/python -m htmlgraph.cli events export-sessions -g .htmlgraph\nPYTHONPATH=src/python .venv/bin/python -m htmlgraph.cli index rebuild -g .htmlgraph</pre>
|
|
2689
|
-
`;
|
|
2690
|
-
overview.innerHTML = html;
|
|
2691
|
-
tools.innerHTML = html;
|
|
2692
|
-
features.innerHTML = html;
|
|
2693
|
-
continuity.innerHTML = html;
|
|
2694
|
-
commits.innerHTML = html;
|
|
2695
|
-
if (commitDag) commitDag.innerHTML = html;
|
|
4020
|
+
containerEl.innerHTML = barsHTML;
|
|
2696
4021
|
}
|
|
2697
4022
|
|
|
2698
|
-
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
fetchAnalytics('features', { limit: 50 }).then(r => r.features),
|
|
2702
|
-
fetchAnalytics('transitions', { limit: 25 }).then(r => r.transitions),
|
|
2703
|
-
]);
|
|
4023
|
+
function renderAgentCostLegend(costs) {
|
|
4024
|
+
const legendEl = document.getElementById('cost-legend');
|
|
4025
|
+
if (!legendEl || costs.byAgent.length === 0) return;
|
|
2704
4026
|
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
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('');
|
|
2709
4036
|
|
|
2710
|
-
|
|
2711
|
-
renderAnalyticsTools(transitions);
|
|
2712
|
-
renderAnalyticsFeatures(features);
|
|
4037
|
+
legendEl.innerHTML = legendItems;
|
|
2713
4038
|
}
|
|
2714
4039
|
|
|
2715
|
-
async function
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
renderAnalyticsContinuity(featureId, data.sessions || []);
|
|
2719
|
-
}
|
|
4040
|
+
async function loadAndRenderAgentCosts() {
|
|
4041
|
+
const container = document.getElementById('agent-costs');
|
|
4042
|
+
if (!container) return;
|
|
2720
4043
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
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
|
+
}
|
|
2725
4052
|
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
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
|
+
}
|
|
2733
4067
|
}
|
|
2734
4068
|
|
|
2735
4069
|
async function ensureAnalyticsLoaded(force = false) {
|
|
@@ -2737,6 +4071,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
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
|
}
|
|
@@ -2814,6 +4149,8 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2814
4149
|
const created = new Date(s.created).toLocaleString();
|
|
2815
4150
|
const updated = new Date(s.updated).toLocaleString();
|
|
2816
4151
|
const statusBadge = s.status === 'active' ? 'status-active' : 'status-done';
|
|
4152
|
+
const hasTranscript = s.properties?.transcript_id ? true : false;
|
|
4153
|
+
const transcriptIcon = hasTranscript ? '<span title="Transcript linked" style="cursor: help;">📝</span>' : '<span style="color: var(--text-muted);">—</span>';
|
|
2817
4154
|
|
|
2818
4155
|
return `
|
|
2819
4156
|
<tr>
|
|
@@ -2827,6 +4164,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2827
4164
|
</td>
|
|
2828
4165
|
<td><span class="badge ${statusBadge}">${s.status}</span></td>
|
|
2829
4166
|
<td>${s.properties?.event_count || 0}</td>
|
|
4167
|
+
<td style="text-align: center;">${transcriptIcon}</td>
|
|
2830
4168
|
<td>${s.properties?.agent || '—'}</td>
|
|
2831
4169
|
<td class="mono">${created}</td>
|
|
2832
4170
|
<td class="mono">${updated}</td>
|
|
@@ -2842,6 +4180,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
2842
4180
|
<th>Session ID</th>
|
|
2843
4181
|
<th>Status</th>
|
|
2844
4182
|
<th>Events</th>
|
|
4183
|
+
<th title="Claude Code Transcript">Tx</th>
|
|
2845
4184
|
<th>Agent</th>
|
|
2846
4185
|
<th>Created</th>
|
|
2847
4186
|
<th>Updated</th>
|
|
@@ -3233,11 +4572,33 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3233
4572
|
}
|
|
3234
4573
|
});
|
|
3235
4574
|
|
|
3236
|
-
// Sort tracks by
|
|
4575
|
+
// Sort tracks by feature completion (incomplete first), then by priority
|
|
3237
4576
|
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
3238
4577
|
const sortedTracks = Array.from(tracked.values())
|
|
3239
|
-
|
|
3240
|
-
|
|
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)
|
|
4581
|
+
.sort((a, b) => {
|
|
4582
|
+
// Calculate completion percentage for each track
|
|
4583
|
+
const aTotal = a.features.length;
|
|
4584
|
+
const aDone = a.features.filter(f => f.status === 'done').length;
|
|
4585
|
+
const aComplete = aTotal > 0 ? (aDone === aTotal) : false;
|
|
4586
|
+
|
|
4587
|
+
const bTotal = b.features.length;
|
|
4588
|
+
const bDone = b.features.filter(f => f.status === 'done').length;
|
|
4589
|
+
const bComplete = bTotal > 0 ? (bDone === bTotal) : false;
|
|
4590
|
+
|
|
4591
|
+
// Completed tracks go to bottom (1), incomplete stay at top (0)
|
|
4592
|
+
if (aComplete !== bComplete) {
|
|
4593
|
+
return aComplete ? 1 : -1;
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
// Within same completion status, sort by priority
|
|
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);
|
|
4601
|
+
});
|
|
3241
4602
|
|
|
3242
4603
|
// Render
|
|
3243
4604
|
renderTrackSections(sortedTracks);
|
|
@@ -3405,18 +4766,51 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3405
4766
|
<div class="track-column-cards">
|
|
3406
4767
|
${byStatus[status].length === 0
|
|
3407
4768
|
? '<div class="empty-column">No items</div>'
|
|
3408
|
-
: 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 `
|
|
3409
4792
|
<div class="card priority-${f.priority}"
|
|
3410
4793
|
data-collection="${f._collection}"
|
|
3411
|
-
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>
|
|
3412
4798
|
<div class="card-title">${f.title}</div>
|
|
3413
4799
|
<div class="card-meta">
|
|
3414
4800
|
<span class="badge priority-${f.priority}">${f.priority}</span>
|
|
3415
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}
|
|
3416
4804
|
<span class="card-path">${f._collection}/${f.id}</span>
|
|
3417
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>
|
|
3418
4812
|
</div>
|
|
3419
|
-
`).join('')}
|
|
4813
|
+
`}).join('')}
|
|
3420
4814
|
</div>
|
|
3421
4815
|
</div>
|
|
3422
4816
|
`).join('')}
|
|
@@ -3622,6 +5016,77 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3622
5016
|
}
|
|
3623
5017
|
}
|
|
3624
5018
|
|
|
5019
|
+
async function fetchTranscriptStats(sessionId) {
|
|
5020
|
+
const container = document.getElementById('transcript-stats-container');
|
|
5021
|
+
if (!container) return;
|
|
5022
|
+
|
|
5023
|
+
try {
|
|
5024
|
+
const response = await fetch(`${API}/sessions/${sessionId}?transcript=true`);
|
|
5025
|
+
if (!response.ok) throw new Error('Failed to load transcript stats');
|
|
5026
|
+
|
|
5027
|
+
const data = await response.json();
|
|
5028
|
+
|
|
5029
|
+
if (!data.transcript_linked) {
|
|
5030
|
+
container.innerHTML = `
|
|
5031
|
+
<div class="transcript-not-linked">
|
|
5032
|
+
<span style="color: var(--text-muted);">No Claude Code transcript linked</span>
|
|
5033
|
+
<div style="font-size: 0.75rem; margin-top: 0.5rem; color: var(--text-muted);">
|
|
5034
|
+
Transcripts are linked automatically on work item completion
|
|
5035
|
+
</div>
|
|
5036
|
+
</div>
|
|
5037
|
+
`;
|
|
5038
|
+
return;
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
// Format duration
|
|
5042
|
+
let durationStr = '—';
|
|
5043
|
+
if (data.duration_seconds) {
|
|
5044
|
+
const mins = Math.floor(data.duration_seconds / 60);
|
|
5045
|
+
const secs = Math.floor(data.duration_seconds % 60);
|
|
5046
|
+
durationStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
5047
|
+
}
|
|
5048
|
+
|
|
5049
|
+
// Build tool breakdown
|
|
5050
|
+
let toolBreakdownHtml = '';
|
|
5051
|
+
if (data.tool_breakdown && Object.keys(data.tool_breakdown).length > 0) {
|
|
5052
|
+
const sortedTools = Object.entries(data.tool_breakdown)
|
|
5053
|
+
.sort((a, b) => b[1] - a[1])
|
|
5054
|
+
.slice(0, 8);
|
|
5055
|
+
toolBreakdownHtml = `
|
|
5056
|
+
<div class="transcript-tools" style="margin-top: 0.75rem;">
|
|
5057
|
+
<div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Tool Usage:</div>
|
|
5058
|
+
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
|
|
5059
|
+
${sortedTools.map(([tool, count]) => `
|
|
5060
|
+
<span class="badge" style="font-size: 0.7rem;">${tool}: ${count}</span>
|
|
5061
|
+
`).join('')}
|
|
5062
|
+
</div>
|
|
5063
|
+
</div>
|
|
5064
|
+
`;
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
container.innerHTML = `
|
|
5068
|
+
<div class="transcript-stats">
|
|
5069
|
+
<div class="transcript-meta" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; margin-bottom: 0.75rem;">
|
|
5070
|
+
<div><strong>User Messages:</strong> ${data.user_messages || 0}</div>
|
|
5071
|
+
<div><strong>Tool Calls:</strong> ${data.tool_calls || 0}</div>
|
|
5072
|
+
<div><strong>Duration:</strong> ${durationStr}</div>
|
|
5073
|
+
<div><strong>Total Entries:</strong> ${data.entry_count || 0}</div>
|
|
5074
|
+
</div>
|
|
5075
|
+
<div class="transcript-badges" style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
|
5076
|
+
${data.has_thinking_traces ? '<span class="badge" style="background: var(--accent);">Has Thinking Traces</span>' : ''}
|
|
5077
|
+
${data.git_branch ? `<span class="badge">Branch: ${data.git_branch}</span>` : ''}
|
|
5078
|
+
</div>
|
|
5079
|
+
${toolBreakdownHtml}
|
|
5080
|
+
<div class="transcript-id" style="margin-top: 0.75rem; font-size: 0.7rem; color: var(--text-muted);">
|
|
5081
|
+
Transcript ID: <code style="font-family: monospace;">${data.transcript_id || '—'}</code>
|
|
5082
|
+
</div>
|
|
5083
|
+
</div>
|
|
5084
|
+
`;
|
|
5085
|
+
} catch (err) {
|
|
5086
|
+
container.innerHTML = `<div class="loading">Error loading transcript: ${err.message}</div>`;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
|
|
3625
5090
|
function renderPanelLoading() {
|
|
3626
5091
|
document.getElementById('panel-title').textContent = 'Loading...';
|
|
3627
5092
|
document.getElementById('panel-body').innerHTML = '<div class="loading">Loading...</div>';
|
|
@@ -3639,6 +5104,24 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3639
5104
|
|
|
3640
5105
|
let bodyHtml = '';
|
|
3641
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
|
+
|
|
3642
5125
|
// Meta section
|
|
3643
5126
|
bodyHtml += `
|
|
3644
5127
|
<div class="panel-section">
|
|
@@ -3647,7 +5130,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3647
5130
|
<span class="badge priority-${node.priority}">${node.priority}</span>
|
|
3648
5131
|
<span class="badge type">${node.type}</span>
|
|
3649
5132
|
<span class="badge">${node.status}</span>
|
|
3650
|
-
${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>` : ''}
|
|
3651
5134
|
</div>
|
|
3652
5135
|
</div>
|
|
3653
5136
|
`;
|
|
@@ -3676,6 +5159,43 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3676
5159
|
`;
|
|
3677
5160
|
}
|
|
3678
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
|
+
|
|
3679
5199
|
// Edges section (excluding implemented-in which gets special handling)
|
|
3680
5200
|
const edgeTypes = Object.keys(node.edges || {}).filter(t => t !== 'implemented-in');
|
|
3681
5201
|
if (edgeTypes.length > 0) {
|
|
@@ -3753,6 +5273,14 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3753
5273
|
<div id="activity-log-container" class="loading">Loading activity log...</div>
|
|
3754
5274
|
</div>
|
|
3755
5275
|
`;
|
|
5276
|
+
|
|
5277
|
+
// Transcript section (for sessions only)
|
|
5278
|
+
bodyHtml += `
|
|
5279
|
+
<div class="panel-section">
|
|
5280
|
+
<h3>Claude Code Transcript</h3>
|
|
5281
|
+
<div id="transcript-stats-container" class="loading">Loading transcript data...</div>
|
|
5282
|
+
</div>
|
|
5283
|
+
`;
|
|
3756
5284
|
}
|
|
3757
5285
|
|
|
3758
5286
|
// Timestamps
|
|
@@ -3768,9 +5296,10 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3768
5296
|
|
|
3769
5297
|
document.getElementById('panel-body').innerHTML = bodyHtml;
|
|
3770
5298
|
|
|
3771
|
-
// Load activity log for sessions
|
|
5299
|
+
// Load activity log and transcript stats for sessions
|
|
3772
5300
|
if (node.type === 'session') {
|
|
3773
5301
|
fetchActivityLog(node._collection || 'sessions', node.id);
|
|
5302
|
+
fetchTranscriptStats(node.id);
|
|
3774
5303
|
}
|
|
3775
5304
|
|
|
3776
5305
|
// Actions
|
|
@@ -3982,219 +5511,771 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
3982
5511
|
return div.innerHTML;
|
|
3983
5512
|
}
|
|
3984
5513
|
|
|
3985
|
-
async function refreshDashboard() {
|
|
3986
|
-
const { status, nodes } = await loadData();
|
|
3987
|
-
await renderKanban(nodes);
|
|
3988
|
-
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();
|
|
3989
5844
|
}
|
|
3990
5845
|
|
|
3991
5846
|
// =====================================================================
|
|
3992
|
-
//
|
|
5847
|
+
// Agent Skills Analysis
|
|
3993
5848
|
// =====================================================================
|
|
3994
5849
|
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
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 };
|
|
4005
5889
|
}
|
|
4006
5890
|
|
|
4007
|
-
function
|
|
4008
|
-
|
|
4009
|
-
.reduce((sum, edges) => sum + edges.length, 0);
|
|
4010
|
-
return Math.min(45 + edgeCount * 3, 60);
|
|
5891
|
+
function getProficiencyColor(level) {
|
|
5892
|
+
return `proficiency-${Math.round(level)}`;
|
|
4011
5893
|
}
|
|
4012
5894
|
|
|
4013
|
-
function
|
|
4014
|
-
const
|
|
4015
|
-
|
|
4016
|
-
|
|
5895
|
+
function getProficiencyLabel(level) {
|
|
5896
|
+
const labels = ['', 'Novice', 'Beginner', 'Intermediate', 'Advanced', 'Expert'];
|
|
5897
|
+
return labels[Math.round(level)] || 'Expert';
|
|
5898
|
+
}
|
|
4017
5899
|
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
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>`;
|
|
4028
5916
|
}
|
|
4029
|
-
|
|
5917
|
+
html += '</div>';
|
|
5918
|
+
return html;
|
|
5919
|
+
}
|
|
4030
5920
|
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
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>`;
|
|
4034
5942
|
}
|
|
4035
|
-
|
|
4036
|
-
return lines;
|
|
4037
5943
|
}
|
|
4038
5944
|
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
title: n.title,
|
|
4043
|
-
status: n.status,
|
|
4044
|
-
type: n.type,
|
|
4045
|
-
priority: n.priority,
|
|
4046
|
-
edges: n.edges || {},
|
|
4047
|
-
_collection: n._collection,
|
|
4048
|
-
x: null,
|
|
4049
|
-
y: null
|
|
4050
|
-
}));
|
|
5945
|
+
// =====================================================================
|
|
5946
|
+
// View Toggle
|
|
5947
|
+
// =====================================================================
|
|
4051
5948
|
|
|
4052
|
-
|
|
4053
|
-
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');
|
|
4054
5956
|
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
edges.forEach(edge => {
|
|
4058
|
-
if (nodeIds.has(edge.target_id)) {
|
|
4059
|
-
graphEdges.push({
|
|
4060
|
-
source: node.id,
|
|
4061
|
-
target: edge.target_id,
|
|
4062
|
-
type: edgeType
|
|
4063
|
-
});
|
|
4064
|
-
}
|
|
4065
|
-
});
|
|
4066
|
-
});
|
|
5957
|
+
buttons.forEach(btn => {
|
|
5958
|
+
btn.classList.toggle('active', btn.dataset.view === view);
|
|
4067
5959
|
});
|
|
4068
5960
|
|
|
4069
|
-
|
|
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
|
+
}
|
|
4070
5997
|
}
|
|
4071
5998
|
|
|
4072
|
-
function renderGraph(nodes) {
|
|
4073
|
-
const svg = document.getElementById('graph-svg');
|
|
4074
|
-
const edgesGroup = document.getElementById('graph-edges');
|
|
4075
|
-
const nodesGroup = document.getElementById('graph-nodes');
|
|
4076
5999
|
|
|
4077
|
-
|
|
4078
|
-
|
|
6000
|
+
// =====================================================================
|
|
6001
|
+
// Init
|
|
6002
|
+
// =====================================================================
|
|
4079
6003
|
|
|
4080
|
-
|
|
6004
|
+
document.getElementById('panel-close').addEventListener('click', closePanel);
|
|
6005
|
+
document.getElementById('panel-overlay').addEventListener('click', closePanel);
|
|
4081
6006
|
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
6007
|
+
document.querySelectorAll('.view-btn').forEach(btn => {
|
|
6008
|
+
btn.addEventListener('click', () => switchView(btn.dataset.view));
|
|
6009
|
+
});
|
|
4085
6010
|
|
|
4086
|
-
|
|
4087
|
-
|
|
6011
|
+
document.getElementById('analytics-refresh').addEventListener('click', () => {
|
|
6012
|
+
ensureAnalyticsLoaded(true);
|
|
6013
|
+
});
|
|
4088
6014
|
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
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
|
+
});
|
|
4094
6020
|
|
|
4095
|
-
|
|
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);
|
|
4096
6029
|
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
.distance(120)
|
|
4101
|
-
.strength(0.5))
|
|
4102
|
-
.force('charge', d3.forceManyBody()
|
|
4103
|
-
.strength(-400))
|
|
4104
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
4105
|
-
.force('collision', d3.forceCollide().radius(70))
|
|
4106
|
-
.on('tick', updatePositions);
|
|
6030
|
+
// Session comparison modal
|
|
6031
|
+
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
6032
|
+
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4107
6033
|
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
line.dataset.source = edge.source.id || edge.source;
|
|
4113
|
-
line.dataset.target = edge.target.id || edge.target;
|
|
4114
|
-
edgesGroup.appendChild(line);
|
|
4115
|
-
});
|
|
6034
|
+
// Graph filter event listeners
|
|
6035
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6036
|
+
cb.addEventListener('change', applyGraphFilters);
|
|
6037
|
+
});
|
|
4116
6038
|
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
g.dataset.collection = node._collection;
|
|
6039
|
+
const graphSearchEl = document.getElementById('graph-search');
|
|
6040
|
+
if (graphSearchEl) {
|
|
6041
|
+
graphSearchEl.addEventListener('input', applyGraphFilters);
|
|
6042
|
+
}
|
|
4122
6043
|
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
6044
|
+
const graphResetBtn = document.getElementById('graph-reset');
|
|
6045
|
+
if (graphResetBtn) {
|
|
6046
|
+
graphResetBtn.addEventListener('click', resetGraphView);
|
|
6047
|
+
}
|
|
4127
6048
|
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
lines.forEach((line, i) => {
|
|
4134
|
-
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
|
4135
|
-
tspan.setAttribute('x', '0');
|
|
4136
|
-
tspan.setAttribute('dy', i === 0 ? `${startY}px` : `${lineHeight}px`);
|
|
4137
|
-
tspan.textContent = line;
|
|
4138
|
-
text.appendChild(tspan);
|
|
4139
|
-
});
|
|
6049
|
+
const graphShowAllBtn = document.getElementById('graph-show-all');
|
|
6050
|
+
if (graphShowAllBtn) {
|
|
6051
|
+
graphShowAllBtn.addEventListener('click', showAllNodes);
|
|
6052
|
+
}
|
|
4140
6053
|
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
6054
|
+
document.addEventListener('keydown', (e) => {
|
|
6055
|
+
if (e.key === 'Escape') closePanel();
|
|
6056
|
+
});
|
|
4144
6057
|
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
6058
|
+
loadData().then(async ({ status, nodes }) => {
|
|
6059
|
+
await renderKanban(nodes);
|
|
6060
|
+
updateKanbanGrid();
|
|
6061
|
+
}).catch(err => {
|
|
6062
|
+
console.error('Error loading dashboard data:', err);
|
|
6063
|
+
});
|
|
4148
6064
|
|
|
4149
|
-
|
|
4150
|
-
|
|
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
|
+
}
|
|
4151
6072
|
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
6073
|
+
function renderGraph(nodes) {
|
|
6074
|
+
if (nodes.length === 0) {
|
|
6075
|
+
if (visNetwork) visNetwork.destroy();
|
|
6076
|
+
visNetwork = null;
|
|
6077
|
+
return;
|
|
6078
|
+
}
|
|
4158
6079
|
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
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()
|
|
4163
6096
|
}
|
|
4164
|
-
}
|
|
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
|
+
}
|
|
4165
6128
|
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
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
|
|
4172
6162
|
}
|
|
4173
|
-
}
|
|
4174
|
-
}
|
|
6163
|
+
}
|
|
6164
|
+
};
|
|
4175
6165
|
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
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);
|
|
4180
6173
|
if (node) {
|
|
4181
|
-
node.
|
|
4182
|
-
node.y = Math.max(30, Math.min(height - 30, node.y));
|
|
4183
|
-
g.setAttribute('transform', `translate(${node.x}, ${node.y})`);
|
|
6174
|
+
openPanel(node._collection, node.id);
|
|
4184
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]));
|
|
4185
6223
|
});
|
|
6224
|
+
});
|
|
6225
|
+
return { agents, skillProfiles };
|
|
6226
|
+
}
|
|
4186
6227
|
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
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>`;
|
|
4197
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>`;
|
|
4198
6279
|
}
|
|
4199
6280
|
}
|
|
4200
6281
|
|
|
@@ -4206,6 +6287,7 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4206
6287
|
const kanban = document.getElementById('kanban');
|
|
4207
6288
|
const graph = document.getElementById('graph-container');
|
|
4208
6289
|
const analytics = document.getElementById('analytics');
|
|
6290
|
+
const agents = document.getElementById('agents');
|
|
4209
6291
|
const sessions = document.getElementById('sessions');
|
|
4210
6292
|
const buttons = document.querySelectorAll('.view-btn');
|
|
4211
6293
|
|
|
@@ -4217,29 +6299,41 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4217
6299
|
kanban.classList.add('active');
|
|
4218
6300
|
graph.classList.remove('active');
|
|
4219
6301
|
analytics.classList.remove('active');
|
|
6302
|
+
agents.classList.remove('active');
|
|
4220
6303
|
sessions.classList.remove('active');
|
|
4221
6304
|
renderKanban(allNodes);
|
|
4222
6305
|
} else if (view === 'graph') {
|
|
4223
6306
|
kanban.classList.remove('active');
|
|
4224
6307
|
graph.classList.add('active');
|
|
4225
6308
|
analytics.classList.remove('active');
|
|
6309
|
+
agents.classList.remove('active');
|
|
4226
6310
|
sessions.classList.remove('active');
|
|
4227
6311
|
renderGraph(allNodes);
|
|
4228
6312
|
} else if (view === 'analytics') {
|
|
4229
6313
|
kanban.classList.remove('active');
|
|
4230
6314
|
graph.classList.remove('active');
|
|
4231
6315
|
analytics.classList.add('active');
|
|
6316
|
+
agents.classList.remove('active');
|
|
4232
6317
|
sessions.classList.remove('active');
|
|
4233
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();
|
|
4234
6326
|
} else if (view === 'sessions') {
|
|
4235
6327
|
kanban.classList.remove('active');
|
|
4236
6328
|
graph.classList.remove('active');
|
|
4237
6329
|
analytics.classList.remove('active');
|
|
6330
|
+
agents.classList.remove('active');
|
|
4238
6331
|
sessions.classList.add('active');
|
|
4239
6332
|
loadAndRenderSessions();
|
|
4240
6333
|
}
|
|
4241
6334
|
}
|
|
4242
6335
|
|
|
6336
|
+
|
|
4243
6337
|
// =====================================================================
|
|
4244
6338
|
// Init
|
|
4245
6339
|
// =====================================================================
|
|
@@ -4274,6 +6368,11 @@ ${node.ts ? node.ts.slice(0, 19).replace('T', ' ') : ''}`;
|
|
|
4274
6368
|
document.getElementById('comparison-close').addEventListener('click', closeComparison);
|
|
4275
6369
|
document.getElementById('comparison-overlay').addEventListener('click', closeComparison);
|
|
4276
6370
|
|
|
6371
|
+
// Graph filter event listeners
|
|
6372
|
+
document.querySelectorAll('.graph-filter-checkbox input').forEach(cb => {
|
|
6373
|
+
cb.addEventListener('change', applyGraphFilters);
|
|
6374
|
+
});
|
|
6375
|
+
|
|
4277
6376
|
document.addEventListener('keydown', (e) => {
|
|
4278
6377
|
if (e.key === 'Escape') closePanel();
|
|
4279
6378
|
});
|