@memberjunction/ng-core-entity-forms 2.54.0 → 2.56.0
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.
- package/README.md +1 -206
- package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts +31 -5
- package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIAgents/ai-agent-form.component.js +558 -502
- package/dist/lib/custom/AIAgents/ai-agent-form.component.js.map +1 -1
- package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts +36 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts.map +1 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.component.js +325 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.component.js.map +1 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts +41 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts.map +1 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.service.js +87 -0
- package/dist/lib/custom/AIAgents/new-agent-dialog.service.js.map +1 -0
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts +6 -0
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js +656 -235
- package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js.map +1 -1
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts +9 -2
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts.map +1 -1
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js +549 -511
- package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js.map +1 -1
- package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts +1 -0
- package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Actions/action-execution-log-form.component.js +4 -0
- package/dist/lib/custom/Actions/action-execution-log-form.component.js.map +1 -1
- package/dist/lib/custom/Actions/action-form.component.d.ts +5 -0
- package/dist/lib/custom/Actions/action-form.component.d.ts.map +1 -1
- package/dist/lib/custom/Actions/action-form.component.js +9 -2
- package/dist/lib/custom/Actions/action-form.component.js.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +5 -4
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -1
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +150 -72
- package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -1
- package/dist/lib/custom/custom-forms.module.d.ts +31 -35
- package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
- package/dist/lib/custom/custom-forms.module.js +23 -43
- package/dist/lib/custom/custom-forms.module.js.map +1 -1
- package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js +11 -12
- package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIAgent/sections/details.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIAgent/sections/details.component.js +13 -4
- package/dist/lib/generated/Entities/AIAgent/sections/details.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIAgentRun/aiagentrun.form.component.js +7 -8
- package/dist/lib/generated/Entities/AIAgentRun/aiagentrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js +7 -8
- package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js +7 -8
- package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js +11 -12
- package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIPrompt/aiprompt.form.component.js +11 -12
- package/dist/lib/generated/Entities/AIPrompt/aiprompt.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIPromptRun/aipromptrun.form.component.js +11 -12
- package/dist/lib/generated/Entities/AIPromptRun/aipromptrun.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js +139 -4
- package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js.map +1 -1
- package/dist/lib/generated/Entities/AIVendor/aivendor.form.component.js +11 -12
- package/dist/lib/generated/Entities/AIVendor/aivendor.form.component.js.map +1 -1
- package/dist/lib/generated/Entities/Action/sections/details.component.d.ts.map +1 -1
- package/dist/lib/generated/Entities/Action/sections/details.component.js +13 -4
- package/dist/lib/generated/Entities/Action/sections/details.component.js.map +1 -1
- package/dist/public-api.d.ts +2 -3
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -16
- package/dist/public-api.js.map +1 -1
- package/package.json +13 -12
- package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.d.ts +0 -273
- package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.d.ts.map +0 -1
- package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.js +0 -1429
- package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.js.map +0 -1
- package/dist/lib/custom/ai-test-harness/agent-execution-node.component.d.ts +0 -19
- package/dist/lib/custom/ai-test-harness/agent-execution-node.component.d.ts.map +0 -1
- package/dist/lib/custom/ai-test-harness/agent-execution-node.component.js +0 -458
- package/dist/lib/custom/ai-test-harness/agent-execution-node.component.js.map +0 -1
- package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.d.ts +0 -78
- package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.d.ts.map +0 -1
- package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.js +0 -157
- package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.js.map +0 -1
- package/dist/lib/custom/ai-test-harness/ai-test-harness.component.d.ts +0 -631
- package/dist/lib/custom/ai-test-harness/ai-test-harness.component.d.ts.map +0 -1
- package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js +0 -3380
- package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js.map +0 -1
- package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts +0 -8
- package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts.map +0 -1
- package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js +0 -57
- package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js.map +0 -1
- package/dist/lib/custom/test-harness-dialog.service.d.ts +0 -111
- package/dist/lib/custom/test-harness-dialog.service.d.ts.map +0 -1
- package/dist/lib/custom/test-harness-dialog.service.js +0 -211
- package/dist/lib/custom/test-harness-dialog.service.js.map +0 -1
|
@@ -1,1429 +0,0 @@
|
|
|
1
|
-
import { Component, Input, ViewChild, ChangeDetectionStrategy, ViewContainerRef, Output, EventEmitter } from '@angular/core';
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
3
|
-
import { Subject, interval } from 'rxjs';
|
|
4
|
-
import { takeUntil } from 'rxjs/operators';
|
|
5
|
-
import { ExecutionNodeComponent } from './agent-execution-node.component';
|
|
6
|
-
import * as i0 from "@angular/core";
|
|
7
|
-
const _c0 = ["executionTreeContainer"];
|
|
8
|
-
const _c1 = ["executionNodesContainer"];
|
|
9
|
-
function AgentExecutionMonitorComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
10
|
-
i0.ɵɵelementStart(0, "span", 6);
|
|
11
|
-
i0.ɵɵelement(1, "span", 19);
|
|
12
|
-
i0.ɵɵtext(2, " LIVE ");
|
|
13
|
-
i0.ɵɵelementEnd();
|
|
14
|
-
} }
|
|
15
|
-
function AgentExecutionMonitorComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
16
|
-
i0.ɵɵelementStart(0, "div", 7)(1, "span", 20);
|
|
17
|
-
i0.ɵɵtext(2, "Current:");
|
|
18
|
-
i0.ɵɵelementEnd();
|
|
19
|
-
i0.ɵɵelementStart(3, "span", 21);
|
|
20
|
-
i0.ɵɵtext(4);
|
|
21
|
-
i0.ɵɵelementEnd();
|
|
22
|
-
i0.ɵɵelementStart(5, "span", 22);
|
|
23
|
-
i0.ɵɵtext(6);
|
|
24
|
-
i0.ɵɵelementEnd()();
|
|
25
|
-
} if (rf & 2) {
|
|
26
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
27
|
-
i0.ɵɵadvance(4);
|
|
28
|
-
i0.ɵɵtextInterpolate(ctx_r1.formatAgentPath(ctx_r1.currentStep.agentPath));
|
|
29
|
-
i0.ɵɵadvance(2);
|
|
30
|
-
i0.ɵɵtextInterpolate(ctx_r1.currentStep.name);
|
|
31
|
-
} }
|
|
32
|
-
function AgentExecutionMonitorComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
33
|
-
const _r3 = i0.ɵɵgetCurrentView();
|
|
34
|
-
i0.ɵɵelementStart(0, "button", 23);
|
|
35
|
-
i0.ɵɵlistener("click", function AgentExecutionMonitorComponent_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewRunClick()); });
|
|
36
|
-
i0.ɵɵelement(1, "i", 24);
|
|
37
|
-
i0.ɵɵtext(2);
|
|
38
|
-
i0.ɵɵelementEnd();
|
|
39
|
-
} if (rf & 2) {
|
|
40
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
41
|
-
i0.ɵɵadvance(2);
|
|
42
|
-
i0.ɵɵtextInterpolate1(" View ", ctx_r1.runType === "agent" ? "Agent" : "Prompt", " Run ");
|
|
43
|
-
} }
|
|
44
|
-
function AgentExecutionMonitorComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
|
|
45
|
-
i0.ɵɵelementStart(0, "div", 11);
|
|
46
|
-
i0.ɵɵelement(1, "i", 25);
|
|
47
|
-
i0.ɵɵelementStart(2, "p");
|
|
48
|
-
i0.ɵɵtext(3, "Waiting for execution to begin...");
|
|
49
|
-
i0.ɵɵelementEnd()();
|
|
50
|
-
} }
|
|
51
|
-
function AgentExecutionMonitorComponent_Conditional_21_Template(rf, ctx) { if (rf & 1) {
|
|
52
|
-
i0.ɵɵelementStart(0, "span", 17);
|
|
53
|
-
i0.ɵɵtext(1);
|
|
54
|
-
i0.ɵɵelementEnd();
|
|
55
|
-
} if (rf & 2) {
|
|
56
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
57
|
-
i0.ɵɵadvance();
|
|
58
|
-
i0.ɵɵtextInterpolate1("(", ctx_r1.stats.failedSteps, " failed)");
|
|
59
|
-
} }
|
|
60
|
-
function AgentExecutionMonitorComponent_Conditional_37_Template(rf, ctx) { if (rf & 1) {
|
|
61
|
-
i0.ɵɵelementStart(0, "div", 14)(1, "span", 15);
|
|
62
|
-
i0.ɵɵtext(2, "Duration");
|
|
63
|
-
i0.ɵɵelementEnd();
|
|
64
|
-
i0.ɵɵelementStart(3, "span", 16);
|
|
65
|
-
i0.ɵɵtext(4);
|
|
66
|
-
i0.ɵɵelementEnd()();
|
|
67
|
-
} if (rf & 2) {
|
|
68
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
69
|
-
i0.ɵɵadvance(4);
|
|
70
|
-
i0.ɵɵtextInterpolate(ctx_r1.formatDuration(ctx_r1.stats.totalDuration));
|
|
71
|
-
} }
|
|
72
|
-
function AgentExecutionMonitorComponent_Conditional_38_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
73
|
-
i0.ɵɵelementStart(0, "span", 26);
|
|
74
|
-
i0.ɵɵtext(1);
|
|
75
|
-
i0.ɵɵelementEnd();
|
|
76
|
-
} if (rf & 2) {
|
|
77
|
-
const type_r4 = ctx.$implicit;
|
|
78
|
-
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
79
|
-
i0.ɵɵadvance();
|
|
80
|
-
i0.ɵɵtextInterpolate2(" ", ctx_r1.stats.stepsByType[type_r4], " ", type_r4, " ");
|
|
81
|
-
} }
|
|
82
|
-
function AgentExecutionMonitorComponent_Conditional_38_Template(rf, ctx) { if (rf & 1) {
|
|
83
|
-
i0.ɵɵelementStart(0, "div", 18);
|
|
84
|
-
i0.ɵɵrepeaterCreate(1, AgentExecutionMonitorComponent_Conditional_38_For_2_Template, 2, 2, "span", 26, i0.ɵɵrepeaterTrackByIdentity);
|
|
85
|
-
i0.ɵɵelementEnd();
|
|
86
|
-
} if (rf & 2) {
|
|
87
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
88
|
-
i0.ɵɵadvance();
|
|
89
|
-
i0.ɵɵrepeater(ctx_r1.getStepTypes());
|
|
90
|
-
} }
|
|
91
|
-
/**
|
|
92
|
-
* AgentExecutionMonitor Component
|
|
93
|
-
*
|
|
94
|
-
* A reusable component for visualizing AI agent execution flow in real-time or historically.
|
|
95
|
-
* Displays a hierarchical tree of execution steps with status, timing, and preview information.
|
|
96
|
-
*
|
|
97
|
-
* Features:
|
|
98
|
-
* - Real-time updates for live executions
|
|
99
|
-
* - Historical playback for completed executions
|
|
100
|
-
* - Collapsible tree structure
|
|
101
|
-
* - Step status indicators with animations
|
|
102
|
-
* - Execution statistics
|
|
103
|
-
* - Token usage and cost tracking
|
|
104
|
-
* - Error display
|
|
105
|
-
* - Input/output preview
|
|
106
|
-
*/
|
|
107
|
-
export class AgentExecutionMonitorComponent {
|
|
108
|
-
constructor(cdr) {
|
|
109
|
-
this.cdr = cdr;
|
|
110
|
-
this.mode = 'historical';
|
|
111
|
-
this.executionData = null; // Can be live updates or historical data
|
|
112
|
-
this.autoExpand = true; // Auto-expand nodes in live mode
|
|
113
|
-
this.runId = null; // ID of the run (agent or prompt)
|
|
114
|
-
this.runType = 'agent'; // Type of run
|
|
115
|
-
this.viewRunClick = new EventEmitter();
|
|
116
|
-
this.executionTree = [];
|
|
117
|
-
this.currentStep = null;
|
|
118
|
-
// Track component references for dynamic components
|
|
119
|
-
this.nodeComponentMap = new Map();
|
|
120
|
-
this.stats = {
|
|
121
|
-
totalSteps: 0,
|
|
122
|
-
completedSteps: 0,
|
|
123
|
-
failedSteps: 0,
|
|
124
|
-
totalTokens: 0,
|
|
125
|
-
totalCost: 0,
|
|
126
|
-
stepsByType: {},
|
|
127
|
-
totalPrompts: 0
|
|
128
|
-
};
|
|
129
|
-
// User interaction tracking
|
|
130
|
-
this.userHasInteracted = false;
|
|
131
|
-
this.userHasScrolled = false;
|
|
132
|
-
this.isAutoScrolling = false;
|
|
133
|
-
this.lastScrollPosition = 0;
|
|
134
|
-
this.scrollThreshold = 50; // pixels from bottom to consider "at bottom"
|
|
135
|
-
// View initialization flag
|
|
136
|
-
this.viewInitialized = false;
|
|
137
|
-
// Track processed step IDs to avoid duplication
|
|
138
|
-
this.processedStepIds = new Set();
|
|
139
|
-
// Track which nodes were added in the current update
|
|
140
|
-
this.newlyAddedNodeIds = new Set();
|
|
141
|
-
this.destroy$ = new Subject();
|
|
142
|
-
}
|
|
143
|
-
ngOnChanges(changes) {
|
|
144
|
-
if (changes['executionData']) {
|
|
145
|
-
// Check if we're starting a new live execution (empty liveSteps array)
|
|
146
|
-
if (this.mode === 'live' &&
|
|
147
|
-
this.executionData &&
|
|
148
|
-
this.executionData.liveSteps &&
|
|
149
|
-
this.executionData.liveSteps.length === 0 &&
|
|
150
|
-
this.executionTree.length > 0) {
|
|
151
|
-
// Clear everything for new run
|
|
152
|
-
this.processedStepIds.clear();
|
|
153
|
-
this.newlyAddedNodeIds.clear();
|
|
154
|
-
this.clearNodeComponents();
|
|
155
|
-
this.executionTree = [];
|
|
156
|
-
this.currentStep = null;
|
|
157
|
-
this.calculateStats();
|
|
158
|
-
}
|
|
159
|
-
// Process data even if it's empty/null (for initial setup)
|
|
160
|
-
this.processExecutionData();
|
|
161
|
-
}
|
|
162
|
-
// Handle mode changes
|
|
163
|
-
if (changes['mode'] && !changes['mode'].firstChange) {
|
|
164
|
-
// Clear live nodes when switching from live to historical
|
|
165
|
-
if (changes['mode'].previousValue === 'live' && changes['mode'].currentValue === 'historical') {
|
|
166
|
-
// Stop live updates
|
|
167
|
-
if (this.updateSubscription) {
|
|
168
|
-
this.updateSubscription.unsubscribe();
|
|
169
|
-
this.updateSubscription = undefined;
|
|
170
|
-
}
|
|
171
|
-
// Clear all tracking data from live mode
|
|
172
|
-
this.processedStepIds.clear();
|
|
173
|
-
this.newlyAddedNodeIds.clear();
|
|
174
|
-
this.clearNodeComponents();
|
|
175
|
-
this.executionTree = [];
|
|
176
|
-
}
|
|
177
|
-
this.processExecutionData();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
ngAfterViewInit() {
|
|
181
|
-
this.viewInitialized = true;
|
|
182
|
-
// Initial setup for scroll behavior
|
|
183
|
-
if (this.mode === 'live') {
|
|
184
|
-
this.checkIfUserAtBottom();
|
|
185
|
-
}
|
|
186
|
-
// If we have data waiting to be rendered, render it now
|
|
187
|
-
if (this.executionData && this.executionTree.length === 0) {
|
|
188
|
-
this.processExecutionData();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
ngOnDestroy() {
|
|
192
|
-
this.destroy$.next();
|
|
193
|
-
this.destroy$.complete();
|
|
194
|
-
if (this.updateSubscription) {
|
|
195
|
-
this.updateSubscription.unsubscribe();
|
|
196
|
-
this.updateSubscription = undefined;
|
|
197
|
-
}
|
|
198
|
-
this.clearNodeComponents();
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Clear all dynamically created components
|
|
202
|
-
*/
|
|
203
|
-
clearNodeComponents() {
|
|
204
|
-
this.nodeComponentMap.forEach(ref => ref.destroy());
|
|
205
|
-
this.nodeComponentMap.clear();
|
|
206
|
-
if (this.executionNodesContainer) {
|
|
207
|
-
this.executionNodesContainer.clear();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Process execution data based on mode
|
|
212
|
-
*/
|
|
213
|
-
processExecutionData() {
|
|
214
|
-
if (this.mode === 'historical') {
|
|
215
|
-
// Clear processed step IDs when processing historical data
|
|
216
|
-
this.processedStepIds.clear();
|
|
217
|
-
// Process historical data
|
|
218
|
-
this.buildTreeFromHistoricalData();
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
// For live mode, only clear if we don't have existing data
|
|
222
|
-
// This prevents losing state when switching tabs
|
|
223
|
-
if (this.executionTree.length === 0) {
|
|
224
|
-
this.processedStepIds.clear();
|
|
225
|
-
this.newlyAddedNodeIds.clear();
|
|
226
|
-
}
|
|
227
|
-
// Set up live updates
|
|
228
|
-
this.setupLiveUpdates();
|
|
229
|
-
}
|
|
230
|
-
// Only calculate stats if we have data
|
|
231
|
-
if (this.executionTree.length > 0) {
|
|
232
|
-
this.calculateStats();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Build tree from historical execution data
|
|
237
|
-
*/
|
|
238
|
-
buildTreeFromHistoricalData() {
|
|
239
|
-
if (!this.executionData) {
|
|
240
|
-
this.clearNodeComponents();
|
|
241
|
-
this.executionTree = [];
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
// Clear existing components for full rebuild
|
|
245
|
-
this.clearNodeComponents();
|
|
246
|
-
// Handle executionTree format - historical mode should only use saved database data
|
|
247
|
-
if (this.executionData.executionTree) {
|
|
248
|
-
const allNodes = this.convertExecutionTree(this.executionData.executionTree);
|
|
249
|
-
// Filter out live-only nodes in historical mode
|
|
250
|
-
this.executionTree = this.filterNodesForMode(allNodes, 'historical');
|
|
251
|
-
}
|
|
252
|
-
else if (this.executionData.steps) {
|
|
253
|
-
// Handle legacy steps format
|
|
254
|
-
const allNodes = this.convertLegacySteps(this.executionData.steps);
|
|
255
|
-
this.executionTree = this.filterNodesForMode(allNodes, 'historical');
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
258
|
-
this.executionTree = [];
|
|
259
|
-
}
|
|
260
|
-
// Render the tree using dynamic components
|
|
261
|
-
if (this.executionNodesContainer) {
|
|
262
|
-
this.renderTree();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Render the execution tree using dynamic components
|
|
267
|
-
*/
|
|
268
|
-
renderTree() {
|
|
269
|
-
if (!this.executionNodesContainer) {
|
|
270
|
-
console.warn('📊 Execution Monitor: No container available for rendering');
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
// Clear and recreate all components for historical view
|
|
274
|
-
this.clearNodeComponents();
|
|
275
|
-
// Create components for each root node
|
|
276
|
-
for (const node of this.executionTree) {
|
|
277
|
-
this.createNodeComponent(node, this.executionNodesContainer);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Create a component for a node and its children
|
|
282
|
-
*/
|
|
283
|
-
createNodeComponent(node, container) {
|
|
284
|
-
const componentRef = container.createComponent(ExecutionNodeComponent);
|
|
285
|
-
const instance = componentRef.instance;
|
|
286
|
-
// Set inputs
|
|
287
|
-
instance.node = node;
|
|
288
|
-
// No need to apply margin here - it's handled in the component template
|
|
289
|
-
// Subscribe to outputs
|
|
290
|
-
instance.toggleNode.subscribe((n) => this.toggleNode(n));
|
|
291
|
-
instance.userInteracted.subscribe(() => this.onUserInteraction());
|
|
292
|
-
// Store reference
|
|
293
|
-
this.nodeComponentMap.set(node.id, componentRef);
|
|
294
|
-
// Force immediate change detection to ensure styles are applied
|
|
295
|
-
componentRef.changeDetectorRef.markForCheck();
|
|
296
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
297
|
-
return componentRef;
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Convert new execution tree format to display nodes
|
|
301
|
-
*/
|
|
302
|
-
convertExecutionTree(nodes, depth = 0) {
|
|
303
|
-
// Sort nodes by StepNumber before processing
|
|
304
|
-
const sortedNodes = [...nodes].sort((a, b) => {
|
|
305
|
-
const aStepNum = a.step?.StepNumber || 0;
|
|
306
|
-
const bStepNum = b.step?.StepNumber || 0;
|
|
307
|
-
return aStepNum - bStepNum;
|
|
308
|
-
});
|
|
309
|
-
return sortedNodes.map((node, index) => {
|
|
310
|
-
// Parse the step name for markdown content (same as live steps)
|
|
311
|
-
const stepName = node.step?.StepName || 'Unknown Step';
|
|
312
|
-
let name = stepName;
|
|
313
|
-
let detailsMarkdown;
|
|
314
|
-
// Check if the step name contains markdown (indicated by newlines or markdown syntax)
|
|
315
|
-
if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
|
|
316
|
-
// Split by newline and use first line as name
|
|
317
|
-
const lines = stepName.split('\n');
|
|
318
|
-
name = lines[0].trim();
|
|
319
|
-
// Rest is markdown details
|
|
320
|
-
if (lines.length > 1) {
|
|
321
|
-
detailsMarkdown = lines.slice(1).join('\n').trim();
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
// Determine display mode based on the step content
|
|
325
|
-
let displayMode = 'both';
|
|
326
|
-
// Progress messages that should only show in live mode
|
|
327
|
-
if (name.includes('Executing') && name.includes('action') && !name.startsWith('Execute Action:')) {
|
|
328
|
-
displayMode = 'live';
|
|
329
|
-
}
|
|
330
|
-
else if (name.includes("Executing agent's initial prompt")) {
|
|
331
|
-
displayMode = 'live';
|
|
332
|
-
}
|
|
333
|
-
else if (name.includes('Re-executing agent prompt with additional context')) {
|
|
334
|
-
displayMode = 'live';
|
|
335
|
-
}
|
|
336
|
-
const treeNode = {
|
|
337
|
-
id: node.step?.ID || `node-${Date.now()}-${index}`,
|
|
338
|
-
name: name,
|
|
339
|
-
type: this.mapStepType(node.executionType || node.step?.StepType),
|
|
340
|
-
status: this.mapStepStatus(node.step?.Status),
|
|
341
|
-
startTime: node.startTime ? new Date(node.startTime) : undefined,
|
|
342
|
-
endTime: node.endTime ? new Date(node.endTime) : undefined,
|
|
343
|
-
duration: node.durationMs,
|
|
344
|
-
agentPath: node.agentHierarchy || [],
|
|
345
|
-
inputPreview: this.createPreview(node.inputData),
|
|
346
|
-
outputPreview: this.createPreview(node.outputData),
|
|
347
|
-
error: node.step?.ErrorMessage,
|
|
348
|
-
expanded: false,
|
|
349
|
-
depth: node.depth !== undefined ? node.depth : depth, // Use node.depth if available, fallback to recursion depth
|
|
350
|
-
tokensUsed: this.extractTokens(node.outputData),
|
|
351
|
-
cost: this.extractCost(node.outputData),
|
|
352
|
-
detailsMarkdown: detailsMarkdown,
|
|
353
|
-
detailsExpanded: false,
|
|
354
|
-
children: node.children ? this.convertExecutionTree(node.children, node.depth !== undefined ? node.depth + 1 : depth + 1) : undefined,
|
|
355
|
-
displayMode: displayMode
|
|
356
|
-
};
|
|
357
|
-
return treeNode;
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Convert legacy step format to tree nodes
|
|
362
|
-
*/
|
|
363
|
-
convertLegacySteps(steps) {
|
|
364
|
-
return steps.map((step, index) => {
|
|
365
|
-
// Parse the step name for markdown content
|
|
366
|
-
const stepName = step.StepName || '';
|
|
367
|
-
let name = stepName;
|
|
368
|
-
let detailsMarkdown;
|
|
369
|
-
// Check if the step name contains markdown
|
|
370
|
-
if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
|
|
371
|
-
const lines = stepName.split('\n');
|
|
372
|
-
name = lines[0].trim();
|
|
373
|
-
if (lines.length > 1) {
|
|
374
|
-
detailsMarkdown = lines.slice(1).join('\n').trim();
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
id: step.ID || `step-${index}`,
|
|
379
|
-
name: name,
|
|
380
|
-
type: this.mapStepType(step.StepType),
|
|
381
|
-
status: this.mapStepStatus(step.Status),
|
|
382
|
-
startTime: step.StartedAt ? new Date(step.StartedAt) : undefined,
|
|
383
|
-
endTime: step.CompletedAt ? new Date(step.CompletedAt) : undefined,
|
|
384
|
-
duration: this.calculateDuration(step.StartedAt, step.CompletedAt),
|
|
385
|
-
agentPath: [],
|
|
386
|
-
inputPreview: this.createPreview(step.InputData),
|
|
387
|
-
outputPreview: this.createPreview(step.OutputData),
|
|
388
|
-
error: step.ErrorMessage,
|
|
389
|
-
expanded: false,
|
|
390
|
-
depth: 0,
|
|
391
|
-
detailsMarkdown: detailsMarkdown,
|
|
392
|
-
detailsExpanded: false
|
|
393
|
-
};
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Map step type strings to our type enum
|
|
398
|
-
*/
|
|
399
|
-
mapStepType(type) {
|
|
400
|
-
const typeMap = {
|
|
401
|
-
'validation': 'validation',
|
|
402
|
-
'prompt': 'prompt',
|
|
403
|
-
'action': 'action',
|
|
404
|
-
'subagent': 'sub-agent',
|
|
405
|
-
'sub-agent': 'sub-agent',
|
|
406
|
-
'decision': 'decision',
|
|
407
|
-
'chat': 'chat'
|
|
408
|
-
};
|
|
409
|
-
return typeMap[type?.toLowerCase()] || 'prompt';
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Map status strings to our status enum
|
|
413
|
-
*/
|
|
414
|
-
mapStepStatus(status) {
|
|
415
|
-
const statusMap = {
|
|
416
|
-
'pending': 'pending',
|
|
417
|
-
'running': 'running',
|
|
418
|
-
'completed': 'completed',
|
|
419
|
-
'failed': 'failed',
|
|
420
|
-
'error': 'failed'
|
|
421
|
-
};
|
|
422
|
-
return statusMap[status?.toLowerCase()] || 'pending';
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Create a preview string from data
|
|
426
|
-
*/
|
|
427
|
-
createPreview(data) {
|
|
428
|
-
if (!data)
|
|
429
|
-
return undefined;
|
|
430
|
-
try {
|
|
431
|
-
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
432
|
-
// Extract meaningful preview
|
|
433
|
-
if (parsed.promptName)
|
|
434
|
-
return `Prompt: ${parsed.promptName}`;
|
|
435
|
-
if (parsed.actionName)
|
|
436
|
-
return `Action: ${parsed.actionName}`;
|
|
437
|
-
if (parsed.subAgentName)
|
|
438
|
-
return `Sub-agent: ${parsed.subAgentName}`;
|
|
439
|
-
if (parsed.message)
|
|
440
|
-
return parsed.message;
|
|
441
|
-
if (parsed.userMessage)
|
|
442
|
-
return parsed.userMessage;
|
|
443
|
-
// Show action results clearly
|
|
444
|
-
if (parsed.actionResult) {
|
|
445
|
-
const result = parsed.actionResult;
|
|
446
|
-
let preview = '';
|
|
447
|
-
if (result.success !== undefined) {
|
|
448
|
-
preview += `Success: ${result.success}\n`;
|
|
449
|
-
}
|
|
450
|
-
if (result.resultCode) {
|
|
451
|
-
preview += `Result Code: ${result.resultCode}\n`;
|
|
452
|
-
}
|
|
453
|
-
if (result.message) {
|
|
454
|
-
preview += `Message: ${result.message}\n`;
|
|
455
|
-
}
|
|
456
|
-
if (result.result) {
|
|
457
|
-
preview += `Result: ${typeof result.result === 'object' ? JSON.stringify(result.result, null, 2) : result.result}`;
|
|
458
|
-
}
|
|
459
|
-
return preview.trim();
|
|
460
|
-
}
|
|
461
|
-
// Legacy action result format
|
|
462
|
-
if (parsed.result) {
|
|
463
|
-
if (typeof parsed.result === 'object') {
|
|
464
|
-
return JSON.stringify(parsed.result, null, 2);
|
|
465
|
-
}
|
|
466
|
-
return String(parsed.result);
|
|
467
|
-
}
|
|
468
|
-
// Show prompt results
|
|
469
|
-
if (parsed.promptResult) {
|
|
470
|
-
const result = parsed.promptResult;
|
|
471
|
-
let preview = '';
|
|
472
|
-
if (result.success !== undefined) {
|
|
473
|
-
preview += `Success: ${result.success}\n`;
|
|
474
|
-
}
|
|
475
|
-
if (result.content) {
|
|
476
|
-
preview += `Content: ${typeof result.content === 'string' ? result.content : JSON.stringify(result.content, null, 2)}`;
|
|
477
|
-
}
|
|
478
|
-
return preview;
|
|
479
|
-
}
|
|
480
|
-
// Fallback to stringified preview
|
|
481
|
-
const str = JSON.stringify(parsed, null, 2);
|
|
482
|
-
return str.length > 500 ? str.substring(0, 500) + '...' : str;
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
return typeof data === 'string' ? data : JSON.stringify(data);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Extract token count from output data
|
|
490
|
-
*/
|
|
491
|
-
extractTokens(data) {
|
|
492
|
-
if (!data)
|
|
493
|
-
return undefined;
|
|
494
|
-
try {
|
|
495
|
-
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
496
|
-
return parsed.promptResult?.tokensUsed || parsed.tokensUsed;
|
|
497
|
-
}
|
|
498
|
-
catch {
|
|
499
|
-
return undefined;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Extract cost from output data
|
|
504
|
-
*/
|
|
505
|
-
extractCost(data) {
|
|
506
|
-
if (!data)
|
|
507
|
-
return undefined;
|
|
508
|
-
try {
|
|
509
|
-
const parsed = typeof data === 'string' ? JSON.parse(data) : data;
|
|
510
|
-
return parsed.promptResult?.totalCost || parsed.totalCost;
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
return undefined;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Calculate duration from timestamps
|
|
518
|
-
*/
|
|
519
|
-
calculateDuration(start, end) {
|
|
520
|
-
if (!start || !end)
|
|
521
|
-
return undefined;
|
|
522
|
-
const startTime = new Date(start).getTime();
|
|
523
|
-
const endTime = new Date(end).getTime();
|
|
524
|
-
return endTime - startTime;
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Set up live updates for real-time execution
|
|
528
|
-
*/
|
|
529
|
-
setupLiveUpdates() {
|
|
530
|
-
// Handle live streaming data format
|
|
531
|
-
if (this.executionData && this.executionData.liveSteps) {
|
|
532
|
-
// Only clear and rebuild if we don't have existing data
|
|
533
|
-
if (this.executionTree.length === 0) {
|
|
534
|
-
this.clearNodeComponents();
|
|
535
|
-
this.executionTree = [];
|
|
536
|
-
// Then append all steps
|
|
537
|
-
this.appendNewLiveSteps(this.executionData.liveSteps);
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
// Just append new steps without clearing
|
|
541
|
-
this.appendNewLiveSteps(this.executionData.liveSteps);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
// Set up interval to monitor for changes (if not already set up)
|
|
545
|
-
if (this.mode === 'live' && !this.updateSubscription) {
|
|
546
|
-
this.updateSubscription = interval(500)
|
|
547
|
-
.pipe(takeUntil(this.destroy$))
|
|
548
|
-
.subscribe(() => {
|
|
549
|
-
// Check for new live steps and update tree
|
|
550
|
-
if (this.executionData && this.executionData.liveSteps) {
|
|
551
|
-
// Instead of replacing entire tree, append new nodes
|
|
552
|
-
this.appendNewLiveSteps(this.executionData.liveSteps);
|
|
553
|
-
this.calculateStats();
|
|
554
|
-
}
|
|
555
|
-
this.updateCurrentStep();
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Convert live steps format to execution tree format
|
|
561
|
-
*/
|
|
562
|
-
convertLiveStepsToTree(liveSteps) {
|
|
563
|
-
const treeNodes = [];
|
|
564
|
-
const nodeMap = new Map();
|
|
565
|
-
// Sort steps by StepNumber to ensure proper ordering
|
|
566
|
-
const sortedSteps = [...liveSteps].sort((a, b) => {
|
|
567
|
-
const aStepNum = a.step?.StepNumber || 0;
|
|
568
|
-
const bStepNum = b.step?.StepNumber || 0;
|
|
569
|
-
return aStepNum - bStepNum;
|
|
570
|
-
});
|
|
571
|
-
for (const step of sortedSteps) {
|
|
572
|
-
// Parse the step name for markdown content
|
|
573
|
-
const stepName = step.step?.StepName || 'Processing...';
|
|
574
|
-
let name = stepName;
|
|
575
|
-
let detailsMarkdown;
|
|
576
|
-
// Check if the step name contains markdown (indicated by newlines or markdown syntax)
|
|
577
|
-
if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
|
|
578
|
-
// Split by newline and use first line as name
|
|
579
|
-
const lines = stepName.split('\n');
|
|
580
|
-
name = lines[0].trim();
|
|
581
|
-
// Rest is markdown details
|
|
582
|
-
if (lines.length > 1) {
|
|
583
|
-
detailsMarkdown = lines.slice(1).join('\n').trim();
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
const node = {
|
|
587
|
-
id: step.step?.ID || `live-${Date.now()}-${Math.random()}`,
|
|
588
|
-
name: name,
|
|
589
|
-
type: this.mapStepType(step.executionType || step.step?.StepType),
|
|
590
|
-
status: 'running', // Live steps are always running
|
|
591
|
-
startTime: step.startTime ? new Date(step.startTime) : new Date(),
|
|
592
|
-
agentPath: step.agentHierarchy || [],
|
|
593
|
-
expanded: false,
|
|
594
|
-
depth: step.depth || 0,
|
|
595
|
-
detailsMarkdown: detailsMarkdown,
|
|
596
|
-
detailsExpanded: false
|
|
597
|
-
};
|
|
598
|
-
// Store in map for hierarchy building
|
|
599
|
-
nodeMap.set(node.id, node);
|
|
600
|
-
// Handle hierarchy by depth
|
|
601
|
-
if (step.depth === 0) {
|
|
602
|
-
treeNodes.push(node);
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
// Find parent based on depth and agent hierarchy
|
|
606
|
-
let parent;
|
|
607
|
-
// Look for a node at depth-1 that could be the parent
|
|
608
|
-
nodeMap.forEach((candidate) => {
|
|
609
|
-
if (candidate.depth === step.depth - 1) {
|
|
610
|
-
parent = candidate;
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
if (parent) {
|
|
614
|
-
if (!parent.children) {
|
|
615
|
-
parent.children = [];
|
|
616
|
-
}
|
|
617
|
-
parent.children.push(node);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
nodeMap.set(node.id, node);
|
|
621
|
-
}
|
|
622
|
-
return treeNodes;
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Update current step indicator
|
|
626
|
-
*/
|
|
627
|
-
updateCurrentStep() {
|
|
628
|
-
// Find the first running step
|
|
629
|
-
const findRunning = (nodes) => {
|
|
630
|
-
for (const node of nodes) {
|
|
631
|
-
if (node.status === 'running')
|
|
632
|
-
return node;
|
|
633
|
-
if (node.children) {
|
|
634
|
-
const child = findRunning(node.children);
|
|
635
|
-
if (child)
|
|
636
|
-
return child;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return null;
|
|
640
|
-
};
|
|
641
|
-
this.currentStep = findRunning(this.executionTree);
|
|
642
|
-
this.cdr.markForCheck();
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Mark previously new nodes as completed
|
|
646
|
-
*/
|
|
647
|
-
markPreviouslyNewNodesAsComplete() {
|
|
648
|
-
// Only mark nodes that were previously marked as newly added
|
|
649
|
-
for (const nodeId of this.newlyAddedNodeIds) {
|
|
650
|
-
const node = this.findNodeById(this.executionTree, nodeId);
|
|
651
|
-
if (node && node.status === 'running') {
|
|
652
|
-
node.status = 'completed';
|
|
653
|
-
// Update the component if it exists
|
|
654
|
-
const componentRef = this.nodeComponentMap.get(nodeId);
|
|
655
|
-
if (componentRef) {
|
|
656
|
-
componentRef.instance.node = { ...node };
|
|
657
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Find a node by ID in the tree
|
|
664
|
-
*/
|
|
665
|
-
findNodeById(nodes, id) {
|
|
666
|
-
for (const node of nodes) {
|
|
667
|
-
if (node.id === id) {
|
|
668
|
-
return node;
|
|
669
|
-
}
|
|
670
|
-
if (node.children) {
|
|
671
|
-
const found = this.findNodeById(node.children, id);
|
|
672
|
-
if (found)
|
|
673
|
-
return found;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
return null;
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Calculate execution statistics
|
|
680
|
-
*/
|
|
681
|
-
calculateStats() {
|
|
682
|
-
this.stats = {
|
|
683
|
-
totalSteps: 0,
|
|
684
|
-
completedSteps: 0,
|
|
685
|
-
failedSteps: 0,
|
|
686
|
-
totalTokens: 0,
|
|
687
|
-
totalCost: 0,
|
|
688
|
-
stepsByType: {},
|
|
689
|
-
totalDuration: 0,
|
|
690
|
-
totalPrompts: 0
|
|
691
|
-
};
|
|
692
|
-
// Check if we have agent run data with token information
|
|
693
|
-
if (this.executionData?.agentRun) {
|
|
694
|
-
// Use token data from agent run if available
|
|
695
|
-
this.stats.totalTokens = this.executionData.agentRun.TotalTokensUsed || 0;
|
|
696
|
-
this.stats.totalCost = this.executionData.agentRun.TotalCost || 0;
|
|
697
|
-
}
|
|
698
|
-
const processNode = (node) => {
|
|
699
|
-
this.stats.totalSteps++;
|
|
700
|
-
if (node.status === 'completed')
|
|
701
|
-
this.stats.completedSteps++;
|
|
702
|
-
if (node.status === 'failed')
|
|
703
|
-
this.stats.failedSteps++;
|
|
704
|
-
// Only accumulate tokens if we don't have agent run data
|
|
705
|
-
if (!this.executionData?.agentRun) {
|
|
706
|
-
if (node.tokensUsed)
|
|
707
|
-
this.stats.totalTokens += node.tokensUsed;
|
|
708
|
-
if (node.cost)
|
|
709
|
-
this.stats.totalCost += node.cost;
|
|
710
|
-
}
|
|
711
|
-
if (node.duration)
|
|
712
|
-
this.stats.totalDuration += node.duration;
|
|
713
|
-
this.stats.stepsByType[node.type] = (this.stats.stepsByType[node.type] || 0) + 1;
|
|
714
|
-
// Count prompts
|
|
715
|
-
if (node.type === 'prompt' || node.promptCount) {
|
|
716
|
-
this.stats.totalPrompts += node.promptCount || 1;
|
|
717
|
-
}
|
|
718
|
-
if (node.children) {
|
|
719
|
-
node.children.forEach(child => processNode(child));
|
|
720
|
-
}
|
|
721
|
-
};
|
|
722
|
-
this.executionTree.forEach(node => processNode(node));
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Toggle node expansion
|
|
726
|
-
*/
|
|
727
|
-
toggleNode(node) {
|
|
728
|
-
const nodeInTree = this.findNodeById(this.executionTree, node.id);
|
|
729
|
-
if (nodeInTree && this.hasExpandableContent(nodeInTree)) {
|
|
730
|
-
nodeInTree.expanded = !nodeInTree.expanded;
|
|
731
|
-
this.userHasInteracted = true;
|
|
732
|
-
// Update the component's node reference to trigger change detection
|
|
733
|
-
const componentRef = this.nodeComponentMap.get(node.id);
|
|
734
|
-
if (componentRef) {
|
|
735
|
-
componentRef.instance.node = { ...nodeInTree };
|
|
736
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
737
|
-
}
|
|
738
|
-
this.cdr.markForCheck();
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* Check if node has expandable content
|
|
743
|
-
*/
|
|
744
|
-
hasExpandableContent(node) {
|
|
745
|
-
return !!(node.children && node.children.length > 0) ||
|
|
746
|
-
node.type === 'sub-agent' ||
|
|
747
|
-
node.type === 'action' ||
|
|
748
|
-
!!node.inputPreview ||
|
|
749
|
-
!!node.outputPreview ||
|
|
750
|
-
!!node.error ||
|
|
751
|
-
!!node.detailsMarkdown;
|
|
752
|
-
}
|
|
753
|
-
/**
|
|
754
|
-
* Handle scroll events
|
|
755
|
-
*/
|
|
756
|
-
onScroll(event) {
|
|
757
|
-
if (this.isAutoScrolling) {
|
|
758
|
-
// Ignore scroll events triggered by auto-scrolling
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
const element = event.target;
|
|
762
|
-
const isAtBottom = this.isScrolledToBottom(element);
|
|
763
|
-
// If user scrolled up from bottom, mark as user interaction
|
|
764
|
-
if (!isAtBottom && this.lastScrollPosition !== element.scrollTop) {
|
|
765
|
-
this.userHasScrolled = true;
|
|
766
|
-
this.userHasInteracted = true;
|
|
767
|
-
}
|
|
768
|
-
// If user scrolled back to bottom, reset interaction flags
|
|
769
|
-
if (isAtBottom) {
|
|
770
|
-
this.userHasScrolled = false;
|
|
771
|
-
this.userHasInteracted = false;
|
|
772
|
-
}
|
|
773
|
-
this.lastScrollPosition = element.scrollTop;
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* Handle user clicks (interaction detection)
|
|
777
|
-
*/
|
|
778
|
-
onUserInteraction() {
|
|
779
|
-
// This is called when user clicks anywhere in the execution tree
|
|
780
|
-
// The toggleNode method will set userHasInteracted for specific interactions
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Check if scroll is at bottom
|
|
784
|
-
*/
|
|
785
|
-
isScrolledToBottom(element) {
|
|
786
|
-
const threshold = this.scrollThreshold;
|
|
787
|
-
return element.scrollHeight - element.scrollTop - element.clientHeight < threshold;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Check if user is at bottom (for initial setup)
|
|
791
|
-
*/
|
|
792
|
-
checkIfUserAtBottom() {
|
|
793
|
-
if (!this.executionTreeContainer)
|
|
794
|
-
return;
|
|
795
|
-
const element = this.executionTreeContainer.nativeElement;
|
|
796
|
-
if (this.isScrolledToBottom(element)) {
|
|
797
|
-
this.userHasScrolled = false;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Auto-scroll to bottom if user hasn't interacted
|
|
802
|
-
*/
|
|
803
|
-
autoScrollToBottom() {
|
|
804
|
-
if (!this.executionTreeContainer || this.userHasInteracted || this.userHasScrolled) {
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
const element = this.executionTreeContainer.nativeElement;
|
|
808
|
-
this.isAutoScrolling = true;
|
|
809
|
-
// Use smooth scrolling for better UX
|
|
810
|
-
element.scrollTo({
|
|
811
|
-
top: element.scrollHeight,
|
|
812
|
-
behavior: 'smooth'
|
|
813
|
-
});
|
|
814
|
-
// Reset auto-scrolling flag after animation
|
|
815
|
-
setTimeout(() => {
|
|
816
|
-
this.isAutoScrolling = false;
|
|
817
|
-
}, 300);
|
|
818
|
-
}
|
|
819
|
-
/**
|
|
820
|
-
* Format agent path for display
|
|
821
|
-
*/
|
|
822
|
-
formatAgentPath(path) {
|
|
823
|
-
return path.join(' → ');
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Format duration for display
|
|
827
|
-
*/
|
|
828
|
-
formatDuration(ms) {
|
|
829
|
-
if (ms < 1000)
|
|
830
|
-
return `${ms}ms`;
|
|
831
|
-
if (ms < 60000)
|
|
832
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
833
|
-
const minutes = Math.floor(ms / 60000);
|
|
834
|
-
const seconds = Math.floor((ms % 60000) / 1000);
|
|
835
|
-
return `${minutes}m ${seconds}s`;
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Get step types for display
|
|
839
|
-
*/
|
|
840
|
-
getStepTypes() {
|
|
841
|
-
return Object.keys(this.stats.stepsByType).sort();
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Format markdown content for display
|
|
845
|
-
*/
|
|
846
|
-
formatMarkdown(markdown) {
|
|
847
|
-
// Basic markdown formatting
|
|
848
|
-
// This is a simple implementation - you might want to use a proper markdown library
|
|
849
|
-
let html = markdown;
|
|
850
|
-
// Headers
|
|
851
|
-
html = html.replace(/^### (.*$)/gim, '<h4>$1</h4>');
|
|
852
|
-
html = html.replace(/^## (.*$)/gim, '<h3>$1</h3>');
|
|
853
|
-
html = html.replace(/^# (.*$)/gim, '<h2>$1</h2>');
|
|
854
|
-
// Bold
|
|
855
|
-
html = html.replace(/\*\*(.*)\*\*/g, '<strong>$1</strong>');
|
|
856
|
-
// Italic
|
|
857
|
-
html = html.replace(/\*(.*)\*/g, '<em>$1</em>');
|
|
858
|
-
// Detect and linkify URLs
|
|
859
|
-
// This regex matches URLs starting with http://, https://, or www.
|
|
860
|
-
const urlRegex = /(?:https?:\/\/|www\.)[^\s<]+/gi;
|
|
861
|
-
html = html.replace(urlRegex, (url) => {
|
|
862
|
-
// Ensure the URL has a protocol
|
|
863
|
-
const href = url.startsWith('http') ? url : `https://${url}`;
|
|
864
|
-
// Truncate long URLs for display
|
|
865
|
-
const displayUrl = url.length > 50 ? url.substring(0, 50) + '...' : url;
|
|
866
|
-
return `<a href="${href}" target="_blank" rel="noopener noreferrer" style="color: #2196f3; text-decoration: underline; font-size: inherit;">${displayUrl}</a>`;
|
|
867
|
-
});
|
|
868
|
-
// Line breaks
|
|
869
|
-
html = html.replace(/\n/g, '<br>');
|
|
870
|
-
// Lists
|
|
871
|
-
html = html.replace(/^\* (.+)$/gim, '<li>$1</li>');
|
|
872
|
-
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
|
873
|
-
// Code blocks
|
|
874
|
-
html = html.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
|
|
875
|
-
// Inline code
|
|
876
|
-
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
877
|
-
return html;
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Append new live steps without re-rendering entire tree
|
|
881
|
-
*/
|
|
882
|
-
appendNewLiveSteps(liveSteps) {
|
|
883
|
-
if (!liveSteps || liveSteps.length === 0)
|
|
884
|
-
return;
|
|
885
|
-
// If view is not initialized yet, wait for it
|
|
886
|
-
if (!this.viewInitialized || !this.executionNodesContainer) {
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
// Sort live steps by stepNumber to ensure proper ordering
|
|
890
|
-
const sortedSteps = [...liveSteps].sort((a, b) => {
|
|
891
|
-
const aStepNum = a.step?.StepNumber || 0;
|
|
892
|
-
const bStepNum = b.step?.StepNumber || 0;
|
|
893
|
-
return aStepNum - bStepNum;
|
|
894
|
-
});
|
|
895
|
-
// Create a map of existing nodes by ID for quick lookup
|
|
896
|
-
const nodeMap = new Map();
|
|
897
|
-
this.buildNodeMap(this.executionTree, nodeMap);
|
|
898
|
-
// First pass: identify which steps are truly NEW (not in processedStepIds)
|
|
899
|
-
const trulyNewStepIds = new Set();
|
|
900
|
-
for (const step of sortedSteps) {
|
|
901
|
-
const stepId = step.step?.ID || this.generateStepId(step);
|
|
902
|
-
if (!this.processedStepIds.has(stepId)) {
|
|
903
|
-
trulyNewStepIds.add(stepId);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
// Only mark previously new nodes as complete if we have truly new steps
|
|
907
|
-
if (trulyNewStepIds.size > 0 && this.newlyAddedNodeIds.size > 0) {
|
|
908
|
-
this.markPreviouslyNewNodesAsComplete();
|
|
909
|
-
// Clear the set for new additions
|
|
910
|
-
this.newlyAddedNodeIds.clear();
|
|
911
|
-
}
|
|
912
|
-
let hasNewNodes = false;
|
|
913
|
-
for (const step of sortedSteps) {
|
|
914
|
-
// Generate a stable ID based on step content to avoid duplicates
|
|
915
|
-
const stepId = step.step?.ID || this.generateStepId(step);
|
|
916
|
-
// Skip if we've already processed this exact step
|
|
917
|
-
if (this.processedStepIds.has(stepId)) {
|
|
918
|
-
continue;
|
|
919
|
-
}
|
|
920
|
-
// Mark as processed
|
|
921
|
-
this.processedStepIds.add(stepId);
|
|
922
|
-
// Check if this node already exists
|
|
923
|
-
const existingNode = nodeMap.get(stepId);
|
|
924
|
-
if (existingNode) {
|
|
925
|
-
// Update existing node in the component
|
|
926
|
-
const componentRef = this.nodeComponentMap.get(stepId);
|
|
927
|
-
if (componentRef) {
|
|
928
|
-
// Preserve current UI state
|
|
929
|
-
const currentExpanded = existingNode.expanded;
|
|
930
|
-
const currentDetailsExpanded = existingNode.detailsExpanded;
|
|
931
|
-
const updatedStatus = step.step?.Status ?
|
|
932
|
-
this.mapStepStatus(step.step.Status) :
|
|
933
|
-
(step.endTime || step.outputData ? 'completed' : existingNode.status);
|
|
934
|
-
if (existingNode.status !== updatedStatus) {
|
|
935
|
-
existingNode.status = updatedStatus;
|
|
936
|
-
}
|
|
937
|
-
if (step.endTime && !existingNode.endTime) {
|
|
938
|
-
existingNode.endTime = new Date(step.endTime);
|
|
939
|
-
existingNode.duration = step.durationMs;
|
|
940
|
-
}
|
|
941
|
-
// Update output data if available
|
|
942
|
-
if (step.outputData && !existingNode.outputPreview) {
|
|
943
|
-
existingNode.outputPreview = this.createPreview(step.outputData);
|
|
944
|
-
existingNode.tokensUsed = this.extractTokens(step.outputData);
|
|
945
|
-
existingNode.cost = this.extractCost(step.outputData);
|
|
946
|
-
}
|
|
947
|
-
// Restore UI state
|
|
948
|
-
existingNode.expanded = currentExpanded;
|
|
949
|
-
existingNode.detailsExpanded = currentDetailsExpanded;
|
|
950
|
-
// Update the component's node reference
|
|
951
|
-
// Important: We need to update the node object directly to ensure
|
|
952
|
-
// Angular detects changes to style bindings that depend on node.depth
|
|
953
|
-
const nodeInstance = componentRef.instance.node;
|
|
954
|
-
Object.assign(nodeInstance, existingNode);
|
|
955
|
-
// Mark for check to ensure Angular updates the view
|
|
956
|
-
componentRef.changeDetectorRef.markForCheck();
|
|
957
|
-
componentRef.changeDetectorRef.detectChanges();
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
// Create new node
|
|
962
|
-
const newNode = this.createNodeFromLiveStep(step);
|
|
963
|
-
// Debug: Log if depth is missing
|
|
964
|
-
if (newNode.depth === 0 && step.depth !== 0) {
|
|
965
|
-
console.warn('Node depth was reset to 0 for step:', step);
|
|
966
|
-
}
|
|
967
|
-
if (step.depth === 0) {
|
|
968
|
-
// Add to tree and create component
|
|
969
|
-
this.executionTree.push(newNode);
|
|
970
|
-
this.createNodeComponent(newNode, this.executionNodesContainer);
|
|
971
|
-
}
|
|
972
|
-
else {
|
|
973
|
-
// Find parent and add as child
|
|
974
|
-
const parentNode = this.findParentNode(this.executionTree, newNode);
|
|
975
|
-
if (parentNode) {
|
|
976
|
-
if (!parentNode.children) {
|
|
977
|
-
parentNode.children = [];
|
|
978
|
-
}
|
|
979
|
-
parentNode.children.push(newNode);
|
|
980
|
-
// Update parent component to show new child
|
|
981
|
-
const parentComponentRef = this.nodeComponentMap.get(parentNode.id);
|
|
982
|
-
if (parentComponentRef) {
|
|
983
|
-
// Update the parent node object directly
|
|
984
|
-
const parentNodeInstance = parentComponentRef.instance.node;
|
|
985
|
-
Object.assign(parentNodeInstance, parentNode);
|
|
986
|
-
parentComponentRef.changeDetectorRef.markForCheck();
|
|
987
|
-
parentComponentRef.changeDetectorRef.detectChanges();
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
// If no parent found, add as root
|
|
992
|
-
this.executionTree.push(newNode);
|
|
993
|
-
this.createNodeComponent(newNode, this.executionNodesContainer);
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
hasNewNodes = true;
|
|
997
|
-
// Only track as newly added if it's truly new
|
|
998
|
-
if (trulyNewStepIds.has(stepId)) {
|
|
999
|
-
this.newlyAddedNodeIds.add(newNode.id);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
if (hasNewNodes) {
|
|
1004
|
-
// Update current step
|
|
1005
|
-
this.updateCurrentStep();
|
|
1006
|
-
// Recalculate stats
|
|
1007
|
-
this.calculateStats();
|
|
1008
|
-
// Trigger change detection
|
|
1009
|
-
this.cdr.markForCheck();
|
|
1010
|
-
// Auto-scroll if user hasn't interacted
|
|
1011
|
-
setTimeout(() => {
|
|
1012
|
-
this.autoScrollToBottom();
|
|
1013
|
-
}, 100);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* Generate a stable ID for a step based on its content
|
|
1018
|
-
*/
|
|
1019
|
-
generateStepId(step) {
|
|
1020
|
-
// Create a stable ID based on step properties that should be unique
|
|
1021
|
-
const parts = [
|
|
1022
|
-
step.executionType || 'unknown',
|
|
1023
|
-
step.depth || 0,
|
|
1024
|
-
step.step?.StepName || '',
|
|
1025
|
-
step.startTime || Date.now()
|
|
1026
|
-
];
|
|
1027
|
-
return `live-${parts.join('-').replace(/[^a-zA-Z0-9-]/g, '')}`;
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
|
-
* Find parent node based on depth
|
|
1031
|
-
*/
|
|
1032
|
-
findParentNode(nodes, childNode) {
|
|
1033
|
-
// We need to find the most recent node at the parent depth
|
|
1034
|
-
// that could logically be this node's parent
|
|
1035
|
-
let potentialParent = null;
|
|
1036
|
-
const findParentRecursive = (searchNodes) => {
|
|
1037
|
-
// Process nodes in reverse order to find the most recent one
|
|
1038
|
-
for (let i = searchNodes.length - 1; i >= 0; i--) {
|
|
1039
|
-
const node = searchNodes[i];
|
|
1040
|
-
// Check if this node could be the parent based on depth
|
|
1041
|
-
if (node.depth === childNode.depth - 1) {
|
|
1042
|
-
// Special handling for sub-agent execution steps
|
|
1043
|
-
if (childNode.agentPath && childNode.agentPath.length > node.agentPath.length) {
|
|
1044
|
-
// This child belongs to a sub-agent
|
|
1045
|
-
// Look for the most recent sub-agent delegation node
|
|
1046
|
-
if (node.type === 'sub-agent' || node.name.includes('Delegating to')) {
|
|
1047
|
-
return node;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
// Keep track of the most recent node at the correct depth
|
|
1051
|
-
if (!potentialParent) {
|
|
1052
|
-
potentialParent = node;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
// Search children recursively
|
|
1056
|
-
if (node.children && node.children.length > 0) {
|
|
1057
|
-
const foundInChildren = findParentRecursive(node.children);
|
|
1058
|
-
if (foundInChildren)
|
|
1059
|
-
return foundInChildren;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
return null;
|
|
1063
|
-
};
|
|
1064
|
-
const found = findParentRecursive(nodes);
|
|
1065
|
-
return found || potentialParent;
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Filter nodes based on display mode
|
|
1069
|
-
*/
|
|
1070
|
-
filterNodesForMode(nodes, mode) {
|
|
1071
|
-
return nodes.filter(node => {
|
|
1072
|
-
// If no displayMode is set, default to showing in both modes
|
|
1073
|
-
if (!node.displayMode) {
|
|
1074
|
-
return true;
|
|
1075
|
-
}
|
|
1076
|
-
// Show if displayMode is 'both' or matches current mode
|
|
1077
|
-
if (node.displayMode === 'both' || node.displayMode === mode) {
|
|
1078
|
-
// Filter children recursively
|
|
1079
|
-
if (node.children) {
|
|
1080
|
-
node.children = this.filterNodesForMode(node.children, mode);
|
|
1081
|
-
}
|
|
1082
|
-
return true;
|
|
1083
|
-
}
|
|
1084
|
-
return false;
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
/**
|
|
1088
|
-
* Build a map of nodes by ID for quick lookup
|
|
1089
|
-
*/
|
|
1090
|
-
buildNodeMap(nodes, map) {
|
|
1091
|
-
for (const node of nodes) {
|
|
1092
|
-
map.set(node.id, node);
|
|
1093
|
-
if (node.children) {
|
|
1094
|
-
this.buildNodeMap(node.children, map);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
/**
|
|
1099
|
-
* Create a single node from a live step
|
|
1100
|
-
*/
|
|
1101
|
-
createNodeFromLiveStep(step) {
|
|
1102
|
-
const stepName = step.step?.StepName || 'Processing...';
|
|
1103
|
-
let name = stepName;
|
|
1104
|
-
let detailsMarkdown;
|
|
1105
|
-
// Check if the step name contains markdown
|
|
1106
|
-
if (stepName.includes('\n') || stepName.includes('**') || stepName.includes('##')) {
|
|
1107
|
-
const lines = stepName.split('\n');
|
|
1108
|
-
name = lines[0].trim();
|
|
1109
|
-
if (lines.length > 1) {
|
|
1110
|
-
detailsMarkdown = lines.slice(1).join('\n').trim();
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
// For live steps, always start as running
|
|
1114
|
-
// The status will be updated to completed in markPreviouslyNewNodesAsComplete
|
|
1115
|
-
let status = 'running';
|
|
1116
|
-
if (step.step?.Status) {
|
|
1117
|
-
// Only use explicit status if it's failed
|
|
1118
|
-
const mappedStatus = this.mapStepStatus(step.step.Status);
|
|
1119
|
-
if (mappedStatus === 'failed') {
|
|
1120
|
-
status = 'failed';
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
// Determine display mode based on the step content
|
|
1124
|
-
let displayMode = 'both';
|
|
1125
|
-
// Progress messages that should only show in live mode
|
|
1126
|
-
if (name.includes('Executing') && name.includes('action') && !name.startsWith('Execute Action:')) {
|
|
1127
|
-
displayMode = 'live';
|
|
1128
|
-
}
|
|
1129
|
-
else if (name.includes("Executing agent's initial prompt")) {
|
|
1130
|
-
displayMode = 'live';
|
|
1131
|
-
}
|
|
1132
|
-
else if (name.includes('Re-executing agent prompt with additional context')) {
|
|
1133
|
-
displayMode = 'live';
|
|
1134
|
-
}
|
|
1135
|
-
// Check displayMode from metadata if available
|
|
1136
|
-
if (step.metadata?.displayMode) {
|
|
1137
|
-
displayMode = step.metadata.displayMode;
|
|
1138
|
-
}
|
|
1139
|
-
return {
|
|
1140
|
-
id: step.step?.ID || this.generateStepId(step),
|
|
1141
|
-
name: name,
|
|
1142
|
-
type: this.mapStepType(step.executionType || step.step?.StepType),
|
|
1143
|
-
status: status,
|
|
1144
|
-
startTime: step.startTime ? new Date(step.startTime) : new Date(),
|
|
1145
|
-
endTime: step.endTime ? new Date(step.endTime) : undefined,
|
|
1146
|
-
duration: step.durationMs,
|
|
1147
|
-
agentPath: step.agentHierarchy || [],
|
|
1148
|
-
expanded: false,
|
|
1149
|
-
depth: step.depth || 0,
|
|
1150
|
-
detailsMarkdown: detailsMarkdown,
|
|
1151
|
-
detailsExpanded: false,
|
|
1152
|
-
inputPreview: this.createPreview(step.inputData),
|
|
1153
|
-
outputPreview: this.createPreview(step.outputData),
|
|
1154
|
-
tokensUsed: this.extractTokens(step.outputData),
|
|
1155
|
-
cost: this.extractCost(step.outputData),
|
|
1156
|
-
displayMode: displayMode
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
/**
|
|
1160
|
-
* Collect all node IDs in the tree
|
|
1161
|
-
*/
|
|
1162
|
-
collectNodeIds(nodes, ids) {
|
|
1163
|
-
for (const node of nodes) {
|
|
1164
|
-
ids.add(node.id);
|
|
1165
|
-
if (node.children) {
|
|
1166
|
-
this.collectNodeIds(node.children, ids);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Preserve expanded state of all nodes
|
|
1172
|
-
*/
|
|
1173
|
-
preserveExpandedState(nodes, expandedState, detailsExpandedState) {
|
|
1174
|
-
for (const node of nodes) {
|
|
1175
|
-
if (node.expanded !== undefined) {
|
|
1176
|
-
expandedState.set(node.id, node.expanded);
|
|
1177
|
-
}
|
|
1178
|
-
if (node.detailsExpanded !== undefined) {
|
|
1179
|
-
detailsExpandedState.set(node.id, node.detailsExpanded);
|
|
1180
|
-
}
|
|
1181
|
-
if (node.children) {
|
|
1182
|
-
this.preserveExpandedState(node.children, expandedState, detailsExpandedState);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
/**
|
|
1187
|
-
* Restore expanded state of all nodes
|
|
1188
|
-
*/
|
|
1189
|
-
restoreExpandedState(nodes, expandedState, detailsExpandedState) {
|
|
1190
|
-
for (const node of nodes) {
|
|
1191
|
-
const expanded = expandedState.get(node.id);
|
|
1192
|
-
if (expanded !== undefined) {
|
|
1193
|
-
node.expanded = expanded;
|
|
1194
|
-
}
|
|
1195
|
-
const detailsExpanded = detailsExpandedState.get(node.id);
|
|
1196
|
-
if (detailsExpanded !== undefined) {
|
|
1197
|
-
node.detailsExpanded = detailsExpanded;
|
|
1198
|
-
}
|
|
1199
|
-
if (node.children) {
|
|
1200
|
-
this.restoreExpandedState(node.children, expandedState, detailsExpandedState);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
/**
|
|
1205
|
-
* Check if execution is complete
|
|
1206
|
-
*/
|
|
1207
|
-
isExecutionComplete() {
|
|
1208
|
-
return this.stats.completedSteps > 0 &&
|
|
1209
|
-
this.stats.completedSteps === this.stats.totalSteps - this.stats.failedSteps;
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* Handle view run button click
|
|
1213
|
-
*/
|
|
1214
|
-
onViewRunClick() {
|
|
1215
|
-
if (this.runId) {
|
|
1216
|
-
this.viewRunClick.emit({ runId: this.runId, runType: this.runType });
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
/**
|
|
1220
|
-
* Append a node to its parent based on depth
|
|
1221
|
-
*/
|
|
1222
|
-
appendToParent(nodes, newNode) {
|
|
1223
|
-
for (const node of nodes) {
|
|
1224
|
-
if (node.depth === newNode.depth - 1) {
|
|
1225
|
-
if (!node.children) {
|
|
1226
|
-
node.children = [];
|
|
1227
|
-
}
|
|
1228
|
-
node.children.push(newNode);
|
|
1229
|
-
return true;
|
|
1230
|
-
}
|
|
1231
|
-
if (node.children && this.appendToParent(node.children, newNode)) {
|
|
1232
|
-
return true;
|
|
1233
|
-
}
|
|
1234
|
-
}
|
|
1235
|
-
return false;
|
|
1236
|
-
}
|
|
1237
|
-
static { this.ɵfac = function AgentExecutionMonitorComponent_Factory(t) { return new (t || AgentExecutionMonitorComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; }
|
|
1238
|
-
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AgentExecutionMonitorComponent, selectors: [["mj-agent-execution-monitor"]], viewQuery: function AgentExecutionMonitorComponent_Query(rf, ctx) { if (rf & 1) {
|
|
1239
|
-
i0.ɵɵviewQuery(_c0, 5);
|
|
1240
|
-
i0.ɵɵviewQuery(_c1, 5, ViewContainerRef);
|
|
1241
|
-
} if (rf & 2) {
|
|
1242
|
-
let _t;
|
|
1243
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.executionTreeContainer = _t.first);
|
|
1244
|
-
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.executionNodesContainer = _t.first);
|
|
1245
|
-
} }, inputs: { mode: "mode", executionData: "executionData", autoExpand: "autoExpand", runId: "runId", runType: "runType" }, outputs: { viewRunClick: "viewRunClick" }, standalone: true, features: [i0.ɵɵNgOnChangesFeature, i0.ɵɵStandaloneFeature], decls: 39, vars: 16, consts: [["executionTreeContainer", ""], ["executionNodesContainer", ""], [1, "execution-monitor"], [1, "monitor-header"], [1, "header-title"], [1, "fa-solid", "fa-diagram-project"], [1, "live-indicator"], [1, "current-status"], [1, "view-run-btn"], [1, "execution-tree", 3, "scroll", "click"], [1, "nodes-container"], [1, "empty-state"], [1, "monitor-footer"], [1, "stats-grid"], [1, "stat-item"], [1, "stat-label"], [1, "stat-value"], [1, "failed-count"], [1, "step-types"], [1, "pulse"], [1, "status-label"], [1, "agent-path"], [1, "step-name"], [1, "view-run-btn", 3, "click"], [1, "fa-solid", "fa-external-link-alt"], [1, "fa-solid", "fa-hourglass-start"], [1, "type-badge"]], template: function AgentExecutionMonitorComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1246
|
-
const _r1 = i0.ɵɵgetCurrentView();
|
|
1247
|
-
i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "div", 4);
|
|
1248
|
-
i0.ɵɵelement(3, "i", 5);
|
|
1249
|
-
i0.ɵɵelementStart(4, "span");
|
|
1250
|
-
i0.ɵɵtext(5, "Execution Monitor");
|
|
1251
|
-
i0.ɵɵelementEnd();
|
|
1252
|
-
i0.ɵɵtemplate(6, AgentExecutionMonitorComponent_Conditional_6_Template, 3, 0, "span", 6);
|
|
1253
|
-
i0.ɵɵelementEnd();
|
|
1254
|
-
i0.ɵɵtemplate(7, AgentExecutionMonitorComponent_Conditional_7_Template, 7, 2, "div", 7)(8, AgentExecutionMonitorComponent_Conditional_8_Template, 3, 1, "button", 8);
|
|
1255
|
-
i0.ɵɵelementEnd();
|
|
1256
|
-
i0.ɵɵelementStart(9, "div", 9, 0);
|
|
1257
|
-
i0.ɵɵlistener("scroll", function AgentExecutionMonitorComponent_Template_div_scroll_9_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onScroll($event)); })("click", function AgentExecutionMonitorComponent_Template_div_click_9_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onUserInteraction()); });
|
|
1258
|
-
i0.ɵɵelementStart(11, "div", 10, 1);
|
|
1259
|
-
i0.ɵɵtemplate(13, AgentExecutionMonitorComponent_Conditional_13_Template, 4, 0, "div", 11);
|
|
1260
|
-
i0.ɵɵelementEnd()();
|
|
1261
|
-
i0.ɵɵelementStart(14, "div", 12)(15, "div", 13)(16, "div", 14)(17, "span", 15);
|
|
1262
|
-
i0.ɵɵtext(18, "Steps");
|
|
1263
|
-
i0.ɵɵelementEnd();
|
|
1264
|
-
i0.ɵɵelementStart(19, "span", 16);
|
|
1265
|
-
i0.ɵɵtext(20);
|
|
1266
|
-
i0.ɵɵtemplate(21, AgentExecutionMonitorComponent_Conditional_21_Template, 2, 1, "span", 17);
|
|
1267
|
-
i0.ɵɵelementEnd()();
|
|
1268
|
-
i0.ɵɵelementStart(22, "div", 14)(23, "span", 15);
|
|
1269
|
-
i0.ɵɵtext(24, "Prompts");
|
|
1270
|
-
i0.ɵɵelementEnd();
|
|
1271
|
-
i0.ɵɵelementStart(25, "span", 16);
|
|
1272
|
-
i0.ɵɵtext(26);
|
|
1273
|
-
i0.ɵɵelementEnd()();
|
|
1274
|
-
i0.ɵɵelementStart(27, "div", 14)(28, "span", 15);
|
|
1275
|
-
i0.ɵɵtext(29, "Tokens");
|
|
1276
|
-
i0.ɵɵelementEnd();
|
|
1277
|
-
i0.ɵɵelementStart(30, "span", 16);
|
|
1278
|
-
i0.ɵɵtext(31);
|
|
1279
|
-
i0.ɵɵelementEnd()();
|
|
1280
|
-
i0.ɵɵelementStart(32, "div", 14)(33, "span", 15);
|
|
1281
|
-
i0.ɵɵtext(34, "Cost");
|
|
1282
|
-
i0.ɵɵelementEnd();
|
|
1283
|
-
i0.ɵɵelementStart(35, "span", 16);
|
|
1284
|
-
i0.ɵɵtext(36);
|
|
1285
|
-
i0.ɵɵelementEnd()();
|
|
1286
|
-
i0.ɵɵtemplate(37, AgentExecutionMonitorComponent_Conditional_37_Template, 5, 1, "div", 14);
|
|
1287
|
-
i0.ɵɵelementEnd();
|
|
1288
|
-
i0.ɵɵtemplate(38, AgentExecutionMonitorComponent_Conditional_38_Template, 3, 0, "div", 18);
|
|
1289
|
-
i0.ɵɵelementEnd()();
|
|
1290
|
-
} if (rf & 2) {
|
|
1291
|
-
i0.ɵɵclassProp("live-mode", ctx.mode === "live");
|
|
1292
|
-
i0.ɵɵadvance(6);
|
|
1293
|
-
i0.ɵɵconditional(ctx.mode === "live" ? 6 : -1);
|
|
1294
|
-
i0.ɵɵadvance();
|
|
1295
|
-
i0.ɵɵconditional(ctx.currentStep && ctx.mode === "live" ? 7 : -1);
|
|
1296
|
-
i0.ɵɵadvance();
|
|
1297
|
-
i0.ɵɵconditional(ctx.mode === "historical" && ctx.runId && ctx.isExecutionComplete() ? 8 : -1);
|
|
1298
|
-
i0.ɵɵadvance();
|
|
1299
|
-
i0.ɵɵclassProp("has-content", ctx.executionTree.length > 0);
|
|
1300
|
-
i0.ɵɵadvance(4);
|
|
1301
|
-
i0.ɵɵconditional(ctx.executionTree.length === 0 ? 13 : -1);
|
|
1302
|
-
i0.ɵɵadvance(7);
|
|
1303
|
-
i0.ɵɵtextInterpolate2(" ", ctx.stats.completedSteps, "/", ctx.stats.totalSteps, " ");
|
|
1304
|
-
i0.ɵɵadvance();
|
|
1305
|
-
i0.ɵɵconditional(ctx.stats.failedSteps > 0 ? 21 : -1);
|
|
1306
|
-
i0.ɵɵadvance(5);
|
|
1307
|
-
i0.ɵɵtextInterpolate(ctx.stats.totalPrompts);
|
|
1308
|
-
i0.ɵɵadvance(5);
|
|
1309
|
-
i0.ɵɵtextInterpolate(ctx.stats.totalTokens.toLocaleString());
|
|
1310
|
-
i0.ɵɵadvance(5);
|
|
1311
|
-
i0.ɵɵtextInterpolate1("$", ctx.stats.totalCost.toFixed(4), "");
|
|
1312
|
-
i0.ɵɵadvance();
|
|
1313
|
-
i0.ɵɵconditional(ctx.stats.totalDuration ? 37 : -1);
|
|
1314
|
-
i0.ɵɵadvance();
|
|
1315
|
-
i0.ɵɵconditional(ctx.getStepTypes().length > 0 ? 38 : -1);
|
|
1316
|
-
} }, dependencies: [CommonModule], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n \n\n .monitor-header[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #2196f3;\n }\n\n .live-indicator[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse[_ngcontent-%COMP%] {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_pulse 2s infinite;\n }\n\n @keyframes _ngcontent-%COMP%_pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label[_ngcontent-%COMP%] {\n font-weight: 500;\n }\n\n .agent-path[_ngcontent-%COMP%] {\n color: #999;\n }\n\n .step-name[_ngcontent-%COMP%] {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n \n\n .execution-tree[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container[_ngcontent-%COMP%] {\n \n\n }\n\n .empty-state[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n }\n\n \n\n .tree-node[_ngcontent-%COMP%] {\n margin-bottom: 4px;\n }\n\n .node-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n user-select: none;\n }\n\n .node-header[_ngcontent-%COMP%]:hover {\n background: #e8f0fe;\n border-color: #2196f3;\n }\n\n .expand-icon[_ngcontent-%COMP%], .expand-spacer[_ngcontent-%COMP%] {\n width: 16px;\n text-align: center;\n color: #666;\n font-size: 10px;\n }\n\n .status-icon[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n font-size: 14px;\n }\n\n .status-pending[_ngcontent-%COMP%] { color: #999; }\n .status-running[_ngcontent-%COMP%] { color: #2196f3; }\n .status-completed[_ngcontent-%COMP%] { color: #4caf50; }\n .status-failed[_ngcontent-%COMP%] { color: #f44336; }\n\n .type-icon[_ngcontent-%COMP%] {\n width: 20px;\n text-align: center;\n font-size: 14px;\n color: #666;\n }\n\n .node-name[_ngcontent-%COMP%] {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: #1a1a1a;\n }\n\n .node-duration[_ngcontent-%COMP%] {\n font-size: 12px;\n color: #666;\n font-weight: 500;\n }\n\n .node-metrics[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n font-size: 12px;\n }\n\n .tokens[_ngcontent-%COMP%] {\n color: #666;\n }\n\n .cost[_ngcontent-%COMP%] {\n color: #2196f3;\n font-weight: 500;\n }\n\n \n\n .node-details[_ngcontent-%COMP%] {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f5f5f5;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .detail-section[_ngcontent-%COMP%] {\n margin-bottom: 8px;\n }\n\n .detail-section[_ngcontent-%COMP%]:last-child {\n margin-bottom: 0;\n }\n\n .detail-section.error[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .detail-label[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n margin-bottom: 4px;\n color: #666;\n }\n\n .detail-section.error[_ngcontent-%COMP%] .detail-label[_ngcontent-%COMP%] {\n color: #d32f2f;\n }\n\n .detail-label[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n }\n\n .detail-content[_ngcontent-%COMP%] {\n padding-left: 18px;\n color: #333;\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n \n\n .monitor-footer[_ngcontent-%COMP%] {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item[_ngcontent-%COMP%] {\n text-align: center;\n }\n\n .stat-label[_ngcontent-%COMP%] {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value[_ngcontent-%COMP%] {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count[_ngcontent-%COMP%] {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n\n \n\n .details-toggle[_ngcontent-%COMP%] {\n margin-left: 8px;\n padding: 2px 6px;\n background: transparent;\n border: 1px solid #ddd;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n color: #666;\n transition: all 0.2s ease;\n }\n \n .details-toggle[_ngcontent-%COMP%]:hover {\n background: #f0f0f0;\n border-color: #2196f3;\n color: #2196f3;\n }\n \n \n\n .markdown-details[_ngcontent-%COMP%] {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f9fafb;\n border-left: 3px solid #2196f3;\n border-radius: 0 4px 4px 0;\n font-size: 13px;\n line-height: 1.6;\n }\n \n .markdown-details[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n font-size: 16px;\n margin: 0 0 8px 0;\n color: #1a1a1a;\n }\n \n .markdown-details[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n font-size: 14px;\n margin: 8px 0 6px 0;\n color: #333;\n }\n \n .markdown-details[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n font-size: 13px;\n margin: 6px 0 4px 0;\n color: #555;\n }\n \n .markdown-details[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] {\n margin: 4px 0;\n padding-left: 20px;\n }\n \n .markdown-details[_ngcontent-%COMP%] li[_ngcontent-%COMP%] {\n margin: 2px 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: #e8f0fe;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n \n .markdown-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] {\n background: #f5f5f5;\n padding: 8px;\n border-radius: 4px;\n overflow-x: auto;\n margin: 8px 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] pre[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: none;\n padding: 0;\n }\n \n .markdown-details[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n font-weight: 600;\n color: #1a1a1a;\n }\n \n .markdown-details[_ngcontent-%COMP%] em[_ngcontent-%COMP%] {\n font-style: italic;\n color: #555;\n }\n\n \n\n .execution-history-toggle[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n padding: 4px 8px;\n margin-left: 4px;\n cursor: pointer;\n color: #666;\n font-size: 12px;\n border-radius: 3px;\n transition: all 0.2s ease;\n }\n \n .execution-history-toggle[_ngcontent-%COMP%]:hover {\n background: #e3f2fd;\n color: #2196f3;\n }\n \n \n\n .view-run-btn[_ngcontent-%COMP%] {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn[_ngcontent-%COMP%]:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n }\n\n \n\n @media (max-width: 768px) {\n .monitor-header[_ngcontent-%COMP%] {\n padding: 12px;\n }\n\n .execution-tree[_ngcontent-%COMP%] {\n padding: 12px;\n }\n\n .node-header[_ngcontent-%COMP%] {\n padding: 6px 8px;\n gap: 6px;\n }\n\n .node-metrics[_ngcontent-%COMP%] {\n display: none;\n }\n \n .markdown-details[_ngcontent-%COMP%] {\n margin-left: 20px;\n font-size: 12px;\n }\n }"], changeDetection: 0 }); }
|
|
1317
|
-
}
|
|
1318
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AgentExecutionMonitorComponent, [{
|
|
1319
|
-
type: Component,
|
|
1320
|
-
args: [{ selector: 'mj-agent-execution-monitor', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1321
|
-
<div class="execution-monitor" [class.live-mode]="mode === 'live'">
|
|
1322
|
-
<!-- Header -->
|
|
1323
|
-
<div class="monitor-header">
|
|
1324
|
-
<div class="header-title">
|
|
1325
|
-
<i class="fa-solid fa-diagram-project"></i>
|
|
1326
|
-
<span>Execution Monitor</span>
|
|
1327
|
-
@if (mode === 'live') {
|
|
1328
|
-
<span class="live-indicator">
|
|
1329
|
-
<span class="pulse"></span>
|
|
1330
|
-
LIVE
|
|
1331
|
-
</span>
|
|
1332
|
-
}
|
|
1333
|
-
</div>
|
|
1334
|
-
@if (currentStep && mode === 'live') {
|
|
1335
|
-
<div class="current-status">
|
|
1336
|
-
<span class="status-label">Current:</span>
|
|
1337
|
-
<span class="agent-path">{{ formatAgentPath(currentStep.agentPath) }}</span>
|
|
1338
|
-
<span class="step-name">{{ currentStep.name }}</span>
|
|
1339
|
-
</div>
|
|
1340
|
-
}
|
|
1341
|
-
@if (mode === 'historical' && runId && isExecutionComplete()) {
|
|
1342
|
-
<button class="view-run-btn" (click)="onViewRunClick()">
|
|
1343
|
-
<i class="fa-solid fa-external-link-alt"></i>
|
|
1344
|
-
View {{ runType === 'agent' ? 'Agent' : 'Prompt' }} Run
|
|
1345
|
-
</button>
|
|
1346
|
-
}
|
|
1347
|
-
</div>
|
|
1348
|
-
|
|
1349
|
-
<!-- Execution Tree -->
|
|
1350
|
-
<div class="execution-tree"
|
|
1351
|
-
#executionTreeContainer
|
|
1352
|
-
[class.has-content]="executionTree.length > 0"
|
|
1353
|
-
(scroll)="onScroll($event)"
|
|
1354
|
-
(click)="onUserInteraction()">
|
|
1355
|
-
<!-- Always render the container, but show empty state when no data -->
|
|
1356
|
-
<div #executionNodesContainer class="nodes-container">
|
|
1357
|
-
@if (executionTree.length === 0) {
|
|
1358
|
-
<div class="empty-state">
|
|
1359
|
-
<i class="fa-solid fa-hourglass-start"></i>
|
|
1360
|
-
<p>Waiting for execution to begin...</p>
|
|
1361
|
-
</div>
|
|
1362
|
-
}
|
|
1363
|
-
</div>
|
|
1364
|
-
</div>
|
|
1365
|
-
|
|
1366
|
-
<!-- Statistics Footer -->
|
|
1367
|
-
<div class="monitor-footer">
|
|
1368
|
-
<div class="stats-grid">
|
|
1369
|
-
<div class="stat-item">
|
|
1370
|
-
<span class="stat-label">Steps</span>
|
|
1371
|
-
<span class="stat-value">
|
|
1372
|
-
{{ stats.completedSteps }}/{{ stats.totalSteps }}
|
|
1373
|
-
@if (stats.failedSteps > 0) {
|
|
1374
|
-
<span class="failed-count">({{ stats.failedSteps }} failed)</span>
|
|
1375
|
-
}
|
|
1376
|
-
</span>
|
|
1377
|
-
</div>
|
|
1378
|
-
<div class="stat-item">
|
|
1379
|
-
<span class="stat-label">Prompts</span>
|
|
1380
|
-
<span class="stat-value">{{ stats.totalPrompts }}</span>
|
|
1381
|
-
</div>
|
|
1382
|
-
<div class="stat-item">
|
|
1383
|
-
<span class="stat-label">Tokens</span>
|
|
1384
|
-
<span class="stat-value">{{ stats.totalTokens.toLocaleString() }}</span>
|
|
1385
|
-
</div>
|
|
1386
|
-
<div class="stat-item">
|
|
1387
|
-
<span class="stat-label">Cost</span>
|
|
1388
|
-
<span class="stat-value">\${{ stats.totalCost.toFixed(4) }}</span>
|
|
1389
|
-
</div>
|
|
1390
|
-
@if (stats.totalDuration) {
|
|
1391
|
-
<div class="stat-item">
|
|
1392
|
-
<span class="stat-label">Duration</span>
|
|
1393
|
-
<span class="stat-value">{{ formatDuration(stats.totalDuration) }}</span>
|
|
1394
|
-
</div>
|
|
1395
|
-
}
|
|
1396
|
-
</div>
|
|
1397
|
-
@if (getStepTypes().length > 0) {
|
|
1398
|
-
<div class="step-types">
|
|
1399
|
-
@for (type of getStepTypes(); track type) {
|
|
1400
|
-
<span class="type-badge">
|
|
1401
|
-
{{ stats.stepsByType[type] }} {{ type }}
|
|
1402
|
-
</span>
|
|
1403
|
-
}
|
|
1404
|
-
</div>
|
|
1405
|
-
}
|
|
1406
|
-
</div>
|
|
1407
|
-
</div>
|
|
1408
|
-
`, styles: ["\n :host {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n }\n \n .execution-monitor {\n display: flex;\n flex-direction: column;\n flex: 1;\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n overflow: hidden;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n }\n\n /* Header */\n .monitor-header {\n padding: 16px;\n background: #f8f9fa;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .header-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n margin-bottom: 8px;\n }\n\n .header-title i {\n color: #2196f3;\n }\n\n .live-indicator {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n background: #ff4444;\n color: white;\n border-radius: 4px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.5px;\n }\n\n .pulse {\n display: inline-block;\n width: 8px;\n height: 8px;\n background: white;\n border-radius: 50%;\n animation: pulse 2s infinite;\n }\n\n @keyframes pulse {\n 0% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.8); }\n 100% { opacity: 1; transform: scale(1); }\n }\n\n .current-status {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n color: #666;\n }\n\n .status-label {\n font-weight: 500;\n }\n\n .agent-path {\n color: #999;\n }\n\n .step-name {\n color: #1a1a1a;\n font-weight: 500;\n }\n\n /* Execution Tree */\n .execution-tree {\n flex: 1;\n overflow-y: auto;\n overflow-x: auto;\n padding: 16px;\n min-height: 0;\n }\n \n .nodes-container {\n /* No extra padding needed, indentation handled by node margins */\n }\n\n .empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n text-align: center;\n }\n\n .empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n }\n\n .empty-state p {\n margin: 0;\n font-size: 14px;\n }\n\n /* Tree Nodes */\n .tree-node {\n margin-bottom: 4px;\n }\n\n .node-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n user-select: none;\n }\n\n .node-header:hover {\n background: #e8f0fe;\n border-color: #2196f3;\n }\n\n .expand-icon, .expand-spacer {\n width: 16px;\n text-align: center;\n color: #666;\n font-size: 10px;\n }\n\n .status-icon {\n width: 20px;\n text-align: center;\n font-size: 14px;\n }\n\n .status-pending { color: #999; }\n .status-running { color: #2196f3; }\n .status-completed { color: #4caf50; }\n .status-failed { color: #f44336; }\n\n .type-icon {\n width: 20px;\n text-align: center;\n font-size: 14px;\n color: #666;\n }\n\n .node-name {\n flex: 1;\n font-size: 13px;\n font-weight: 500;\n color: #1a1a1a;\n }\n\n .node-duration {\n font-size: 12px;\n color: #666;\n font-weight: 500;\n }\n\n .node-metrics {\n display: flex;\n gap: 12px;\n font-size: 12px;\n }\n\n .tokens {\n color: #666;\n }\n\n .cost {\n color: #2196f3;\n font-weight: 500;\n }\n\n /* Node Details */\n .node-details {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f5f5f5;\n border-radius: 4px;\n font-size: 12px;\n }\n\n .detail-section {\n margin-bottom: 8px;\n }\n\n .detail-section:last-child {\n margin-bottom: 0;\n }\n\n .detail-section.error {\n color: #d32f2f;\n }\n\n .detail-label {\n display: flex;\n align-items: center;\n gap: 6px;\n font-weight: 600;\n margin-bottom: 4px;\n color: #666;\n }\n\n .detail-section.error .detail-label {\n color: #d32f2f;\n }\n\n .detail-label i {\n font-size: 12px;\n }\n\n .detail-content {\n padding-left: 18px;\n color: #333;\n line-height: 1.4;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n /* Footer */\n .monitor-footer {\n padding: 16px;\n background: #f8f9fa;\n border-top: 1px solid #e0e0e0;\n }\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));\n gap: 16px;\n margin-bottom: 12px;\n }\n\n .stat-item {\n text-align: center;\n }\n\n .stat-label {\n display: block;\n font-size: 11px;\n color: #666;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .stat-value {\n display: block;\n font-size: 16px;\n font-weight: 600;\n color: #1a1a1a;\n }\n\n .failed-count {\n color: #f44336;\n font-size: 12px;\n font-weight: normal;\n }\n\n .step-types {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n justify-content: center;\n }\n\n .type-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #e3f2fd;\n color: #1976d2;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n }\n\n /* Details Toggle Button */\n .details-toggle {\n margin-left: 8px;\n padding: 2px 6px;\n background: transparent;\n border: 1px solid #ddd;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n color: #666;\n transition: all 0.2s ease;\n }\n \n .details-toggle:hover {\n background: #f0f0f0;\n border-color: #2196f3;\n color: #2196f3;\n }\n \n /* Markdown Details Section */\n .markdown-details {\n margin: 8px 0 8px 44px;\n padding: 12px;\n background: #f9fafb;\n border-left: 3px solid #2196f3;\n border-radius: 0 4px 4px 0;\n font-size: 13px;\n line-height: 1.6;\n }\n \n .markdown-details h2 {\n font-size: 16px;\n margin: 0 0 8px 0;\n color: #1a1a1a;\n }\n \n .markdown-details h3 {\n font-size: 14px;\n margin: 8px 0 6px 0;\n color: #333;\n }\n \n .markdown-details h4 {\n font-size: 13px;\n margin: 6px 0 4px 0;\n color: #555;\n }\n \n .markdown-details ul {\n margin: 4px 0;\n padding-left: 20px;\n }\n \n .markdown-details li {\n margin: 2px 0;\n }\n \n .markdown-details code {\n background: #e8f0fe;\n padding: 1px 4px;\n border-radius: 3px;\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n }\n \n .markdown-details pre {\n background: #f5f5f5;\n padding: 8px;\n border-radius: 4px;\n overflow-x: auto;\n margin: 8px 0;\n }\n \n .markdown-details pre code {\n background: none;\n padding: 0;\n }\n \n .markdown-details strong {\n font-weight: 600;\n color: #1a1a1a;\n }\n \n .markdown-details em {\n font-style: italic;\n color: #555;\n }\n\n /* Button styling for execution history toggle */\n .execution-history-toggle {\n background: transparent;\n border: none;\n padding: 4px 8px;\n margin-left: 4px;\n cursor: pointer;\n color: #666;\n font-size: 12px;\n border-radius: 3px;\n transition: all 0.2s ease;\n }\n \n .execution-history-toggle:hover {\n background: #e3f2fd;\n color: #2196f3;\n }\n \n /* View Run Button */\n .view-run-btn {\n background: #2196f3;\n color: white;\n border: none;\n padding: 6px 12px;\n border-radius: 4px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n transition: all 0.2s ease;\n margin-left: auto;\n }\n \n .view-run-btn:hover {\n background: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0,0,0,0.2);\n }\n \n .view-run-btn i {\n font-size: 12px;\n }\n\n /* Responsive */\n @media (max-width: 768px) {\n .monitor-header {\n padding: 12px;\n }\n\n .execution-tree {\n padding: 12px;\n }\n\n .node-header {\n padding: 6px 8px;\n gap: 6px;\n }\n\n .node-metrics {\n display: none;\n }\n \n .markdown-details {\n margin-left: 20px;\n font-size: 12px;\n }\n }\n "] }]
|
|
1409
|
-
}], () => [{ type: i0.ChangeDetectorRef }], { mode: [{
|
|
1410
|
-
type: Input
|
|
1411
|
-
}], executionData: [{
|
|
1412
|
-
type: Input
|
|
1413
|
-
}], autoExpand: [{
|
|
1414
|
-
type: Input
|
|
1415
|
-
}], runId: [{
|
|
1416
|
-
type: Input
|
|
1417
|
-
}], runType: [{
|
|
1418
|
-
type: Input
|
|
1419
|
-
}], viewRunClick: [{
|
|
1420
|
-
type: Output
|
|
1421
|
-
}], executionTreeContainer: [{
|
|
1422
|
-
type: ViewChild,
|
|
1423
|
-
args: ['executionTreeContainer']
|
|
1424
|
-
}], executionNodesContainer: [{
|
|
1425
|
-
type: ViewChild,
|
|
1426
|
-
args: ['executionNodesContainer', { read: ViewContainerRef }]
|
|
1427
|
-
}] }); })();
|
|
1428
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AgentExecutionMonitorComponent, { className: "AgentExecutionMonitorComponent", filePath: "src/lib/custom/ai-test-harness/agent-execution-monitor.component.ts", lineNumber: 634 }); })();
|
|
1429
|
-
//# sourceMappingURL=agent-execution-monitor.component.js.map
|