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
|
@@ -0,0 +1,1100 @@
|
|
|
1
|
+
<!-- Grouped Activity Feed - Display events organized by conversation turn (user prompt) -->
|
|
2
|
+
<div class="view-container activity-feed-view">
|
|
3
|
+
<div class="view-header">
|
|
4
|
+
<h2>Agent Activity Feed</h2>
|
|
5
|
+
<div class="view-info">
|
|
6
|
+
<small>Events grouped by conversation turn. Click to expand/collapse child events.</small>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="view-filters">
|
|
9
|
+
<div class="trace-controls">
|
|
10
|
+
<button type="button" class="btn-small" onclick="expandAllConversationTurns()" title="Expand all conversation turns">Expand All</button>
|
|
11
|
+
<button type="button" class="btn-small" onclick="collapseAllConversationTurns()" title="Collapse all conversation turns">Collapse All</button>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Spawner Activity Filter -->
|
|
17
|
+
<div class="spawner-filter">
|
|
18
|
+
<label for="agent-type-filter">Filter:</label>
|
|
19
|
+
<select id="agent-type-filter" onchange="filterByAgentType(this.value)">
|
|
20
|
+
<option value="all">All Activity</option>
|
|
21
|
+
<option value="direct">Direct Actions Only</option>
|
|
22
|
+
<option value="spawner">Spawner Delegations Only</option>
|
|
23
|
+
<optgroup label="Specific Spawners">
|
|
24
|
+
<option value="gemini">Gemini (FREE)</option>
|
|
25
|
+
<option value="codex">Codex (Paid)</option>
|
|
26
|
+
<option value="copilot">Copilot (GitHub)</option>
|
|
27
|
+
</optgroup>
|
|
28
|
+
</select>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div class="conversation-feed">
|
|
32
|
+
{% if conversation_turns %}
|
|
33
|
+
<div class="conversation-turns-list">
|
|
34
|
+
{% for turn in conversation_turns %}
|
|
35
|
+
<!-- Conversation Turn Container -->
|
|
36
|
+
<div class="conversation-turn"
|
|
37
|
+
data-turn-id="{{ turn.userQuery.event_id }}"
|
|
38
|
+
data-spawner-type="{% if turn.has_spawner %}spawner{% else %}direct{% endif %}"
|
|
39
|
+
data-agent="{{ turn.userQuery.agent_id }}">
|
|
40
|
+
<!-- User Query Parent Row (Clickable) -->
|
|
41
|
+
<div class="userquery-parent"
|
|
42
|
+
onclick="toggleConversationTurn('{{ turn.userQuery.event_id }}')"
|
|
43
|
+
data-turn-id="{{ turn.userQuery.event_id }}">
|
|
44
|
+
|
|
45
|
+
<!-- Expand/Collapse Toggle -->
|
|
46
|
+
<span class="expand-toggle-turn" id="toggle-{{ turn.userQuery.event_id }}">▶</span>
|
|
47
|
+
|
|
48
|
+
<!-- User Prompt Text -->
|
|
49
|
+
<div class="prompt-section">
|
|
50
|
+
<span class="prompt-text" title="{{ turn.userQuery.prompt }}">
|
|
51
|
+
{{ turn.userQuery.prompt[:100] }}{% if turn.userQuery.prompt|length > 100 %}...{% endif %}
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<!-- Stats Badges -->
|
|
56
|
+
<div class="turn-stats">
|
|
57
|
+
{% if turn.stats.tool_count > 0 %}
|
|
58
|
+
<span class="stat-badge tool-count" title="Tool calls">
|
|
59
|
+
{{ turn.stats.tool_count }}
|
|
60
|
+
</span>
|
|
61
|
+
{% endif %}
|
|
62
|
+
|
|
63
|
+
<span class="stat-badge duration" title="Total duration">
|
|
64
|
+
{{ "%.2f"|format(turn.stats.total_duration) }}s
|
|
65
|
+
</span>
|
|
66
|
+
|
|
67
|
+
{% if turn.stats.success_count > 0 %}
|
|
68
|
+
<span class="stat-badge success" title="Successful operations">
|
|
69
|
+
✓ {{ turn.stats.success_count }}
|
|
70
|
+
</span>
|
|
71
|
+
{% endif %}
|
|
72
|
+
|
|
73
|
+
{% if turn.stats.error_count > 0 %}
|
|
74
|
+
<span class="stat-badge error" title="Errors">
|
|
75
|
+
✗ {{ turn.stats.error_count }}
|
|
76
|
+
</span>
|
|
77
|
+
{% endif %}
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Timestamp -->
|
|
81
|
+
<div class="turn-timestamp">
|
|
82
|
+
{{ turn.userQuery.timestamp }}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Child Events Container (Hidden by default) -->
|
|
87
|
+
<div class="turn-children collapsed" id="children-{{ turn.userQuery.event_id }}">
|
|
88
|
+
{% if turn.children %}
|
|
89
|
+
{% for child in turn.children recursive %}
|
|
90
|
+
<!-- Child Event Row -->
|
|
91
|
+
<div class="child-event-row depth-{{ child.depth|default(0) }}"
|
|
92
|
+
data-event-id="{{ child.event_id }}"
|
|
93
|
+
style="margin-left: {{ (child.depth|default(0)) * 20 }}px;">
|
|
94
|
+
<!-- Tree Connector -->
|
|
95
|
+
<span class="tree-connector">
|
|
96
|
+
{% if loop.last and not child.children %}└─{% else %}├─{% endif %}
|
|
97
|
+
</span>
|
|
98
|
+
|
|
99
|
+
<!-- Tool Name -->
|
|
100
|
+
<span class="child-tool-name">{{ child.tool_name }}</span>
|
|
101
|
+
|
|
102
|
+
<!-- Summary/Input -->
|
|
103
|
+
<span class="child-summary" title="{{ child.summary }}">
|
|
104
|
+
{{ child.summary[:80] }}{% if child.summary|length > 80 %}...{% endif %}
|
|
105
|
+
</span>
|
|
106
|
+
|
|
107
|
+
<!-- Agent Badge with Spawner Support -->
|
|
108
|
+
{% if child.spawner_type %}
|
|
109
|
+
<!-- Spawner delegation: show orchestrator → spawned AI -->
|
|
110
|
+
<span class="child-agent-badge agent-{{ child.agent|lower|replace(' ', '-') }}">
|
|
111
|
+
{{ child.agent }}
|
|
112
|
+
{% if child.model %}
|
|
113
|
+
<span class="model-indicator">{{ child.model }}</span>
|
|
114
|
+
{% endif %}
|
|
115
|
+
</span>
|
|
116
|
+
<span class="delegation-arrow">→</span>
|
|
117
|
+
<span class="spawner-badge spawner-{{ child.spawner_type|lower }}">
|
|
118
|
+
{{ child.spawned_agent or child.subagent_type or child.spawner_type }}
|
|
119
|
+
{% if child.cost_usd %}
|
|
120
|
+
<span class="cost-badge">${{ "%.2f"|format(child.cost_usd) }}</span>
|
|
121
|
+
{% endif %}
|
|
122
|
+
</span>
|
|
123
|
+
{% else %}
|
|
124
|
+
<!-- Regular agent: just show agent name + model if available -->
|
|
125
|
+
<span class="child-agent-badge agent-{{ child.agent|lower|replace(' ', '-') }}">
|
|
126
|
+
{{ child.agent }}
|
|
127
|
+
{% if child.model %}
|
|
128
|
+
<span class="model-indicator">{{ child.model }}</span>
|
|
129
|
+
{% endif %}
|
|
130
|
+
</span>
|
|
131
|
+
{% endif %}
|
|
132
|
+
|
|
133
|
+
<!-- Feature Attribution -->
|
|
134
|
+
{% if child.feature_id %}
|
|
135
|
+
<span class="child-feature-badge" title="Linked to {{ child.feature_id }}">
|
|
136
|
+
#{{ child.feature_id[:8] }}
|
|
137
|
+
</span>
|
|
138
|
+
{% endif %}
|
|
139
|
+
|
|
140
|
+
<!-- Duration -->
|
|
141
|
+
<span class="child-duration">
|
|
142
|
+
{{ "%.2f"|format(child.duration_seconds) }}s
|
|
143
|
+
</span>
|
|
144
|
+
|
|
145
|
+
<!-- Timestamp -->
|
|
146
|
+
<span class="child-timestamp">
|
|
147
|
+
{{ child.timestamp }}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<!-- Recursively render nested children -->
|
|
151
|
+
{% if child.children %}
|
|
152
|
+
{{ loop(child.children) }}
|
|
153
|
+
{% endif %}
|
|
154
|
+
{% endfor %}
|
|
155
|
+
{% else %}
|
|
156
|
+
<div class="no-children-message">
|
|
157
|
+
<span class="tree-connector">└─</span>
|
|
158
|
+
<span class="text-muted">No child events</span>
|
|
159
|
+
</div>
|
|
160
|
+
{% endif %}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
{% endfor %}
|
|
164
|
+
</div>
|
|
165
|
+
{% else %}
|
|
166
|
+
<div class="empty-state">
|
|
167
|
+
<p>No conversation turns found</p>
|
|
168
|
+
<small>Agent activity will appear here as tasks are executed</small>
|
|
169
|
+
</div>
|
|
170
|
+
{% endif %}
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<!-- Auto-refresh indicator -->
|
|
174
|
+
<div class="auto-refresh-indicator">
|
|
175
|
+
<span class="refresh-dot"></span>
|
|
176
|
+
Live updates enabled (via WebSocket)
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<style>
|
|
181
|
+
/* ============================================
|
|
182
|
+
Grouped Activity Feed - Conversation Turns
|
|
183
|
+
============================================ */
|
|
184
|
+
|
|
185
|
+
.conversation-feed {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-direction: column;
|
|
188
|
+
font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
|
|
189
|
+
font-size: 0.8rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Conversation Turns List Container */
|
|
193
|
+
.conversation-turns-list {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
gap: 0.5rem;
|
|
197
|
+
padding: 0.5rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Individual Conversation Turn */
|
|
201
|
+
.conversation-turn {
|
|
202
|
+
border-radius: 4px;
|
|
203
|
+
overflow: hidden;
|
|
204
|
+
background: var(--bg-base);
|
|
205
|
+
border: 1px solid var(--border-subtle);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* User Query Parent Row - Clickable Header */
|
|
209
|
+
.userquery-parent {
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
gap: 0.75rem;
|
|
213
|
+
padding: 1rem;
|
|
214
|
+
background: rgba(200, 255, 0, 0.08);
|
|
215
|
+
border-left: 3px solid var(--accent, #c8ff00);
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
transition: all 0.15s ease;
|
|
218
|
+
user-select: none;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.userquery-parent:hover {
|
|
222
|
+
background: rgba(200, 255, 0, 0.12);
|
|
223
|
+
border-left-color: #a3e635;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Expand/Collapse Toggle */
|
|
227
|
+
.expand-toggle-turn {
|
|
228
|
+
display: inline-flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
justify-content: center;
|
|
231
|
+
width: 24px;
|
|
232
|
+
height: 24px;
|
|
233
|
+
font-size: 0.8rem;
|
|
234
|
+
color: var(--accent-lime, #a3e635);
|
|
235
|
+
transition: transform 0.2s ease;
|
|
236
|
+
flex-shrink: 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.expand-toggle-turn.expanded {
|
|
240
|
+
transform: rotate(90deg);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Prompt Section */
|
|
244
|
+
.prompt-section {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: 0.5rem;
|
|
248
|
+
flex: 1;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.prompt-icon {
|
|
253
|
+
font-size: 1.1rem;
|
|
254
|
+
flex-shrink: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.prompt-text {
|
|
258
|
+
color: var(--text-primary);
|
|
259
|
+
font-weight: 500;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
text-overflow: ellipsis;
|
|
262
|
+
white-space: nowrap;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Turn Stats Container */
|
|
266
|
+
.turn-stats {
|
|
267
|
+
display: flex;
|
|
268
|
+
align-items: center;
|
|
269
|
+
gap: 0.4rem;
|
|
270
|
+
flex-shrink: 0;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Stat Badges */
|
|
274
|
+
.stat-badge {
|
|
275
|
+
display: inline-block;
|
|
276
|
+
padding: 0.25rem 0.5rem;
|
|
277
|
+
font-size: 0.65rem;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
text-transform: uppercase;
|
|
280
|
+
letter-spacing: 0.05em;
|
|
281
|
+
border-radius: 3px;
|
|
282
|
+
background: rgba(255, 255, 255, 0.1);
|
|
283
|
+
color: var(--text-secondary);
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.stat-badge.tool-count {
|
|
288
|
+
background: rgba(163, 230, 53, 0.2);
|
|
289
|
+
color: #a3e635;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.stat-badge.duration {
|
|
293
|
+
background: rgba(100, 200, 255, 0.2);
|
|
294
|
+
color: #64c8ff;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.stat-badge.success {
|
|
298
|
+
background: rgba(34, 197, 94, 0.2);
|
|
299
|
+
color: #22c55e;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.stat-badge.error {
|
|
303
|
+
background: rgba(239, 68, 68, 0.2);
|
|
304
|
+
color: #ef4444;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Turn Timestamp */
|
|
308
|
+
.turn-timestamp {
|
|
309
|
+
font-size: 0.65rem;
|
|
310
|
+
color: var(--text-muted);
|
|
311
|
+
flex-shrink: 0;
|
|
312
|
+
min-width: 140px;
|
|
313
|
+
text-align: right;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/* Child Events Container */
|
|
317
|
+
.turn-children {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
background: rgba(163, 230, 53, 0.02);
|
|
321
|
+
border-top: 1px solid var(--border-subtle);
|
|
322
|
+
padding: 0.5rem 1rem 0.5rem 2.5rem; /* Indent children under parent prompt */
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.turn-children.collapsed {
|
|
326
|
+
display: none;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Child Event Row */
|
|
330
|
+
.child-event-row {
|
|
331
|
+
display: flex;
|
|
332
|
+
align-items: center;
|
|
333
|
+
gap: 0.2rem;
|
|
334
|
+
padding: 0.5rem 0;
|
|
335
|
+
border-bottom: 1px solid rgba(163, 230, 53, 0.1);
|
|
336
|
+
font-size: 0.75rem;
|
|
337
|
+
margin-left: 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.child-event-row:last-child {
|
|
341
|
+
border-bottom: none;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Tree Connector */
|
|
345
|
+
.tree-connector {
|
|
346
|
+
color: var(--accent-lime, #a3e635);
|
|
347
|
+
font-size: 0.9rem;
|
|
348
|
+
font-weight: bold;
|
|
349
|
+
font-family: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
|
|
350
|
+
white-space: nowrap; /* Changed from 'pre' to collapse template whitespace */
|
|
351
|
+
flex-shrink: 0;
|
|
352
|
+
width: 24px; /* Fixed width for consistent alignment */
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/* Child Tool Name */
|
|
356
|
+
.child-tool-name {
|
|
357
|
+
display: inline-block;
|
|
358
|
+
padding: 0.1rem 0.3rem;
|
|
359
|
+
background: rgba(255, 255, 255, 0.05);
|
|
360
|
+
color: var(--text-primary);
|
|
361
|
+
border-radius: 2px;
|
|
362
|
+
font-weight: 500;
|
|
363
|
+
flex-shrink: 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* Child Summary */
|
|
367
|
+
.child-summary {
|
|
368
|
+
color: var(--text-secondary);
|
|
369
|
+
flex: 1;
|
|
370
|
+
min-width: 0;
|
|
371
|
+
overflow: hidden;
|
|
372
|
+
text-overflow: ellipsis;
|
|
373
|
+
white-space: nowrap;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Child Agent Badge */
|
|
377
|
+
.child-agent-badge {
|
|
378
|
+
display: inline-flex;
|
|
379
|
+
align-items: center;
|
|
380
|
+
gap: 0.25rem;
|
|
381
|
+
padding: 0.1rem 0.35rem;
|
|
382
|
+
font-size: 0.6rem;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
text-transform: uppercase;
|
|
385
|
+
letter-spacing: 0.05em;
|
|
386
|
+
border-radius: 2px;
|
|
387
|
+
background: rgba(255, 255, 255, 0.08);
|
|
388
|
+
color: var(--text-secondary);
|
|
389
|
+
flex-shrink: 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* Model Indicator Badge */
|
|
393
|
+
.model-indicator {
|
|
394
|
+
display: inline-block;
|
|
395
|
+
padding: 0.05rem 0.2rem;
|
|
396
|
+
font-size: 0.55rem;
|
|
397
|
+
background: rgba(100, 200, 255, 0.15);
|
|
398
|
+
color: #64c8ff;
|
|
399
|
+
border-radius: 1px;
|
|
400
|
+
font-weight: 700;
|
|
401
|
+
letter-spacing: 0.02em;
|
|
402
|
+
text-transform: capitalize;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.child-agent-badge.agent-claude-code,
|
|
406
|
+
.child-agent-badge.agent-claude {
|
|
407
|
+
background: rgba(200, 255, 0, 0.15);
|
|
408
|
+
color: #c8ff00;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.child-agent-badge.agent-gemini,
|
|
412
|
+
.child-agent-badge.agent-gemini-2-0-flash-exp {
|
|
413
|
+
background: rgba(74, 222, 128, 0.15);
|
|
414
|
+
color: #4ade80;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/* Feature Badge */
|
|
418
|
+
.child-feature-badge {
|
|
419
|
+
display: inline-block;
|
|
420
|
+
padding: 0.1rem 0.35rem;
|
|
421
|
+
font-size: 0.65rem;
|
|
422
|
+
font-weight: 700;
|
|
423
|
+
background: rgba(255, 255, 255, 0.05);
|
|
424
|
+
color: var(--accent-lime);
|
|
425
|
+
border: 1px solid rgba(163, 230, 53, 0.3);
|
|
426
|
+
border-radius: 2px;
|
|
427
|
+
font-family: 'JetBrains Mono', monospace;
|
|
428
|
+
flex-shrink: 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* Child Duration */
|
|
432
|
+
.child-duration {
|
|
433
|
+
display: inline-block;
|
|
434
|
+
padding: 0.1rem 0.3rem;
|
|
435
|
+
background: rgba(100, 200, 255, 0.1);
|
|
436
|
+
color: #64c8ff;
|
|
437
|
+
border-radius: 2px;
|
|
438
|
+
font-size: 0.65rem;
|
|
439
|
+
flex-shrink: 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Child Timestamp */
|
|
443
|
+
.child-timestamp {
|
|
444
|
+
color: var(--text-muted);
|
|
445
|
+
font-size: 0.65rem;
|
|
446
|
+
flex-shrink: 0;
|
|
447
|
+
min-width: 140px;
|
|
448
|
+
text-align: right;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* No Children Message */
|
|
452
|
+
.no-children-message {
|
|
453
|
+
display: flex;
|
|
454
|
+
align-items: center;
|
|
455
|
+
gap: 0.5rem;
|
|
456
|
+
padding: 0.6rem 0;
|
|
457
|
+
color: var(--text-muted);
|
|
458
|
+
font-size: 0.75rem;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* ============================================
|
|
462
|
+
Control Buttons
|
|
463
|
+
============================================ */
|
|
464
|
+
|
|
465
|
+
.trace-controls {
|
|
466
|
+
display: flex;
|
|
467
|
+
gap: 0.5rem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.btn-small {
|
|
471
|
+
padding: 0.3rem 0.6rem;
|
|
472
|
+
font-size: 0.65rem;
|
|
473
|
+
background: var(--bg-darker);
|
|
474
|
+
border: 1px solid var(--border-subtle);
|
|
475
|
+
color: var(--text-secondary);
|
|
476
|
+
cursor: pointer;
|
|
477
|
+
border-radius: 2px;
|
|
478
|
+
transition: all 0.15s ease;
|
|
479
|
+
text-transform: uppercase;
|
|
480
|
+
letter-spacing: 0.05em;
|
|
481
|
+
font-family: inherit;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.btn-small:hover {
|
|
485
|
+
background: rgba(163, 230, 53, 0.1);
|
|
486
|
+
border-color: var(--accent-lime, #a3e635);
|
|
487
|
+
color: var(--accent-lime, #a3e635);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* ============================================
|
|
491
|
+
Empty State
|
|
492
|
+
============================================ */
|
|
493
|
+
|
|
494
|
+
.empty-state {
|
|
495
|
+
padding: 3rem;
|
|
496
|
+
text-align: center;
|
|
497
|
+
color: var(--text-muted);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.empty-state p {
|
|
501
|
+
font-size: 1rem;
|
|
502
|
+
margin-bottom: 0.5rem;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/* ============================================
|
|
506
|
+
Auto-refresh Indicator
|
|
507
|
+
============================================ */
|
|
508
|
+
|
|
509
|
+
.auto-refresh-indicator {
|
|
510
|
+
display: flex;
|
|
511
|
+
align-items: center;
|
|
512
|
+
gap: 0.5rem;
|
|
513
|
+
padding: 0.5rem 1rem;
|
|
514
|
+
font-size: 0.65rem;
|
|
515
|
+
color: var(--text-muted);
|
|
516
|
+
background: var(--bg-darker);
|
|
517
|
+
border-top: 1px solid var(--border-subtle);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.refresh-dot {
|
|
521
|
+
width: 6px;
|
|
522
|
+
height: 6px;
|
|
523
|
+
background: var(--accent-lime, #a3e635);
|
|
524
|
+
border-radius: 50%;
|
|
525
|
+
animation: pulse 2s ease-in-out infinite;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
@keyframes pulse {
|
|
529
|
+
0%, 100% { opacity: 1; }
|
|
530
|
+
50% { opacity: 0.4; }
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/* ============================================
|
|
534
|
+
Responsive Adjustments
|
|
535
|
+
============================================ */
|
|
536
|
+
|
|
537
|
+
@media (max-width: 1200px) {
|
|
538
|
+
.turn-timestamp,
|
|
539
|
+
.child-timestamp {
|
|
540
|
+
display: none;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
@media (max-width: 900px) {
|
|
545
|
+
.turn-stats {
|
|
546
|
+
flex-wrap: wrap;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.child-summary {
|
|
550
|
+
max-width: 150px;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
@media (max-width: 700px) {
|
|
555
|
+
.userquery-parent {
|
|
556
|
+
flex-wrap: wrap;
|
|
557
|
+
gap: 0.5rem;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
.prompt-section {
|
|
561
|
+
width: 100%;
|
|
562
|
+
order: 2;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.turn-stats {
|
|
566
|
+
order: 3;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.child-event-row {
|
|
570
|
+
flex-wrap: wrap;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.child-summary {
|
|
574
|
+
width: 100%;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* ============================================
|
|
579
|
+
Live Spawner Indicator Styles
|
|
580
|
+
============================================ */
|
|
581
|
+
|
|
582
|
+
.live-spawner-indicator {
|
|
583
|
+
display: none;
|
|
584
|
+
align-items: center;
|
|
585
|
+
gap: 0.75rem;
|
|
586
|
+
padding: 0.75rem 1rem;
|
|
587
|
+
margin: 0.5rem;
|
|
588
|
+
background: linear-gradient(135deg, rgba(163, 230, 53, 0.1) 0%, rgba(100, 200, 255, 0.1) 100%);
|
|
589
|
+
border: 1px solid rgba(163, 230, 53, 0.3);
|
|
590
|
+
border-radius: 6px;
|
|
591
|
+
font-size: 0.8rem;
|
|
592
|
+
transition: all 0.3s ease;
|
|
593
|
+
overflow: hidden;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.live-spawner-indicator.active {
|
|
597
|
+
display: flex;
|
|
598
|
+
border-color: rgba(163, 230, 53, 0.6);
|
|
599
|
+
box-shadow: 0 0 20px rgba(163, 230, 53, 0.2);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.live-spawner-indicator.completed {
|
|
603
|
+
display: flex;
|
|
604
|
+
background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(34, 197, 94, 0.05) 100%);
|
|
605
|
+
border-color: rgba(34, 197, 94, 0.5);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.live-spawner-indicator.failed {
|
|
609
|
+
display: flex;
|
|
610
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.15) 0%, rgba(239, 68, 68, 0.05) 100%);
|
|
611
|
+
border-color: rgba(239, 68, 68, 0.5);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.live-spawner-indicator.fade-out {
|
|
615
|
+
opacity: 0;
|
|
616
|
+
transform: translateY(-10px);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/* Spawner Icons */
|
|
620
|
+
.live-spawner-icon {
|
|
621
|
+
width: 24px;
|
|
622
|
+
height: 24px;
|
|
623
|
+
border-radius: 50%;
|
|
624
|
+
display: flex;
|
|
625
|
+
align-items: center;
|
|
626
|
+
justify-content: center;
|
|
627
|
+
flex-shrink: 0;
|
|
628
|
+
position: relative;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.live-spawner-icon::before {
|
|
632
|
+
content: '';
|
|
633
|
+
width: 12px;
|
|
634
|
+
height: 12px;
|
|
635
|
+
border-radius: 50%;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.live-spawner-icon.spawner-gemini {
|
|
639
|
+
background: rgba(74, 222, 128, 0.2);
|
|
640
|
+
}
|
|
641
|
+
.live-spawner-icon.spawner-gemini::before {
|
|
642
|
+
background: #4ade80;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
.live-spawner-icon.spawner-codex {
|
|
646
|
+
background: rgba(100, 200, 255, 0.2);
|
|
647
|
+
}
|
|
648
|
+
.live-spawner-icon.spawner-codex::before {
|
|
649
|
+
background: #64c8ff;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.live-spawner-icon.spawner-copilot {
|
|
653
|
+
background: rgba(168, 85, 247, 0.2);
|
|
654
|
+
}
|
|
655
|
+
.live-spawner-icon.spawner-copilot::before {
|
|
656
|
+
background: #a855f7;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/* Pulsing animation for active spawners */
|
|
660
|
+
.live-spawner-icon.pulsing::after {
|
|
661
|
+
content: '';
|
|
662
|
+
position: absolute;
|
|
663
|
+
width: 100%;
|
|
664
|
+
height: 100%;
|
|
665
|
+
border-radius: 50%;
|
|
666
|
+
border: 2px solid currentColor;
|
|
667
|
+
animation: spawner-pulse 1.5s ease-out infinite;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.live-spawner-icon.spawner-gemini.pulsing::after { border-color: #4ade80; }
|
|
671
|
+
.live-spawner-icon.spawner-codex.pulsing::after { border-color: #64c8ff; }
|
|
672
|
+
.live-spawner-icon.spawner-copilot.pulsing::after { border-color: #a855f7; }
|
|
673
|
+
|
|
674
|
+
@keyframes spawner-pulse {
|
|
675
|
+
0% {
|
|
676
|
+
transform: scale(1);
|
|
677
|
+
opacity: 1;
|
|
678
|
+
}
|
|
679
|
+
100% {
|
|
680
|
+
transform: scale(2);
|
|
681
|
+
opacity: 0;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/* Streaming animation */
|
|
686
|
+
.live-spawner-icon.streaming::after {
|
|
687
|
+
content: '';
|
|
688
|
+
position: absolute;
|
|
689
|
+
width: 6px;
|
|
690
|
+
height: 6px;
|
|
691
|
+
background: currentColor;
|
|
692
|
+
border-radius: 50%;
|
|
693
|
+
animation: spawner-stream 0.6s ease-in-out infinite;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
@keyframes spawner-stream {
|
|
697
|
+
0%, 100% { opacity: 0.3; transform: translateX(-8px); }
|
|
698
|
+
50% { opacity: 1; transform: translateX(8px); }
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/* Done state */
|
|
702
|
+
.live-spawner-icon.done::after {
|
|
703
|
+
content: '';
|
|
704
|
+
position: absolute;
|
|
705
|
+
width: 8px;
|
|
706
|
+
height: 4px;
|
|
707
|
+
border-left: 2px solid #22c55e;
|
|
708
|
+
border-bottom: 2px solid #22c55e;
|
|
709
|
+
transform: rotate(-45deg);
|
|
710
|
+
top: 8px;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/* Error state */
|
|
714
|
+
.live-spawner-icon.error::before {
|
|
715
|
+
background: #ef4444;
|
|
716
|
+
}
|
|
717
|
+
.live-spawner-icon.error::after {
|
|
718
|
+
content: '!';
|
|
719
|
+
position: absolute;
|
|
720
|
+
color: white;
|
|
721
|
+
font-size: 10px;
|
|
722
|
+
font-weight: bold;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Text elements */
|
|
726
|
+
.live-spawner-text {
|
|
727
|
+
color: var(--text-primary);
|
|
728
|
+
white-space: nowrap;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.live-spawner-text strong {
|
|
732
|
+
color: var(--accent-lime, #a3e635);
|
|
733
|
+
letter-spacing: 0.05em;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.live-spawner-prompt,
|
|
737
|
+
.live-spawner-message {
|
|
738
|
+
flex: 1;
|
|
739
|
+
color: var(--text-secondary);
|
|
740
|
+
overflow: hidden;
|
|
741
|
+
text-overflow: ellipsis;
|
|
742
|
+
white-space: nowrap;
|
|
743
|
+
font-style: italic;
|
|
744
|
+
opacity: 0.8;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.live-spawner-progress {
|
|
748
|
+
padding: 0.15rem 0.4rem;
|
|
749
|
+
background: rgba(100, 200, 255, 0.2);
|
|
750
|
+
color: #64c8ff;
|
|
751
|
+
border-radius: 3px;
|
|
752
|
+
font-weight: 600;
|
|
753
|
+
font-size: 0.7rem;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.live-spawner-duration {
|
|
757
|
+
padding: 0.15rem 0.4rem;
|
|
758
|
+
background: rgba(34, 197, 94, 0.2);
|
|
759
|
+
color: #22c55e;
|
|
760
|
+
border-radius: 3px;
|
|
761
|
+
font-weight: 600;
|
|
762
|
+
font-size: 0.7rem;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.live-spawner-tokens {
|
|
766
|
+
padding: 0.15rem 0.4rem;
|
|
767
|
+
background: rgba(163, 230, 53, 0.2);
|
|
768
|
+
color: #a3e635;
|
|
769
|
+
border-radius: 3px;
|
|
770
|
+
font-size: 0.65rem;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.live-spawner-details {
|
|
774
|
+
color: var(--text-muted);
|
|
775
|
+
font-size: 0.7rem;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.live-spawner-error {
|
|
779
|
+
color: #ef4444;
|
|
780
|
+
font-size: 0.7rem;
|
|
781
|
+
overflow: hidden;
|
|
782
|
+
text-overflow: ellipsis;
|
|
783
|
+
white-space: nowrap;
|
|
784
|
+
max-width: 300px;
|
|
785
|
+
}
|
|
786
|
+
</style>
|
|
787
|
+
|
|
788
|
+
<script>
|
|
789
|
+
// JavaScript functions for expand/collapse functionality
|
|
790
|
+
// Called from onclick handlers in the template
|
|
791
|
+
|
|
792
|
+
function toggleConversationTurn(turnId) {
|
|
793
|
+
const childrenContainer = document.getElementById(`children-${turnId}`);
|
|
794
|
+
const toggleButton = document.getElementById(`toggle-${turnId}`);
|
|
795
|
+
|
|
796
|
+
if (childrenContainer) {
|
|
797
|
+
childrenContainer.classList.toggle('collapsed');
|
|
798
|
+
if (toggleButton) {
|
|
799
|
+
toggleButton.classList.toggle('expanded');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function expandAllConversationTurns() {
|
|
805
|
+
// Get all child containers and remove the 'collapsed' class
|
|
806
|
+
document.querySelectorAll('.turn-children').forEach(container => {
|
|
807
|
+
container.classList.remove('collapsed');
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Rotate all toggle buttons
|
|
811
|
+
document.querySelectorAll('.expand-toggle-turn').forEach(toggle => {
|
|
812
|
+
toggle.classList.add('expanded');
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function collapseAllConversationTurns() {
|
|
817
|
+
// Get all child containers and add the 'collapsed' class
|
|
818
|
+
document.querySelectorAll('.turn-children').forEach(container => {
|
|
819
|
+
container.classList.add('collapsed');
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// Rotate all toggle buttons back
|
|
823
|
+
document.querySelectorAll('.expand-toggle-turn').forEach(toggle => {
|
|
824
|
+
toggle.classList.remove('expanded');
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Filter conversation turns by agent type
|
|
830
|
+
* @param {string} filterValue - Filter type: 'all', 'direct', 'spawner', or specific spawner type ('gemini', 'codex', 'copilot')
|
|
831
|
+
*/
|
|
832
|
+
function filterByAgentType(filterValue) {
|
|
833
|
+
const conversationTurns = document.querySelectorAll('.conversation-turn');
|
|
834
|
+
|
|
835
|
+
conversationTurns.forEach(turn => {
|
|
836
|
+
const spawnType = turn.getAttribute('data-spawner-type');
|
|
837
|
+
let shouldShow = false;
|
|
838
|
+
|
|
839
|
+
switch (filterValue) {
|
|
840
|
+
case 'all':
|
|
841
|
+
// Show all conversation turns
|
|
842
|
+
shouldShow = true;
|
|
843
|
+
break;
|
|
844
|
+
|
|
845
|
+
case 'direct':
|
|
846
|
+
// Show only direct (non-spawner) turns
|
|
847
|
+
shouldShow = spawnType === 'direct';
|
|
848
|
+
break;
|
|
849
|
+
|
|
850
|
+
case 'spawner':
|
|
851
|
+
// Show only spawner delegation turns
|
|
852
|
+
shouldShow = spawnType === 'spawner';
|
|
853
|
+
break;
|
|
854
|
+
|
|
855
|
+
case 'gemini':
|
|
856
|
+
case 'codex':
|
|
857
|
+
case 'copilot':
|
|
858
|
+
// Show only turns that contain child events with matching spawner type
|
|
859
|
+
shouldShow = hasChildWithSpawnerType(turn, filterValue);
|
|
860
|
+
break;
|
|
861
|
+
|
|
862
|
+
default:
|
|
863
|
+
shouldShow = true;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Apply visibility
|
|
867
|
+
if (shouldShow) {
|
|
868
|
+
turn.style.display = '';
|
|
869
|
+
} else {
|
|
870
|
+
turn.style.display = 'none';
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Check if a conversation turn has any child events with the specified spawner type
|
|
877
|
+
* @param {HTMLElement} turn - The conversation turn element
|
|
878
|
+
* @param {string} spawnerType - The spawner type to search for ('gemini', 'codex', 'copilot')
|
|
879
|
+
* @returns {boolean} True if the turn contains at least one child with the spawner type
|
|
880
|
+
*/
|
|
881
|
+
function hasChildWithSpawnerType(turn, spawnerType) {
|
|
882
|
+
// Look for spawner badges that match the spawner type
|
|
883
|
+
const spawnerBadges = turn.querySelectorAll(`.spawner-badge.spawner-${spawnerType}`);
|
|
884
|
+
return spawnerBadges.length > 0;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ============================================
|
|
888
|
+
// Live Spawner Event Handling via WebSocket
|
|
889
|
+
// ============================================
|
|
890
|
+
|
|
891
|
+
// Track active spawner sessions by parent_event_id
|
|
892
|
+
const activeSpawners = new Map();
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Handle incoming spawner events from WebSocket
|
|
896
|
+
* Called from the main WebSocket handler in dashboard
|
|
897
|
+
*/
|
|
898
|
+
function handleSpawnerEvent(event) {
|
|
899
|
+
const { event_type, spawner_type, parent_event_id, data, timestamp } = event;
|
|
900
|
+
|
|
901
|
+
console.log('[SpawnerEvent]', event_type, spawner_type, data);
|
|
902
|
+
|
|
903
|
+
switch (event_type) {
|
|
904
|
+
case 'spawner_start':
|
|
905
|
+
showSpawnerStarted(spawner_type, parent_event_id, data, timestamp);
|
|
906
|
+
break;
|
|
907
|
+
case 'spawner_phase':
|
|
908
|
+
updateSpawnerPhase(spawner_type, parent_event_id, data);
|
|
909
|
+
break;
|
|
910
|
+
case 'spawner_complete':
|
|
911
|
+
showSpawnerCompleted(spawner_type, parent_event_id, data, timestamp);
|
|
912
|
+
break;
|
|
913
|
+
case 'spawner_tool_use':
|
|
914
|
+
showSpawnerToolUse(spawner_type, parent_event_id, data);
|
|
915
|
+
break;
|
|
916
|
+
case 'spawner_message':
|
|
917
|
+
showSpawnerMessage(spawner_type, parent_event_id, data);
|
|
918
|
+
break;
|
|
919
|
+
default:
|
|
920
|
+
console.log('[SpawnerEvent] Unknown event type:', event_type);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Show spawner started indicator
|
|
926
|
+
*/
|
|
927
|
+
function showSpawnerStarted(spawnerType, parentEventId, data, timestamp) {
|
|
928
|
+
// Store in active spawners
|
|
929
|
+
activeSpawners.set(parentEventId || `spawner-${Date.now()}`, {
|
|
930
|
+
spawnerType,
|
|
931
|
+
startTime: new Date(timestamp),
|
|
932
|
+
phase: 'initializing',
|
|
933
|
+
data
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
// Create or update the live activity indicator
|
|
937
|
+
const indicator = getOrCreateLiveIndicator();
|
|
938
|
+
indicator.innerHTML = `
|
|
939
|
+
<span class="live-spawner-icon spawner-${spawnerType}"></span>
|
|
940
|
+
<span class="live-spawner-text">
|
|
941
|
+
<strong>${spawnerType.toUpperCase()}</strong> starting...
|
|
942
|
+
</span>
|
|
943
|
+
<span class="live-spawner-prompt">${(data.prompt_preview || '').substring(0, 60)}...</span>
|
|
944
|
+
`;
|
|
945
|
+
indicator.classList.add('active');
|
|
946
|
+
indicator.classList.remove('completed', 'failed');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Update spawner phase/progress
|
|
951
|
+
*/
|
|
952
|
+
function updateSpawnerPhase(spawnerType, parentEventId, data) {
|
|
953
|
+
const indicator = getOrCreateLiveIndicator();
|
|
954
|
+
const phaseText = data.phase || 'processing';
|
|
955
|
+
const progress = data.progress;
|
|
956
|
+
|
|
957
|
+
let progressHtml = '';
|
|
958
|
+
if (progress !== undefined && progress !== null) {
|
|
959
|
+
progressHtml = `<span class="live-spawner-progress">${progress}%</span>`;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
indicator.innerHTML = `
|
|
963
|
+
<span class="live-spawner-icon spawner-${spawnerType} pulsing"></span>
|
|
964
|
+
<span class="live-spawner-text">
|
|
965
|
+
<strong>${spawnerType.toUpperCase()}</strong> ${phaseText}
|
|
966
|
+
</span>
|
|
967
|
+
${progressHtml}
|
|
968
|
+
${data.details ? `<span class="live-spawner-details">${data.details}</span>` : ''}
|
|
969
|
+
`;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Show spawner completed
|
|
974
|
+
*/
|
|
975
|
+
function showSpawnerCompleted(spawnerType, parentEventId, data, timestamp) {
|
|
976
|
+
const indicator = getOrCreateLiveIndicator();
|
|
977
|
+
const success = data.success;
|
|
978
|
+
const duration = data.duration_seconds ? `${data.duration_seconds.toFixed(1)}s` : '';
|
|
979
|
+
|
|
980
|
+
indicator.classList.remove('active');
|
|
981
|
+
indicator.classList.add(success ? 'completed' : 'failed');
|
|
982
|
+
|
|
983
|
+
if (success) {
|
|
984
|
+
indicator.innerHTML = `
|
|
985
|
+
<span class="live-spawner-icon spawner-${spawnerType} done"></span>
|
|
986
|
+
<span class="live-spawner-text">
|
|
987
|
+
<strong>${spawnerType.toUpperCase()}</strong> completed
|
|
988
|
+
</span>
|
|
989
|
+
<span class="live-spawner-duration">${duration}</span>
|
|
990
|
+
${data.tokens_used ? `<span class="live-spawner-tokens">${data.tokens_used} tokens</span>` : ''}
|
|
991
|
+
`;
|
|
992
|
+
} else {
|
|
993
|
+
indicator.innerHTML = `
|
|
994
|
+
<span class="live-spawner-icon spawner-${spawnerType} error"></span>
|
|
995
|
+
<span class="live-spawner-text">
|
|
996
|
+
<strong>${spawnerType.toUpperCase()}</strong> failed
|
|
997
|
+
</span>
|
|
998
|
+
<span class="live-spawner-error">${data.error || 'Unknown error'}</span>
|
|
999
|
+
`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Remove from active spawners
|
|
1003
|
+
if (parentEventId) {
|
|
1004
|
+
activeSpawners.delete(parentEventId);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Auto-hide after 5 seconds
|
|
1008
|
+
setTimeout(() => {
|
|
1009
|
+
if (!indicator.classList.contains('active')) {
|
|
1010
|
+
indicator.classList.add('fade-out');
|
|
1011
|
+
setTimeout(() => {
|
|
1012
|
+
indicator.classList.remove('fade-out', 'completed', 'failed');
|
|
1013
|
+
indicator.innerHTML = '';
|
|
1014
|
+
}, 500);
|
|
1015
|
+
}
|
|
1016
|
+
}, 5000);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Show spawner tool use (brief flash)
|
|
1021
|
+
*/
|
|
1022
|
+
function showSpawnerToolUse(spawnerType, parentEventId, data) {
|
|
1023
|
+
const indicator = getOrCreateLiveIndicator();
|
|
1024
|
+
const toolName = data.tool_name || 'unknown';
|
|
1025
|
+
|
|
1026
|
+
// Quick update showing tool use
|
|
1027
|
+
const currentHtml = indicator.innerHTML;
|
|
1028
|
+
indicator.innerHTML = `
|
|
1029
|
+
<span class="live-spawner-icon spawner-${spawnerType} tool-use"></span>
|
|
1030
|
+
<span class="live-spawner-text">
|
|
1031
|
+
<strong>${spawnerType.toUpperCase()}</strong> using ${toolName}
|
|
1032
|
+
</span>
|
|
1033
|
+
`;
|
|
1034
|
+
|
|
1035
|
+
// Revert after brief display (if still active)
|
|
1036
|
+
setTimeout(() => {
|
|
1037
|
+
if (activeSpawners.has(parentEventId)) {
|
|
1038
|
+
// Still active, show executing state
|
|
1039
|
+
indicator.innerHTML = `
|
|
1040
|
+
<span class="live-spawner-icon spawner-${spawnerType} pulsing"></span>
|
|
1041
|
+
<span class="live-spawner-text">
|
|
1042
|
+
<strong>${spawnerType.toUpperCase()}</strong> executing
|
|
1043
|
+
</span>
|
|
1044
|
+
`;
|
|
1045
|
+
}
|
|
1046
|
+
}, 1500);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* Show spawner message (streaming text)
|
|
1051
|
+
*/
|
|
1052
|
+
function showSpawnerMessage(spawnerType, parentEventId, data) {
|
|
1053
|
+
// Just update the indicator with message preview
|
|
1054
|
+
const indicator = getOrCreateLiveIndicator();
|
|
1055
|
+
const preview = (data.message_preview || '').substring(0, 80);
|
|
1056
|
+
|
|
1057
|
+
indicator.innerHTML = `
|
|
1058
|
+
<span class="live-spawner-icon spawner-${spawnerType} streaming"></span>
|
|
1059
|
+
<span class="live-spawner-text">
|
|
1060
|
+
<strong>${spawnerType.toUpperCase()}</strong> responding
|
|
1061
|
+
</span>
|
|
1062
|
+
<span class="live-spawner-message">${preview}...</span>
|
|
1063
|
+
`;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Get or create the live activity indicator element
|
|
1068
|
+
*/
|
|
1069
|
+
function getOrCreateLiveIndicator() {
|
|
1070
|
+
let indicator = document.getElementById('live-spawner-indicator');
|
|
1071
|
+
if (!indicator) {
|
|
1072
|
+
indicator = document.createElement('div');
|
|
1073
|
+
indicator.id = 'live-spawner-indicator';
|
|
1074
|
+
indicator.className = 'live-spawner-indicator';
|
|
1075
|
+
|
|
1076
|
+
// Insert at top of conversation feed
|
|
1077
|
+
const feed = document.querySelector('.conversation-feed');
|
|
1078
|
+
if (feed) {
|
|
1079
|
+
feed.insertBefore(indicator, feed.firstChild);
|
|
1080
|
+
} else {
|
|
1081
|
+
// Fallback: insert in activity feed view
|
|
1082
|
+
const view = document.querySelector('.activity-feed-view');
|
|
1083
|
+
if (view) {
|
|
1084
|
+
const header = view.querySelector('.view-header');
|
|
1085
|
+
if (header && header.nextSibling) {
|
|
1086
|
+
view.insertBefore(indicator, header.nextSibling);
|
|
1087
|
+
} else {
|
|
1088
|
+
view.appendChild(indicator);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return indicator;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Export for use by main WebSocket handler
|
|
1097
|
+
if (typeof window !== 'undefined') {
|
|
1098
|
+
window.handleSpawnerEvent = handleSpawnerEvent;
|
|
1099
|
+
}
|
|
1100
|
+
</script>
|