htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +51 -1
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/agent_registry.py +2 -1
- htmlgraph/analytics/__init__.py +8 -1
- htmlgraph/analytics/cli.py +86 -20
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +10 -6
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +67 -27
- htmlgraph/analytics_index.py +53 -20
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +2 -1
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/base.py +57 -2
- htmlgraph/builders/bug.py +19 -3
- htmlgraph/builders/chore.py +19 -3
- htmlgraph/builders/epic.py +19 -3
- htmlgraph/builders/feature.py +27 -3
- htmlgraph/builders/insight.py +2 -1
- htmlgraph/builders/metric.py +2 -1
- htmlgraph/builders/pattern.py +2 -1
- htmlgraph/builders/phase.py +19 -3
- htmlgraph/builders/spike.py +29 -3
- htmlgraph/builders/track.py +42 -1
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +197 -14
- htmlgraph/collections/bug.py +2 -1
- htmlgraph/collections/chore.py +2 -1
- htmlgraph/collections/epic.py +2 -1
- htmlgraph/collections/feature.py +2 -1
- htmlgraph/collections/insight.py +2 -1
- htmlgraph/collections/metric.py +2 -1
- htmlgraph/collections/pattern.py +2 -1
- htmlgraph/collections/phase.py +2 -1
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +13 -2
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +14 -1
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +2 -1
- htmlgraph/converter.py +116 -7
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2246 -248
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +2 -1
- htmlgraph/deploy.py +26 -27
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +2 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +86 -37
- htmlgraph/event_migration.py +2 -1
- htmlgraph/file_watcher.py +12 -8
- htmlgraph/find_api.py +2 -1
- htmlgraph/git_events.py +67 -9
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +790 -99
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/installer.py +5 -1
- htmlgraph/hooks/orchestrator.py +327 -76
- htmlgraph/hooks/orchestrator_reflector.py +31 -4
- htmlgraph/hooks/post_tool_use_failure.py +32 -7
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +92 -19
- htmlgraph/hooks/pretooluse.py +527 -7
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +99 -4
- htmlgraph/hooks/validator.py +212 -91
- htmlgraph/ids.py +2 -1
- htmlgraph/learning.py +125 -100
- htmlgraph/mcp_server.py +2 -1
- htmlgraph/models.py +217 -18
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +2 -1
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +115 -4
- htmlgraph/parallel.py +2 -1
- htmlgraph/parser.py +86 -6
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +2 -1
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +295 -107
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +285 -3
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +2 -1
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +33 -1
- htmlgraph/track_manager.py +38 -0
- htmlgraph/transcript.py +18 -5
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +2 -1
- htmlgraph/work_type_utils.py +2 -1
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -4839
- htmlgraph/sdk.py +0 -2359
- htmlgraph-0.20.1.dist-info/RECORD +0 -118
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{ title }} - HtmlGraph</title>
|
|
7
|
+
<script src="/static/htmx.min.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="/static/style.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body class="dark-theme">
|
|
11
|
+
<div class="dashboard-container">
|
|
12
|
+
<!-- Header -->
|
|
13
|
+
<header class="dashboard-header">
|
|
14
|
+
<div class="header-content">
|
|
15
|
+
<h1 class="logo">
|
|
16
|
+
<span class="logo-icon">⚡</span>
|
|
17
|
+
HtmlGraph Dashboard
|
|
18
|
+
</h1>
|
|
19
|
+
<div class="header-stats">
|
|
20
|
+
<div class="stat-badge">
|
|
21
|
+
<span class="stat-label">Events</span>
|
|
22
|
+
<span class="stat-value" id="event-count">0</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="stat-badge">
|
|
25
|
+
<span class="stat-label">Agents</span>
|
|
26
|
+
<span class="stat-value" id="agent-count">0</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="stat-badge">
|
|
29
|
+
<span class="stat-label">Sessions</span>
|
|
30
|
+
<span class="stat-value" id="session-count">0</span>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<!-- Navigation Tabs -->
|
|
37
|
+
<nav class="tabs-navigation">
|
|
38
|
+
<button class="tab-button active"
|
|
39
|
+
hx-get="/views/activity-feed"
|
|
40
|
+
hx-target="#content-area"
|
|
41
|
+
hx-trigger="click"
|
|
42
|
+
data-tab="activity">
|
|
43
|
+
<span class="tab-icon">📋</span>
|
|
44
|
+
Activity Feed
|
|
45
|
+
</button>
|
|
46
|
+
<button class="tab-button"
|
|
47
|
+
hx-get="/views/orchestration"
|
|
48
|
+
hx-target="#content-area"
|
|
49
|
+
hx-trigger="click"
|
|
50
|
+
data-tab="orchestration">
|
|
51
|
+
<span class="tab-icon">🔗</span>
|
|
52
|
+
Orchestration
|
|
53
|
+
</button>
|
|
54
|
+
<button class="tab-button"
|
|
55
|
+
hx-get="/views/work-items"
|
|
56
|
+
hx-target="#content-area"
|
|
57
|
+
hx-trigger="click"
|
|
58
|
+
data-tab="work-items">
|
|
59
|
+
<span class="tab-icon">🎯</span>
|
|
60
|
+
Work Items
|
|
61
|
+
</button>
|
|
62
|
+
<button class="tab-button"
|
|
63
|
+
hx-get="/views/agents"
|
|
64
|
+
hx-target="#content-area"
|
|
65
|
+
hx-trigger="click"
|
|
66
|
+
data-tab="agents">
|
|
67
|
+
<span class="tab-icon">🤖</span>
|
|
68
|
+
Agents
|
|
69
|
+
</button>
|
|
70
|
+
<button class="tab-button"
|
|
71
|
+
hx-get="/views/metrics"
|
|
72
|
+
hx-target="#content-area"
|
|
73
|
+
hx-trigger="click"
|
|
74
|
+
data-tab="metrics">
|
|
75
|
+
<span class="tab-icon">📊</span>
|
|
76
|
+
Metrics
|
|
77
|
+
</button>
|
|
78
|
+
<button class="tab-button"
|
|
79
|
+
hx-get="/views/spawners"
|
|
80
|
+
hx-target="#content-area"
|
|
81
|
+
hx-trigger="click"
|
|
82
|
+
data-tab="spawners">
|
|
83
|
+
<span class="tab-icon">🚀</span>
|
|
84
|
+
Spawners
|
|
85
|
+
</button>
|
|
86
|
+
</nav>
|
|
87
|
+
|
|
88
|
+
<!-- Content Area -->
|
|
89
|
+
<main class="content-area" id="content-area">
|
|
90
|
+
<div class="loading-indicator">
|
|
91
|
+
<div class="spinner"></div>
|
|
92
|
+
<p>Loading dashboard...</p>
|
|
93
|
+
</div>
|
|
94
|
+
</main>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- WebSocket for live updates -->
|
|
98
|
+
<script>
|
|
99
|
+
let eventCount = 0;
|
|
100
|
+
let agentSet = new Set();
|
|
101
|
+
let sessionCount = 0;
|
|
102
|
+
let processedEventIds = new Set();
|
|
103
|
+
|
|
104
|
+
// Load initial stats from server
|
|
105
|
+
async function loadInitialStats() {
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch('/api/initial-stats');
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
|
|
110
|
+
// Update header stats
|
|
111
|
+
eventCount = data.total_events || 0;
|
|
112
|
+
sessionCount = data.total_sessions || 0;
|
|
113
|
+
|
|
114
|
+
// Update agent set from database
|
|
115
|
+
if (data.agents) {
|
|
116
|
+
data.agents.forEach(agent => agentSet.add(agent));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update UI
|
|
120
|
+
document.getElementById('event-count').textContent = eventCount;
|
|
121
|
+
document.getElementById('agent-count').textContent = agentSet.size;
|
|
122
|
+
document.getElementById('session-count').textContent = sessionCount;
|
|
123
|
+
|
|
124
|
+
console.log('Initial stats loaded:', data);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Failed to load initial stats:', error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Initialize dashboard on load
|
|
131
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
132
|
+
// Load initial stats
|
|
133
|
+
loadInitialStats();
|
|
134
|
+
|
|
135
|
+
// Load initial activity feed
|
|
136
|
+
htmx.ajax('GET', '/views/activity-feed', {target: '#content-area'});
|
|
137
|
+
|
|
138
|
+
// Connect WebSocket for real-time updates
|
|
139
|
+
connectWebSocket();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Convert timestamps after HTMX loads Activity Feed
|
|
143
|
+
document.body.addEventListener('htmx:afterSettle', function(evt) {
|
|
144
|
+
if (evt.detail.target.id === 'content-area') {
|
|
145
|
+
// Activity Feed has been loaded, convert timestamps to local timezone
|
|
146
|
+
if (typeof convertTimestampsToLocal === 'function') {
|
|
147
|
+
convertTimestampsToLocal();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
function connectWebSocket() {
|
|
153
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
154
|
+
const ws = new WebSocket(wsProtocol + '//' + window.location.host + '/ws/events');
|
|
155
|
+
|
|
156
|
+
ws.onopen = function(event) {
|
|
157
|
+
console.log('WebSocket connected for real-time events');
|
|
158
|
+
updateWebSocketStatus(true);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
ws.onmessage = function(event) {
|
|
162
|
+
try {
|
|
163
|
+
const data = JSON.parse(event.data);
|
|
164
|
+
console.log('[WebSocket] Received message type:', data.type);
|
|
165
|
+
|
|
166
|
+
if (data.type === 'event') {
|
|
167
|
+
// Prevent duplicate event insertions
|
|
168
|
+
if (processedEventIds.has(data.event_id)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
processedEventIds.add(data.event_id);
|
|
172
|
+
|
|
173
|
+
// Update stats
|
|
174
|
+
eventCount++;
|
|
175
|
+
if (data.agent_id) {
|
|
176
|
+
agentSet.add(data.agent_id);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Update header
|
|
180
|
+
document.getElementById('event-count').textContent = eventCount;
|
|
181
|
+
document.getElementById('agent-count').textContent = agentSet.size;
|
|
182
|
+
|
|
183
|
+
// Add animation class to event count badge
|
|
184
|
+
const badge = document.getElementById('event-count').parentElement;
|
|
185
|
+
badge.classList.add('pulse');
|
|
186
|
+
setTimeout(() => badge.classList.remove('pulse'), 500);
|
|
187
|
+
|
|
188
|
+
// Insert new event into Activity Feed if visible
|
|
189
|
+
insertNewEventIntoActivityFeed(data);
|
|
190
|
+
}
|
|
191
|
+
// Handle live spawner events for real-time streaming
|
|
192
|
+
else if (data.type === 'spawner_event') {
|
|
193
|
+
console.log('[WebSocket] spawner_event received:', data.event_type, data.spawner_type, 'handler exists:', typeof window.handleSpawnerEvent === 'function');
|
|
194
|
+
// Delegate to activity-feed.html handler if available
|
|
195
|
+
if (typeof window.handleSpawnerEvent === 'function') {
|
|
196
|
+
window.handleSpawnerEvent(data);
|
|
197
|
+
} else {
|
|
198
|
+
console.warn('[WebSocket] handleSpawnerEvent not available, spawner event dropped:', data.event_type, data.spawner_type);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
console.error('WebSocket message error:', e);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
ws.onerror = function(event) {
|
|
207
|
+
console.error('WebSocket error:', event);
|
|
208
|
+
updateWebSocketStatus(false);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
ws.onclose = function(event) {
|
|
212
|
+
console.log('WebSocket disconnected, reconnecting in 3s...');
|
|
213
|
+
updateWebSocketStatus(false);
|
|
214
|
+
setTimeout(connectWebSocket, 3000);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function updateWebSocketStatus(isConnected) {
|
|
219
|
+
// Update live update indicator in activity feed
|
|
220
|
+
const indicator = document.querySelector('.auto-refresh-indicator');
|
|
221
|
+
if (indicator) {
|
|
222
|
+
const dot = indicator.querySelector('.refresh-dot');
|
|
223
|
+
if (isConnected) {
|
|
224
|
+
dot.style.backgroundColor = '#10b981';
|
|
225
|
+
indicator.style.opacity = '1';
|
|
226
|
+
} else {
|
|
227
|
+
dot.style.backgroundColor = '#ef4444';
|
|
228
|
+
indicator.style.opacity = '0.6';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function insertNewEventIntoActivityFeed(eventData) {
|
|
234
|
+
// Check if activity feed is currently displayed
|
|
235
|
+
const activityFeed = document.querySelector('.activity-feed-view');
|
|
236
|
+
if (!activityFeed) {
|
|
237
|
+
// Activity feed not visible, skip insertion
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const activityList = activityFeed.querySelector('.activity-list');
|
|
242
|
+
if (!activityList) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Check if there's an empty state message
|
|
247
|
+
const emptyState = activityList.querySelector('.empty-state');
|
|
248
|
+
if (emptyState) {
|
|
249
|
+
// Replace empty state with table
|
|
250
|
+
emptyState.remove();
|
|
251
|
+
// Create table if it doesn't exist
|
|
252
|
+
if (!activityList.querySelector('.activity-table')) {
|
|
253
|
+
const table = `
|
|
254
|
+
<table class="activity-table">
|
|
255
|
+
<thead>
|
|
256
|
+
<tr>
|
|
257
|
+
<th class="col-timestamp">Timestamp</th>
|
|
258
|
+
<th class="col-agent">Agent</th>
|
|
259
|
+
<th class="col-tool">Tool</th>
|
|
260
|
+
<th class="col-input">Input</th>
|
|
261
|
+
<th class="col-output">Output</th>
|
|
262
|
+
<th class="col-status">Status</th>
|
|
263
|
+
<th class="col-id">ID</th>
|
|
264
|
+
</tr>
|
|
265
|
+
</thead>
|
|
266
|
+
<tbody></tbody>
|
|
267
|
+
</table>
|
|
268
|
+
`;
|
|
269
|
+
activityList.insertAdjacentHTML('afterbegin', table);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Get or create tbody
|
|
274
|
+
const table = activityList.querySelector('.activity-table');
|
|
275
|
+
if (!table) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const tbody = table.querySelector('tbody');
|
|
280
|
+
if (!tbody) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Create new table row HTML
|
|
285
|
+
const eventRow = createActivityRowHTML(eventData);
|
|
286
|
+
|
|
287
|
+
// Handle hierarchical placement
|
|
288
|
+
if (eventData.parent_event_id) {
|
|
289
|
+
// Try to find parent row
|
|
290
|
+
const parentRow = tbody.querySelector(`tr[data-event-id="${eventData.parent_event_id}"]`);
|
|
291
|
+
if (parentRow) {
|
|
292
|
+
// Insert after parent row
|
|
293
|
+
parentRow.insertAdjacentHTML('afterend', eventRow);
|
|
294
|
+
highlightRow(tbody.querySelector(`tr[data-event-id="${eventData.event_id}"]`));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Default: Insert at the top of tbody
|
|
300
|
+
const firstRow = tbody.querySelector('tr');
|
|
301
|
+
if (firstRow) {
|
|
302
|
+
firstRow.insertAdjacentHTML('beforebegin', eventRow);
|
|
303
|
+
} else {
|
|
304
|
+
tbody.insertAdjacentHTML('afterbegin', eventRow);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Highlight the new row
|
|
308
|
+
highlightRow(tbody.querySelector('tr:first-child'));
|
|
309
|
+
|
|
310
|
+
// Convert new event timestamp to local timezone
|
|
311
|
+
if (typeof convertTimestampsToLocal === 'function') {
|
|
312
|
+
convertTimestampsToLocal();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Keep only last 100 events in feed to prevent memory issues
|
|
316
|
+
const allRows = tbody.querySelectorAll('tr');
|
|
317
|
+
if (allRows.length > 100) {
|
|
318
|
+
// Remove oldest items from bottom
|
|
319
|
+
const itemsToRemove = allRows.length - 100;
|
|
320
|
+
for (let i = 0; i < itemsToRemove; i++) {
|
|
321
|
+
allRows[allRows.length - 1 - i].remove();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function highlightRow(row) {
|
|
327
|
+
if (row) {
|
|
328
|
+
row.classList.add('new-event-highlight');
|
|
329
|
+
// Remove highlight after animation
|
|
330
|
+
setTimeout(() => {
|
|
331
|
+
row.classList.remove('new-event-highlight');
|
|
332
|
+
}, 2000);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function createActivityRowHTML(eventData) {
|
|
337
|
+
// Determine event type emoji
|
|
338
|
+
let eventEmoji = '📋';
|
|
339
|
+
if (eventData.event_type === 'delegation') {
|
|
340
|
+
eventEmoji = '🔗';
|
|
341
|
+
} else if (eventData.event_type === 'tool_call') {
|
|
342
|
+
eventEmoji = '🔨';
|
|
343
|
+
} else if (eventData.event_type === 'completion') {
|
|
344
|
+
eventEmoji = '🎉';
|
|
345
|
+
} else if (eventData.event_type === 'tool_result') {
|
|
346
|
+
eventEmoji = '✅';
|
|
347
|
+
} else if (eventData.event_type === 'error') {
|
|
348
|
+
eventEmoji = '❌';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const inputSummary = eventData.input_summary ? eventData.input_summary.substring(0, 100) : '';
|
|
352
|
+
const inputTruncated = eventData.input_summary && eventData.input_summary.length > 100 ? '…' : '';
|
|
353
|
+
const outputSummary = eventData.output_summary ? eventData.output_summary.substring(0, 100) : '';
|
|
354
|
+
const outputTruncated = eventData.output_summary && eventData.output_summary.length > 100 ? '…' : '';
|
|
355
|
+
|
|
356
|
+
const isChild = !!eventData.parent_event_id;
|
|
357
|
+
const rowClass = isChild ? 'child-row' : 'parent-row';
|
|
358
|
+
const indentStyle = isChild ? 'padding-left: 2rem;' : '';
|
|
359
|
+
const borderStyle = isChild ? 'border-left: 4px solid var(--text-muted);' : 'border-left: 4px solid var(--accent);';
|
|
360
|
+
|
|
361
|
+
// Create table row HTML
|
|
362
|
+
const html = `
|
|
363
|
+
<tr class="activity-row ${rowClass} event-${eventData.status || 'pending'}"
|
|
364
|
+
data-event-id="${escapeHtml(eventData.event_id)}"
|
|
365
|
+
style="${borderStyle}">
|
|
366
|
+
<td class="col-timestamp" style="${indentStyle}">
|
|
367
|
+
<span class="event-type-badge" title="${escapeHtml(eventData.event_type)}">
|
|
368
|
+
${eventEmoji}
|
|
369
|
+
</span>
|
|
370
|
+
<span class="timestamp-text" data-utc-time="${escapeHtml(eventData.timestamp)}">${escapeHtml(eventData.timestamp)}</span>
|
|
371
|
+
</td>
|
|
372
|
+
<td class="col-agent">
|
|
373
|
+
<span class="agent-badge agent-${escapeHtml(eventData.agent_id.toLowerCase())}">${escapeHtml(eventData.agent_id)}</span>
|
|
374
|
+
${isChild ? `<span class="child-indicator" title="Child of ${escapeHtml(eventData.parent_event_id.substring(0, 8))}">↳</span>` : ''}
|
|
375
|
+
</td>
|
|
376
|
+
<td class="col-tool">
|
|
377
|
+
${eventData.tool_name ? `<code class="tool-name">${escapeHtml(eventData.tool_name)}</code>` : '<span class="text-muted">—</span>'}
|
|
378
|
+
</td>
|
|
379
|
+
<td class="col-input">
|
|
380
|
+
${inputSummary ? `<span class="truncate" title="${escapeHtml(eventData.input_summary)}">${escapeHtml(inputSummary)}${inputTruncated}</span>` : '<span class="text-muted">—</span>'}
|
|
381
|
+
</td>
|
|
382
|
+
<td class="col-output">
|
|
383
|
+
${outputSummary ? `<span class="truncate" title="${escapeHtml(eventData.output_summary)}">${escapeHtml(outputSummary)}${outputTruncated}</span>` : '<span class="text-muted">—</span>'}
|
|
384
|
+
</td>
|
|
385
|
+
<td class="col-status">
|
|
386
|
+
<span class="status-badge status-${eventData.status || 'pending'}">${escapeHtml(eventData.status || 'pending')}</span>
|
|
387
|
+
</td>
|
|
388
|
+
<td class="col-id">
|
|
389
|
+
<code class="event-id-code" title="${escapeHtml(eventData.event_id)}">${escapeHtml(eventData.event_id.substring(0, 8))}</code>
|
|
390
|
+
</td>
|
|
391
|
+
</tr>
|
|
392
|
+
`;
|
|
393
|
+
return html;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function escapeHtml(text) {
|
|
397
|
+
if (!text) return '';
|
|
398
|
+
const div = document.createElement('div');
|
|
399
|
+
div.textContent = text;
|
|
400
|
+
return div.innerHTML;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Convert UTC timestamps to local timezone
|
|
404
|
+
function convertTimestampsToLocal() {
|
|
405
|
+
const timestampElements = document.querySelectorAll('[data-utc-time]');
|
|
406
|
+
console.log('convertTimestampsToLocal() called, found', timestampElements.length, 'timestamps to convert');
|
|
407
|
+
timestampElements.forEach(element => {
|
|
408
|
+
const utcTime = element.getAttribute('data-utc-time');
|
|
409
|
+
if (utcTime) {
|
|
410
|
+
try {
|
|
411
|
+
// Parse ISO 8601 UTC time - convert naive datetime to UTC format
|
|
412
|
+
// Input: "2026-01-06 18:01:19" → "2026-01-06T18:01:19Z"
|
|
413
|
+
const date = new Date(utcTime.replace(' ', 'T') + 'Z');
|
|
414
|
+
// Convert to local timezone using Intl API for best compatibility
|
|
415
|
+
const localTime = new Intl.DateTimeFormat('en-US', {
|
|
416
|
+
year: 'numeric',
|
|
417
|
+
month: '2-digit',
|
|
418
|
+
day: '2-digit',
|
|
419
|
+
hour: '2-digit',
|
|
420
|
+
minute: '2-digit',
|
|
421
|
+
second: '2-digit',
|
|
422
|
+
hour12: false,
|
|
423
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
424
|
+
}).format(date);
|
|
425
|
+
// Replace the displayed timestamp with local time
|
|
426
|
+
element.textContent = localTime;
|
|
427
|
+
// Add title attribute to show full ISO format on hover
|
|
428
|
+
element.setAttribute('title', `UTC: ${utcTime} | Local: ${localTime}`);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
console.warn('Failed to convert timestamp:', utcTime, err);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Handle tab switching
|
|
437
|
+
document.querySelectorAll('.tab-button').forEach(button => {
|
|
438
|
+
button.addEventListener('click', function() {
|
|
439
|
+
// Update active state
|
|
440
|
+
document.querySelectorAll('.tab-button').forEach(b => {
|
|
441
|
+
b.classList.remove('active');
|
|
442
|
+
});
|
|
443
|
+
this.classList.add('active');
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// ============================================
|
|
448
|
+
// Jaeger-Style Trace Interactivity Functions
|
|
449
|
+
// (Must be global for HTMX-loaded partials)
|
|
450
|
+
// ============================================
|
|
451
|
+
|
|
452
|
+
// Toggle expand/collapse with animation
|
|
453
|
+
function toggleTrace(id, event) {
|
|
454
|
+
if (event) event.stopPropagation();
|
|
455
|
+
|
|
456
|
+
const parentRow = document.querySelector(`[data-id="${id}"]`);
|
|
457
|
+
const children = document.querySelectorAll(`[data-parent="${id}"]`);
|
|
458
|
+
const toggle = document.querySelector(`[data-id="${id}"] .expand-toggle`);
|
|
459
|
+
|
|
460
|
+
children.forEach(child => {
|
|
461
|
+
child.classList.toggle('collapsed');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
if (toggle) {
|
|
465
|
+
toggle.classList.toggle('expanded');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Toggle expanded class on parent row for visual styling
|
|
469
|
+
if (parentRow) {
|
|
470
|
+
parentRow.classList.toggle('expanded');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Update breadcrumbs if drilling into a trace
|
|
474
|
+
updateBreadcrumbs(id);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Highlight ancestor path on hover (Jaeger pattern)
|
|
478
|
+
function highlightAncestors(row) {
|
|
479
|
+
clearAncestorHighlight();
|
|
480
|
+
|
|
481
|
+
let parentId = row.dataset.parent;
|
|
482
|
+
while (parentId) {
|
|
483
|
+
const parent = document.querySelector(`[data-id="${parentId}"]`);
|
|
484
|
+
if (parent) {
|
|
485
|
+
parent.classList.add('ancestor-highlight');
|
|
486
|
+
parentId = parent.dataset.parent;
|
|
487
|
+
} else {
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Clear all ancestor highlights
|
|
494
|
+
function clearAncestorHighlight() {
|
|
495
|
+
document.querySelectorAll('.ancestor-highlight').forEach(el => {
|
|
496
|
+
el.classList.remove('ancestor-highlight');
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Expand all traces
|
|
501
|
+
function expandAllTraces() {
|
|
502
|
+
document.querySelectorAll('.child-row').forEach(child => {
|
|
503
|
+
child.classList.remove('collapsed');
|
|
504
|
+
});
|
|
505
|
+
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
506
|
+
toggle.classList.add('expanded');
|
|
507
|
+
});
|
|
508
|
+
document.querySelectorAll('.parent-row.has-children').forEach(parent => {
|
|
509
|
+
parent.classList.add('expanded');
|
|
510
|
+
});
|
|
511
|
+
// Show breadcrumbs when all expanded
|
|
512
|
+
const breadcrumbs = document.getElementById('trace-breadcrumbs');
|
|
513
|
+
if (breadcrumbs) breadcrumbs.style.display = 'flex';
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Collapse all traces
|
|
517
|
+
function collapseAllTraces() {
|
|
518
|
+
document.querySelectorAll('.child-row').forEach(child => {
|
|
519
|
+
child.classList.add('collapsed');
|
|
520
|
+
});
|
|
521
|
+
document.querySelectorAll('.expand-toggle').forEach(toggle => {
|
|
522
|
+
toggle.classList.remove('expanded');
|
|
523
|
+
});
|
|
524
|
+
document.querySelectorAll('.parent-row.has-children').forEach(parent => {
|
|
525
|
+
parent.classList.remove('expanded');
|
|
526
|
+
});
|
|
527
|
+
// Hide breadcrumbs when all collapsed
|
|
528
|
+
const breadcrumbs = document.getElementById('trace-breadcrumbs');
|
|
529
|
+
if (breadcrumbs) breadcrumbs.style.display = 'none';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Breadcrumb management
|
|
533
|
+
let breadcrumbStack = ['root'];
|
|
534
|
+
|
|
535
|
+
function updateBreadcrumbs(id) {
|
|
536
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
537
|
+
if (!breadcrumbsContainer) return;
|
|
538
|
+
|
|
539
|
+
const row = document.querySelector(`[data-id="${id}"]`);
|
|
540
|
+
if (!row) return;
|
|
541
|
+
|
|
542
|
+
// Only show breadcrumbs when we have nested navigation
|
|
543
|
+
const depth = parseInt(row.dataset.depth || '0');
|
|
544
|
+
if (depth > 0 || breadcrumbStack.length > 1) {
|
|
545
|
+
breadcrumbsContainer.style.display = 'flex';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Get tool name or operation for breadcrumb label
|
|
549
|
+
const toolName = row.querySelector('.tool-name');
|
|
550
|
+
const label = toolName ? toolName.textContent : `Trace ${id.substring(0, 8)}`;
|
|
551
|
+
|
|
552
|
+
// Add to breadcrumb stack if not already present
|
|
553
|
+
if (!breadcrumbStack.includes(id)) {
|
|
554
|
+
breadcrumbStack.push(id);
|
|
555
|
+
|
|
556
|
+
const separator = document.createElement('span');
|
|
557
|
+
separator.className = 'separator';
|
|
558
|
+
separator.textContent = '>';
|
|
559
|
+
|
|
560
|
+
const crumb = document.createElement('span');
|
|
561
|
+
crumb.className = 'breadcrumb active';
|
|
562
|
+
crumb.dataset.id = id;
|
|
563
|
+
crumb.textContent = label;
|
|
564
|
+
crumb.onclick = () => navigateToBreadcrumb(id);
|
|
565
|
+
|
|
566
|
+
// Remove active class from previous breadcrumbs
|
|
567
|
+
breadcrumbsContainer.querySelectorAll('.breadcrumb').forEach(b => {
|
|
568
|
+
b.classList.remove('active');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
breadcrumbsContainer.appendChild(separator);
|
|
572
|
+
breadcrumbsContainer.appendChild(crumb);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function navigateToBreadcrumb(id) {
|
|
577
|
+
// Find position in stack and remove everything after
|
|
578
|
+
const index = breadcrumbStack.indexOf(id);
|
|
579
|
+
if (index === -1) return;
|
|
580
|
+
|
|
581
|
+
// Remove breadcrumbs after this one
|
|
582
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
583
|
+
if (!breadcrumbsContainer) return;
|
|
584
|
+
|
|
585
|
+
const allCrumbs = breadcrumbsContainer.querySelectorAll('.breadcrumb, .separator');
|
|
586
|
+
|
|
587
|
+
let removing = false;
|
|
588
|
+
allCrumbs.forEach(el => {
|
|
589
|
+
if (removing) {
|
|
590
|
+
el.remove();
|
|
591
|
+
}
|
|
592
|
+
if (el.dataset && el.dataset.id === id) {
|
|
593
|
+
el.classList.add('active');
|
|
594
|
+
removing = true;
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Update stack
|
|
599
|
+
breadcrumbStack = breadcrumbStack.slice(0, index + 1);
|
|
600
|
+
|
|
601
|
+
// Hide breadcrumbs if back to root
|
|
602
|
+
if (breadcrumbStack.length <= 1) {
|
|
603
|
+
breadcrumbsContainer.style.display = 'none';
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function resetBreadcrumbs() {
|
|
608
|
+
const breadcrumbsContainer = document.getElementById('trace-breadcrumbs');
|
|
609
|
+
if (!breadcrumbsContainer) return;
|
|
610
|
+
|
|
611
|
+
breadcrumbsContainer.innerHTML = '<span class="breadcrumb" data-id="root" onclick="resetBreadcrumbs()">Session</span>';
|
|
612
|
+
breadcrumbsContainer.style.display = 'none';
|
|
613
|
+
breadcrumbStack = ['root'];
|
|
614
|
+
|
|
615
|
+
// Collapse all traces
|
|
616
|
+
collapseAllTraces();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Initialize timestamps in activity feed after HTMX swap
|
|
620
|
+
function initializeActivityFeedTimestamps() {
|
|
621
|
+
document.querySelectorAll('.timestamp-text[data-utc-time]').forEach(el => {
|
|
622
|
+
const utcTime = el.dataset.utcTime;
|
|
623
|
+
if (utcTime) {
|
|
624
|
+
try {
|
|
625
|
+
const date = new Date(utcTime);
|
|
626
|
+
el.textContent = date.toLocaleTimeString();
|
|
627
|
+
el.title = date.toLocaleString();
|
|
628
|
+
} catch (e) {
|
|
629
|
+
// Keep original text if parsing fails
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Re-initialize after HTMX swaps in activity feed
|
|
636
|
+
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
|
637
|
+
if (evt.detail.target && evt.detail.target.id === 'content-area') {
|
|
638
|
+
initializeActivityFeedTimestamps();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// ============================================
|
|
643
|
+
// Spawner Activity Filtering
|
|
644
|
+
// ============================================
|
|
645
|
+
|
|
646
|
+
function filterByAgentType(filterType) {
|
|
647
|
+
const turns = document.querySelectorAll('.conversation-turn');
|
|
648
|
+
turns.forEach(turn => {
|
|
649
|
+
const spawnerType = turn.dataset.spawnerType || 'direct';
|
|
650
|
+
|
|
651
|
+
let shouldShow = false;
|
|
652
|
+
if (filterType === 'all') {
|
|
653
|
+
shouldShow = true;
|
|
654
|
+
} else if (filterType === 'direct') {
|
|
655
|
+
shouldShow = spawnerType === 'direct';
|
|
656
|
+
} else if (filterType === 'spawner') {
|
|
657
|
+
shouldShow = spawnerType !== 'direct';
|
|
658
|
+
} else {
|
|
659
|
+
// Specific spawner type (gemini, codex, copilot)
|
|
660
|
+
shouldShow = spawnerType === filterType;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (shouldShow) {
|
|
664
|
+
turn.classList.remove('hidden');
|
|
665
|
+
} else {
|
|
666
|
+
turn.classList.add('hidden');
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
</script>
|
|
671
|
+
|
|
672
|
+
<!-- Styles for real-time event highlighting and spawner badges -->
|
|
673
|
+
<style>
|
|
674
|
+
.new-event-highlight {
|
|
675
|
+
animation: highlightPulse 2s ease-out;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/* ============================================
|
|
679
|
+
Spawner Badge Styling
|
|
680
|
+
============================================ */
|
|
681
|
+
|
|
682
|
+
.delegation-arrow {
|
|
683
|
+
color: var(--text-secondary);
|
|
684
|
+
margin: 0 0.5rem;
|
|
685
|
+
font-size: 0.9em;
|
|
686
|
+
font-weight: normal;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.spawner-badge {
|
|
690
|
+
display: inline-flex;
|
|
691
|
+
align-items: center;
|
|
692
|
+
gap: 0.25rem;
|
|
693
|
+
padding: 0.25rem 0.6rem;
|
|
694
|
+
border-radius: 4px;
|
|
695
|
+
font-size: 0.85rem;
|
|
696
|
+
font-weight: 500;
|
|
697
|
+
border: 1px solid;
|
|
698
|
+
background: white;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.spawner-badge.spawner-gemini {
|
|
702
|
+
background: #e8f5e9;
|
|
703
|
+
color: #2e7d32;
|
|
704
|
+
border-color: #4caf50;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.spawner-badge.spawner-codex {
|
|
708
|
+
background: #e3f2fd;
|
|
709
|
+
color: #1565c0;
|
|
710
|
+
border-color: #2196f3;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.spawner-badge.spawner-copilot {
|
|
714
|
+
background: #f3e5f5;
|
|
715
|
+
color: #6a1b9a;
|
|
716
|
+
border-color: #9c27b0;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.cost-badge {
|
|
720
|
+
font-size: 0.75rem;
|
|
721
|
+
opacity: 0.8;
|
|
722
|
+
margin-left: 0.25rem;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Activity feed spawner filter */
|
|
726
|
+
.spawner-filter {
|
|
727
|
+
padding: 0.75rem 1rem;
|
|
728
|
+
margin: 0.5rem 0.5rem 1rem 0.5rem;
|
|
729
|
+
border: 1px solid var(--border-subtle);
|
|
730
|
+
border-radius: 4px;
|
|
731
|
+
background: rgba(163, 230, 53, 0.02);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.spawner-filter label {
|
|
735
|
+
margin-right: 0.5rem;
|
|
736
|
+
font-weight: 500;
|
|
737
|
+
font-size: 0.85rem;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.spawner-filter select {
|
|
741
|
+
padding: 0.4rem 0.6rem;
|
|
742
|
+
border: 1px solid var(--border-subtle);
|
|
743
|
+
border-radius: 4px;
|
|
744
|
+
background: var(--bg-base);
|
|
745
|
+
color: var(--text-primary);
|
|
746
|
+
cursor: pointer;
|
|
747
|
+
font-size: 0.85rem;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.spawner-filter select:hover {
|
|
751
|
+
border-color: var(--accent, #c8ff00);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/* Hide turns based on filter */
|
|
755
|
+
.conversation-turn.hidden {
|
|
756
|
+
display: none;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
@keyframes highlightPulse {
|
|
760
|
+
0% {
|
|
761
|
+
background-color: rgba(16, 185, 129, 0.2);
|
|
762
|
+
border-left: 4px solid #10b981;
|
|
763
|
+
}
|
|
764
|
+
100% {
|
|
765
|
+
background-color: transparent;
|
|
766
|
+
border-left: 4px solid transparent;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.auto-refresh-indicator {
|
|
771
|
+
transition: opacity 0.3s ease;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.refresh-dot {
|
|
775
|
+
display: inline-block;
|
|
776
|
+
width: 8px;
|
|
777
|
+
height: 8px;
|
|
778
|
+
border-radius: 50%;
|
|
779
|
+
margin-right: 8px;
|
|
780
|
+
background-color: #10b981;
|
|
781
|
+
animation: pulse-dot 2s infinite;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
@keyframes pulse-dot {
|
|
785
|
+
0%, 100% {
|
|
786
|
+
opacity: 1;
|
|
787
|
+
}
|
|
788
|
+
50% {
|
|
789
|
+
opacity: 0.5;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
</style>
|
|
793
|
+
</body>
|
|
794
|
+
</html>
|