@memberjunction/ng-core-entity-forms 2.51.0 → 2.52.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.
Files changed (54) hide show
  1. package/dist/lib/custom/AIAgents/ai-agent-form.component.js +1 -1
  2. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts +0 -1
  3. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts.map +1 -1
  4. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js +282 -278
  5. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js.map +1 -1
  6. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.d.ts.map +1 -0
  7. package/dist/lib/custom/{AIAgents → ai-test-harness}/agent-execution-monitor.component.js +18 -5
  8. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.js.map +1 -0
  9. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.d.ts.map +1 -0
  10. package/dist/lib/custom/{AIAgents → ai-test-harness}/agent-execution-node.component.js +4 -4
  11. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.js.map +1 -0
  12. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.d.ts.map +1 -0
  13. package/dist/lib/custom/{AIAgents → ai-test-harness}/ai-test-harness-dialog.component.js +1 -1
  14. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.js.map +1 -0
  15. package/dist/lib/custom/{AIAgents → ai-test-harness}/ai-test-harness.component.d.ts +88 -7
  16. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.d.ts.map +1 -0
  17. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js +3140 -0
  18. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js.map +1 -0
  19. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts +8 -0
  20. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts.map +1 -0
  21. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js +57 -0
  22. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js.map +1 -0
  23. package/dist/lib/custom/custom-forms.module.d.ts +15 -15
  24. package/dist/lib/custom/custom-forms.module.js +8 -8
  25. package/dist/lib/custom/custom-forms.module.js.map +1 -1
  26. package/dist/lib/custom/test-harness-dialog.service.js +1 -1
  27. package/dist/lib/custom/test-harness-dialog.service.js.map +1 -1
  28. package/dist/lib/generated/Entities/AIPrompt/sections/details.component.d.ts.map +1 -1
  29. package/dist/lib/generated/Entities/AIPrompt/sections/details.component.js +94 -4
  30. package/dist/lib/generated/Entities/AIPrompt/sections/details.component.js.map +1 -1
  31. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.d.ts.map +1 -1
  32. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js +103 -4
  33. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js.map +1 -1
  34. package/dist/public-api.d.ts +2 -2
  35. package/dist/public-api.d.ts.map +1 -1
  36. package/dist/public-api.js +2 -2
  37. package/dist/public-api.js.map +1 -1
  38. package/package.json +12 -12
  39. package/dist/lib/custom/AIAgents/agent-execution-monitor.component.d.ts.map +0 -1
  40. package/dist/lib/custom/AIAgents/agent-execution-monitor.component.js.map +0 -1
  41. package/dist/lib/custom/AIAgents/agent-execution-node.component.d.ts.map +0 -1
  42. package/dist/lib/custom/AIAgents/agent-execution-node.component.js.map +0 -1
  43. package/dist/lib/custom/AIAgents/ai-test-harness-dialog.component.d.ts.map +0 -1
  44. package/dist/lib/custom/AIAgents/ai-test-harness-dialog.component.js.map +0 -1
  45. package/dist/lib/custom/AIAgents/ai-test-harness.component.d.ts.map +0 -1
  46. package/dist/lib/custom/AIAgents/ai-test-harness.component.js +0 -2658
  47. package/dist/lib/custom/AIAgents/ai-test-harness.component.js.map +0 -1
  48. package/dist/lib/custom/AIPrompts/ai-prompt-execution-dialog.component.d.ts +0 -99
  49. package/dist/lib/custom/AIPrompts/ai-prompt-execution-dialog.component.d.ts.map +0 -1
  50. package/dist/lib/custom/AIPrompts/ai-prompt-execution-dialog.component.js +0 -830
  51. package/dist/lib/custom/AIPrompts/ai-prompt-execution-dialog.component.js.map +0 -1
  52. /package/dist/lib/custom/{AIAgents → ai-test-harness}/agent-execution-monitor.component.d.ts +0 -0
  53. /package/dist/lib/custom/{AIAgents → ai-test-harness}/agent-execution-node.component.d.ts +0 -0
  54. /package/dist/lib/custom/{AIAgents → ai-test-harness}/ai-test-harness-dialog.component.d.ts +0 -0
@@ -0,0 +1,3140 @@
1
+ import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
2
+ import { Metadata } from '@memberjunction/core';
3
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
4
+ import { Subject } from 'rxjs';
5
+ import { AIEngineBase } from '@memberjunction/ai-engine-base';
6
+ import * as i0 from "@angular/core";
7
+ import * as i1 from "@angular/platform-browser";
8
+ import * as i2 from "@progress/kendo-angular-dialog";
9
+ import * as i3 from "@angular/common";
10
+ import * as i4 from "./agent-execution-monitor.component";
11
+ import * as i5 from "@angular/forms";
12
+ import * as i6 from "@progress/kendo-angular-layout";
13
+ import * as i7 from "@progress/kendo-angular-inputs";
14
+ import * as i8 from "@progress/kendo-angular-dropdowns";
15
+ import * as i9 from "@progress/kendo-angular-buttons";
16
+ const _c0 = ["messagesContainer"];
17
+ const _c1 = ["fileInput"];
18
+ const _c2 = ["messageInput"];
19
+ const _c3 = ["saveDialogInput"];
20
+ const _forTrack0 = ($index, $item) => $item.id;
21
+ const _c4 = () => ({ text: "String", value: "string" });
22
+ const _c5 = () => ({ text: "Number", value: "number" });
23
+ const _c6 = () => ({ text: "Boolean", value: "boolean" });
24
+ const _c7 = () => ({ text: "Object", value: "object" });
25
+ const _c8 = (a0, a1, a2, a3) => [a0, a1, a2, a3];
26
+ function AITestHarnessComponent_div_0_Conditional_2_Conditional_6_Template(rf, ctx) { if (rf & 1) {
27
+ i0.ɵɵelementStart(0, "p");
28
+ i0.ɵɵtext(1);
29
+ i0.ɵɵelementEnd();
30
+ } if (rf & 2) {
31
+ const ctx_r1 = i0.ɵɵnextContext(3);
32
+ i0.ɵɵadvance();
33
+ i0.ɵɵtextInterpolate(ctx_r1.entity.Description);
34
+ } }
35
+ function AITestHarnessComponent_div_0_Conditional_2_Template(rf, ctx) { if (rf & 1) {
36
+ const _r1 = i0.ɵɵgetCurrentView();
37
+ i0.ɵɵelementStart(0, "div", 9)(1, "div", 13);
38
+ i0.ɵɵelement(2, "i", 14);
39
+ i0.ɵɵelementStart(3, "div")(4, "h3");
40
+ i0.ɵɵtext(5);
41
+ i0.ɵɵelementEnd();
42
+ i0.ɵɵtemplate(6, AITestHarnessComponent_div_0_Conditional_2_Conditional_6_Template, 2, 1, "p");
43
+ i0.ɵɵelementEnd()();
44
+ i0.ɵɵelementStart(7, "div", 15)(8, "button", 16);
45
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_2_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.clearConversation()); });
46
+ i0.ɵɵelement(9, "i", 17);
47
+ i0.ɵɵtext(10, " Clear ");
48
+ i0.ɵɵelementEnd();
49
+ i0.ɵɵelementStart(11, "button", 18);
50
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_2_Template_button_click_11_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.saveConversation()); });
51
+ i0.ɵɵelement(12, "i", 19);
52
+ i0.ɵɵtext(13, " Save ");
53
+ i0.ɵɵelementEnd();
54
+ i0.ɵɵelementStart(14, "button", 20);
55
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_2_Template_button_click_14_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.exportConversation()); });
56
+ i0.ɵɵelement(15, "i", 21);
57
+ i0.ɵɵtext(16, " Export ");
58
+ i0.ɵɵelementEnd();
59
+ i0.ɵɵelementStart(17, "button", 22);
60
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_2_Template_button_click_17_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.importConversation()); });
61
+ i0.ɵɵelement(18, "i", 23);
62
+ i0.ɵɵtext(19, " Import ");
63
+ i0.ɵɵelementEnd();
64
+ i0.ɵɵelementStart(20, "input", 24, 0);
65
+ i0.ɵɵlistener("change", function AITestHarnessComponent_div_0_Conditional_2_Template_input_change_20_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onFileSelected($event)); });
66
+ i0.ɵɵelementEnd();
67
+ i0.ɵɵelementStart(22, "button", 25);
68
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_2_Template_button_click_22_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.toggleSidebar()); });
69
+ i0.ɵɵelement(23, "i", 14);
70
+ i0.ɵɵelementEnd()()();
71
+ } if (rf & 2) {
72
+ const ctx_r1 = i0.ɵɵnextContext(2);
73
+ i0.ɵɵadvance(2);
74
+ i0.ɵɵclassProp("fa-robot", ctx_r1.mode === "agent")("fa-comment-dots", ctx_r1.mode === "prompt");
75
+ i0.ɵɵadvance(3);
76
+ i0.ɵɵtextInterpolate(ctx_r1.getEntityName());
77
+ i0.ɵɵadvance();
78
+ i0.ɵɵconditional(ctx_r1.entity.Description ? 6 : -1);
79
+ i0.ɵɵadvance(2);
80
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting || ctx_r1.conversationMessages.length === 0);
81
+ i0.ɵɵadvance(3);
82
+ i0.ɵɵproperty("disabled", ctx_r1.conversationMessages.length === 0);
83
+ i0.ɵɵadvance(3);
84
+ i0.ɵɵproperty("disabled", ctx_r1.conversationMessages.length === 0);
85
+ i0.ɵɵadvance(9);
86
+ i0.ɵɵclassProp("fa-chevron-right", !ctx_r1.showSidebar)("fa-chevron-left", ctx_r1.showSidebar);
87
+ } }
88
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_5_Template(rf, ctx) { if (rf & 1) {
89
+ i0.ɵɵelementStart(0, "div", 28);
90
+ i0.ɵɵelement(1, "i", 46);
91
+ i0.ɵɵelementStart(2, "h4");
92
+ i0.ɵɵtext(3, "Start a conversation");
93
+ i0.ɵɵelementEnd();
94
+ i0.ɵɵelementStart(4, "p");
95
+ i0.ɵɵtext(5, "Send a message to begin testing the AI agent");
96
+ i0.ɵɵelementEnd()();
97
+ } }
98
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_3_Template(rf, ctx) { if (rf & 1) {
99
+ i0.ɵɵelement(0, "i", 59);
100
+ i0.ɵɵtext(1, " You ");
101
+ } }
102
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_4_Template(rf, ctx) { if (rf & 1) {
103
+ i0.ɵɵelement(0, "i", 60);
104
+ i0.ɵɵtext(1);
105
+ } if (rf & 2) {
106
+ const ctx_r1 = i0.ɵɵnextContext(4);
107
+ i0.ɵɵadvance();
108
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.getEntityName() || "Assistant", " ");
109
+ } }
110
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_7_Template(rf, ctx) { if (rf & 1) {
111
+ i0.ɵɵelementStart(0, "span", 50);
112
+ i0.ɵɵelement(1, "i", 34);
113
+ i0.ɵɵtext(2);
114
+ i0.ɵɵelementEnd();
115
+ } if (rf & 2) {
116
+ const message_r5 = i0.ɵɵnextContext().$implicit;
117
+ const ctx_r1 = i0.ɵɵnextContext(3);
118
+ i0.ɵɵadvance(2);
119
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.formatElapsedTime(message_r5.elapsedTime), " ");
120
+ } }
121
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_8_Template(rf, ctx) { if (rf & 1) {
122
+ i0.ɵɵelementStart(0, "span", 51);
123
+ i0.ɵɵtext(1);
124
+ i0.ɵɵelementEnd();
125
+ } if (rf & 2) {
126
+ const message_r5 = i0.ɵɵnextContext().$implicit;
127
+ const ctx_r1 = i0.ɵɵnextContext(3);
128
+ i0.ɵɵproperty("title", ctx_r1.getExecutionSummary(message_r5));
129
+ i0.ɵɵadvance();
130
+ i0.ɵɵtextInterpolate(ctx_r1.formatExecutionTime(message_r5.executionTime));
131
+ } }
132
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_9_Template(rf, ctx) { if (rf & 1) {
133
+ const _r6 = i0.ɵɵgetCurrentView();
134
+ i0.ɵɵelementStart(0, "button", 61);
135
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r6); const message_r5 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showRawJsonDialog(message_r5)); });
136
+ i0.ɵɵelement(1, "i", 62);
137
+ i0.ɵɵelementEnd();
138
+ } }
139
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_13_Template(rf, ctx) { if (rf & 1) {
140
+ i0.ɵɵelementStart(0, "div", 56)(1, "span", 63);
141
+ i0.ɵɵtext(2);
142
+ i0.ɵɵelementStart(3, "span", 64);
143
+ i0.ɵɵtext(4, "|");
144
+ i0.ɵɵelementEnd()()();
145
+ } if (rf & 2) {
146
+ const message_r5 = i0.ɵɵnextContext().$implicit;
147
+ i0.ɵɵadvance(2);
148
+ i0.ɵɵtextInterpolate(message_r5.streamingContent);
149
+ } }
150
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_14_Template(rf, ctx) { if (rf & 1) {
151
+ i0.ɵɵelement(0, "div", 57);
152
+ } if (rf & 2) {
153
+ const message_r5 = i0.ɵɵnextContext().$implicit;
154
+ const ctx_r1 = i0.ɵɵnextContext(3);
155
+ i0.ɵɵproperty("innerHTML", ctx_r1.getFormattedContent(message_r5), i0.ɵɵsanitizeHtml);
156
+ } }
157
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_15_Template(rf, ctx) { if (rf & 1) {
158
+ i0.ɵɵelementStart(0, "div", 58);
159
+ i0.ɵɵelement(1, "i", 65);
160
+ i0.ɵɵtext(2);
161
+ i0.ɵɵelementEnd();
162
+ } if (rf & 2) {
163
+ const message_r5 = i0.ɵɵnextContext().$implicit;
164
+ i0.ɵɵadvance(2);
165
+ i0.ɵɵtextInterpolate1(" ", message_r5.error, " ");
166
+ } }
167
+ function AITestHarnessComponent_div_0_Conditional_4_For_7_Template(rf, ctx) { if (rf & 1) {
168
+ const _r4 = i0.ɵɵgetCurrentView();
169
+ i0.ɵɵelementStart(0, "div", 29)(1, "div", 47)(2, "span", 48);
170
+ i0.ɵɵtemplate(3, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_3_Template, 2, 0)(4, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_4_Template, 2, 1);
171
+ i0.ɵɵelementEnd();
172
+ i0.ɵɵelementStart(5, "span", 49);
173
+ i0.ɵɵtext(6);
174
+ i0.ɵɵelementEnd();
175
+ i0.ɵɵtemplate(7, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_7_Template, 3, 1, "span", 50)(8, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_8_Template, 2, 2, "span", 51)(9, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_9_Template, 2, 0, "button", 52);
176
+ i0.ɵɵelementEnd();
177
+ i0.ɵɵelementStart(10, "div", 53)(11, "button", 54);
178
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_For_7_Template_button_click_11_listener() { const message_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.copyMessage(message_r5)); });
179
+ i0.ɵɵelement(12, "i", 55);
180
+ i0.ɵɵelementEnd();
181
+ i0.ɵɵtemplate(13, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_13_Template, 5, 1, "div", 56)(14, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_14_Template, 1, 1, "div", 57)(15, AITestHarnessComponent_div_0_Conditional_4_For_7_Conditional_15_Template, 3, 1, "div", 58);
182
+ i0.ɵɵelementEnd()();
183
+ } if (rf & 2) {
184
+ const message_r5 = ctx.$implicit;
185
+ const ctx_r1 = i0.ɵɵnextContext(3);
186
+ i0.ɵɵproperty("ngClass", ctx_r1.getMessageClass(message_r5));
187
+ i0.ɵɵadvance(3);
188
+ i0.ɵɵconditional(message_r5.role === "user" ? 3 : 4);
189
+ i0.ɵɵadvance(3);
190
+ i0.ɵɵtextInterpolate(ctx_r1.formatTimestamp(message_r5.timestamp));
191
+ i0.ɵɵadvance();
192
+ i0.ɵɵconditional(message_r5.isStreaming && message_r5.elapsedTime !== undefined ? 7 : message_r5.executionTime ? 8 : -1);
193
+ i0.ɵɵadvance(2);
194
+ i0.ɵɵconditional(ctx_r1.showRawToggle(message_r5) ? 9 : -1);
195
+ i0.ɵɵadvance(4);
196
+ i0.ɵɵconditional(message_r5.isStreaming ? 13 : 14);
197
+ i0.ɵɵadvance(2);
198
+ i0.ɵɵconditional(message_r5.error ? 15 : -1);
199
+ } }
200
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_13_Template(rf, ctx) { if (rf & 1) {
201
+ i0.ɵɵelement(0, "i", 34);
202
+ } }
203
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_14_Template(rf, ctx) { if (rf & 1) {
204
+ i0.ɵɵelement(0, "i", 35);
205
+ } }
206
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_18_Template(rf, ctx) { if (rf & 1) {
207
+ const _r7 = i0.ɵɵgetCurrentView();
208
+ i0.ɵɵelementStart(0, "button", 39);
209
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_18_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.selectTab("agentVariables")); });
210
+ i0.ɵɵelement(1, "i", 62);
211
+ i0.ɵɵtext(2, " Agent Variables ");
212
+ i0.ɵɵelementEnd();
213
+ i0.ɵɵelementStart(3, "button", 66);
214
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_18_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.selectTab("executionMonitor")); });
215
+ i0.ɵɵelement(4, "i", 67);
216
+ i0.ɵɵtext(5, " Execution Monitor ");
217
+ i0.ɵɵelementEnd();
218
+ } if (rf & 2) {
219
+ const ctx_r1 = i0.ɵɵnextContext(3);
220
+ i0.ɵɵclassProp("active", ctx_r1.activeTab === "agentVariables");
221
+ i0.ɵɵproperty("disabled", ctx_r1.executionMonitorMode === "live" && ctx_r1.activeTab !== "agentVariables");
222
+ i0.ɵɵadvance(3);
223
+ i0.ɵɵclassProp("active", ctx_r1.activeTab === "executionMonitor");
224
+ } }
225
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_19_Template(rf, ctx) { if (rf & 1) {
226
+ const _r8 = i0.ɵɵgetCurrentView();
227
+ i0.ɵɵelementStart(0, "button", 66);
228
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_19_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.selectTab("templateVariables")); });
229
+ i0.ɵɵelement(1, "i", 62);
230
+ i0.ɵɵtext(2, " Template Variables ");
231
+ i0.ɵɵelementEnd();
232
+ i0.ɵɵelementStart(3, "button", 66);
233
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_19_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.selectTab("modelSettings")); });
234
+ i0.ɵɵelement(4, "i", 68);
235
+ i0.ɵɵtext(5, " Model Settings ");
236
+ i0.ɵɵelementEnd();
237
+ } if (rf & 2) {
238
+ const ctx_r1 = i0.ɵɵnextContext(3);
239
+ i0.ɵɵclassProp("active", ctx_r1.activeTab === "templateVariables");
240
+ i0.ɵɵadvance(3);
241
+ i0.ɵɵclassProp("active", ctx_r1.activeTab === "modelSettings");
242
+ } }
243
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_23_Template(rf, ctx) { if (rf & 1) {
244
+ i0.ɵɵelementStart(0, "span", 41);
245
+ i0.ɵɵtext(1);
246
+ i0.ɵɵelementEnd();
247
+ } if (rf & 2) {
248
+ const ctx_r1 = i0.ɵɵnextContext(3);
249
+ i0.ɵɵadvance();
250
+ i0.ɵɵtextInterpolate(ctx_r1.savedConversations.length);
251
+ } }
252
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_Conditional_7_Template(rf, ctx) { if (rf & 1) {
253
+ i0.ɵɵelementStart(0, "div", 71)(1, "p");
254
+ i0.ɵɵtext(2, "No variables defined. Click \"Add Variable\" to get started.");
255
+ i0.ɵɵelementEnd()();
256
+ } }
257
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template(rf, ctx) { if (rf & 1) {
258
+ const _r10 = i0.ɵɵgetCurrentView();
259
+ i0.ɵɵelementStart(0, "div", 72)(1, "kendo-textbox", 75);
260
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template_kendo_textbox_ngModelChange_1_listener($event) { const variable_r11 = i0.ɵɵrestoreView(_r10).$implicit; i0.ɵɵtwoWayBindingSet(variable_r11.name, $event) || (variable_r11.name = $event); return i0.ɵɵresetView($event); });
261
+ i0.ɵɵelementEnd();
262
+ i0.ɵɵelementStart(2, "kendo-dropdownlist", 76);
263
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template_kendo_dropdownlist_ngModelChange_2_listener($event) { const variable_r11 = i0.ɵɵrestoreView(_r10).$implicit; i0.ɵɵtwoWayBindingSet(variable_r11.type, $event) || (variable_r11.type = $event); return i0.ɵɵresetView($event); });
264
+ i0.ɵɵelementEnd();
265
+ i0.ɵɵelementStart(3, "kendo-textbox", 77);
266
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template_kendo_textbox_ngModelChange_3_listener($event) { const variable_r11 = i0.ɵɵrestoreView(_r10).$implicit; i0.ɵɵtwoWayBindingSet(variable_r11.value, $event) || (variable_r11.value = $event); return i0.ɵɵresetView($event); });
267
+ i0.ɵɵelementEnd();
268
+ i0.ɵɵelementStart(4, "button", 78);
269
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template_button_click_4_listener() { const $index_r12 = i0.ɵɵrestoreView(_r10).$index; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.removeAgentVariable($index_r12)); });
270
+ i0.ɵɵelement(5, "i", 79);
271
+ i0.ɵɵelementEnd()();
272
+ } if (rf & 2) {
273
+ const variable_r11 = ctx.$implicit;
274
+ const ctx_r1 = i0.ɵɵnextContext(4);
275
+ i0.ɵɵadvance();
276
+ i0.ɵɵtwoWayProperty("ngModel", variable_r11.name);
277
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting);
278
+ i0.ɵɵadvance();
279
+ i0.ɵɵtwoWayProperty("ngModel", variable_r11.type);
280
+ i0.ɵɵproperty("data", i0.ɵɵpureFunction4(14, _c8, i0.ɵɵpureFunction0(10, _c4), i0.ɵɵpureFunction0(11, _c5), i0.ɵɵpureFunction0(12, _c6), i0.ɵɵpureFunction0(13, _c7)))("valuePrimitive", true)("disabled", ctx_r1.isExecuting);
281
+ i0.ɵɵadvance();
282
+ i0.ɵɵtwoWayProperty("ngModel", variable_r11.value);
283
+ i0.ɵɵproperty("placeholder", variable_r11.type === "object" ? "JSON value" : "Value")("disabled", ctx_r1.isExecuting);
284
+ i0.ɵɵadvance();
285
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting);
286
+ } }
287
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_Template(rf, ctx) { if (rf & 1) {
288
+ const _r9 = i0.ɵɵgetCurrentView();
289
+ i0.ɵɵelementStart(0, "div", 43)(1, "div", 69)(2, "h4");
290
+ i0.ɵɵtext(3, "Agent Variables");
291
+ i0.ɵɵelementEnd();
292
+ i0.ɵɵelementStart(4, "p");
293
+ i0.ɵɵtext(5, "Variables available during agent execution and template rendering");
294
+ i0.ɵɵelementEnd()();
295
+ i0.ɵɵelementStart(6, "div", 70);
296
+ i0.ɵɵtemplate(7, AITestHarnessComponent_div_0_Conditional_4_Conditional_25_Conditional_7_Template, 3, 0, "div", 71);
297
+ i0.ɵɵrepeaterCreate(8, AITestHarnessComponent_div_0_Conditional_4_Conditional_25_For_9_Template, 6, 19, "div", 72, i0.ɵɵrepeaterTrackByIndex);
298
+ i0.ɵɵelementEnd();
299
+ i0.ɵɵelementStart(10, "button", 73);
300
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_25_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.addAgentVariable()); });
301
+ i0.ɵɵelement(11, "i", 74);
302
+ i0.ɵɵtext(12, " Add Variable ");
303
+ i0.ɵɵelementEnd()();
304
+ } if (rf & 2) {
305
+ const ctx_r1 = i0.ɵɵnextContext(3);
306
+ i0.ɵɵadvance(7);
307
+ i0.ɵɵconditional(ctx_r1.agentVariables.length === 0 ? 7 : -1);
308
+ i0.ɵɵadvance();
309
+ i0.ɵɵrepeater(ctx_r1.agentVariables);
310
+ i0.ɵɵadvance(2);
311
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting);
312
+ } }
313
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_26_Template(rf, ctx) { if (rf & 1) {
314
+ i0.ɵɵelementStart(0, "div", 44);
315
+ i0.ɵɵelement(1, "mj-agent-execution-monitor", 80);
316
+ i0.ɵɵelementEnd();
317
+ } if (rf & 2) {
318
+ const ctx_r1 = i0.ɵɵnextContext(3);
319
+ i0.ɵɵadvance();
320
+ i0.ɵɵproperty("mode", ctx_r1.executionMonitorMode)("executionData", ctx_r1.currentExecutionData)("autoExpand", true);
321
+ } }
322
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template(rf, ctx) { if (rf & 1) {
323
+ const _r14 = i0.ɵɵgetCurrentView();
324
+ i0.ɵɵelementStart(0, "div", 72)(1, "kendo-textbox", 75);
325
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template_kendo_textbox_ngModelChange_1_listener($event) { const variable_r15 = i0.ɵɵrestoreView(_r14).$implicit; i0.ɵɵtwoWayBindingSet(variable_r15.name, $event) || (variable_r15.name = $event); return i0.ɵɵresetView($event); });
326
+ i0.ɵɵelementEnd();
327
+ i0.ɵɵelementStart(2, "kendo-dropdownlist", 76);
328
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template_kendo_dropdownlist_ngModelChange_2_listener($event) { const variable_r15 = i0.ɵɵrestoreView(_r14).$implicit; i0.ɵɵtwoWayBindingSet(variable_r15.type, $event) || (variable_r15.type = $event); return i0.ɵɵresetView($event); });
329
+ i0.ɵɵelementEnd();
330
+ i0.ɵɵelementStart(3, "kendo-textbox", 77);
331
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template_kendo_textbox_ngModelChange_3_listener($event) { const variable_r15 = i0.ɵɵrestoreView(_r14).$implicit; i0.ɵɵtwoWayBindingSet(variable_r15.value, $event) || (variable_r15.value = $event); return i0.ɵɵresetView($event); });
332
+ i0.ɵɵelementEnd();
333
+ i0.ɵɵelementStart(4, "button", 82);
334
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template_button_click_4_listener() { const $index_r16 = i0.ɵɵrestoreView(_r14).$index; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.removeTemplateVariable($index_r16)); });
335
+ i0.ɵɵelement(5, "i", 79);
336
+ i0.ɵɵelementEnd()();
337
+ } if (rf & 2) {
338
+ const variable_r15 = ctx.$implicit;
339
+ const ctx_r1 = i0.ɵɵnextContext(4);
340
+ i0.ɵɵadvance();
341
+ i0.ɵɵtwoWayProperty("ngModel", variable_r15.name);
342
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting);
343
+ i0.ɵɵadvance();
344
+ i0.ɵɵtwoWayProperty("ngModel", variable_r15.type);
345
+ i0.ɵɵproperty("data", i0.ɵɵpureFunction4(13, _c8, i0.ɵɵpureFunction0(9, _c4), i0.ɵɵpureFunction0(10, _c5), i0.ɵɵpureFunction0(11, _c6), i0.ɵɵpureFunction0(12, _c7)))("valuePrimitive", true)("disabled", ctx_r1.isExecuting);
346
+ i0.ɵɵadvance();
347
+ i0.ɵɵtwoWayProperty("ngModel", variable_r15.value);
348
+ i0.ɵɵproperty("placeholder", variable_r15.type === "object" ? "JSON value" : "Value")("disabled", ctx_r1.isExecuting);
349
+ } }
350
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_Template(rf, ctx) { if (rf & 1) {
351
+ const _r13 = i0.ɵɵgetCurrentView();
352
+ i0.ɵɵelementStart(0, "div", 43)(1, "div", 69)(2, "h4");
353
+ i0.ɵɵtext(3, "Template Variables");
354
+ i0.ɵɵelementEnd();
355
+ i0.ɵɵelementStart(4, "p");
356
+ i0.ɵɵtext(5, "Variables for prompt template rendering");
357
+ i0.ɵɵelementEnd()();
358
+ i0.ɵɵelementStart(6, "div", 70);
359
+ i0.ɵɵrepeaterCreate(7, AITestHarnessComponent_div_0_Conditional_4_Conditional_27_For_8_Template, 6, 18, "div", 72, i0.ɵɵrepeaterTrackByIndex);
360
+ i0.ɵɵelementEnd();
361
+ i0.ɵɵelementStart(9, "button", 81);
362
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_27_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r13); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.addTemplateVariable()); });
363
+ i0.ɵɵelement(10, "i", 74);
364
+ i0.ɵɵtext(11, " Add Variable ");
365
+ i0.ɵɵelementEnd()();
366
+ } if (rf & 2) {
367
+ const ctx_r1 = i0.ɵɵnextContext(3);
368
+ i0.ɵɵadvance(7);
369
+ i0.ɵɵrepeater(ctx_r1.templateVariables);
370
+ } }
371
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_18_Template(rf, ctx) { if (rf & 1) {
372
+ i0.ɵɵtext(0, " Using default vendor for the default model ");
373
+ } }
374
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_19_Template(rf, ctx) { if (rf & 1) {
375
+ i0.ɵɵtext(0, " Only one vendor available for this model ");
376
+ } }
377
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_20_Template(rf, ctx) { if (rf & 1) {
378
+ i0.ɵɵtext(0, " Select the inference provider for this model ");
379
+ } }
380
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_79_Template(rf, ctx) { if (rf & 1) {
381
+ const _r18 = i0.ɵɵgetCurrentView();
382
+ i0.ɵɵelementStart(0, "div", 86)(1, "label", 109);
383
+ i0.ɵɵtext(2, " Top Log Probs ");
384
+ i0.ɵɵelementEnd();
385
+ i0.ɵɵelementStart(3, "kendo-numerictextbox", 110);
386
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_79_Template_kendo_numerictextbox_ngModelChange_3_listener($event) { i0.ɵɵrestoreView(_r18); const ctx_r1 = i0.ɵɵnextContext(4); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.topLogProbs, $event) || (ctx_r1.advancedParams.topLogProbs = $event); return i0.ɵɵresetView($event); });
387
+ i0.ɵɵelementEnd()();
388
+ } if (rf & 2) {
389
+ const ctx_r1 = i0.ɵɵnextContext(4);
390
+ i0.ɵɵadvance(3);
391
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.topLogProbs);
392
+ i0.ɵɵproperty("min", 2)("max", 20)("step", 1)("format", "n0");
393
+ } }
394
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template(rf, ctx) { if (rf & 1) {
395
+ const _r17 = i0.ɵɵgetCurrentView();
396
+ i0.ɵɵelementStart(0, "div", 45)(1, "div", 69)(2, "h4");
397
+ i0.ɵɵtext(3, "Model Settings");
398
+ i0.ɵɵelementEnd();
399
+ i0.ɵɵelementStart(4, "p");
400
+ i0.ɵɵtext(5, "Configure AI model parameters");
401
+ i0.ɵɵelementEnd();
402
+ i0.ɵɵelementStart(6, "button", 83);
403
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.resetToPromptDefaults()); });
404
+ i0.ɵɵelement(7, "i", 84);
405
+ i0.ɵɵelementEnd()();
406
+ i0.ɵɵelementStart(8, "div", 85)(9, "div", 86)(10, "label");
407
+ i0.ɵɵtext(11, "AI Model");
408
+ i0.ɵɵelementEnd();
409
+ i0.ɵɵelementStart(12, "kendo-dropdownlist", 87);
410
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_dropdownlist_ngModelChange_12_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.selectedModelId, $event) || (ctx_r1.selectedModelId = $event); return i0.ɵɵresetView($event); });
411
+ i0.ɵɵlistener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_dropdownlist_ngModelChange_12_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.onModelSelectionChange()); });
412
+ i0.ɵɵelementEnd()();
413
+ i0.ɵɵelementStart(13, "div", 86)(14, "label");
414
+ i0.ɵɵtext(15, "AI Vendor");
415
+ i0.ɵɵelementEnd();
416
+ i0.ɵɵelementStart(16, "kendo-dropdownlist", 88);
417
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_dropdownlist_ngModelChange_16_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.selectedVendorId, $event) || (ctx_r1.selectedVendorId = $event); return i0.ɵɵresetView($event); });
418
+ i0.ɵɵelementEnd();
419
+ i0.ɵɵelementStart(17, "div", 89);
420
+ i0.ɵɵtemplate(18, AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_18_Template, 1, 0)(19, AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_19_Template, 1, 0)(20, AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_20_Template, 1, 0);
421
+ i0.ɵɵelementEnd()();
422
+ i0.ɵɵelementStart(21, "div", 86)(22, "label");
423
+ i0.ɵɵtext(23, "Response Format");
424
+ i0.ɵɵelementEnd();
425
+ i0.ɵɵelementStart(24, "kendo-dropdownlist", 90);
426
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_dropdownlist_ngModelChange_24_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.selectedResponseFormat, $event) || (ctx_r1.selectedResponseFormat = $event); return i0.ɵɵresetView($event); });
427
+ i0.ɵɵelementEnd();
428
+ i0.ɵɵelementStart(25, "div", 89);
429
+ i0.ɵɵtext(26, " Expected format for AI response (defaults to prompt setting) ");
430
+ i0.ɵɵelementEnd()();
431
+ i0.ɵɵelementStart(27, "div", 86)(28, "label");
432
+ i0.ɵɵtext(29, "Max Tokens");
433
+ i0.ɵɵelementEnd();
434
+ i0.ɵɵelementStart(30, "kendo-numerictextbox", 91);
435
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_30_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.maxTokens, $event) || (ctx_r1.maxTokens = $event); return i0.ɵɵresetView($event); });
436
+ i0.ɵɵelementEnd();
437
+ i0.ɵɵelementStart(31, "div", 89);
438
+ i0.ɵɵtext(32, " Maximum length of generated response ");
439
+ i0.ɵɵelementEnd()();
440
+ i0.ɵɵelementStart(33, "div", 86)(34, "label", 92)(35, "input", 93);
441
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_input_ngModelChange_35_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.skipValidation, $event) || (ctx_r1.skipValidation = $event); return i0.ɵɵresetView($event); });
442
+ i0.ɵɵelementEnd();
443
+ i0.ɵɵtext(36, " Skip Validation ");
444
+ i0.ɵɵelementEnd();
445
+ i0.ɵɵelementStart(37, "div", 89);
446
+ i0.ɵɵtext(38, " Skip output validation during testing ");
447
+ i0.ɵɵelementEnd()();
448
+ i0.ɵɵelementStart(39, "kendo-expansionpanel", 94);
449
+ i0.ɵɵlistener("action", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_expansionpanel_action_39_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleAdvancedParams()); });
450
+ i0.ɵɵelementStart(40, "div", 95)(41, "div", 86)(42, "label", 96);
451
+ i0.ɵɵtext(43, " Temperature ");
452
+ i0.ɵɵelementEnd();
453
+ i0.ɵɵelementStart(44, "kendo-numerictextbox", 97);
454
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_44_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.temperature, $event) || (ctx_r1.advancedParams.temperature = $event); return i0.ɵɵresetView($event); });
455
+ i0.ɵɵelementEnd()();
456
+ i0.ɵɵelementStart(45, "div", 86)(46, "label", 98);
457
+ i0.ɵɵtext(47, " Top P ");
458
+ i0.ɵɵelementEnd();
459
+ i0.ɵɵelementStart(48, "kendo-numerictextbox", 97);
460
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_48_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.topP, $event) || (ctx_r1.advancedParams.topP = $event); return i0.ɵɵresetView($event); });
461
+ i0.ɵɵelementEnd()();
462
+ i0.ɵɵelementStart(49, "div", 86)(50, "label", 99);
463
+ i0.ɵɵtext(51, " Top K ");
464
+ i0.ɵɵelementEnd();
465
+ i0.ɵɵelementStart(52, "kendo-numerictextbox", 97);
466
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_52_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.topK, $event) || (ctx_r1.advancedParams.topK = $event); return i0.ɵɵresetView($event); });
467
+ i0.ɵɵelementEnd()();
468
+ i0.ɵɵelementStart(53, "div", 86)(54, "label", 100);
469
+ i0.ɵɵtext(55, " Min P ");
470
+ i0.ɵɵelementEnd();
471
+ i0.ɵɵelementStart(56, "kendo-numerictextbox", 97);
472
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_56_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.minP, $event) || (ctx_r1.advancedParams.minP = $event); return i0.ɵɵresetView($event); });
473
+ i0.ɵɵelementEnd()();
474
+ i0.ɵɵelementStart(57, "div", 86)(58, "label", 101);
475
+ i0.ɵɵtext(59, " Frequency Penalty ");
476
+ i0.ɵɵelementEnd();
477
+ i0.ɵɵelementStart(60, "kendo-numerictextbox", 97);
478
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_60_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.frequencyPenalty, $event) || (ctx_r1.advancedParams.frequencyPenalty = $event); return i0.ɵɵresetView($event); });
479
+ i0.ɵɵelementEnd()();
480
+ i0.ɵɵelementStart(61, "div", 86)(62, "label", 102);
481
+ i0.ɵɵtext(63, " Presence Penalty ");
482
+ i0.ɵɵelementEnd();
483
+ i0.ɵɵelementStart(64, "kendo-numerictextbox", 97);
484
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_64_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.presencePenalty, $event) || (ctx_r1.advancedParams.presencePenalty = $event); return i0.ɵɵresetView($event); });
485
+ i0.ɵɵelementEnd()();
486
+ i0.ɵɵelementStart(65, "div", 86)(66, "label", 103);
487
+ i0.ɵɵtext(67, " Seed ");
488
+ i0.ɵɵelementEnd();
489
+ i0.ɵɵelementStart(68, "kendo-numerictextbox", 104);
490
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_numerictextbox_ngModelChange_68_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.seed, $event) || (ctx_r1.advancedParams.seed = $event); return i0.ɵɵresetView($event); });
491
+ i0.ɵɵelementEnd()();
492
+ i0.ɵɵelementStart(69, "div", 86)(70, "label", 105);
493
+ i0.ɵɵtext(71, " Stop Sequences ");
494
+ i0.ɵɵelementEnd();
495
+ i0.ɵɵelementStart(72, "kendo-textarea", 106);
496
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_textarea_ngModelChange_72_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.stopSequencesText, $event) || (ctx_r1.stopSequencesText = $event); return i0.ɵɵresetView($event); });
497
+ i0.ɵɵlistener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_textarea_ngModelChange_72_listener() { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.updateStopSequences()); });
498
+ i0.ɵɵelementEnd();
499
+ i0.ɵɵelementStart(73, "div", 89);
500
+ i0.ɵɵtext(74, " Separate multiple sequences with commas ");
501
+ i0.ɵɵelementEnd()();
502
+ i0.ɵɵelementStart(75, "div", 86)(76, "label", 107);
503
+ i0.ɵɵtext(77, " Include Log Probs ");
504
+ i0.ɵɵelementEnd();
505
+ i0.ɵɵelementStart(78, "kendo-switch", 108);
506
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template_kendo_switch_ngModelChange_78_listener($event) { i0.ɵɵrestoreView(_r17); const ctx_r1 = i0.ɵɵnextContext(3); i0.ɵɵtwoWayBindingSet(ctx_r1.advancedParams.includeLogProbs, $event) || (ctx_r1.advancedParams.includeLogProbs = $event); return i0.ɵɵresetView($event); });
507
+ i0.ɵɵelementEnd()();
508
+ i0.ɵɵtemplate(79, AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Conditional_79_Template, 4, 5, "div", 86);
509
+ i0.ɵɵelementEnd()()()();
510
+ } if (rf & 2) {
511
+ const ctx_r1 = i0.ɵɵnextContext(3);
512
+ i0.ɵɵadvance(12);
513
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.selectedModelId);
514
+ i0.ɵɵproperty("data", ctx_r1.availableModels)("valuePrimitive", true);
515
+ i0.ɵɵadvance(4);
516
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.selectedVendorId);
517
+ i0.ɵɵproperty("data", ctx_r1.availableVendors)("valuePrimitive", true)("disabled", ctx_r1.availableVendors.length <= 1 || !ctx_r1.selectedModelId);
518
+ i0.ɵɵadvance(2);
519
+ i0.ɵɵconditional(!ctx_r1.selectedModelId ? 18 : ctx_r1.availableVendors.length <= 1 ? 19 : 20);
520
+ i0.ɵɵadvance(6);
521
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.selectedResponseFormat);
522
+ i0.ɵɵproperty("data", ctx_r1.responseFormatOptions);
523
+ i0.ɵɵadvance(6);
524
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.maxTokens);
525
+ i0.ɵɵproperty("min", 100)("max", 8000)("step", 100);
526
+ i0.ɵɵadvance(5);
527
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.skipValidation);
528
+ i0.ɵɵadvance(4);
529
+ i0.ɵɵproperty("expanded", ctx_r1.advancedParamsExpanded);
530
+ i0.ɵɵadvance(5);
531
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.temperature);
532
+ i0.ɵɵproperty("min", 0)("max", 2)("step", 0.1)("format", "n1");
533
+ i0.ɵɵadvance(4);
534
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.topP);
535
+ i0.ɵɵproperty("min", 0)("max", 1)("step", 0.01)("format", "n2");
536
+ i0.ɵɵadvance(4);
537
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.topK);
538
+ i0.ɵɵproperty("min", 1)("max", 100)("step", 1)("format", "n0");
539
+ i0.ɵɵadvance(4);
540
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.minP);
541
+ i0.ɵɵproperty("min", 0)("max", 1)("step", 0.01)("format", "n2");
542
+ i0.ɵɵadvance(4);
543
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.frequencyPenalty);
544
+ i0.ɵɵproperty("min", -2)("max", 2)("step", 0.1)("format", "n1");
545
+ i0.ɵɵadvance(4);
546
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.presencePenalty);
547
+ i0.ɵɵproperty("min", -2)("max", 2)("step", 0.1)("format", "n1");
548
+ i0.ɵɵadvance(4);
549
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.seed);
550
+ i0.ɵɵproperty("format", "n0");
551
+ i0.ɵɵadvance(4);
552
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.stopSequencesText);
553
+ i0.ɵɵproperty("rows", 2);
554
+ i0.ɵɵadvance(6);
555
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.advancedParams.includeLogProbs);
556
+ i0.ɵɵadvance();
557
+ i0.ɵɵconditional(ctx_r1.advancedParams.includeLogProbs ? 79 : -1);
558
+ } }
559
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Conditional_6_Template(rf, ctx) { if (rf & 1) {
560
+ const _r19 = i0.ɵɵgetCurrentView();
561
+ i0.ɵɵelementStart(0, "button", 115);
562
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Conditional_6_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r19); const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.newConversation()); });
563
+ i0.ɵɵelement(1, "i", 74);
564
+ i0.ɵɵtext(2, " New ");
565
+ i0.ɵɵelementEnd();
566
+ } }
567
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Conditional_8_Template(rf, ctx) { if (rf & 1) {
568
+ i0.ɵɵelementStart(0, "div", 113);
569
+ i0.ɵɵelement(1, "i", 116);
570
+ i0.ɵɵelementStart(2, "p");
571
+ i0.ɵɵtext(3, "No saved conversations");
572
+ i0.ɵɵelementEnd()();
573
+ } }
574
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_For_10_Template(rf, ctx) { if (rf & 1) {
575
+ const _r20 = i0.ɵɵgetCurrentView();
576
+ i0.ɵɵelementStart(0, "div", 117);
577
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_For_10_Template_div_click_0_listener() { const conv_r21 = i0.ɵɵrestoreView(_r20).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.loadConversation(conv_r21)); });
578
+ i0.ɵɵelementStart(1, "div", 118)(2, "h5");
579
+ i0.ɵɵtext(3);
580
+ i0.ɵɵelementEnd();
581
+ i0.ɵɵelementStart(4, "p");
582
+ i0.ɵɵtext(5);
583
+ i0.ɵɵelementEnd();
584
+ i0.ɵɵelementStart(6, "span", 119);
585
+ i0.ɵɵtext(7);
586
+ i0.ɵɵpipe(8, "date");
587
+ i0.ɵɵelementEnd()();
588
+ i0.ɵɵelementStart(9, "button", 120);
589
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_For_10_Template_button_click_9_listener($event) { const conv_r21 = i0.ɵɵrestoreView(_r20).$implicit; const ctx_r1 = i0.ɵɵnextContext(4); return i0.ɵɵresetView(ctx_r1.deleteConversation(conv_r21, $event)); });
590
+ i0.ɵɵelement(10, "i", 121);
591
+ i0.ɵɵelementEnd()();
592
+ } if (rf & 2) {
593
+ const conv_r21 = ctx.$implicit;
594
+ const ctx_r1 = i0.ɵɵnextContext(4);
595
+ i0.ɵɵclassProp("current", conv_r21.id === ctx_r1.currentConversationId);
596
+ i0.ɵɵadvance(3);
597
+ i0.ɵɵtextInterpolate(conv_r21.name);
598
+ i0.ɵɵadvance(2);
599
+ i0.ɵɵtextInterpolate1("", conv_r21.messages.length, " messages");
600
+ i0.ɵɵadvance(2);
601
+ i0.ɵɵtextInterpolate(i0.ɵɵpipeBind2(8, 5, conv_r21.updatedAt, "short"));
602
+ } }
603
+ function AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Template(rf, ctx) { if (rf & 1) {
604
+ i0.ɵɵelementStart(0, "div", 43)(1, "div", 69)(2, "h4");
605
+ i0.ɵɵtext(3, "Saved Conversations");
606
+ i0.ɵɵelementEnd();
607
+ i0.ɵɵelementStart(4, "p");
608
+ i0.ɵɵtext(5, "Previously saved test sessions");
609
+ i0.ɵɵelementEnd();
610
+ i0.ɵɵtemplate(6, AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Conditional_6_Template, 3, 0, "button", 111);
611
+ i0.ɵɵelementEnd();
612
+ i0.ɵɵelementStart(7, "div", 112);
613
+ i0.ɵɵtemplate(8, AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Conditional_8_Template, 4, 0, "div", 113);
614
+ i0.ɵɵrepeaterCreate(9, AITestHarnessComponent_div_0_Conditional_4_Conditional_29_For_10_Template, 11, 8, "div", 114, _forTrack0);
615
+ i0.ɵɵelementEnd()();
616
+ } if (rf & 2) {
617
+ const ctx_r1 = i0.ɵɵnextContext(3);
618
+ i0.ɵɵadvance(6);
619
+ i0.ɵɵconditional(ctx_r1.currentConversationId ? 6 : -1);
620
+ i0.ɵɵadvance(2);
621
+ i0.ɵɵconditional(ctx_r1.savedConversations.length === 0 ? 8 : -1);
622
+ i0.ɵɵadvance();
623
+ i0.ɵɵrepeater(ctx_r1.savedConversations);
624
+ } }
625
+ function AITestHarnessComponent_div_0_Conditional_4_Template(rf, ctx) { if (rf & 1) {
626
+ const _r3 = i0.ɵɵgetCurrentView();
627
+ i0.ɵɵelementStart(0, "kendo-splitter", 11)(1, "kendo-splitter-pane", 26)(2, "div", 12)(3, "div", 27, 1);
628
+ i0.ɵɵtemplate(5, AITestHarnessComponent_div_0_Conditional_4_Conditional_5_Template, 6, 0, "div", 28);
629
+ i0.ɵɵrepeaterCreate(6, AITestHarnessComponent_div_0_Conditional_4_For_7_Template, 16, 7, "div", 29, _forTrack0);
630
+ i0.ɵɵelementEnd();
631
+ i0.ɵɵelementStart(8, "div", 30)(9, "div", 31)(10, "kendo-textarea", 32, 2);
632
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_4_Template_kendo_textarea_ngModelChange_10_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.currentUserMessage, $event) || (ctx_r1.currentUserMessage = $event); return i0.ɵɵresetView($event); });
633
+ i0.ɵɵlistener("keypress", function AITestHarnessComponent_div_0_Conditional_4_Template_kendo_textarea_keypress_10_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.handleKeyPress($event)); });
634
+ i0.ɵɵelementEnd();
635
+ i0.ɵɵelementStart(12, "button", 33);
636
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Template_button_click_12_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.sendMessage()); });
637
+ i0.ɵɵtemplate(13, AITestHarnessComponent_div_0_Conditional_4_Conditional_13_Template, 1, 0, "i", 34)(14, AITestHarnessComponent_div_0_Conditional_4_Conditional_14_Template, 1, 0, "i", 35);
638
+ i0.ɵɵelementEnd()()()()();
639
+ i0.ɵɵelementStart(15, "kendo-splitter-pane", 36)(16, "div", 37)(17, "div", 38);
640
+ i0.ɵɵtemplate(18, AITestHarnessComponent_div_0_Conditional_4_Conditional_18_Template, 6, 5)(19, AITestHarnessComponent_div_0_Conditional_4_Conditional_19_Template, 6, 4);
641
+ i0.ɵɵelementStart(20, "button", 39);
642
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_4_Template_button_click_20_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.selectTab("savedConversations")); });
643
+ i0.ɵɵelement(21, "i", 40);
644
+ i0.ɵɵtext(22, " Saved ");
645
+ i0.ɵɵtemplate(23, AITestHarnessComponent_div_0_Conditional_4_Conditional_23_Template, 2, 1, "span", 41);
646
+ i0.ɵɵelementEnd()();
647
+ i0.ɵɵelementStart(24, "div", 42);
648
+ i0.ɵɵtemplate(25, AITestHarnessComponent_div_0_Conditional_4_Conditional_25_Template, 13, 2, "div", 43)(26, AITestHarnessComponent_div_0_Conditional_4_Conditional_26_Template, 2, 3, "div", 44)(27, AITestHarnessComponent_div_0_Conditional_4_Conditional_27_Template, 12, 0, "div", 43)(28, AITestHarnessComponent_div_0_Conditional_4_Conditional_28_Template, 80, 52, "div", 45)(29, AITestHarnessComponent_div_0_Conditional_4_Conditional_29_Template, 11, 2, "div", 43);
649
+ i0.ɵɵelementEnd()()()();
650
+ } if (rf & 2) {
651
+ const ctx_r1 = i0.ɵɵnextContext(2);
652
+ i0.ɵɵadvance();
653
+ i0.ɵɵproperty("collapsible", false)("resizable", true)("min", "400px");
654
+ i0.ɵɵadvance(4);
655
+ i0.ɵɵconditional(ctx_r1.conversationMessages.length === 0 ? 5 : -1);
656
+ i0.ɵɵadvance();
657
+ i0.ɵɵrepeater(ctx_r1.conversationMessages);
658
+ i0.ɵɵadvance(4);
659
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.currentUserMessage);
660
+ i0.ɵɵproperty("rows", 3)("disabled", ctx_r1.isExecuting);
661
+ i0.ɵɵadvance(2);
662
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting || !ctx_r1.currentUserMessage.trim());
663
+ i0.ɵɵadvance();
664
+ i0.ɵɵconditional(ctx_r1.isExecuting ? 13 : 14);
665
+ i0.ɵɵadvance(2);
666
+ i0.ɵɵproperty("collapsible", false)("resizable", true)("size", "30%")("min", "300px");
667
+ i0.ɵɵadvance(3);
668
+ i0.ɵɵconditional(ctx_r1.mode === "agent" ? 18 : 19);
669
+ i0.ɵɵadvance(2);
670
+ i0.ɵɵclassProp("active", ctx_r1.activeTab === "savedConversations");
671
+ i0.ɵɵproperty("disabled", ctx_r1.executionMonitorMode === "live" && ctx_r1.activeTab !== "savedConversations");
672
+ i0.ɵɵadvance(3);
673
+ i0.ɵɵconditional(ctx_r1.savedConversations.length > 0 ? 23 : -1);
674
+ i0.ɵɵadvance(2);
675
+ i0.ɵɵconditional(ctx_r1.activeTab === "agentVariables" ? 25 : -1);
676
+ i0.ɵɵadvance();
677
+ i0.ɵɵconditional(ctx_r1.activeTab === "executionMonitor" ? 26 : -1);
678
+ i0.ɵɵadvance();
679
+ i0.ɵɵconditional(ctx_r1.activeTab === "templateVariables" ? 27 : -1);
680
+ i0.ɵɵadvance();
681
+ i0.ɵɵconditional(ctx_r1.activeTab === "modelSettings" ? 28 : -1);
682
+ i0.ɵɵadvance();
683
+ i0.ɵɵconditional(ctx_r1.activeTab === "savedConversations" ? 29 : -1);
684
+ } }
685
+ function AITestHarnessComponent_div_0_Conditional_5_Conditional_3_Template(rf, ctx) { if (rf & 1) {
686
+ i0.ɵɵelementStart(0, "div", 28);
687
+ i0.ɵɵelement(1, "i", 46);
688
+ i0.ɵɵelementStart(2, "h4");
689
+ i0.ɵɵtext(3, "Start a conversation");
690
+ i0.ɵɵelementEnd();
691
+ i0.ɵɵelementStart(4, "p");
692
+ i0.ɵɵtext(5, "Send a message to begin testing the AI agent");
693
+ i0.ɵɵelementEnd()();
694
+ } }
695
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_3_Template(rf, ctx) { if (rf & 1) {
696
+ i0.ɵɵelement(0, "i", 59);
697
+ i0.ɵɵtext(1, " You ");
698
+ } }
699
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_4_Template(rf, ctx) { if (rf & 1) {
700
+ i0.ɵɵelement(0, "i", 60);
701
+ i0.ɵɵtext(1);
702
+ } if (rf & 2) {
703
+ const ctx_r1 = i0.ɵɵnextContext(4);
704
+ i0.ɵɵadvance();
705
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.getEntityName() || "Assistant", " ");
706
+ } }
707
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_7_Template(rf, ctx) { if (rf & 1) {
708
+ i0.ɵɵelementStart(0, "span", 50);
709
+ i0.ɵɵelement(1, "i", 34);
710
+ i0.ɵɵtext(2);
711
+ i0.ɵɵelementEnd();
712
+ } if (rf & 2) {
713
+ const message_r24 = i0.ɵɵnextContext().$implicit;
714
+ const ctx_r1 = i0.ɵɵnextContext(3);
715
+ i0.ɵɵadvance(2);
716
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.formatElapsedTime(message_r24.elapsedTime), " ");
717
+ } }
718
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_8_Template(rf, ctx) { if (rf & 1) {
719
+ i0.ɵɵelementStart(0, "span", 51);
720
+ i0.ɵɵtext(1);
721
+ i0.ɵɵelementEnd();
722
+ } if (rf & 2) {
723
+ const message_r24 = i0.ɵɵnextContext().$implicit;
724
+ const ctx_r1 = i0.ɵɵnextContext(3);
725
+ i0.ɵɵproperty("title", ctx_r1.getExecutionSummary(message_r24));
726
+ i0.ɵɵadvance();
727
+ i0.ɵɵtextInterpolate(ctx_r1.formatExecutionTime(message_r24.executionTime));
728
+ } }
729
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_9_Template(rf, ctx) { if (rf & 1) {
730
+ const _r25 = i0.ɵɵgetCurrentView();
731
+ i0.ɵɵelementStart(0, "button", 61);
732
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_9_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r25); const message_r24 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showRawJsonDialog(message_r24)); });
733
+ i0.ɵɵelement(1, "i", 62);
734
+ i0.ɵɵelementEnd();
735
+ } }
736
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_10_Template(rf, ctx) { if (rf & 1) {
737
+ const _r26 = i0.ɵɵgetCurrentView();
738
+ i0.ɵɵelementStart(0, "button", 123);
739
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_10_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r26); const message_r24 = i0.ɵɵnextContext().$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.showMessageExecutionHistory(message_r24)); });
740
+ i0.ɵɵelement(1, "i", 67);
741
+ i0.ɵɵelementEnd();
742
+ } }
743
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_14_Template(rf, ctx) { if (rf & 1) {
744
+ i0.ɵɵelementStart(0, "div", 56)(1, "span", 63);
745
+ i0.ɵɵtext(2);
746
+ i0.ɵɵelementStart(3, "span", 64);
747
+ i0.ɵɵtext(4, "|");
748
+ i0.ɵɵelementEnd()()();
749
+ } if (rf & 2) {
750
+ const message_r24 = i0.ɵɵnextContext().$implicit;
751
+ i0.ɵɵadvance(2);
752
+ i0.ɵɵtextInterpolate(message_r24.streamingContent);
753
+ } }
754
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_15_Template(rf, ctx) { if (rf & 1) {
755
+ i0.ɵɵelement(0, "div", 57);
756
+ } if (rf & 2) {
757
+ const message_r24 = i0.ɵɵnextContext().$implicit;
758
+ const ctx_r1 = i0.ɵɵnextContext(3);
759
+ i0.ɵɵproperty("innerHTML", ctx_r1.getFormattedContent(message_r24), i0.ɵɵsanitizeHtml);
760
+ } }
761
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_16_Template(rf, ctx) { if (rf & 1) {
762
+ i0.ɵɵelementStart(0, "div", 58);
763
+ i0.ɵɵelement(1, "i", 65);
764
+ i0.ɵɵtext(2);
765
+ i0.ɵɵelementEnd();
766
+ } if (rf & 2) {
767
+ const message_r24 = i0.ɵɵnextContext().$implicit;
768
+ i0.ɵɵadvance(2);
769
+ i0.ɵɵtextInterpolate1(" ", message_r24.error, " ");
770
+ } }
771
+ function AITestHarnessComponent_div_0_Conditional_5_For_5_Template(rf, ctx) { if (rf & 1) {
772
+ const _r23 = i0.ɵɵgetCurrentView();
773
+ i0.ɵɵelementStart(0, "div", 29)(1, "div", 47)(2, "span", 48);
774
+ i0.ɵɵtemplate(3, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_3_Template, 2, 0)(4, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_4_Template, 2, 1);
775
+ i0.ɵɵelementEnd();
776
+ i0.ɵɵelementStart(5, "span", 49);
777
+ i0.ɵɵtext(6);
778
+ i0.ɵɵelementEnd();
779
+ i0.ɵɵtemplate(7, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_7_Template, 3, 1, "span", 50)(8, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_8_Template, 2, 2, "span", 51)(9, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_9_Template, 2, 0, "button", 52)(10, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_10_Template, 2, 0, "button", 122);
780
+ i0.ɵɵelementEnd();
781
+ i0.ɵɵelementStart(11, "div", 53)(12, "button", 54);
782
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_5_For_5_Template_button_click_12_listener() { const message_r24 = i0.ɵɵrestoreView(_r23).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.copyMessage(message_r24)); });
783
+ i0.ɵɵelement(13, "i", 55);
784
+ i0.ɵɵelementEnd();
785
+ i0.ɵɵtemplate(14, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_14_Template, 5, 1, "div", 56)(15, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_15_Template, 1, 1, "div", 57)(16, AITestHarnessComponent_div_0_Conditional_5_For_5_Conditional_16_Template, 3, 1, "div", 58);
786
+ i0.ɵɵelementEnd()();
787
+ } if (rf & 2) {
788
+ const message_r24 = ctx.$implicit;
789
+ const ctx_r1 = i0.ɵɵnextContext(3);
790
+ i0.ɵɵproperty("ngClass", ctx_r1.getMessageClass(message_r24));
791
+ i0.ɵɵadvance(3);
792
+ i0.ɵɵconditional(message_r24.role === "user" ? 3 : 4);
793
+ i0.ɵɵadvance(3);
794
+ i0.ɵɵtextInterpolate(ctx_r1.formatTimestamp(message_r24.timestamp));
795
+ i0.ɵɵadvance();
796
+ i0.ɵɵconditional(message_r24.isStreaming && message_r24.elapsedTime !== undefined ? 7 : message_r24.executionTime ? 8 : -1);
797
+ i0.ɵɵadvance(2);
798
+ i0.ɵɵconditional(ctx_r1.showRawToggle(message_r24) ? 9 : -1);
799
+ i0.ɵɵadvance();
800
+ i0.ɵɵconditional(message_r24.executionData && ctx_r1.mode === "agent" && message_r24.role === "assistant" ? 10 : -1);
801
+ i0.ɵɵadvance(4);
802
+ i0.ɵɵconditional(message_r24.isStreaming ? 14 : 15);
803
+ i0.ɵɵadvance(2);
804
+ i0.ɵɵconditional(message_r24.error ? 16 : -1);
805
+ } }
806
+ function AITestHarnessComponent_div_0_Conditional_5_Conditional_11_Template(rf, ctx) { if (rf & 1) {
807
+ i0.ɵɵelement(0, "i", 34);
808
+ } }
809
+ function AITestHarnessComponent_div_0_Conditional_5_Conditional_12_Template(rf, ctx) { if (rf & 1) {
810
+ i0.ɵɵelement(0, "i", 35);
811
+ } }
812
+ function AITestHarnessComponent_div_0_Conditional_5_Template(rf, ctx) { if (rf & 1) {
813
+ const _r22 = i0.ɵɵgetCurrentView();
814
+ i0.ɵɵelementStart(0, "div", 12)(1, "div", 27, 1);
815
+ i0.ɵɵtemplate(3, AITestHarnessComponent_div_0_Conditional_5_Conditional_3_Template, 6, 0, "div", 28);
816
+ i0.ɵɵrepeaterCreate(4, AITestHarnessComponent_div_0_Conditional_5_For_5_Template, 17, 8, "div", 29, _forTrack0);
817
+ i0.ɵɵelementEnd();
818
+ i0.ɵɵelementStart(6, "div", 30)(7, "div", 31)(8, "kendo-textarea", 32, 2);
819
+ i0.ɵɵtwoWayListener("ngModelChange", function AITestHarnessComponent_div_0_Conditional_5_Template_kendo_textarea_ngModelChange_8_listener($event) { i0.ɵɵrestoreView(_r22); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.currentUserMessage, $event) || (ctx_r1.currentUserMessage = $event); return i0.ɵɵresetView($event); });
820
+ i0.ɵɵlistener("keypress", function AITestHarnessComponent_div_0_Conditional_5_Template_kendo_textarea_keypress_8_listener($event) { i0.ɵɵrestoreView(_r22); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.handleKeyPress($event)); });
821
+ i0.ɵɵelementEnd();
822
+ i0.ɵɵelementStart(10, "button", 33);
823
+ i0.ɵɵlistener("click", function AITestHarnessComponent_div_0_Conditional_5_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r22); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.sendMessage()); });
824
+ i0.ɵɵtemplate(11, AITestHarnessComponent_div_0_Conditional_5_Conditional_11_Template, 1, 0, "i", 34)(12, AITestHarnessComponent_div_0_Conditional_5_Conditional_12_Template, 1, 0, "i", 35);
825
+ i0.ɵɵelementEnd()()()();
826
+ } if (rf & 2) {
827
+ const ctx_r1 = i0.ɵɵnextContext(2);
828
+ i0.ɵɵadvance(3);
829
+ i0.ɵɵconditional(ctx_r1.conversationMessages.length === 0 ? 3 : -1);
830
+ i0.ɵɵadvance();
831
+ i0.ɵɵrepeater(ctx_r1.conversationMessages);
832
+ i0.ɵɵadvance(4);
833
+ i0.ɵɵtwoWayProperty("ngModel", ctx_r1.currentUserMessage);
834
+ i0.ɵɵproperty("rows", 3)("disabled", ctx_r1.isExecuting);
835
+ i0.ɵɵadvance(2);
836
+ i0.ɵɵproperty("disabled", ctx_r1.isExecuting || !ctx_r1.currentUserMessage.trim());
837
+ i0.ɵɵadvance();
838
+ i0.ɵɵconditional(ctx_r1.isExecuting ? 11 : 12);
839
+ } }
840
+ function AITestHarnessComponent_div_0_Template(rf, ctx) { if (rf & 1) {
841
+ i0.ɵɵelementStart(0, "div", 7)(1, "div", 8);
842
+ i0.ɵɵtemplate(2, AITestHarnessComponent_div_0_Conditional_2_Template, 24, 13, "div", 9);
843
+ i0.ɵɵelementEnd();
844
+ i0.ɵɵelementStart(3, "div", 10);
845
+ i0.ɵɵtemplate(4, AITestHarnessComponent_div_0_Conditional_4_Template, 30, 23, "kendo-splitter", 11)(5, AITestHarnessComponent_div_0_Conditional_5_Template, 13, 6, "div", 12);
846
+ i0.ɵɵelementEnd()();
847
+ } if (rf & 2) {
848
+ const ctx_r1 = i0.ɵɵnextContext();
849
+ i0.ɵɵadvance(2);
850
+ i0.ɵɵconditional(ctx_r1.entity ? 2 : -1);
851
+ i0.ɵɵadvance(2);
852
+ i0.ɵɵconditional(ctx_r1.showSidebar ? 4 : 5);
853
+ } }
854
+ function AITestHarnessComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
855
+ const _r27 = i0.ɵɵgetCurrentView();
856
+ i0.ɵɵelementStart(0, "kendo-dialog", 124);
857
+ i0.ɵɵlistener("close", function AITestHarnessComponent_Conditional_1_Template_kendo_dialog_close_0_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelSaveDialog()); });
858
+ i0.ɵɵelementStart(1, "div", 125)(2, "p");
859
+ i0.ɵɵtext(3, "Enter a name for this conversation:");
860
+ i0.ɵɵelementEnd();
861
+ i0.ɵɵelementStart(4, "input", 126, 3);
862
+ i0.ɵɵlistener("keyup", function AITestHarnessComponent_Conditional_1_Template_input_keyup_4_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.updateTempConversation()); });
863
+ i0.ɵɵelementEnd()();
864
+ i0.ɵɵelementStart(6, "kendo-dialog-actions")(7, "button", 127);
865
+ i0.ɵɵlistener("click", function AITestHarnessComponent_Conditional_1_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelSaveDialog()); });
866
+ i0.ɵɵtext(8, "Cancel");
867
+ i0.ɵɵelementEnd();
868
+ i0.ɵɵelementStart(9, "button", 128);
869
+ i0.ɵɵlistener("click", function AITestHarnessComponent_Conditional_1_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r27); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.confirmSaveConversation()); });
870
+ i0.ɵɵtext(10, " Save ");
871
+ i0.ɵɵelementEnd()()();
872
+ } if (rf & 2) {
873
+ const ctx_r1 = i0.ɵɵnextContext();
874
+ i0.ɵɵproperty("title", "Save Conversation")("width", 400);
875
+ i0.ɵɵadvance(9);
876
+ i0.ɵɵproperty("primary", true)("disabled", ctx_r1.tempConversationName === "");
877
+ } }
878
+ function AITestHarnessComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
879
+ const _r28 = i0.ɵɵgetCurrentView();
880
+ i0.ɵɵelementStart(0, "kendo-dialog", 129);
881
+ i0.ɵɵlistener("close", function AITestHarnessComponent_Conditional_2_Template_kendo_dialog_close_0_listener() { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelLoadConversation()); });
882
+ i0.ɵɵelementStart(1, "div", 125)(2, "p");
883
+ i0.ɵɵtext(3, "Loading this conversation will replace the current one. Any unsaved changes will be lost.");
884
+ i0.ɵɵelementEnd();
885
+ i0.ɵɵelementStart(4, "p");
886
+ i0.ɵɵtext(5, "Do you want to continue?");
887
+ i0.ɵɵelementEnd()();
888
+ i0.ɵɵelementStart(6, "kendo-dialog-actions")(7, "button", 127);
889
+ i0.ɵɵlistener("click", function AITestHarnessComponent_Conditional_2_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.cancelLoadConversation()); });
890
+ i0.ɵɵtext(8, "Cancel");
891
+ i0.ɵɵelementEnd();
892
+ i0.ɵɵelementStart(9, "button", 130);
893
+ i0.ɵɵlistener("click", function AITestHarnessComponent_Conditional_2_Template_button_click_9_listener() { i0.ɵɵrestoreView(_r28); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.confirmLoadConversation()); });
894
+ i0.ɵɵtext(10, " Load Conversation ");
895
+ i0.ɵɵelementEnd()()();
896
+ } if (rf & 2) {
897
+ i0.ɵɵproperty("title", "Replace Current Conversation?")("width", 400);
898
+ i0.ɵɵadvance(9);
899
+ i0.ɵɵproperty("primary", true);
900
+ } }
901
+ /**
902
+ * Comprehensive test harness component for AI Agent development and testing.
903
+ * Provides a full-featured chat interface with conversation management, data context configuration,
904
+ * template data management, streaming responses, conversation persistence, and import/export capabilities.
905
+ *
906
+ * ## Key Features:
907
+ * - **Interactive Chat**: Real-time conversation with AI agents
908
+ * - **Streaming Support**: Live streaming of agent responses with elapsed time tracking
909
+ * - **Data Context Management**: Configure variables passed to agent during execution
910
+ * - **Template Data Management**: Manage template variables for agent prompts
911
+ * - **Conversation Persistence**: Save/load conversations with full state restoration
912
+ * - **Import/Export**: JSON-based conversation backup and sharing
913
+ * - **Content Formatting**: Automatic detection and rendering of Markdown, JSON, and plain text
914
+ * - **Raw Content Toggle**: View both processed and raw AI responses
915
+ * - **Error Handling**: Comprehensive error display and user feedback
916
+ *
917
+ * ## Usage:
918
+ * ```html
919
+ * <mj-ai-agent-test-harness
920
+ * [aiAgent]="myAgent"
921
+ * [isVisible]="true"
922
+ * (visibilityChange)="onVisibilityChanged($event)">
923
+ * </mj-ai-agent-test-harness>
924
+ * ```
925
+ *
926
+ * @example
927
+ * ```typescript
928
+ * // Using with agent entity
929
+ * const agent = await metadata.GetEntityObject<AIAgentEntity>('AI Agents');
930
+ * await agent.Load('agent-id');
931
+ * this.testHarness.aiAgent = agent;
932
+ * this.testHarness.isVisible = true;
933
+ * ```
934
+ */
935
+ export class AITestHarnessComponent {
936
+ /**
937
+ * Creates a new AI Test Harness component instance.
938
+ * @param sanitizer - Angular DomSanitizer for safe HTML rendering of formatted content
939
+ * @param windowService - Kendo WindowService for creating modal windows
940
+ * @param viewContainerRef - Angular ViewContainerRef for window positioning
941
+ * @param cdr - Angular ChangeDetectorRef for managing change detection
942
+ */
943
+ constructor(sanitizer, windowService, viewContainerRef, cdr) {
944
+ this.sanitizer = sanitizer;
945
+ this.windowService = windowService;
946
+ this.viewContainerRef = viewContainerRef;
947
+ this.cdr = cdr;
948
+ /** The mode of operation - either 'agent' or 'prompt' */
949
+ this.mode = 'agent';
950
+ /** The entity to test - either an AI Agent or AI Prompt */
951
+ this.entity = null;
952
+ this._isVisible = false;
953
+ /** Event emitted when the visibility state changes, allowing parent components to react */
954
+ this.visibilityChange = new EventEmitter();
955
+ // === Conversation State ===
956
+ /** Complete array of all messages in the current conversation session */
957
+ this.conversationMessages = [];
958
+ /** Current text input by the user (bound to textarea) */
959
+ this.currentUserMessage = '';
960
+ /** Whether an agent execution is currently in progress */
961
+ this.isExecuting = false;
962
+ // === Data Context Management ===
963
+ /** Unified variables for agent execution (combines data context and template data) */
964
+ this.agentVariables = [];
965
+ // === Prompt-specific properties ===
966
+ /** Variables for prompt template rendering */
967
+ this.templateVariables = [];
968
+ /** Selected AI model for prompt execution */
969
+ this.selectedModelId = '';
970
+ /** Selected AI vendor for prompt execution */
971
+ this.selectedVendorId = '';
972
+ /** Available AI vendors for the selected model */
973
+ this.availableVendors = [];
974
+ /** Default model for the prompt (cached for display) */
975
+ this.defaultModelName = '';
976
+ /** Maximum tokens for prompt execution */
977
+ this.maxTokens = null;
978
+ /** Whether to skip validation when running prompts */
979
+ this.skipValidation = false;
980
+ /** Advanced LLM Parameters */
981
+ this.advancedParams = {
982
+ temperature: null,
983
+ topP: null,
984
+ topK: null,
985
+ minP: null,
986
+ frequencyPenalty: null,
987
+ presencePenalty: null,
988
+ seed: null,
989
+ stopSequences: [],
990
+ includeLogProbs: false,
991
+ topLogProbs: 2
992
+ };
993
+ /** Raw stop sequences input for textarea */
994
+ this.stopSequencesText = '';
995
+ /** Whether advanced parameters panel is expanded */
996
+ this.advancedParamsExpanded = false;
997
+ /** Available AI models for prompt execution */
998
+ this.availableModels = [];
999
+ /** Available response format options */
1000
+ this.responseFormatOptions = [
1001
+ { text: 'Any', value: 'Any' },
1002
+ { text: 'Text', value: 'Text' },
1003
+ { text: 'Markdown', value: 'Markdown' },
1004
+ { text: 'JSON', value: 'JSON' },
1005
+ { text: 'Model Specific', value: 'ModelSpecific' }
1006
+ ];
1007
+ /** Selected response format for prompt execution */
1008
+ this.selectedResponseFormat = this.responseFormatOptions[0];
1009
+ // === UI State Management ===
1010
+ /** Whether the configuration sidebar is currently visible */
1011
+ this.showSidebar = true;
1012
+ /** Currently active tab in the sidebar */
1013
+ this.activeTab = 'agentVariables';
1014
+ /** Array of saved conversation sessions loaded from localStorage */
1015
+ this.savedConversations = [];
1016
+ /** ID of the currently active/loaded conversation, if any */
1017
+ this.currentConversationId = null;
1018
+ /** Flag to control JSON dialog visibility */
1019
+ this.showJsonDialog = false;
1020
+ /** Current JSON content to display in the dialog */
1021
+ this.currentJsonContent = '';
1022
+ /** Reference to the JSON window when open */
1023
+ this.jsonWindowRef = null;
1024
+ // === Execution Monitor Properties ===
1025
+ /** Mode for the execution monitor component */
1026
+ this.executionMonitorMode = 'historical';
1027
+ /** Current execution data for the monitor */
1028
+ this.currentExecutionData = null;
1029
+ /** Agent conversation state tracking */
1030
+ this.agentConversationState = null;
1031
+ this.lastAgentReturnValue = null;
1032
+ this.subAgentHistory = [];
1033
+ /** Whether to show the save conversation dialog */
1034
+ this.showSaveDialog = false;
1035
+ /** Name for the new conversation being saved */
1036
+ this.newConversationName = '';
1037
+ /** Temporary name for the dialog input to avoid binding conflicts */
1038
+ this.tempConversationName = '';
1039
+ /** Whether to show the load confirmation dialog */
1040
+ this.showLoadConfirmDialog = false;
1041
+ /** Conversation pending to be loaded */
1042
+ this.pendingLoadConversation = null;
1043
+ /** Reference to the current message for JSON display */
1044
+ this.currentJsonMessage = null;
1045
+ // === Private State & Intervals ===
1046
+ /** Subject for component destruction cleanup */
1047
+ this.destroy$ = new Subject();
1048
+ /** Flag indicating that auto-scroll is needed on next view check */
1049
+ this.scrollNeeded = false;
1050
+ /** MemberJunction metadata instance for entity operations */
1051
+ this._metadata = new Metadata();
1052
+ /** Track if input has been focused to prevent repeated focusing */
1053
+ this._hasFocused = false;
1054
+ /** Default model object for the prompt (cached for vendor lookup) */
1055
+ this.defaultModel = null;
1056
+ }
1057
+ /** @deprecated Use 'entity' instead. Kept for backward compatibility. */
1058
+ get aiAgent() {
1059
+ return this.isAgentEntity(this.entity) ? this.entity : null;
1060
+ }
1061
+ set aiAgent(value) {
1062
+ this.entity = value;
1063
+ if (value) {
1064
+ this.mode = 'agent';
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Controls the visibility of the test harness. When set to true, automatically resets the harness state.
1069
+ * This property is typically controlled by parent components or dialog wrappers.
1070
+ */
1071
+ get isVisible() {
1072
+ return this._isVisible;
1073
+ }
1074
+ set isVisible(value) {
1075
+ const wasVisible = this._isVisible;
1076
+ this._isVisible = value;
1077
+ if (value && !wasVisible) {
1078
+ this.resetHarness();
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Component initialization. Loads saved conversations, sets up event subscriptions,
1083
+ * and initializes the harness to a clean state.
1084
+ */
1085
+ ngOnInit() {
1086
+ // Ensure we have an entity
1087
+ if (!this.entity && this.aiAgent) {
1088
+ // Handle backward compatibility
1089
+ this.entity = this.aiAgent;
1090
+ this.mode = 'agent';
1091
+ }
1092
+ this.loadSavedConversations();
1093
+ this.subscribeToEvents();
1094
+ this.resetHarness();
1095
+ // Load models if in prompt mode
1096
+ if (this.mode === 'prompt') {
1097
+ this.loadAvailableModels();
1098
+ this.loadPromptDefaults();
1099
+ }
1100
+ }
1101
+ /**
1102
+ * Responds to input property changes, particularly when entity is set/changed
1103
+ */
1104
+ ngOnChanges(changes) {
1105
+ if (changes['entity']) {
1106
+ if (changes['entity'].currentValue) {
1107
+ this.loadSavedConversations();
1108
+ }
1109
+ }
1110
+ if (changes['aiAgent']) {
1111
+ if (!this.entity && this.aiAgent) {
1112
+ this.entity = this.aiAgent;
1113
+ this.mode = 'agent';
1114
+ this.loadSavedConversations();
1115
+ }
1116
+ }
1117
+ if (changes['isVisible']) {
1118
+ if (changes['isVisible'].currentValue) {
1119
+ // Reset focus flag when dialog becomes visible
1120
+ this._hasFocused = false;
1121
+ }
1122
+ }
1123
+ }
1124
+ /**
1125
+ * Component cleanup. Destroys subscriptions and clears any active intervals
1126
+ * to prevent memory leaks.
1127
+ */
1128
+ ngOnDestroy() {
1129
+ this.destroy$.next();
1130
+ this.destroy$.complete();
1131
+ if (this.elapsedTimeInterval) {
1132
+ clearInterval(this.elapsedTimeInterval);
1133
+ }
1134
+ if (this._mjGlobalEventSub) {
1135
+ this._mjGlobalEventSub.unsubscribe();
1136
+ }
1137
+ if (this._agentStreamSub) {
1138
+ this._agentStreamSub.unsubscribe();
1139
+ }
1140
+ }
1141
+ /**
1142
+ * Post-view check hook that handles auto-scrolling to the bottom of the message container
1143
+ * when new content is added.
1144
+ */
1145
+ ngAfterViewChecked() {
1146
+ if (this.scrollNeeded) {
1147
+ this.scrollToBottom();
1148
+ this.scrollNeeded = false;
1149
+ }
1150
+ // Auto-focus message input when dialog first becomes visible
1151
+ // Use Promise.resolve to schedule this after current change detection cycle
1152
+ if (this.isVisible && !this._hasFocused && this.messageInput) {
1153
+ this._hasFocused = true;
1154
+ Promise.resolve().then(() => {
1155
+ this.messageInput?.focus();
1156
+ });
1157
+ }
1158
+ }
1159
+ subscribeToEvents() {
1160
+ // Set up direct GraphQL subscription for agent execution stream
1161
+ const dataProvider = Metadata.Provider;
1162
+ const _providerPushStatusSub = dataProvider.PushStatusUpdates().subscribe((status) => {
1163
+ const message = JSON.parse(status.message || '{}');
1164
+ if (message?.resolver === 'RunAIAgentResolver') {
1165
+ // Handle different types of streaming messages
1166
+ if (message?.type === 'ExecutionProgress' && message.data?.progress) {
1167
+ const userMsg = message.data.progress.message;
1168
+ // Update streaming message content
1169
+ const streamingMessage = this.conversationMessages.find(m => m.isStreaming);
1170
+ if (streamingMessage) {
1171
+ // Clear any typing animation interval
1172
+ const typingInterval = streamingMessage._typingInterval;
1173
+ if (typingInterval) {
1174
+ clearInterval(typingInterval);
1175
+ delete streamingMessage._typingInterval;
1176
+ }
1177
+ // Update the streaming content with the status message
1178
+ streamingMessage.streamingContent = userMsg;
1179
+ this.scrollNeeded = true;
1180
+ }
1181
+ // Update execution monitor with live progress
1182
+ if (!this.currentExecutionData) {
1183
+ this.currentExecutionData = { liveSteps: [] };
1184
+ }
1185
+ this.executionMonitorMode = 'live';
1186
+ // Add or update the current step in the execution tree
1187
+ const stepInfo = {
1188
+ step: {
1189
+ ID: `temp-${Date.now()}`,
1190
+ StepName: userMsg,
1191
+ StepType: message.data.progress.step || 'unknown',
1192
+ Status: 'Running',
1193
+ StartedAt: new Date()
1194
+ },
1195
+ executionType: message.data.progress.step || 'unknown',
1196
+ agentHierarchy: message.data.progress.agentHierarchy || [],
1197
+ depth: message.data.progress.depth || 0,
1198
+ startTime: new Date()
1199
+ };
1200
+ // For live mode, we'll append steps as they come
1201
+ if (!this.currentExecutionData.liveSteps) {
1202
+ this.currentExecutionData.liveSteps = [];
1203
+ }
1204
+ this.currentExecutionData.liveSteps.push(stepInfo);
1205
+ // Trigger change detection for the monitor
1206
+ this.currentExecutionData = { ...this.currentExecutionData };
1207
+ }
1208
+ else if (message?.type === 'StreamingContent' && message.data?.streaming) {
1209
+ const streamingMessage = this.conversationMessages.find(m => m.isStreaming);
1210
+ if (streamingMessage) {
1211
+ // Append streaming content
1212
+ if (!streamingMessage.streamingContent) {
1213
+ streamingMessage.streamingContent = '';
1214
+ }
1215
+ streamingMessage.streamingContent = message.data.streaming.content;
1216
+ this.scrollNeeded = true;
1217
+ }
1218
+ }
1219
+ else if (message?.type === 'partial_result' && message.data?.partialResult) {
1220
+ // Could update execution monitor with partial results
1221
+ }
1222
+ else if (message?.type === 'complete') {
1223
+ // Switch execution monitor to historical mode with final data
1224
+ this.executionMonitorMode = 'historical';
1225
+ }
1226
+ }
1227
+ });
1228
+ }
1229
+ /**
1230
+ * Loads available AI models for prompt execution.
1231
+ * Only called when the harness is in prompt mode.
1232
+ */
1233
+ async loadAvailableModels() {
1234
+ await AIEngineBase.Instance.Config(false);
1235
+ // Filter models by the prompt's AIModelTypeID if it exists
1236
+ let filteredModels = [];
1237
+ if (this.entity && 'AIModelTypeID' in this.entity) {
1238
+ const prompt = this.entity;
1239
+ if (prompt.AIModelTypeID) {
1240
+ filteredModels = AIEngineBase.Instance.Models.filter(model => model.AIModelTypeID === prompt.AIModelTypeID && model.IsActive);
1241
+ }
1242
+ else {
1243
+ // No model type restriction, show all active models
1244
+ filteredModels = AIEngineBase.Instance.Models.filter(model => model.IsActive);
1245
+ }
1246
+ // Set default response format from prompt with slight delay for Kendo dropdown
1247
+ setTimeout(() => {
1248
+ const format = this.responseFormatOptions.find(f => f.value.trim().toLowerCase() === prompt.ResponseFormat.trim().toLowerCase());
1249
+ if (format) {
1250
+ this.selectedResponseFormat = format;
1251
+ }
1252
+ else {
1253
+ this.selectedResponseFormat = this.responseFormatOptions[0]; // Default to 'Any'
1254
+ }
1255
+ }, 0);
1256
+ }
1257
+ else {
1258
+ // Not a prompt entity, show all active models
1259
+ filteredModels = AIEngineBase.Instance.Models.filter(model => model.IsActive);
1260
+ }
1261
+ // Sort models by name
1262
+ filteredModels.sort((a, b) => a.Name.localeCompare(b.Name));
1263
+ // Determine the default model for this prompt
1264
+ if (this.entity && 'AIModelTypeID' in this.entity) {
1265
+ const prompt = this.entity;
1266
+ this.defaultModelName = await this.getDefaultModelName(prompt);
1267
+ }
1268
+ // Add a blank option at the beginning with the default model name
1269
+ this.availableModels = [
1270
+ { ID: '', Name: this.defaultModelName ? `-- Default: ${this.defaultModelName} --` : '-- Use Default Model --' },
1271
+ ...filteredModels
1272
+ ];
1273
+ // Don't auto-select a model - let the dropdown show the blank option
1274
+ this.selectedModelId = '';
1275
+ // If we have a default model, load its default vendor
1276
+ if (this.defaultModelName) {
1277
+ await this.loadDefaultVendor();
1278
+ }
1279
+ else {
1280
+ this.selectedVendorId = '';
1281
+ this.availableVendors = [];
1282
+ }
1283
+ }
1284
+ /**
1285
+ * Gets the default model name for a prompt based on its configuration
1286
+ */
1287
+ async getDefaultModelName(prompt) {
1288
+ try {
1289
+ // Get prompt-specific model associations
1290
+ const promptModels = AIEngineBase.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
1291
+ (pm.Status === 'Active' || pm.Status === 'Preview'));
1292
+ let defaultModel = null;
1293
+ if (promptModels.length > 0) {
1294
+ // Sort by priority (higher priority first)
1295
+ promptModels.sort((a, b) => (b.Priority || 0) - (a.Priority || 0));
1296
+ // Find the first active model
1297
+ for (const pm of promptModels) {
1298
+ const model = AIEngineBase.Instance.Models.find(m => m.ID === pm.ModelID && m.IsActive);
1299
+ if (model) {
1300
+ defaultModel = model;
1301
+ break;
1302
+ }
1303
+ }
1304
+ }
1305
+ // If no prompt-specific model, use selection strategy
1306
+ if (!defaultModel) {
1307
+ const candidates = AIEngineBase.Instance.Models.filter(m => m.IsActive &&
1308
+ (!prompt.AIModelTypeID || m.AIModelTypeID === prompt.AIModelTypeID));
1309
+ if (candidates.length > 0) {
1310
+ // Apply selection strategy
1311
+ if (prompt.SelectionStrategy === 'ByPower') {
1312
+ candidates.sort((a, b) => {
1313
+ switch (prompt.PowerPreference) {
1314
+ case 'Lowest':
1315
+ return (a.PowerRank || 0) - (b.PowerRank || 0);
1316
+ case 'Highest':
1317
+ case 'Balanced':
1318
+ default:
1319
+ return (b.PowerRank || 0) - (a.PowerRank || 0);
1320
+ }
1321
+ });
1322
+ }
1323
+ defaultModel = candidates[0];
1324
+ }
1325
+ }
1326
+ // Cache the default model object for vendor lookup
1327
+ this.defaultModel = defaultModel;
1328
+ return defaultModel ? defaultModel.Name : '';
1329
+ }
1330
+ catch (error) {
1331
+ console.error('Error getting default model name:', error);
1332
+ return '';
1333
+ }
1334
+ }
1335
+ /**
1336
+ * Loads the vendors for the default model
1337
+ */
1338
+ async loadDefaultVendor() {
1339
+ try {
1340
+ if (!this.defaultModel) {
1341
+ this.availableVendors = [];
1342
+ return;
1343
+ }
1344
+ // Get vendors that offer this model - same logic as loadVendorsForModel
1345
+ const modelVendors = AIEngineBase.Instance.ModelVendors.filter(mv => mv.ModelID === this.defaultModel.ID &&
1346
+ mv.Status === 'Active' &&
1347
+ mv.Type?.trim().toLowerCase() === 'inference provider');
1348
+ // Map to vendor objects with priority from ModelVendor
1349
+ const vendorObjects = [];
1350
+ for (const mv of modelVendors) {
1351
+ const vendor = AIEngineBase.Instance.Vendors.find(v => v.ID === mv.VendorID);
1352
+ if (vendor) {
1353
+ vendorObjects.push({
1354
+ ID: vendor.ID,
1355
+ Name: vendor.Name,
1356
+ Priority: mv.Priority || 999 // Use ModelVendor priority
1357
+ });
1358
+ }
1359
+ }
1360
+ // Sort by priority (lower number = higher priority)
1361
+ vendorObjects.sort((a, b) => a.Priority - b.Priority);
1362
+ this.availableVendors = vendorObjects;
1363
+ // Select the highest priority vendor
1364
+ if (vendorObjects.length > 0) {
1365
+ this.selectedVendorId = vendorObjects[0].ID;
1366
+ }
1367
+ }
1368
+ catch (error) {
1369
+ console.error('Error loading default vendor:', error);
1370
+ this.availableVendors = [];
1371
+ }
1372
+ }
1373
+ /**
1374
+ * Handles model selection change and loads available vendors for the selected model
1375
+ */
1376
+ onModelSelectionChange() {
1377
+ this.selectedVendorId = '';
1378
+ this.availableVendors = [];
1379
+ if (!this.selectedModelId) {
1380
+ // When default model is selected, load default vendor
1381
+ if (this.defaultModelName) {
1382
+ this.loadDefaultVendor();
1383
+ }
1384
+ return;
1385
+ }
1386
+ // Ensure AIEngineBase is configured
1387
+ if (!AIEngineBase.Instance.Models || AIEngineBase.Instance.Models.length === 0) {
1388
+ AIEngineBase.Instance.Config(false).then(() => {
1389
+ this.loadVendorsForModel();
1390
+ });
1391
+ }
1392
+ else {
1393
+ this.loadVendorsForModel();
1394
+ }
1395
+ }
1396
+ /**
1397
+ * Loads available vendors for the selected model
1398
+ */
1399
+ loadVendorsForModel() {
1400
+ if (!this.selectedModelId) {
1401
+ return;
1402
+ }
1403
+ // Get model-specific vendors
1404
+ const modelVendors = AIEngineBase.Instance.ModelVendors.filter(mv => mv.ModelID === this.selectedModelId &&
1405
+ mv.Status === 'Active' &&
1406
+ mv.Type?.trim().toLowerCase() === 'inference provider');
1407
+ // Map to vendor objects with priority from ModelVendor
1408
+ const vendorObjects = [];
1409
+ for (const mv of modelVendors) {
1410
+ const vendor = AIEngineBase.Instance.Vendors.find(v => v.ID === mv.VendorID);
1411
+ if (vendor) {
1412
+ // For now, include all vendors. TODO: Filter by vendor type when available
1413
+ vendorObjects.push({
1414
+ ID: vendor.ID,
1415
+ Name: vendor.Name,
1416
+ Priority: mv.Priority || 999 // Use ModelVendor priority
1417
+ });
1418
+ }
1419
+ }
1420
+ // Sort by priority (lower number = higher priority)
1421
+ vendorObjects.sort((a, b) => a.Priority - b.Priority);
1422
+ this.availableVendors = vendorObjects;
1423
+ // Auto-select the highest priority vendor if only one or set default
1424
+ if (vendorObjects.length === 1) {
1425
+ this.selectedVendorId = vendorObjects[0].ID;
1426
+ }
1427
+ else if (vendorObjects.length > 1) {
1428
+ // Select the highest priority (lowest priority number)
1429
+ this.selectedVendorId = vendorObjects[0].ID;
1430
+ }
1431
+ }
1432
+ /**
1433
+ * Loads default parameter values from the AI prompt entity
1434
+ */
1435
+ loadPromptDefaults() {
1436
+ if (this.mode === 'prompt' && this.entity && this.isPromptEntity(this.entity)) {
1437
+ const prompt = this.entity;
1438
+ // Load default values from prompt entity
1439
+ if (prompt.Temperature != null)
1440
+ this.advancedParams.temperature = prompt.Temperature;
1441
+ if (prompt.TopP != null)
1442
+ this.advancedParams.topP = prompt.TopP;
1443
+ if (prompt.TopK != null)
1444
+ this.advancedParams.topK = prompt.TopK;
1445
+ if (prompt.MinP != null)
1446
+ this.advancedParams.minP = prompt.MinP;
1447
+ if (prompt.FrequencyPenalty != null)
1448
+ this.advancedParams.frequencyPenalty = prompt.FrequencyPenalty;
1449
+ if (prompt.PresencePenalty != null)
1450
+ this.advancedParams.presencePenalty = prompt.PresencePenalty;
1451
+ if (prompt.Seed != null)
1452
+ this.advancedParams.seed = prompt.Seed;
1453
+ if (prompt.StopSequences) {
1454
+ this.stopSequencesText = prompt.StopSequences;
1455
+ this.advancedParams.stopSequences = prompt.StopSequences.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
1456
+ }
1457
+ if (prompt.IncludeLogProbs != null)
1458
+ this.advancedParams.includeLogProbs = prompt.IncludeLogProbs;
1459
+ if (prompt.TopLogProbs != null)
1460
+ this.advancedParams.topLogProbs = prompt.TopLogProbs;
1461
+ }
1462
+ }
1463
+ /**
1464
+ * Resets all model settings to the prompt defaults
1465
+ */
1466
+ resetToPromptDefaults() {
1467
+ if (this.mode === 'prompt' && this.entity && this.isPromptEntity(this.entity)) {
1468
+ const prompt = this.entity;
1469
+ // Reset model selection to default
1470
+ this.selectedModelId = '';
1471
+ // Reset vendor - will be loaded by loadDefaultVendor
1472
+ this.selectedVendorId = '';
1473
+ // Reset response format to prompt's setting
1474
+ const format = this.responseFormatOptions.find(f => f.value.trim().toLowerCase() === prompt.ResponseFormat.trim().toLowerCase());
1475
+ this.selectedResponseFormat = format || this.responseFormatOptions[0];
1476
+ // Reset max tokens
1477
+ this.maxTokens = null;
1478
+ // Reset skip validation
1479
+ this.skipValidation = false;
1480
+ // Reset advanced parameters
1481
+ this.advancedParams = {
1482
+ temperature: null,
1483
+ topP: null,
1484
+ topK: null,
1485
+ minP: null,
1486
+ frequencyPenalty: null,
1487
+ presencePenalty: null,
1488
+ seed: null,
1489
+ stopSequences: [],
1490
+ includeLogProbs: false,
1491
+ topLogProbs: 2
1492
+ };
1493
+ this.stopSequencesText = '';
1494
+ // Reload prompt defaults for advanced params
1495
+ this.loadPromptDefaults();
1496
+ // Reload default vendor for the default model
1497
+ if (this.defaultModelName) {
1498
+ this.loadDefaultVendor();
1499
+ }
1500
+ }
1501
+ }
1502
+ /**
1503
+ * Resets the test harness to its initial state, clearing all conversations,
1504
+ * variables, and UI state. Called automatically when the harness becomes visible.
1505
+ */
1506
+ resetHarness() {
1507
+ this.conversationMessages = [];
1508
+ this.currentUserMessage = '';
1509
+ this.isExecuting = false;
1510
+ this.agentVariables = [{ name: '', value: '', type: 'string' }];
1511
+ this.templateVariables = [];
1512
+ this.currentConversationId = null;
1513
+ this.showSidebar = true;
1514
+ this.currentExecutionData = null;
1515
+ this.executionMonitorMode = 'historical';
1516
+ // Reset conversation state
1517
+ this.agentConversationState = null;
1518
+ this.lastAgentReturnValue = null;
1519
+ this.subAgentHistory = [];
1520
+ // Set default tab based on mode
1521
+ this.activeTab = this.mode === 'agent' ? 'agentVariables' : 'templateVariables';
1522
+ // Reset advanced parameters
1523
+ this.advancedParams = {
1524
+ temperature: null,
1525
+ topP: null,
1526
+ topK: null,
1527
+ minP: null,
1528
+ frequencyPenalty: null,
1529
+ presencePenalty: null,
1530
+ seed: null,
1531
+ stopSequences: [],
1532
+ includeLogProbs: false,
1533
+ topLogProbs: 2
1534
+ };
1535
+ this.stopSequencesText = '';
1536
+ this.advancedParamsExpanded = false;
1537
+ this.skipValidation = false;
1538
+ }
1539
+ /**
1540
+ * Starts a new conversation
1541
+ */
1542
+ newConversation() {
1543
+ if (this.conversationMessages.length > 0 && !this.currentConversationId) {
1544
+ // Unsaved conversation exists - use our custom dialog
1545
+ if (confirm('You have an unsaved conversation. Would you like to save it first?')) {
1546
+ this.saveConversation();
1547
+ return;
1548
+ }
1549
+ }
1550
+ this.resetHarness();
1551
+ MJNotificationService.Instance.CreateSimpleNotification('Started new conversation', 'info', 2000);
1552
+ }
1553
+ /**
1554
+ * Closes the test harness by setting visibility to false and emitting the change event.
1555
+ * Used by dialog implementations and close buttons.
1556
+ */
1557
+ close() {
1558
+ this._isVisible = false;
1559
+ this.visibilityChange.emit(false);
1560
+ }
1561
+ /**
1562
+ * Toggles the visibility of the configuration sidebar.
1563
+ * Allows users to show/hide the data context and conversation management panels.
1564
+ */
1565
+ toggleSidebar() {
1566
+ this.showSidebar = !this.showSidebar;
1567
+ }
1568
+ /**
1569
+ * Switches to the specified tab in the configuration sidebar.
1570
+ * @param tab - The tab to activate
1571
+ */
1572
+ selectTab(tab) {
1573
+ this.activeTab = tab;
1574
+ // If switching to execution monitor tab, ensure it has the latest data
1575
+ if (tab === 'executionMonitor' && this.conversationMessages.length > 0) {
1576
+ const lastAssistantMessage = this.conversationMessages
1577
+ .filter(m => m.role === 'assistant' && m.executionData)
1578
+ .pop();
1579
+ if (lastAssistantMessage && lastAssistantMessage.executionData) {
1580
+ this.currentExecutionData = lastAssistantMessage.executionData;
1581
+ this.executionMonitorMode = 'historical';
1582
+ }
1583
+ }
1584
+ }
1585
+ /**
1586
+ * Shows the execution history for a specific message
1587
+ * @param message - The message to show execution history for
1588
+ */
1589
+ showMessageExecutionHistory(message) {
1590
+ if (message.executionData && this.mode === 'agent') {
1591
+ this.currentExecutionData = message.executionData;
1592
+ this.executionMonitorMode = 'historical';
1593
+ // Switch to execution monitor tab if not already there
1594
+ if (this.activeTab !== 'executionMonitor') {
1595
+ this.selectTab('executionMonitor');
1596
+ }
1597
+ }
1598
+ }
1599
+ /**
1600
+ * Extract sub-agent nodes from execution tree
1601
+ */
1602
+ extractSubAgentNodes(executionTree) {
1603
+ const subAgentNodes = [];
1604
+ const traverse = (nodes) => {
1605
+ for (const node of nodes) {
1606
+ if (node.executionType === 'sub-agent') {
1607
+ subAgentNodes.push({
1608
+ agentName: node.step?.StepName,
1609
+ result: node.outputData,
1610
+ timestamp: node.endTime || node.startTime
1611
+ });
1612
+ }
1613
+ if (node.children) {
1614
+ traverse(node.children);
1615
+ }
1616
+ }
1617
+ };
1618
+ traverse(executionTree);
1619
+ return subAgentNodes;
1620
+ }
1621
+ /**
1622
+ * Sends the current user message to the AI agent and initiates execution.
1623
+ * Handles message validation, conversation updates, UI state management, and agent execution.
1624
+ * Automatically clears the input field and triggers auto-scroll after sending.
1625
+ *
1626
+ * @example
1627
+ * ```typescript
1628
+ * // Called when user presses Enter or clicks Send button
1629
+ * await this.sendMessage();
1630
+ * ```
1631
+ */
1632
+ async sendMessage() {
1633
+ if (!this.currentUserMessage.trim() || !this.entity) {
1634
+ return;
1635
+ }
1636
+ // Add user message to conversation
1637
+ const userMessage = {
1638
+ id: this.generateMessageId(),
1639
+ role: 'user',
1640
+ content: this.currentUserMessage.trim(),
1641
+ timestamp: new Date()
1642
+ };
1643
+ this.conversationMessages.push(userMessage);
1644
+ // Clear input and update change detection
1645
+ const messageToSend = this.currentUserMessage;
1646
+ this.currentUserMessage = '';
1647
+ this.cdr.detectChanges();
1648
+ // Scroll to bottom
1649
+ this.scrollNeeded = true;
1650
+ // Auto-switch to execution monitor tab if in agent mode
1651
+ if (this.mode === 'agent' && this.activeTab !== 'executionMonitor') {
1652
+ this.selectTab('executionMonitor');
1653
+ }
1654
+ // Execute based on mode
1655
+ if (this.mode === 'agent') {
1656
+ await this.executeAgent(messageToSend);
1657
+ }
1658
+ else {
1659
+ await this.executePrompt(messageToSend);
1660
+ }
1661
+ }
1662
+ async executeAgent(userMessage) {
1663
+ if (!this.entity || !this.isAgentEntity(this.entity))
1664
+ return;
1665
+ this.isExecuting = true;
1666
+ // Clear previous execution data when starting a new run
1667
+ this.currentExecutionData = { liveSteps: [] };
1668
+ this.executionMonitorMode = 'live';
1669
+ // Add placeholder assistant message for streaming
1670
+ const assistantMessage = {
1671
+ id: this.generateMessageId(),
1672
+ role: 'assistant',
1673
+ content: '',
1674
+ timestamp: new Date(),
1675
+ isStreaming: true,
1676
+ streamingContent: this.mode === 'agent' ? 'Running agent...' : 'Running prompt...',
1677
+ agentRunId: '',
1678
+ streamingStartTime: Date.now(),
1679
+ elapsedTime: 0
1680
+ };
1681
+ this.conversationMessages.push(assistantMessage);
1682
+ this.scrollNeeded = true;
1683
+ // Start elapsed time counter
1684
+ this.startElapsedTimeCounter(assistantMessage);
1685
+ // Initialize execution monitor for live mode
1686
+ this.currentExecutionData = { liveSteps: [] };
1687
+ this.executionMonitorMode = 'live';
1688
+ try {
1689
+ // Get GraphQL data provider
1690
+ const dataProvider = Metadata.Provider;
1691
+ // Build conversation history for context
1692
+ const messages = this.conversationMessages
1693
+ .filter(m => !m.isStreaming)
1694
+ .map(m => ({
1695
+ role: m.role,
1696
+ content: m.content
1697
+ }));
1698
+ // Build data context - include conversation state if available
1699
+ const dataContext = this.buildDataContext();
1700
+ const templateData = this.buildTemplateData();
1701
+ // Add conversation state to data context if we have it
1702
+ if (this.agentConversationState) {
1703
+ dataContext._conversationState = this.agentConversationState;
1704
+ }
1705
+ if (this.lastAgentReturnValue) {
1706
+ dataContext._lastReturnValue = this.lastAgentReturnValue;
1707
+ }
1708
+ if (this.subAgentHistory.length > 0) {
1709
+ dataContext._subAgentHistory = this.subAgentHistory;
1710
+ }
1711
+ // Generate a session ID for this execution
1712
+ const sessionId = dataProvider.sessionId;
1713
+ // Execute the agent
1714
+ const query = `
1715
+ mutation RunAIAgent($agentId: String!, $messages: String!, $sessionId: String!, $data: String, $templateData: String) {
1716
+ RunAIAgent(agentId: $agentId, messages: $messages, sessionId: $sessionId, data: $data, templateData: $templateData) {
1717
+ success
1718
+ errorMessage
1719
+ executionTimeMs
1720
+ payload
1721
+ }
1722
+ }
1723
+ `;
1724
+ const variables = {
1725
+ agentId: this.entity.ID,
1726
+ messages: JSON.stringify(messages),
1727
+ sessionId: sessionId,
1728
+ data: Object.keys(dataContext).length > 0 ? JSON.stringify(dataContext) : null,
1729
+ templateData: Object.keys(templateData).length > 0 ? JSON.stringify(templateData) : null
1730
+ };
1731
+ // Start typing animation while we wait for the first real stream
1732
+ this.startTypingAnimation(assistantMessage);
1733
+ let result;
1734
+ try {
1735
+ result = await dataProvider.ExecuteGQL(query, variables);
1736
+ }
1737
+ catch (gqlError) {
1738
+ console.error('❌ AI Test Harness: GraphQL execution failed', {
1739
+ error: gqlError,
1740
+ message: gqlError?.message,
1741
+ networkError: gqlError?.networkError,
1742
+ graphQLErrors: gqlError?.graphQLErrors,
1743
+ statusCode: gqlError?.networkError?.statusCode
1744
+ });
1745
+ throw gqlError;
1746
+ }
1747
+ const executionResult = result?.RunAIAgent;
1748
+ // Stop elapsed time counter
1749
+ if (this.elapsedTimeInterval) {
1750
+ clearInterval(this.elapsedTimeInterval);
1751
+ this.elapsedTimeInterval = null;
1752
+ }
1753
+ // Update assistant message with result
1754
+ assistantMessage.isStreaming = false;
1755
+ if (executionResult?.success) {
1756
+ // Parse the payload to get the full execution result
1757
+ const fullResult = JSON.parse(executionResult.payload);
1758
+ // Store execution data with the message
1759
+ assistantMessage.executionData = fullResult;
1760
+ // Merge live steps with final result if needed
1761
+ if (this.currentExecutionData && this.currentExecutionData.liveSteps && fullResult) {
1762
+ // Preserve the live steps we collected during execution
1763
+ fullResult.liveSteps = this.currentExecutionData.liveSteps;
1764
+ }
1765
+ // Update execution monitor with the new data
1766
+ this.currentExecutionData = fullResult;
1767
+ this.executionMonitorMode = 'historical';
1768
+ // Preserve conversation state from the result
1769
+ if (fullResult.returnValue) {
1770
+ this.lastAgentReturnValue = fullResult.returnValue;
1771
+ // Extract conversation state if present
1772
+ if (fullResult.returnValue.conversationState) {
1773
+ this.agentConversationState = fullResult.returnValue.conversationState;
1774
+ }
1775
+ // Track sub-agent executions
1776
+ if (fullResult.executionTree) {
1777
+ const subAgentNodes = this.extractSubAgentNodes(fullResult.executionTree);
1778
+ if (subAgentNodes.length > 0) {
1779
+ this.subAgentHistory.push(...subAgentNodes);
1780
+ }
1781
+ }
1782
+ }
1783
+ // Extract the user message from the nested returnValue structure
1784
+ let displayContent = 'No response generated';
1785
+ if (fullResult.returnValue) {
1786
+ if (typeof fullResult.returnValue === 'object' &&
1787
+ fullResult.returnValue.nextStep?.userMessage) {
1788
+ // This is the expected structure for chat responses
1789
+ displayContent = fullResult.returnValue.nextStep.userMessage;
1790
+ }
1791
+ else if (typeof fullResult.returnValue === 'string') {
1792
+ // Simple string response
1793
+ displayContent = fullResult.returnValue;
1794
+ }
1795
+ else {
1796
+ // Other structured response - show as JSON
1797
+ displayContent = JSON.stringify(fullResult.returnValue, null, 2);
1798
+ }
1799
+ }
1800
+ assistantMessage.content = displayContent;
1801
+ assistantMessage.executionTime = executionResult.executionTimeMs;
1802
+ assistantMessage.agentRunId = fullResult.agentRun?.ID || assistantMessage.agentRunId;
1803
+ // Store the full result as raw content for debugging/inspection
1804
+ assistantMessage.rawContent = executionResult.payload;
1805
+ }
1806
+ else {
1807
+ console.error('❌ AI Test Harness: Execution failed', {
1808
+ success: executionResult?.success,
1809
+ errorMessage: executionResult?.errorMessage,
1810
+ hasPayload: !!executionResult?.payload
1811
+ });
1812
+ assistantMessage.content = 'I encountered an error processing your request.';
1813
+ assistantMessage.error = executionResult?.errorMessage || 'Unknown error occurred';
1814
+ // Try to parse error payload if available
1815
+ if (executionResult?.payload) {
1816
+ try {
1817
+ JSON.parse(executionResult.payload); // Validate it's valid JSON
1818
+ assistantMessage.rawContent = executionResult.payload;
1819
+ }
1820
+ catch {
1821
+ // Ignore parse errors
1822
+ }
1823
+ }
1824
+ }
1825
+ delete assistantMessage.streamingContent;
1826
+ this.scrollNeeded = true;
1827
+ // Auto-save conversation
1828
+ this.autoSaveConversation();
1829
+ }
1830
+ catch (error) {
1831
+ console.error('❌ AI Test Harness: Caught error during agent execution', {
1832
+ error: error,
1833
+ message: error?.message,
1834
+ stack: error?.stack,
1835
+ type: error?.constructor?.name
1836
+ });
1837
+ // Update assistant message with error
1838
+ const lastMessage = this.conversationMessages[this.conversationMessages.length - 1];
1839
+ if (lastMessage && lastMessage.role === 'assistant') {
1840
+ lastMessage.isStreaming = false;
1841
+ lastMessage.content = 'I encountered an error processing your request.';
1842
+ lastMessage.error = error.message;
1843
+ delete lastMessage.streamingContent;
1844
+ }
1845
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to execute agent: ' + error.message, 'error', 6000);
1846
+ }
1847
+ finally {
1848
+ this.isExecuting = false;
1849
+ if (this.elapsedTimeInterval) {
1850
+ clearInterval(this.elapsedTimeInterval);
1851
+ this.elapsedTimeInterval = null;
1852
+ }
1853
+ // Focus back on the input
1854
+ this.focusMessageInput();
1855
+ }
1856
+ }
1857
+ async executePrompt(userMessage) {
1858
+ if (!this.entity || !this.isPromptEntity(this.entity))
1859
+ return;
1860
+ this.isExecuting = true;
1861
+ // Add placeholder assistant message for streaming
1862
+ const assistantMessage = {
1863
+ id: this.generateMessageId(),
1864
+ role: 'assistant',
1865
+ content: '',
1866
+ timestamp: new Date(),
1867
+ isStreaming: true,
1868
+ streamingContent: this.mode === 'agent' ? 'Running agent...' : 'Running prompt...',
1869
+ agentRunId: '',
1870
+ streamingStartTime: Date.now(),
1871
+ elapsedTime: 0
1872
+ };
1873
+ this.conversationMessages.push(assistantMessage);
1874
+ this.scrollNeeded = true;
1875
+ // Start elapsed time counter
1876
+ this.startElapsedTimeCounter(assistantMessage);
1877
+ try {
1878
+ // Get GraphQL data provider
1879
+ const dataProvider = Metadata.Provider;
1880
+ // Build template variables from user input
1881
+ const templateVariables = this.buildTemplateVariables();
1882
+ // Prepare data context (template variables + user message)
1883
+ const dataContext = {
1884
+ ...templateVariables,
1885
+ userMessage: userMessage
1886
+ };
1887
+ // Build conversation messages
1888
+ const messages = this.conversationMessages
1889
+ .filter(m => !m.isStreaming && m.content) // Only include non-streaming messages with content
1890
+ .map(m => ({
1891
+ role: m.role,
1892
+ content: m.content
1893
+ }));
1894
+ // Execute the prompt using RunAIPrompt
1895
+ const query = `
1896
+ mutation RunAIPrompt($promptId: String!, $data: String, $modelId: String, $vendorId: String, $configurationId: String, $skipValidation: Boolean, $templateData: String, $responseFormat: String, $temperature: Float, $topP: Float, $topK: Int, $minP: Float, $frequencyPenalty: Float, $presencePenalty: Float, $seed: Int, $stopSequences: [String!], $includeLogProbs: Boolean, $topLogProbs: Int, $messages: String) {
1897
+ RunAIPrompt(promptId: $promptId, data: $data, modelId: $modelId, vendorId: $vendorId, configurationId: $configurationId, skipValidation: $skipValidation, templateData: $templateData, responseFormat: $responseFormat, temperature: $temperature, topP: $topP, topK: $topK, minP: $minP, frequencyPenalty: $frequencyPenalty, presencePenalty: $presencePenalty, seed: $seed, stopSequences: $stopSequences, includeLogProbs: $includeLogProbs, topLogProbs: $topLogProbs, messages: $messages) {
1898
+ success
1899
+ output
1900
+ parsedResult
1901
+ error
1902
+ executionTimeMs
1903
+ tokensUsed
1904
+ promptRunId
1905
+ rawResult
1906
+ validationResult
1907
+ }
1908
+ }
1909
+ `;
1910
+ const variables = {
1911
+ promptId: this.entity.ID,
1912
+ data: JSON.stringify(dataContext),
1913
+ modelId: this.selectedModelId || undefined,
1914
+ vendorId: this.selectedVendorId || undefined,
1915
+ configurationId: undefined, // Use default configuration
1916
+ skipValidation: this.skipValidation,
1917
+ templateData: null, // Additional template context if needed
1918
+ responseFormat: this.selectedResponseFormat?.value,
1919
+ temperature: this.advancedParams.temperature ?? undefined,
1920
+ topP: this.advancedParams.topP ?? undefined,
1921
+ topK: this.advancedParams.topK ?? undefined,
1922
+ minP: this.advancedParams.minP ?? undefined,
1923
+ frequencyPenalty: this.advancedParams.frequencyPenalty ?? undefined,
1924
+ presencePenalty: this.advancedParams.presencePenalty ?? undefined,
1925
+ seed: this.advancedParams.seed ?? undefined,
1926
+ stopSequences: this.advancedParams.stopSequences.length > 0 ? this.advancedParams.stopSequences : undefined,
1927
+ includeLogProbs: this.advancedParams.includeLogProbs,
1928
+ topLogProbs: this.advancedParams.includeLogProbs ? this.advancedParams.topLogProbs : undefined,
1929
+ messages: messages.length > 0 ? JSON.stringify(messages) : undefined
1930
+ };
1931
+ const result = await dataProvider.ExecuteGQL(query, variables);
1932
+ const executionResult = result?.RunAIPrompt;
1933
+ // Stop elapsed time counter
1934
+ if (this.elapsedTimeInterval) {
1935
+ clearInterval(this.elapsedTimeInterval);
1936
+ this.elapsedTimeInterval = null;
1937
+ }
1938
+ // Update assistant message with result
1939
+ assistantMessage.isStreaming = false;
1940
+ if (executionResult?.success) {
1941
+ // Use parsedResult if available, otherwise fall back to output
1942
+ assistantMessage.content = executionResult.parsedResult || executionResult.output || 'No response generated';
1943
+ assistantMessage.executionTime = executionResult.executionTimeMs;
1944
+ // Store the complete execution result for JSON display
1945
+ assistantMessage.rawContent = JSON.stringify(executionResult, null, 2);
1946
+ // Store execution metadata
1947
+ if (executionResult.promptRunId) {
1948
+ assistantMessage.agentRunId = executionResult.promptRunId;
1949
+ }
1950
+ }
1951
+ else {
1952
+ assistantMessage.content = 'I encountered an error processing your request.';
1953
+ assistantMessage.error = executionResult?.error || 'Unknown error occurred';
1954
+ }
1955
+ delete assistantMessage.streamingContent;
1956
+ this.scrollNeeded = true;
1957
+ // Auto-save conversation
1958
+ this.autoSaveConversation();
1959
+ // Focus back on input
1960
+ this.focusMessageInput();
1961
+ }
1962
+ catch (error) {
1963
+ // Update assistant message with error
1964
+ const lastMessage = this.conversationMessages[this.conversationMessages.length - 1];
1965
+ if (lastMessage && lastMessage.role === 'assistant') {
1966
+ lastMessage.isStreaming = false;
1967
+ lastMessage.content = 'I encountered an error processing your request.';
1968
+ lastMessage.error = error.message;
1969
+ delete lastMessage.streamingContent;
1970
+ }
1971
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to execute prompt: ' + error.message, 'error', 6000);
1972
+ }
1973
+ finally {
1974
+ this.isExecuting = false;
1975
+ if (this.elapsedTimeInterval) {
1976
+ clearInterval(this.elapsedTimeInterval);
1977
+ this.elapsedTimeInterval = null;
1978
+ }
1979
+ // Focus back on the input
1980
+ this.focusMessageInput();
1981
+ }
1982
+ }
1983
+ startTypingAnimation(message) {
1984
+ const initialMessages = [
1985
+ "I'm processing your request...",
1986
+ "Let me think about that...",
1987
+ "Working on your request...",
1988
+ "Analyzing your question...",
1989
+ "Processing..."
1990
+ ];
1991
+ const selectedMessage = initialMessages[Math.floor(Math.random() * initialMessages.length)];
1992
+ let index = 0;
1993
+ // Store the interval handle on the message itself so we can cancel it when real streaming starts
1994
+ const typingInterval = setInterval(() => {
1995
+ if (index < selectedMessage.length && message.isStreaming) {
1996
+ message.streamingContent = selectedMessage.substring(0, index + 1);
1997
+ index++;
1998
+ this.scrollNeeded = true;
1999
+ }
2000
+ else {
2001
+ clearInterval(typingInterval);
2002
+ }
2003
+ }, 50);
2004
+ // Store interval reference in case we need to clear it
2005
+ message._typingInterval = typingInterval;
2006
+ }
2007
+ buildDataContext() {
2008
+ const context = {};
2009
+ // Use unified agent variables
2010
+ for (const variable of this.agentVariables) {
2011
+ if (variable.name.trim()) {
2012
+ context[variable.name] = this.convertVariableValue(variable.value, variable.type);
2013
+ }
2014
+ }
2015
+ return context;
2016
+ }
2017
+ buildTemplateData() {
2018
+ // For backward compatibility, return the same data as buildDataContext
2019
+ // since we've unified the variables
2020
+ return this.buildDataContext();
2021
+ }
2022
+ buildTemplateVariables() {
2023
+ const variables = {};
2024
+ for (const variable of this.templateVariables) {
2025
+ if (variable.name.trim()) {
2026
+ variables[variable.name] = this.convertVariableValue(variable.value, variable.type);
2027
+ }
2028
+ }
2029
+ return variables;
2030
+ }
2031
+ convertVariableValue(value, type) {
2032
+ switch (type) {
2033
+ case 'number':
2034
+ return parseFloat(value) || 0;
2035
+ case 'boolean':
2036
+ return value.toLowerCase() === 'true';
2037
+ case 'object':
2038
+ try {
2039
+ return JSON.parse(value);
2040
+ }
2041
+ catch {
2042
+ return value;
2043
+ }
2044
+ default:
2045
+ return value;
2046
+ }
2047
+ }
2048
+ /**
2049
+ * Adds a new empty agent variable to the collection.
2050
+ * Agent variables are passed to the agent during execution for dynamic content and template rendering.
2051
+ */
2052
+ addAgentVariable() {
2053
+ this.agentVariables.push({
2054
+ name: '',
2055
+ value: '',
2056
+ type: 'string'
2057
+ });
2058
+ }
2059
+ /**
2060
+ * Removes an agent variable at the specified index.
2061
+ * @param index - Zero-based index of the variable to remove
2062
+ */
2063
+ removeAgentVariable(index) {
2064
+ this.agentVariables.splice(index, 1);
2065
+ }
2066
+ /**
2067
+ * Adds a new empty template variable to the collection (prompt mode only).
2068
+ * Template variables are used for prompt template rendering.
2069
+ */
2070
+ addTemplateVariable() {
2071
+ this.templateVariables.push({
2072
+ name: '',
2073
+ value: '',
2074
+ type: 'string'
2075
+ });
2076
+ }
2077
+ /**
2078
+ * Removes a template variable at the specified index (prompt mode only).
2079
+ * @param index - Zero-based index of the variable to remove
2080
+ */
2081
+ removeTemplateVariable(index) {
2082
+ this.templateVariables.splice(index, 1);
2083
+ }
2084
+ /**
2085
+ * Clears the current conversation after user confirmation.
2086
+ * Resets both the message history and the current conversation ID.
2087
+ */
2088
+ clearConversation() {
2089
+ if (this.conversationMessages.length > 0) {
2090
+ if (confirm('Are you sure you want to clear the conversation?')) {
2091
+ this.conversationMessages = [];
2092
+ this.currentConversationId = null;
2093
+ }
2094
+ }
2095
+ }
2096
+ scrollToBottom() {
2097
+ try {
2098
+ if (this.messagesContainer) {
2099
+ this.messagesContainer.nativeElement.scrollTop = this.messagesContainer.nativeElement.scrollHeight;
2100
+ }
2101
+ }
2102
+ catch (err) {
2103
+ // Error scrolling to bottom
2104
+ }
2105
+ }
2106
+ generateMessageId() {
2107
+ return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2108
+ }
2109
+ // Saved conversations functionality
2110
+ loadSavedConversations() {
2111
+ try {
2112
+ const storageKey = this.getStorageKey();
2113
+ const saved = localStorage.getItem(storageKey);
2114
+ if (saved) {
2115
+ const parsedData = JSON.parse(saved);
2116
+ this.savedConversations = parsedData || [];
2117
+ // Convert date strings back to Date objects
2118
+ this.savedConversations.forEach(conv => {
2119
+ conv.createdAt = new Date(conv.createdAt);
2120
+ conv.updatedAt = new Date(conv.updatedAt);
2121
+ conv.messages.forEach(msg => {
2122
+ msg.timestamp = new Date(msg.timestamp);
2123
+ });
2124
+ });
2125
+ }
2126
+ else {
2127
+ this.savedConversations = [];
2128
+ }
2129
+ }
2130
+ catch (error) {
2131
+ // Error loading saved conversations
2132
+ this.savedConversations = [];
2133
+ }
2134
+ }
2135
+ /**
2136
+ * Gets the storage key for saved conversations based on entity type and ID
2137
+ */
2138
+ getStorageKey() {
2139
+ if (!this.entity) {
2140
+ return '';
2141
+ }
2142
+ const entityType = this.mode === 'agent' ? 'agent' : 'prompt';
2143
+ const entityId = this.entity.ID || 'unknown';
2144
+ return `mj_test_harness_${entityType}_${entityId}_conversations`;
2145
+ }
2146
+ /**
2147
+ * Saves the current conversation to localStorage with user-provided name.
2148
+ * Handles both creating new conversations and updating existing ones.
2149
+ * Automatically limits storage to 50 conversations to prevent excessive memory usage.
2150
+ *
2151
+ * @example
2152
+ * ```typescript
2153
+ * // User clicks save button
2154
+ * this.saveConversation(); // Prompts for name and saves
2155
+ * ```
2156
+ */
2157
+ saveConversation() {
2158
+ if (this.conversationMessages.length === 0) {
2159
+ MJNotificationService.Instance.CreateSimpleNotification('No messages to save', 'warning', 3000);
2160
+ return;
2161
+ }
2162
+ // If updating existing conversation, pre-fill the name
2163
+ if (this.currentConversationId) {
2164
+ const currentConv = this.savedConversations.find(c => c.id === this.currentConversationId);
2165
+ this.tempConversationName = currentConv ? currentConv.name : '';
2166
+ }
2167
+ else {
2168
+ this.tempConversationName = '';
2169
+ }
2170
+ this.showSaveDialog = true;
2171
+ // Focus on input after dialog renders
2172
+ setTimeout(() => {
2173
+ if (this.saveDialogInput && this.saveDialogInput.nativeElement) {
2174
+ const input = this.saveDialogInput.nativeElement;
2175
+ input.focus();
2176
+ input.select();
2177
+ }
2178
+ }, 100);
2179
+ }
2180
+ updateTempConversation() {
2181
+ this.tempConversationName = this.saveDialogInput?.nativeElement.value.trim() || '';
2182
+ }
2183
+ /**
2184
+ * Handles the save dialog confirmation
2185
+ */
2186
+ confirmSaveConversation() {
2187
+ // Use the temp name from the input
2188
+ const convoName = this.saveDialogInput?.nativeElement.value;
2189
+ const trimmedName = convoName?.trim() || '';
2190
+ if (!trimmedName) {
2191
+ return;
2192
+ }
2193
+ this.newConversationName = trimmedName;
2194
+ const conversation = {
2195
+ id: this.generateMessageId(),
2196
+ name: this.newConversationName.trim(),
2197
+ agentId: this.entity?.ID || '',
2198
+ agentName: this.getEntityName() || '',
2199
+ messages: [...this.conversationMessages],
2200
+ dataContext: this.buildDataContext(),
2201
+ templateData: this.mode === 'agent' ? this.buildTemplateData() : {},
2202
+ templateVariables: this.mode === 'prompt' ? this.buildTemplateVariables() : {},
2203
+ advancedParams: this.mode === 'prompt' ? this.advancedParams : undefined,
2204
+ selectedModelId: this.mode === 'prompt' ? this.selectedModelId : undefined,
2205
+ selectedVendorId: this.mode === 'prompt' ? this.selectedVendorId : undefined,
2206
+ skipValidation: this.mode === 'prompt' ? this.skipValidation : undefined,
2207
+ createdAt: new Date(),
2208
+ updatedAt: new Date()
2209
+ };
2210
+ if (this.currentConversationId) {
2211
+ // Update existing conversation
2212
+ const index = this.savedConversations.findIndex(c => c.id === this.currentConversationId);
2213
+ if (index >= 0) {
2214
+ conversation.id = this.currentConversationId;
2215
+ conversation.createdAt = this.savedConversations[index].createdAt;
2216
+ conversation.name = this.newConversationName.trim(); // Use the edited name
2217
+ this.savedConversations[index] = conversation;
2218
+ }
2219
+ else {
2220
+ // Current ID not found, treat as new
2221
+ this.savedConversations.unshift(conversation);
2222
+ this.currentConversationId = conversation.id;
2223
+ }
2224
+ }
2225
+ else {
2226
+ // Add new conversation
2227
+ this.savedConversations.unshift(conversation);
2228
+ this.currentConversationId = conversation.id;
2229
+ }
2230
+ // Limit to 50 saved conversations
2231
+ if (this.savedConversations.length > 50) {
2232
+ this.savedConversations = this.savedConversations.slice(0, 50);
2233
+ }
2234
+ // Save to localStorage
2235
+ this.saveConversationsToStorage();
2236
+ MJNotificationService.Instance.CreateSimpleNotification('Conversation saved successfully', 'success', 3000);
2237
+ this.showSaveDialog = false;
2238
+ this.newConversationName = '';
2239
+ this.tempConversationName = '';
2240
+ }
2241
+ /**
2242
+ * Cancels the save dialog
2243
+ */
2244
+ cancelSaveDialog() {
2245
+ this.showSaveDialog = false;
2246
+ this.newConversationName = '';
2247
+ this.tempConversationName = '';
2248
+ }
2249
+ // Removed debug methods - no longer needed with ngModel binding
2250
+ /**
2251
+ * Saves conversations to localStorage
2252
+ */
2253
+ saveConversationsToStorage() {
2254
+ try {
2255
+ const storageKey = this.getStorageKey();
2256
+ localStorage.setItem(storageKey, JSON.stringify(this.savedConversations));
2257
+ }
2258
+ catch (error) {
2259
+ // Error saving conversations
2260
+ throw error;
2261
+ }
2262
+ }
2263
+ autoSaveConversation() {
2264
+ if (this.currentConversationId && this.conversationMessages.length > 0) {
2265
+ const index = this.savedConversations.findIndex(c => c.id === this.currentConversationId);
2266
+ if (index >= 0) {
2267
+ this.savedConversations[index].messages = [...this.conversationMessages];
2268
+ this.savedConversations[index].dataContext = this.buildDataContext();
2269
+ if (this.mode === 'agent') {
2270
+ this.savedConversations[index].templateData = this.buildTemplateData();
2271
+ }
2272
+ else {
2273
+ this.savedConversations[index].templateVariables = this.buildTemplateVariables();
2274
+ this.savedConversations[index].advancedParams = this.advancedParams;
2275
+ this.savedConversations[index].selectedModelId = this.selectedModelId;
2276
+ this.savedConversations[index].selectedVendorId = this.selectedVendorId;
2277
+ this.savedConversations[index].skipValidation = this.skipValidation;
2278
+ }
2279
+ this.savedConversations[index].updatedAt = new Date();
2280
+ // Save to localStorage
2281
+ try {
2282
+ this.saveConversationsToStorage();
2283
+ }
2284
+ catch (error) {
2285
+ // Error auto-saving conversation
2286
+ }
2287
+ }
2288
+ }
2289
+ }
2290
+ loadConversation(conversation) {
2291
+ if (this.conversationMessages.length > 0) {
2292
+ this.pendingLoadConversation = conversation;
2293
+ this.showLoadConfirmDialog = true;
2294
+ return;
2295
+ }
2296
+ this.doLoadConversation(conversation);
2297
+ }
2298
+ /**
2299
+ * Confirms loading a conversation after dialog confirmation
2300
+ */
2301
+ confirmLoadConversation() {
2302
+ if (this.pendingLoadConversation) {
2303
+ this.doLoadConversation(this.pendingLoadConversation);
2304
+ this.showLoadConfirmDialog = false;
2305
+ this.pendingLoadConversation = null;
2306
+ }
2307
+ }
2308
+ /**
2309
+ * Cancels loading a conversation
2310
+ */
2311
+ cancelLoadConversation() {
2312
+ this.showLoadConfirmDialog = false;
2313
+ this.pendingLoadConversation = null;
2314
+ }
2315
+ /**
2316
+ * Actually loads the conversation
2317
+ */
2318
+ doLoadConversation(conversation) {
2319
+ this.conversationMessages = [...conversation.messages];
2320
+ this.currentConversationId = conversation.id;
2321
+ // Restore agent variables (unified from dataContext and templateData)
2322
+ this.agentVariables = [];
2323
+ const allVariables = { ...conversation.dataContext, ...(conversation.templateData || {}) };
2324
+ for (const [key, value] of Object.entries(allVariables)) {
2325
+ this.agentVariables.push({
2326
+ name: key,
2327
+ value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2328
+ type: typeof value === 'boolean' ? 'boolean' :
2329
+ typeof value === 'number' ? 'number' :
2330
+ typeof value === 'object' ? 'object' : 'string'
2331
+ });
2332
+ }
2333
+ // Restore template variables for prompt mode
2334
+ if (this.mode === 'prompt') {
2335
+ this.templateVariables = [];
2336
+ for (const [key, value] of Object.entries(conversation.templateVariables || {})) {
2337
+ this.templateVariables.push({
2338
+ name: key,
2339
+ value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2340
+ type: typeof value === 'boolean' ? 'boolean' :
2341
+ typeof value === 'number' ? 'number' :
2342
+ typeof value === 'object' ? 'object' : 'string'
2343
+ });
2344
+ }
2345
+ // Restore advanced parameters
2346
+ if (conversation.advancedParams) {
2347
+ this.advancedParams = { ...this.advancedParams, ...conversation.advancedParams };
2348
+ // Restore stop sequences text
2349
+ this.stopSequencesText = this.advancedParams.stopSequences?.join(', ') || '';
2350
+ }
2351
+ // Restore model and vendor selection
2352
+ if (conversation.selectedModelId !== undefined) {
2353
+ this.selectedModelId = conversation.selectedModelId;
2354
+ // Trigger vendor loading if model is selected
2355
+ if (this.selectedModelId) {
2356
+ this.onModelSelectionChange();
2357
+ // After vendors load, restore vendor selection
2358
+ setTimeout(() => {
2359
+ if (conversation.selectedVendorId !== undefined) {
2360
+ this.selectedVendorId = conversation.selectedVendorId;
2361
+ }
2362
+ }, 100);
2363
+ }
2364
+ }
2365
+ if (conversation.selectedVendorId !== undefined && !this.selectedModelId) {
2366
+ this.selectedVendorId = conversation.selectedVendorId;
2367
+ }
2368
+ // Restore skip validation setting
2369
+ if (conversation.skipValidation !== undefined) {
2370
+ this.skipValidation = conversation.skipValidation;
2371
+ }
2372
+ }
2373
+ // Load execution data from the last assistant message if available
2374
+ const lastAssistantMessage = this.conversationMessages
2375
+ .filter(m => m.role === 'assistant' && m.executionData)
2376
+ .pop();
2377
+ if (lastAssistantMessage && lastAssistantMessage.executionData) {
2378
+ this.currentExecutionData = lastAssistantMessage.executionData;
2379
+ this.executionMonitorMode = 'historical';
2380
+ // If execution monitor tab is active, ensure it updates
2381
+ if (this.activeTab === 'executionMonitor') {
2382
+ // Force change detection
2383
+ setTimeout(() => {
2384
+ this.currentExecutionData = { ...lastAssistantMessage.executionData };
2385
+ }, 0);
2386
+ }
2387
+ }
2388
+ else {
2389
+ // Clear execution data if no execution found
2390
+ this.currentExecutionData = null;
2391
+ this.executionMonitorMode = 'historical';
2392
+ }
2393
+ this.scrollNeeded = true;
2394
+ MJNotificationService.Instance.CreateSimpleNotification('Conversation loaded', 'info', 3000);
2395
+ }
2396
+ async deleteConversation(conversation, event) {
2397
+ event.stopPropagation();
2398
+ if (confirm(`Delete conversation "${conversation.name}"?`)) {
2399
+ const index = this.savedConversations.findIndex(c => c.id === conversation.id);
2400
+ if (index >= 0) {
2401
+ this.savedConversations.splice(index, 1);
2402
+ // Save to localStorage
2403
+ this.saveConversationsToStorage();
2404
+ if (this.currentConversationId === conversation.id) {
2405
+ this.currentConversationId = null;
2406
+ }
2407
+ MJNotificationService.Instance.CreateSimpleNotification('Conversation deleted', 'info', 3000);
2408
+ }
2409
+ }
2410
+ }
2411
+ exportConversation() {
2412
+ if (this.conversationMessages.length === 0) {
2413
+ MJNotificationService.Instance.CreateSimpleNotification('No messages to export', 'warning', 3000);
2414
+ return;
2415
+ }
2416
+ const exportData = {
2417
+ entity: {
2418
+ id: this.entity?.ID,
2419
+ name: this.getEntityName(),
2420
+ type: this.mode
2421
+ },
2422
+ messages: this.conversationMessages,
2423
+ dataContext: this.buildDataContext(),
2424
+ exportedAt: new Date().toISOString()
2425
+ };
2426
+ // Add mode-specific data
2427
+ if (this.mode === 'agent') {
2428
+ exportData.templateData = this.buildTemplateData();
2429
+ }
2430
+ else {
2431
+ exportData.templateVariables = this.buildTemplateVariables();
2432
+ exportData.modelSettings = {
2433
+ modelId: this.selectedModelId,
2434
+ vendorId: this.selectedVendorId,
2435
+ maxTokens: this.maxTokens,
2436
+ skipValidation: this.skipValidation
2437
+ };
2438
+ exportData.advancedParams = this.advancedParams;
2439
+ }
2440
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
2441
+ const url = window.URL.createObjectURL(blob);
2442
+ const link = document.createElement('a');
2443
+ link.href = url;
2444
+ const entityName = this.getEntityName()?.replace(/[^a-zA-Z0-9]/g, '-') || 'conversation';
2445
+ link.download = `${this.mode}-conversation-${entityName}-${new Date().toISOString().slice(0, 10)}.json`;
2446
+ link.click();
2447
+ window.URL.revokeObjectURL(url);
2448
+ }
2449
+ importConversation() {
2450
+ this.fileInput.nativeElement.click();
2451
+ }
2452
+ onFileSelected(event) {
2453
+ const input = event.target;
2454
+ if (input.files && input.files[0]) {
2455
+ const file = input.files[0];
2456
+ const reader = new FileReader();
2457
+ reader.onload = (e) => {
2458
+ try {
2459
+ const data = JSON.parse(e.target?.result);
2460
+ // Validate and import
2461
+ if (data.messages && Array.isArray(data.messages)) {
2462
+ if (this.conversationMessages.length > 0) {
2463
+ if (!confirm('Importing will replace the current conversation. Continue?')) {
2464
+ return;
2465
+ }
2466
+ }
2467
+ this.conversationMessages = data.messages.map((msg) => ({
2468
+ ...msg,
2469
+ timestamp: new Date(msg.timestamp)
2470
+ }));
2471
+ // Import agent variables (unified from dataContext and templateData)
2472
+ this.agentVariables = [];
2473
+ const importedVariables = { ...(data.dataContext || {}), ...(data.templateData || {}) };
2474
+ for (const [key, value] of Object.entries(importedVariables)) {
2475
+ this.agentVariables.push({
2476
+ name: key,
2477
+ value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2478
+ type: typeof value === 'boolean' ? 'boolean' :
2479
+ typeof value === 'number' ? 'number' :
2480
+ typeof value === 'object' ? 'object' : 'string'
2481
+ });
2482
+ }
2483
+ // Template data is already imported into agentVariables above
2484
+ // Import advanced parameters if in prompt mode
2485
+ if (this.mode === 'prompt' && data.advancedParams) {
2486
+ this.advancedParams = { ...this.advancedParams, ...data.advancedParams };
2487
+ this.stopSequencesText = this.advancedParams.stopSequences?.join(', ') || '';
2488
+ }
2489
+ this.currentConversationId = null;
2490
+ this.scrollNeeded = true;
2491
+ MJNotificationService.Instance.CreateSimpleNotification('Conversation imported successfully', 'success', 3000);
2492
+ }
2493
+ else {
2494
+ throw new Error('Invalid conversation format');
2495
+ }
2496
+ }
2497
+ catch (error) {
2498
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to import conversation: ' + error.message, 'error', 5000);
2499
+ }
2500
+ // Reset file input
2501
+ input.value = '';
2502
+ };
2503
+ reader.readAsText(file);
2504
+ }
2505
+ }
2506
+ /**
2507
+ * Handles keyboard input in the message textarea.
2508
+ * Sends message on Enter (without Shift) and allows multi-line input with Shift+Enter.
2509
+ * @param event - Keyboard event from the textarea
2510
+ */
2511
+ handleKeyPress(event) {
2512
+ if (event.key === 'Enter' && !event.shiftKey) {
2513
+ event.preventDefault();
2514
+ this.sendMessage();
2515
+ }
2516
+ }
2517
+ /**
2518
+ * Generates CSS class names for message display based on message properties.
2519
+ * @param message - The conversation message to generate classes for
2520
+ * @returns Space-separated CSS class string for styling
2521
+ */
2522
+ getMessageClass(message) {
2523
+ return `message message-${message.role}${message.isStreaming ? ' streaming' : ''}${message.error ? ' error' : ''}`;
2524
+ }
2525
+ /**
2526
+ * Formats a Date object into a user-friendly time string.
2527
+ * @param date - Date object to format
2528
+ * @returns Formatted time string in HH:MM format
2529
+ */
2530
+ formatTimestamp(date) {
2531
+ return date.toLocaleTimeString('en-US', {
2532
+ hour: '2-digit',
2533
+ minute: '2-digit'
2534
+ });
2535
+ }
2536
+ /**
2537
+ * Formats execution time from milliseconds into a human-readable string.
2538
+ * Automatically selects appropriate units (minutes, seconds, or milliseconds).
2539
+ * @param milliseconds - Execution time in milliseconds
2540
+ * @returns Formatted time string (e.g., "2m 30.5s", "1.23s", "500ms")
2541
+ */
2542
+ formatExecutionTime(milliseconds) {
2543
+ if (milliseconds >= 60000) {
2544
+ const minutes = Math.floor(milliseconds / 60000);
2545
+ const seconds = ((milliseconds % 60000) / 1000).toFixed(2);
2546
+ return `${minutes}m ${seconds}s`;
2547
+ }
2548
+ else if (milliseconds >= 1000) {
2549
+ return `${(milliseconds / 1000).toFixed(2)}s`;
2550
+ }
2551
+ else {
2552
+ return `${milliseconds}ms`;
2553
+ }
2554
+ }
2555
+ formatElapsedTime(milliseconds) {
2556
+ const seconds = Math.floor(milliseconds / 1000);
2557
+ const ms = milliseconds % 1000;
2558
+ if (seconds > 0) {
2559
+ return `${seconds}.${Math.floor(ms / 100)}s`;
2560
+ }
2561
+ else {
2562
+ return `${ms}ms`;
2563
+ }
2564
+ }
2565
+ startElapsedTimeCounter(message) {
2566
+ if (this.elapsedTimeInterval) {
2567
+ clearInterval(this.elapsedTimeInterval);
2568
+ }
2569
+ this.elapsedTimeInterval = setInterval(() => {
2570
+ if (message.streamingStartTime && message.isStreaming) {
2571
+ message.elapsedTime = Date.now() - message.streamingStartTime;
2572
+ }
2573
+ else {
2574
+ clearInterval(this.elapsedTimeInterval);
2575
+ this.elapsedTimeInterval = null;
2576
+ }
2577
+ }, 100); // Update every 100ms for smooth counter
2578
+ }
2579
+ /**
2580
+ * Shows the raw JSON dialog for a specific message
2581
+ * @param message - The message to show raw JSON for
2582
+ */
2583
+ showRawJsonDialog(message) {
2584
+ if (message.rawContent) {
2585
+ // Close any existing window
2586
+ if (this.jsonWindowRef) {
2587
+ this.jsonWindowRef.close();
2588
+ }
2589
+ try {
2590
+ // Try to parse and format the JSON
2591
+ const parsed = JSON.parse(message.rawContent);
2592
+ // If this is an agent result with execution tree, enhance the display
2593
+ if (parsed.executionTree) {
2594
+ this.currentJsonContent = JSON.stringify({
2595
+ ...parsed,
2596
+ _executionTreeSummary: this.summarizeExecutionTree(parsed.executionTree)
2597
+ }, null, 2);
2598
+ }
2599
+ else {
2600
+ this.currentJsonContent = JSON.stringify(parsed, null, 2);
2601
+ }
2602
+ }
2603
+ catch {
2604
+ // If not valid JSON, show as-is
2605
+ this.currentJsonContent = message.rawContent;
2606
+ }
2607
+ // Import the JsonViewerWindowComponent dynamically
2608
+ import('./json-viewer-window.component').then(({ JsonViewerWindowComponent }) => {
2609
+ // Create the window using WindowService
2610
+ // Calculate center position accounting for scroll
2611
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2612
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
2613
+ const centerTop = Math.max(50, (window.innerHeight - 700) / 2 + scrollTop);
2614
+ const centerLeft = Math.max(50, (window.innerWidth - 900) / 2 + scrollLeft);
2615
+ this.jsonWindowRef = this.windowService.open({
2616
+ title: 'Raw JSON Response',
2617
+ content: JsonViewerWindowComponent,
2618
+ width: 900,
2619
+ height: 700,
2620
+ minWidth: 600,
2621
+ minHeight: 400,
2622
+ resizable: true,
2623
+ draggable: true,
2624
+ top: centerTop,
2625
+ left: centerLeft
2626
+ });
2627
+ // Pass the JSON content to the component
2628
+ const windowContent = this.jsonWindowRef.content.instance;
2629
+ windowContent.jsonContent = this.currentJsonContent;
2630
+ // Handle window close
2631
+ this.jsonWindowRef.result.subscribe((result) => {
2632
+ this.jsonWindowRef = null;
2633
+ this.currentJsonContent = '';
2634
+ });
2635
+ });
2636
+ }
2637
+ }
2638
+ /**
2639
+ * Creates a summary of the execution tree for easier viewing
2640
+ * @param executionTree - The execution tree to summarize
2641
+ * @returns A human-readable summary of the execution
2642
+ */
2643
+ summarizeExecutionTree(executionTree) {
2644
+ if (!executionTree || !Array.isArray(executionTree))
2645
+ return null;
2646
+ const summary = {
2647
+ totalSteps: 0,
2648
+ stepsByType: {},
2649
+ hierarchy: []
2650
+ };
2651
+ const processNode = (node, level = 0) => {
2652
+ summary.totalSteps++;
2653
+ // Count by type
2654
+ const type = node.executionType || 'unknown';
2655
+ summary.stepsByType[type] = (summary.stepsByType[type] || 0) + 1;
2656
+ // Create hierarchical summary
2657
+ const nodeSummary = {
2658
+ level,
2659
+ stepName: node.step?.StepName || 'Unknown Step',
2660
+ type: node.executionType,
2661
+ duration: node.durationMs ? `${node.durationMs}ms` : 'N/A',
2662
+ status: node.step?.Status || 'Unknown',
2663
+ agentPath: node.agentHierarchy?.join(' → ') || '',
2664
+ hasInputData: !!node.inputData,
2665
+ hasOutputData: !!node.outputData
2666
+ };
2667
+ summary.hierarchy.push(nodeSummary);
2668
+ // Process children recursively
2669
+ if (node.children && node.children.length > 0) {
2670
+ node.children.forEach((child) => processNode(child, level + 1));
2671
+ }
2672
+ };
2673
+ executionTree.forEach(node => processNode(node));
2674
+ return summary;
2675
+ }
2676
+ /**
2677
+ * Copies the message content to clipboard.
2678
+ * @param message - The message to copy
2679
+ */
2680
+ async copyMessage(message) {
2681
+ try {
2682
+ // Convert content to string if needed
2683
+ const content = typeof message.content === 'string'
2684
+ ? message.content
2685
+ : JSON.stringify(message.content);
2686
+ await navigator.clipboard.writeText(content);
2687
+ MJNotificationService.Instance.CreateSimpleNotification('Message copied to clipboard', 'success', 2000);
2688
+ }
2689
+ catch (error) {
2690
+ MJNotificationService.Instance.CreateSimpleNotification('Failed to copy message', 'error', 3000);
2691
+ }
2692
+ }
2693
+ /**
2694
+ * Focuses the message input textarea.
2695
+ */
2696
+ focusMessageInput() {
2697
+ if (this.messageInput) {
2698
+ setTimeout(() => {
2699
+ this.messageInput.focus();
2700
+ }, 100);
2701
+ }
2702
+ }
2703
+ /**
2704
+ * Determines if the provided content is valid JSON.
2705
+ * @param content - String content to test
2706
+ * @returns True if content can be parsed as JSON, false otherwise
2707
+ */
2708
+ isJsonContent(content) {
2709
+ if (!content)
2710
+ return false;
2711
+ try {
2712
+ JSON.parse(content);
2713
+ return true;
2714
+ }
2715
+ catch {
2716
+ return false;
2717
+ }
2718
+ }
2719
+ /**
2720
+ * Formats JSON content with proper indentation for display.
2721
+ * @param content - JSON string to format
2722
+ * @returns Formatted JSON string or original content if parsing fails
2723
+ */
2724
+ formatJson(content) {
2725
+ try {
2726
+ return JSON.stringify(JSON.parse(content), null, 2);
2727
+ }
2728
+ catch {
2729
+ return content;
2730
+ }
2731
+ }
2732
+ /**
2733
+ * Automatically detects the content type of a message for appropriate rendering.
2734
+ * Uses pattern matching to identify JSON, Markdown, or plain text content.
2735
+ * @param content - Content string to analyze
2736
+ * @returns Detected content type ('markdown', 'json', or 'text')
2737
+ */
2738
+ detectContentType(content) {
2739
+ if (!content)
2740
+ return 'text';
2741
+ // Check if it's JSON
2742
+ if (this.isJsonContent(content)) {
2743
+ return 'json';
2744
+ }
2745
+ // Check for markdown indicators
2746
+ const markdownPatterns = [
2747
+ /^#{1,6}\s/m, // Headers
2748
+ /\*\*[^*]+\*\*/, // Bold
2749
+ /\*[^*]+\*/, // Italic
2750
+ /\[([^\]]+)\]\(([^)]+)\)/, // Links
2751
+ /```[\s\S]*?```/, // Code blocks
2752
+ /`[^`]+`/, // Inline code
2753
+ /^[-*+]\s/m, // Lists
2754
+ /^\d+\.\s/m // Numbered lists
2755
+ ];
2756
+ if (markdownPatterns.some(pattern => pattern.test(content))) {
2757
+ return 'markdown';
2758
+ }
2759
+ return 'text';
2760
+ }
2761
+ renderMarkdown(content) {
2762
+ // Basic markdown to HTML conversion with improved formatting
2763
+ let html = content;
2764
+ // Escape HTML first
2765
+ html = html.replace(/&/g, '&amp;')
2766
+ .replace(/</g, '&lt;')
2767
+ .replace(/>/g, '&gt;');
2768
+ // Code blocks with language support
2769
+ html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
2770
+ const language = lang || '';
2771
+ const className = language ? ` class="language-${language}"` : '';
2772
+ return `<pre class="code-block"><code${className}>${code.trim()}</code></pre>`;
2773
+ });
2774
+ // Regular code blocks without language
2775
+ html = html.replace(/```([\s\S]*?)```/g, '<pre class="code-block"><code>$1</code></pre>');
2776
+ // Inline code
2777
+ html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
2778
+ // Headers
2779
+ html = html.replace(/^### (.+)$/gm, '<h3 class="markdown-h3">$1</h3>');
2780
+ html = html.replace(/^## (.+)$/gm, '<h2 class="markdown-h2">$1</h2>');
2781
+ html = html.replace(/^# (.+)$/gm, '<h1 class="markdown-h1">$1</h1>');
2782
+ // Bold
2783
+ html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
2784
+ // Italic
2785
+ html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
2786
+ // Links
2787
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" class="markdown-link">$1</a>');
2788
+ // Unordered lists
2789
+ html = html.replace(/^[*-+] (.+)$/gm, '<li>$1</li>');
2790
+ html = html.replace(/((?:<li>.*<\/li>\s*)+)/g, '<ul class="markdown-list">$1</ul>');
2791
+ // Numbered lists
2792
+ html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
2793
+ html = html.replace(/((?:<li>.*<\/li>\s*)+)/g, '<ol class="markdown-list">$1</ol>');
2794
+ // Paragraphs and line breaks - improved handling
2795
+ html = html.replace(/\n\s*\n/g, '</p><p class="markdown-paragraph">');
2796
+ html = html.replace(/\n/g, '<br>');
2797
+ html = `<p class="markdown-paragraph">${html}</p>`;
2798
+ // Clean up empty paragraphs
2799
+ html = html.replace(/<p class="markdown-paragraph"><\/p>/g, '');
2800
+ return this.sanitizer.bypassSecurityTrustHtml(`<div class="markdown-content">${html}</div>`);
2801
+ }
2802
+ getFormattedContent(message) {
2803
+ const content = message.content;
2804
+ const contentStr = typeof content === 'string' ? content : String(content);
2805
+ const contentType = this.detectContentType(contentStr);
2806
+ if (contentType === 'json') {
2807
+ // Try to extract human-readable content from JSON
2808
+ const extractedContent = this.extractHumanReadableContent(contentStr);
2809
+ if (extractedContent) {
2810
+ // Just render the extracted content
2811
+ return this.renderMarkdown(extractedContent);
2812
+ }
2813
+ else {
2814
+ // Fallback to inline code editor for JSON display
2815
+ return this.renderJsonWithCodeEditor(contentStr);
2816
+ }
2817
+ }
2818
+ else if (contentType === 'markdown') {
2819
+ return this.renderMarkdown(contentStr);
2820
+ }
2821
+ else {
2822
+ // Convert plain text to markdown for consistent formatting
2823
+ return this.renderMarkdown(contentStr);
2824
+ }
2825
+ }
2826
+ /**
2827
+ * Renders JSON content using an inline code editor component
2828
+ */
2829
+ renderJsonWithCodeEditor(jsonStr) {
2830
+ try {
2831
+ // Format the JSON for display
2832
+ const formattedJson = this.formatJson(jsonStr);
2833
+ // Generate a unique ID for this editor instance
2834
+ const editorId = `json-editor-${this.generateMessageId()}`;
2835
+ // Create the HTML with a placeholder div that we'll replace with the code editor
2836
+ const html = `
2837
+ <div class="inline-json-editor" data-editor-id="${editorId}" data-json-content="${this.escapeHtmlAttribute(formattedJson)}">
2838
+ <div class="json-editor-container" style="height: 300px; width: 100%; border: 1px solid #e0e0e0; border-radius: 4px; overflow: hidden;">
2839
+ <pre style="margin: 0; padding: 12px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 13px; overflow: auto; height: 100%;">${this.escapeHtml(formattedJson)}</pre>
2840
+ </div>
2841
+ </div>
2842
+ `;
2843
+ // Note: In a real implementation, we would need to dynamically create the code editor component
2844
+ // For now, we'll use a styled pre tag as a fallback
2845
+ return this.sanitizer.bypassSecurityTrustHtml(html);
2846
+ }
2847
+ catch {
2848
+ // If JSON parsing fails, show as plain text
2849
+ const html = `<pre style="margin: 0; padding: 12px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 13px; overflow: auto; background: #f8f9fa; border-radius: 4px;">${this.escapeHtml(jsonStr)}</pre>`;
2850
+ return this.sanitizer.bypassSecurityTrustHtml(html);
2851
+ }
2852
+ }
2853
+ /**
2854
+ * Escapes HTML content for use in attributes
2855
+ */
2856
+ escapeHtmlAttribute(text) {
2857
+ return text
2858
+ .replace(/&/g, '&amp;')
2859
+ .replace(/"/g, '&quot;')
2860
+ .replace(/'/g, '&#39;')
2861
+ .replace(/</g, '&lt;')
2862
+ .replace(/>/g, '&gt;');
2863
+ }
2864
+ /**
2865
+ * Renders JSON content with human-readable content prominently displayed
2866
+ * and raw JSON in a collapsible section below with proper text wrapping.
2867
+ */
2868
+ /**
2869
+ * Gets a summary of the execution for tooltip display
2870
+ * @param message - The message to get execution summary for
2871
+ * @returns A brief summary of the execution steps
2872
+ */
2873
+ getExecutionSummary(message) {
2874
+ if (!message.rawContent)
2875
+ return '';
2876
+ try {
2877
+ const parsed = JSON.parse(message.rawContent);
2878
+ if (parsed.executionTree) {
2879
+ const summary = this.summarizeExecutionTree(parsed.executionTree);
2880
+ const stepCounts = Object.entries(summary.stepsByType)
2881
+ .map(([type, count]) => `${count} ${type}`)
2882
+ .join(', ');
2883
+ return `${summary.totalSteps} steps executed (${stepCounts})`;
2884
+ }
2885
+ }
2886
+ catch {
2887
+ // Ignore parse errors
2888
+ }
2889
+ return '';
2890
+ }
2891
+ /**
2892
+ * Closes the JSON dialog
2893
+ */
2894
+ closeJsonDialog() {
2895
+ if (this.jsonWindowRef) {
2896
+ this.jsonWindowRef.close();
2897
+ this.jsonWindowRef = null;
2898
+ }
2899
+ this.showJsonDialog = false;
2900
+ this.currentJsonContent = '';
2901
+ }
2902
+ /**
2903
+ * Copies the JSON content to clipboard
2904
+ */
2905
+ copyJsonContent() {
2906
+ if (this.currentJsonContent) {
2907
+ navigator.clipboard.writeText(this.currentJsonContent).then(() => {
2908
+ // Success - JSON copied
2909
+ }).catch((err) => {
2910
+ // Error copying
2911
+ });
2912
+ }
2913
+ }
2914
+ /**
2915
+ * Extracts human-readable content from JSON responses.
2916
+ * Prioritizes userMessage field first, then checks other common fields.
2917
+ * @param jsonStr - JSON string to extract content from
2918
+ * @returns Extracted human-readable content or null if none found
2919
+ */
2920
+ extractHumanReadableContent(jsonStr) {
2921
+ try {
2922
+ const parsed = JSON.parse(jsonStr);
2923
+ // Priority 1: Always check userMessage first, regardless of taskComplete status
2924
+ if (parsed.userMessage && typeof parsed.userMessage === 'string' && parsed.userMessage.trim()) {
2925
+ return parsed.userMessage;
2926
+ }
2927
+ // Priority 2: Check for nested userMessage in nextStep or other objects
2928
+ if (parsed.nextStep && parsed.nextStep.userMessage &&
2929
+ typeof parsed.nextStep.userMessage === 'string' && parsed.nextStep.userMessage.trim()) {
2930
+ return parsed.nextStep.userMessage;
2931
+ }
2932
+ // Priority 3: Other common human-readable fields
2933
+ const contentFields = [
2934
+ 'message', 'content', 'response', 'text', 'output',
2935
+ 'result', 'answer', 'reply', 'description', 'summary'
2936
+ ];
2937
+ for (const field of contentFields) {
2938
+ if (parsed[field] && typeof parsed[field] === 'string' && parsed[field].trim()) {
2939
+ return parsed[field];
2940
+ }
2941
+ }
2942
+ // Priority 4: Check nested objects for content
2943
+ for (const key of Object.keys(parsed)) {
2944
+ if (typeof parsed[key] === 'object' && parsed[key] !== null) {
2945
+ for (const field of ['userMessage', 'message', 'content']) {
2946
+ if (parsed[key][field] && typeof parsed[key][field] === 'string' && parsed[key][field].trim()) {
2947
+ return parsed[key][field];
2948
+ }
2949
+ }
2950
+ }
2951
+ }
2952
+ // Priority 5: If it's a simple string value, return it
2953
+ if (typeof parsed === 'string' && parsed.trim()) {
2954
+ return parsed;
2955
+ }
2956
+ // Priority 6: If it's an object with a single string property, consider returning it
2957
+ const keys = Object.keys(parsed);
2958
+ if (keys.length === 1 && typeof parsed[keys[0]] === 'string' && parsed[keys[0]].trim()) {
2959
+ return parsed[keys[0]];
2960
+ }
2961
+ return null;
2962
+ }
2963
+ catch {
2964
+ return null;
2965
+ }
2966
+ }
2967
+ /**
2968
+ * Checks if a message contains JSON content that has extractable human-readable content.
2969
+ * Used to determine if the raw toggle should be shown.
2970
+ * @param message - The conversation message to check
2971
+ * @returns True if the message has extractable content different from raw JSON
2972
+ */
2973
+ hasExtractableContent(message) {
2974
+ if (!message.content || message.role === 'user') {
2975
+ return false;
2976
+ }
2977
+ const contentStr = typeof message.content === 'string' ? message.content : String(message.content);
2978
+ if (!this.isJsonContent(contentStr)) {
2979
+ return false;
2980
+ }
2981
+ const extractedContent = this.extractHumanReadableContent(contentStr);
2982
+ return extractedContent !== null && extractedContent !== contentStr;
2983
+ }
2984
+ /**
2985
+ * Determines whether to show the raw toggle button for a message.
2986
+ * Combines the logic for raw content availability and extractable content.
2987
+ * @param message - The conversation message to check
2988
+ * @returns True if the raw toggle should be displayed
2989
+ */
2990
+ showRawToggle(message) {
2991
+ return (message.rawContent && !message.isStreaming) || this.hasExtractableContent(message);
2992
+ }
2993
+ escapeHtml(text) {
2994
+ const div = document.createElement('div');
2995
+ div.textContent = text;
2996
+ return div.innerHTML;
2997
+ }
2998
+ /**
2999
+ * Formats JSON for proper display with text wrapping instead of wide code blocks.
3000
+ * Creates a structured, readable format that respects container width.
3001
+ */
3002
+ formatJsonForDisplay(jsonStr) {
3003
+ try {
3004
+ const parsed = JSON.parse(jsonStr);
3005
+ return this.createJsonDisplayHtml(parsed, 0);
3006
+ }
3007
+ catch {
3008
+ // If JSON parsing fails, escape and display as-is with wrapping
3009
+ return `<div class="json-fallback">${this.escapeHtml(jsonStr)}</div>`;
3010
+ }
3011
+ }
3012
+ /**
3013
+ * Creates formatted HTML for JSON display with proper indentation and wrapping.
3014
+ */
3015
+ createJsonDisplayHtml(obj, depth = 0) {
3016
+ const indent = ' '.repeat(depth);
3017
+ const nextIndent = ' '.repeat(depth + 1);
3018
+ if (obj === null)
3019
+ return `<span class="json-null">null</span>`;
3020
+ if (typeof obj === 'boolean')
3021
+ return `<span class="json-boolean">${obj}</span>`;
3022
+ if (typeof obj === 'number')
3023
+ return `<span class="json-number">${obj}</span>`;
3024
+ if (typeof obj === 'string') {
3025
+ const escaped = this.escapeHtml(obj);
3026
+ return `<span class="json-string">"<span class="json-string-content">${escaped}</span>"</span>`;
3027
+ }
3028
+ if (Array.isArray(obj)) {
3029
+ if (obj.length === 0)
3030
+ return '<span class="json-bracket">[]</span>';
3031
+ const items = obj.map(item => `<div class="json-array-item">${nextIndent}${this.createJsonDisplayHtml(item, depth + 1)}</div>`).join(',\n');
3032
+ return `<span class="json-bracket">[</span>\n${items}\n<div class="json-indent">${indent}</div><span class="json-bracket">]</span>`;
3033
+ }
3034
+ if (typeof obj === 'object') {
3035
+ const keys = Object.keys(obj);
3036
+ if (keys.length === 0)
3037
+ return '<span class="json-bracket">{}</span>';
3038
+ const properties = keys.map(key => {
3039
+ const escapedKey = this.escapeHtml(key);
3040
+ const value = this.createJsonDisplayHtml(obj[key], depth + 1);
3041
+ return `<div class="json-property">${nextIndent}<span class="json-key">"${escapedKey}"</span><span class="json-colon">:</span> ${value}</div>`;
3042
+ }).join(',\n');
3043
+ return `<span class="json-bracket">{</span>\n${properties}\n<div class="json-indent">${indent}</div><span class="json-bracket">}</span>`;
3044
+ }
3045
+ return String(obj);
3046
+ }
3047
+ /**
3048
+ * Type guard to check if entity is an AI Agent
3049
+ */
3050
+ isAgentEntity(entity) {
3051
+ // Check using the EntityInfo property from BaseEntity
3052
+ return entity && entity.EntityInfo && entity.EntityInfo.Name === 'AI Agents';
3053
+ }
3054
+ /**
3055
+ * Type guard to check if entity is an AI Prompt
3056
+ */
3057
+ isPromptEntity(entity) {
3058
+ // Check using the EntityInfo property from BaseEntity
3059
+ const result = entity && entity.EntityInfo && entity.EntityInfo.Name === 'AI Prompts';
3060
+ return result;
3061
+ }
3062
+ /**
3063
+ * Gets the display name of the current entity
3064
+ */
3065
+ getEntityName() {
3066
+ if (!this.entity)
3067
+ return '';
3068
+ return this.entity.Name || 'Untitled';
3069
+ }
3070
+ /**
3071
+ * Updates stop sequences from the textarea input
3072
+ */
3073
+ updateStopSequences() {
3074
+ if (this.stopSequencesText.trim() === '') {
3075
+ this.advancedParams.stopSequences = [];
3076
+ }
3077
+ else {
3078
+ // Split by comma and trim each sequence
3079
+ this.advancedParams.stopSequences = this.stopSequencesText
3080
+ .split(',')
3081
+ .map(s => s.trim())
3082
+ .filter(s => s.length > 0);
3083
+ }
3084
+ }
3085
+ /**
3086
+ * Toggles the advanced parameters expansion panel
3087
+ */
3088
+ toggleAdvancedParams() {
3089
+ this.advancedParamsExpanded = !this.advancedParamsExpanded;
3090
+ }
3091
+ static { this.ɵfac = function AITestHarnessComponent_Factory(t) { return new (t || AITestHarnessComponent)(i0.ɵɵdirectiveInject(i1.DomSanitizer), i0.ɵɵdirectiveInject(i2.WindowService), i0.ɵɵdirectiveInject(i0.ViewContainerRef), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); }; }
3092
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AITestHarnessComponent, selectors: [["mj-ai-test-harness"]], viewQuery: function AITestHarnessComponent_Query(rf, ctx) { if (rf & 1) {
3093
+ i0.ɵɵviewQuery(_c0, 5);
3094
+ i0.ɵɵviewQuery(_c1, 5);
3095
+ i0.ɵɵviewQuery(_c2, 5);
3096
+ i0.ɵɵviewQuery(_c3, 5);
3097
+ } if (rf & 2) {
3098
+ let _t;
3099
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.messagesContainer = _t.first);
3100
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.fileInput = _t.first);
3101
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.messageInput = _t.first);
3102
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.saveDialogInput = _t.first);
3103
+ } }, inputs: { mode: "mode", entity: "entity", aiAgent: "aiAgent", isVisible: "isVisible" }, outputs: { visibilityChange: "visibilityChange" }, features: [i0.ɵɵNgOnChangesFeature], decls: 3, vars: 3, consts: [["fileInput", ""], ["messagesContainer", ""], ["messageInput", ""], ["saveDialogInput", ""], ["class", "harness-container", "kendoWindowContainer", "", 4, "ngIf"], [1, "save-dialog", 3, "title", "width"], [1, "confirm-dialog", 3, "title", "width"], ["kendoWindowContainer", "", 1, "harness-container"], [1, "harness-header"], [1, "agent-info"], [1, "harness-body"], ["orientation", "horizontal", 2, "height", "100%", "width", "100%"], [1, "chat-area"], [1, "agent-details"], [1, "fa-solid"], [1, "header-actions"], ["title", "Clear conversation", 1, "custom-button", 3, "click", "disabled"], [1, "fa-solid", "fa-broom"], ["title", "Save conversation", 1, "custom-button", 3, "click", "disabled"], [1, "fa-solid", "fa-save"], ["title", "Export conversation", 1, "custom-button", 3, "click", "disabled"], [1, "fa-solid", "fa-download"], ["title", "Import conversation", 1, "custom-button", 3, "click"], [1, "fa-solid", "fa-upload"], ["type", "file", "accept", ".json", 2, "display", "none", 3, "change"], ["title", "Toggle sidebar", 1, "custom-button", "icon-only", 3, "click"], [3, "collapsible", "resizable", "min"], [1, "messages-container"], [1, "empty-state"], [3, "ngClass"], [1, "input-area"], [1, "input-container"], ["placeholder", "Type your message here... (Press Enter to send, Shift+Enter for new line)", 1, "message-input", 3, "ngModelChange", "keypress", "ngModel", "rows", "disabled"], ["title", "Send message", 1, "send-button", 3, "click", "disabled"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "fa-solid", "fa-paper-plane"], [3, "collapsible", "resizable", "size", "min"], [1, "sidebar"], [1, "sidebar-tabs"], [1, "tab-button", 3, "click", "disabled"], [1, "fa-solid", "fa-history"], [1, "badge"], [1, "sidebar-content"], [1, "tab-content"], [1, "tab-content", 2, "padding", "0", "height", "100%"], [1, "tab-content", 2, "overflow-y", "auto"], [1, "fa-solid", "fa-comments"], [1, "message-header"], [1, "message-role"], [1, "message-time"], [1, "execution-time", "streaming"], [1, "execution-time", 3, "title"], ["title", "View raw JSON response", 1, "raw-toggle"], [1, "message-content"], ["title", "Copy message", 1, "copy-button-in-bubble", 3, "click"], [1, "fa-solid", "fa-copy"], [1, "streaming-wrapper"], [3, "innerHTML"], [1, "error-details"], [1, "fa-solid", "fa-user"], [1, "fa-solid", "fa-robot"], ["title", "View raw JSON response", 1, "raw-toggle", 3, "click"], [1, "fa-solid", "fa-code"], [1, "streaming-content"], [1, "cursor"], [1, "fa-solid", "fa-exclamation-triangle"], [1, "tab-button", 3, "click"], [1, "fa-solid", "fa-diagram-project"], [1, "fa-solid", "fa-sliders"], [1, "tab-header"], [1, "variables-list"], [1, "empty-variables"], [1, "variable-item"], [1, "add-button", 3, "click", "disabled"], [1, "fa-solid", "fa-plus"], ["placeholder", "Variable name", 1, "variable-name", 3, "ngModelChange", "ngModel", "disabled"], ["textField", "text", "valueField", "value", 1, "variable-type", 3, "ngModelChange", "ngModel", "data", "valuePrimitive", "disabled"], [1, "variable-value", 3, "ngModelChange", "ngModel", "placeholder", "disabled"], ["title", "Remove variable", 1, "remove-button", 3, "click", "disabled"], [1, "fa-solid", "fa-times"], [3, "mode", "executionData", "autoExpand"], [1, "add-button", 3, "click"], ["title", "Remove variable", 1, "remove-button", 3, "click"], ["title", "Reset to Prompt Settings", 1, "custom-button", "icon-only", 3, "click"], [1, "fa-solid", "fa-undo"], [1, "model-settings"], [1, "setting-group"], ["textField", "Name", "valueField", "ID", "placeholder", "Select a model...", 1, "model-selector", 3, "ngModelChange", "ngModel", "data", "valuePrimitive"], ["textField", "Name", "valueField", "ID", "placeholder", "Select a vendor...", 1, "vendor-selector", 3, "ngModelChange", "ngModel", "data", "valuePrimitive", "disabled"], [1, "setting-help"], ["textField", "text", "valueField", "value", "placeholder", "Select format...", 1, "format-selector", 3, "ngModelChange", "ngModel", "data"], ["placeholder", "Max tokens...", 1, "max-tokens-input", 3, "ngModelChange", "ngModel", "min", "max", "step"], [1, "checkbox-label"], ["type", "checkbox", 1, "skip-validation-checkbox", 3, "ngModelChange", "ngModel"], ["title", "Advanced Parameters", 1, "advanced-params-panel", 3, "action", "expanded"], [1, "advanced-settings"], ["title", "Controls randomness: 0 = more focused and deterministic, 2 = more random and creative"], ["placeholder", "None", 1, "advanced-param-input", 3, "ngModelChange", "ngModel", "min", "max", "step", "format"], ["title", "Nucleus sampling: only consider tokens with cumulative probability up to this value. 1 = consider all tokens"], ["title", "Only sample from the top K tokens. Lower values reduce randomness"], ["title", "Minimum probability for token selection. Filters out low-probability tokens"], ["title", "Penalizes tokens based on their frequency in the text. Positive values reduce repetition"], ["title", "Penalizes tokens that have appeared in the text. Positive values increase topic diversity"], ["title", "Random seed for deterministic generation. Same seed produces same output"], ["placeholder", "None", 1, "advanced-param-input", 3, "ngModelChange", "ngModel", "format"], ["title", "Sequences that will stop generation when encountered. Enter comma-separated values"], ["placeholder", "Enter comma-separated sequences", 1, "advanced-param-input", 3, "ngModelChange", "ngModel", "rows"], ["title", "Include log probabilities of the output tokens in the response"], [1, "advanced-param-switch", 3, "ngModelChange", "ngModel"], ["title", "Number of top log probabilities to include for each token"], ["placeholder", "2", 1, "advanced-param-input", 3, "ngModelChange", "ngModel", "min", "max", "step", "format"], ["title", "Start a new conversation", 1, "custom-button", "small", "primary"], [1, "saved-list"], [1, "empty-saved"], [1, "saved-item", 3, "current"], ["title", "Start a new conversation", 1, "custom-button", "small", "primary", 3, "click"], [1, "fa-solid", "fa-folder-open"], [1, "saved-item", 3, "click"], [1, "saved-info"], [1, "saved-date"], ["title", "Delete conversation", 1, "delete-button", 3, "click"], [1, "fa-solid", "fa-trash"], ["title", "View execution details", 1, "execution-history-toggle"], ["title", "View execution details", 1, "execution-history-toggle", 3, "click"], [1, "save-dialog", 3, "close", "title", "width"], [1, "dialog-content"], ["type", "text", "placeholder", "Conversation name", 1, "k-textbox", "k-input", "k-input-md", "k-rounded-md", "k-input-solid", "full-width", 3, "keyup"], ["kendoButton", "", 3, "click"], ["kendoButton", "", 3, "click", "primary", "disabled"], [1, "confirm-dialog", 3, "close", "title", "width"], ["kendoButton", "", 3, "click", "primary"]], template: function AITestHarnessComponent_Template(rf, ctx) { if (rf & 1) {
3104
+ i0.ɵɵtemplate(0, AITestHarnessComponent_div_0_Template, 6, 2, "div", 4)(1, AITestHarnessComponent_Conditional_1_Template, 11, 4, "kendo-dialog", 5)(2, AITestHarnessComponent_Conditional_2_Template, 11, 3, "kendo-dialog", 6);
3105
+ } if (rf & 2) {
3106
+ i0.ɵɵproperty("ngIf", ctx.isVisible);
3107
+ i0.ɵɵadvance();
3108
+ i0.ɵɵconditional(ctx.showSaveDialog ? 1 : -1);
3109
+ i0.ɵɵadvance();
3110
+ i0.ɵɵconditional(ctx.showLoadConfirmDialog ? 2 : -1);
3111
+ } }, dependencies: [i3.NgClass, i3.NgIf, i4.AgentExecutionMonitorComponent, i5.CheckboxControlValueAccessor, i5.NgControlStatus, i5.NgModel, i6.ExpansionPanelComponent, i6.SplitterComponent, i6.SplitterPaneComponent, i2.DialogComponent, i2.DialogActionsComponent, i2.WindowContainerDirective, i7.TextAreaComponent, i7.TextBoxComponent, i7.SwitchComponent, i7.NumericTextBoxComponent, i8.DropDownListComponent, i9.ButtonComponent, i3.DatePipe], styles: ["\n\n.harness-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: #f8f9fa;\n}\n\n\n\n.custom-button[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n color: #495057;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.custom-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #f8f9fa;\n border-color: #adb5bd;\n color: #212529;\n}\n\n.custom-button[_ngcontent-%COMP%]:active:not(:disabled) {\n background: #e9ecef;\n border-color: #adb5bd;\n}\n\n.custom-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.custom-button.icon-only[_ngcontent-%COMP%] {\n padding: 6px 8px;\n}\n\n.custom-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.send-button[_ngcontent-%COMP%] {\n background: #0d6efd;\n border: 1px solid #0d6efd;\n color: white;\n border-radius: 4px;\n padding: 8px 16px;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n font-family: inherit;\n}\n\n.send-button[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: #0b5ed7;\n border-color: #0a58ca;\n}\n\n.send-button[_ngcontent-%COMP%]:active:not(:disabled) {\n background: #0a58ca;\n border-color: #0a58ca;\n}\n\n.send-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.send-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 16px;\n}\n\n\n\n.add-button[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #0d6efd;\n color: #0d6efd;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.add-button[_ngcontent-%COMP%]:hover {\n background: #0d6efd;\n color: white;\n}\n\n\n\n.remove-button[_ngcontent-%COMP%], \n.delete-button[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n color: #dc3545;\n padding: 4px 8px;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.remove-button[_ngcontent-%COMP%]:hover, \n.delete-button[_ngcontent-%COMP%]:hover {\n background: rgba(220, 53, 69, 0.1);\n color: #bd2130;\n}\n\n\n\n.copy-button-in-bubble[_ngcontent-%COMP%] {\n position: absolute;\n top: 8px;\n right: 8px;\n background: transparent;\n border: none;\n padding: 6px;\n font-size: 14px;\n color: #adb5bd;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n opacity: 0;\n z-index: 10; \n\n}\n\n.message-content[_ngcontent-%COMP%]:hover .copy-button-in-bubble[_ngcontent-%COMP%] {\n opacity: 1;\n}\n\n.copy-button-in-bubble[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.08);\n color: #6c757d;\n}\n\n.copy-button-in-bubble[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n\n\n.model-settings[_ngcontent-%COMP%] {\n padding: 16px;\n}\n\n.setting-group[_ngcontent-%COMP%] {\n margin-bottom: 24px;\n}\n\n.setting-group[_ngcontent-%COMP%] label[_ngcontent-%COMP%] {\n display: block;\n margin-bottom: 8px;\n font-weight: 600;\n color: #495057;\n font-size: 14px;\n}\n\n.model-selector[_ngcontent-%COMP%], \n.vendor-selector[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.temperature-slider[_ngcontent-%COMP%] {\n width: 100%;\n margin-top: 8px;\n}\n\n.max-tokens-input[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.setting-help[_ngcontent-%COMP%] {\n margin-top: 6px;\n font-size: 12px;\n color: #6c757d;\n font-style: italic;\n}\n\n\n\n.checkbox-label[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n font-weight: 600;\n color: #495057;\n font-size: 14px;\n cursor: pointer;\n}\n\n.skip-validation-checkbox[_ngcontent-%COMP%] {\n margin-right: 8px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n}\n\n\n\n.harness-header[_ngcontent-%COMP%] {\n background: white;\n border-bottom: 1px solid #dee2e6;\n padding: 16px 20px;\n}\n\n.agent-info[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.agent-details[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.agent-details[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 28px;\n color: #17a2b8;\n}\n\n.agent-details[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #212529;\n}\n\n.agent-details[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 4px 0 0 0;\n font-size: 14px;\n color: #6c757d;\n}\n\n.header-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n}\n\n\n\n.harness-body[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n\n\n.chat-area[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: white;\n border-right: 1px solid #dee2e6;\n height: 100%;\n min-height: 0; \n\n}\n\n\n\n.messages-container[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n scroll-behavior: smooth;\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 text-align: center;\n color: #6c757d;\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%] h4[_ngcontent-%COMP%] {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 500;\n}\n\n.empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 14px;\n}\n\n\n\n.message[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n animation: _ngcontent-%COMP%_fadeIn 0.3s ease-in;\n}\n\n@keyframes _ngcontent-%COMP%_fadeIn {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.message-user[_ngcontent-%COMP%] {\n margin-left: auto;\n max-width: 70%;\n}\n\n.message-assistant[_ngcontent-%COMP%] {\n margin-right: auto;\n max-width: 70%;\n}\n\n.message-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n font-size: 13px;\n}\n\n.message-role[_ngcontent-%COMP%] {\n font-weight: 600;\n color: #495057;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.message-role[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n.message-user[_ngcontent-%COMP%] .message-role[_ngcontent-%COMP%] {\n color: #6f42c1;\n}\n\n.message-assistant[_ngcontent-%COMP%] .message-role[_ngcontent-%COMP%] {\n color: #17a2b8;\n}\n\n.message-time[_ngcontent-%COMP%] {\n color: #6c757d;\n font-size: 12px;\n}\n\n.execution-time[_ngcontent-%COMP%] {\n background: #e9ecef;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 11px;\n color: #495057;\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.execution-time.streaming[_ngcontent-%COMP%] {\n background: #fff3cd;\n color: #856404;\n border: 1px solid #ffeaa7;\n}\n\n.raw-toggle[_ngcontent-%COMP%] {\n background: transparent;\n border: none;\n padding: 4px 8px;\n font-size: 12px;\n color: #6c757d;\n cursor: pointer;\n transition: all 0.2s;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n}\n\n.raw-toggle[_ngcontent-%COMP%]:hover {\n background: rgba(0, 0, 0, 0.05);\n color: #495057;\n}\n\n.raw-toggle[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n\n\n\n.message-user[_ngcontent-%COMP%] .message-content[_ngcontent-%COMP%] {\n background: #e7f3ff;\n border: 1px solid #b8daff;\n}\n\n.message-assistant[_ngcontent-%COMP%] .message-content[_ngcontent-%COMP%] {\n background: #f8f9fa;\n border: 1px solid #e9ecef;\n}\n\n.message.streaming[_ngcontent-%COMP%] .message-content[_ngcontent-%COMP%] {\n background: #fff3cd;\n border: 1px solid #ffeaa7;\n}\n\n.streaming-content[_ngcontent-%COMP%] {\n display: inline;\n}\n\n.streaming-wrapper[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.cursor[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_blink 1s infinite;\n font-weight: 100;\n color: #495057;\n}\n\n@keyframes _ngcontent-%COMP%_blink {\n 0%, 50% { opacity: 1; }\n 51%, 100% { opacity: 0; }\n}\n\n.error-details[_ngcontent-%COMP%] {\n margin-top: 8px;\n padding: 8px 12px;\n background: #f8d7da;\n border: 1px solid #f5c6cb;\n border-radius: 6px;\n color: #721c24;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.error-details[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: #dc3545;\n}\n\n\n\n.save-dialog[_ngcontent-%COMP%], \n.confirm-dialog[_ngcontent-%COMP%] {\n .dialog-content {\n padding: 20px;\n \n p {\n margin: 0 0 16px 0;\n color: #333;\n font-size: 14px;\n line-height: 1.5;\n \n &:last-child {\n margin-bottom: 0;\n }\n }\n \n .full-width {\n width: 100%;\n }\n \n input.k-textbox {\n padding: 8px 12px;\n font-size: 14px;\n line-height: 1.5;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n \n &:focus {\n outline: none;\n border-color: #ff6358;\n box-shadow: 0 0 0 0.25rem rgba(255, 99, 88, 0.25);\n }\n }\n }\n}\n\n\n\n.markdown-content[_ngcontent-%COMP%] {\n line-height: 1.6;\n word-wrap: break-word;\n overflow-wrap: break-word;\n max-width: 100%;\n}\n\n\n\n.inline-json-editor[_ngcontent-%COMP%] {\n margin: 8px 0;\n \n .json-editor-container {\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n position: relative;\n max-height: 400px;\n \n pre {\n margin: 0;\n padding: 12px;\n font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;\n font-size: 13px;\n line-height: 1.5;\n overflow: auto;\n height: 100%;\n background: transparent;\n color: #333;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n }\n}\n\n.markdown-paragraph[_ngcontent-%COMP%] {\n margin: 8px 0;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.markdown-h1[_ngcontent-%COMP%] {\n font-size: 1.5em;\n font-weight: 600;\n margin: 16px 0 8px 0;\n color: #212529;\n border-bottom: 2px solid #e9ecef;\n padding-bottom: 4px;\n}\n\n.markdown-h2[_ngcontent-%COMP%] {\n font-size: 1.3em;\n font-weight: 600;\n margin: 14px 0 6px 0;\n color: #212529;\n border-bottom: 1px solid #e9ecef;\n padding-bottom: 2px;\n}\n\n.markdown-h3[_ngcontent-%COMP%] {\n font-size: 1.1em;\n font-weight: 600;\n margin: 12px 0 4px 0;\n color: #212529;\n}\n\n.code-block[_ngcontent-%COMP%] {\n background: #f8f9fa;\n border: 1px solid #e9ecef;\n border-radius: 6px;\n padding: 16px;\n margin: 12px 0;\n overflow-x: auto;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.5;\n color: #212529;\n white-space: pre;\n word-wrap: normal;\n max-width: 100%;\n}\n\n.code-block[_ngcontent-%COMP%] code[_ngcontent-%COMP%] {\n background: none;\n border: none;\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre;\n word-wrap: normal;\n}\n\n.language-json[_ngcontent-%COMP%] {\n color: #d73e48;\n}\n\n.inline-code[_ngcontent-%COMP%] {\n background: #f1f3f4;\n border: 1px solid #dadce0;\n border-radius: 4px;\n padding: 2px 6px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 0.9em;\n color: #d73e48;\n white-space: nowrap;\n}\n\n.markdown-list[_ngcontent-%COMP%] {\n margin: 8px 0;\n padding-left: 24px;\n}\n\n.markdown-list[_ngcontent-%COMP%] li[_ngcontent-%COMP%] {\n margin: 4px 0;\n line-height: 1.5;\n}\n\n.markdown-link[_ngcontent-%COMP%] {\n color: #0d6efd;\n text-decoration: underline;\n transition: color 0.2s;\n}\n\n.markdown-link[_ngcontent-%COMP%]:hover {\n color: #0a58ca;\n text-decoration: none;\n}\n\n\n\n.message-content[_ngcontent-%COMP%] {\n background: #f8f9fa;\n border-radius: 12px;\n padding: 12px 40px 12px 16px; \n\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n position: relative;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n overflow-x: hidden;\n}\n\n\n\n.json-content-wrapper[_ngcontent-%COMP%] {\n margin: 8px 0;\n}\n\n.json-main-content[_ngcontent-%COMP%] {\n margin-bottom: 12px;\n}\n\n.json-raw-section[_ngcontent-%COMP%] {\n margin-top: 12px;\n}\n\n.json-toggle-button[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n color: #495057;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.json-toggle-button[_ngcontent-%COMP%]:hover {\n background: #f8f9fa;\n border-color: #adb5bd;\n color: #212529;\n}\n\n.json-toggle-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n\n\n.json-raw-section[_ngcontent-%COMP%] .code-block[_ngcontent-%COMP%] {\n margin: 8px 0 0 0;\n font-size: 12px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n\n\n@media (max-width: 768px) {\n .json-raw-section[_ngcontent-%COMP%] .code-block[_ngcontent-%COMP%] {\n font-size: 11px;\n max-height: 200px;\n }\n}\n\n\n\n.json-raw-section[_ngcontent-%COMP%] {\n margin-top: 12px;\n}\n\n\n\n.input-area[_ngcontent-%COMP%] {\n padding: 16px;\n background: white;\n border-top: 1px solid #dee2e6;\n flex-shrink: 0; \n\n margin-top: auto; \n\n}\n\n.input-container[_ngcontent-%COMP%] {\n display: flex;\n gap: 12px;\n align-items: center;\n}\n\n.message-input[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n.message-input[_ngcontent-%COMP%] textarea[_ngcontent-%COMP%] {\n resize: none;\n font-size: 14px;\n}\n\n.send-button[_ngcontent-%COMP%] {\n margin-bottom: 1px;\n}\n\n\n\n.sidebar[_ngcontent-%COMP%] {\n width: 100%; \n\n background: white;\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n\n\n.harness-body[_ngcontent-%COMP%] kendo-splitter[_ngcontent-%COMP%] {\n height: 100%;\n}\n\n.harness-body[_ngcontent-%COMP%] kendo-splitter-pane[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n\n\n.sidebar-tabs[_ngcontent-%COMP%] {\n display: flex;\n background: #f8f9fa;\n border-bottom: 1px solid #dee2e6;\n}\n\n.tab-button[_ngcontent-%COMP%] {\n flex: 1;\n padding: 12px 8px;\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n color: #6c757d;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.tab-button[_ngcontent-%COMP%]:hover {\n background: #e9ecef;\n color: #495057;\n}\n\n.tab-button.active[_ngcontent-%COMP%] {\n color: #0d6efd;\n border-bottom-color: #0d6efd;\n background: white;\n}\n\n.tab-button[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 14px;\n}\n\n.tab-button[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: #6c757d;\n color: white;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 10px;\n margin-left: 4px;\n}\n\n.tab-button.active[_ngcontent-%COMP%] .badge[_ngcontent-%COMP%] {\n background: #0d6efd;\n}\n\n.tab-button[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n\n\n.sidebar-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n width: 100%; \n\n}\n\n.tab-content[_ngcontent-%COMP%] {\n padding: 20px;\n width: 100%; \n\n box-sizing: border-box; \n\n}\n\n.tab-header[_ngcontent-%COMP%] {\n margin-bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n position: relative;\n}\n\n.tab-header[_ngcontent-%COMP%] .custom-button.icon-only[_ngcontent-%COMP%] {\n position: absolute;\n top: 0;\n right: 0;\n}\n\n.tab-header[_ngcontent-%COMP%] h4[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 16px;\n font-weight: 600;\n color: #212529;\n}\n\n.tab-header[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 13px;\n color: #6c757d;\n}\n\n.tab-header[_ngcontent-%COMP%] .custom-button.small[_ngcontent-%COMP%] {\n align-self: flex-start;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n.tab-header[_ngcontent-%COMP%] .custom-button.small[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n margin-right: 4px;\n}\n\n\n\n.variables-list[_ngcontent-%COMP%] {\n margin-bottom: 16px;\n width: 100%;\n}\n\n.variable-item[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n align-items: center;\n width: 100%;\n}\n\n.variable-name[_ngcontent-%COMP%] {\n width: 30%; \n\n min-width: 100px;\n}\n\n.variable-type[_ngcontent-%COMP%] {\n width: 25%; \n\n min-width: 80px;\n}\n\n.variable-value[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0; \n\n}\n\n.remove-button[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.add-variable-button[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.empty-variables[_ngcontent-%COMP%] {\n text-align: center;\n padding: 40px 20px;\n color: #6c757d;\n}\n\n\n\n.saved-list[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.empty-saved[_ngcontent-%COMP%] {\n text-align: center;\n padding: 40px 20px;\n color: #6c757d;\n}\n\n.empty-saved[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.saved-item[_ngcontent-%COMP%] {\n border: 1px solid #dee2e6;\n border-radius: 8px;\n padding: 12px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.saved-item[_ngcontent-%COMP%]:hover {\n background: #f8f9fa;\n border-color: #adb5bd;\n}\n\n.saved-item.current[_ngcontent-%COMP%] {\n background: #e7f3ff;\n border-color: #0d6efd;\n}\n\n.saved-info[_ngcontent-%COMP%] h5[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 14px;\n font-weight: 600;\n color: #212529;\n}\n\n.saved-info[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0 0 4px 0;\n font-size: 12px;\n color: #6c757d;\n}\n\n.saved-date[_ngcontent-%COMP%] {\n font-size: 11px;\n color: #6c757d;\n}\n\n\n\n@media (max-width: 1200px) {\n .sidebar[_ngcontent-%COMP%] {\n width: 320px;\n }\n}\n\n@media (max-width: 992px) {\n .message-user[_ngcontent-%COMP%], \n .message-assistant[_ngcontent-%COMP%] {\n max-width: 85%;\n }\n \n .sidebar[_ngcontent-%COMP%] {\n width: 280px;\n }\n}\n\n\n\n.messages-container[_ngcontent-%COMP%]::-webkit-scrollbar, \n.sidebar-content[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 8px;\n}\n\n.messages-container[_ngcontent-%COMP%]::-webkit-scrollbar-track, \n.sidebar-content[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background: #f1f1f1;\n}\n\n.messages-container[_ngcontent-%COMP%]::-webkit-scrollbar-thumb, \n.sidebar-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: #c1c1c1;\n border-radius: 4px;\n}\n\n.messages-container[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover, \n.sidebar-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background: #a1a1a1;\n}\n\n\n\n .ai-agent-test-harness .k-dialog-wrapper {\n border-radius: 8px;\n overflow: hidden;\n}\n\n .ai-agent-test-harness .k-dialog-titlebar {\n background: #343a40;\n color: white;\n}\n\n .ai-agent-test-harness .k-dialog-title {\n font-weight: 600;\n}\n\n .ai-agent-test-harness .k-dialog-titlebar-actions .k-button {\n color: white;\n}\n\n .ai-agent-test-harness .k-dialog-content {\n padding: 0;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n\n\n\n\n\n.advanced-params-panel[_ngcontent-%COMP%] {\n margin-top: 24px;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n}\n\n.advanced-params-panel[_ngcontent-%COMP%] .k-expander-title {\n font-weight: 600;\n color: #495057;\n}\n\n.advanced-settings[_ngcontent-%COMP%] {\n padding: 16px;\n}\n\n.advanced-param-input[_ngcontent-%COMP%] {\n width: 100%;\n}\n\n.advanced-param-switch[_ngcontent-%COMP%] {\n margin-top: 8px;\n}\n\n\n\n.format-selector[_ngcontent-%COMP%] {\n width: 100%;\n}"] }); }
3112
+ }
3113
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AITestHarnessComponent, [{
3114
+ type: Component,
3115
+ args: [{ selector: 'mj-ai-test-harness', template: "<div class=\"harness-container\" *ngIf=\"isVisible\" kendoWindowContainer>\n <!-- Header -->\n <div class=\"harness-header\">\n @if (entity) {\n <div class=\"agent-info\">\n <div class=\"agent-details\">\n <i class=\"fa-solid\" [class.fa-robot]=\"mode === 'agent'\" [class.fa-comment-dots]=\"mode === 'prompt'\"></i>\n <div>\n <h3>{{ getEntityName() }}</h3>\n @if (entity.Description) {\n <p>{{ entity.Description }}</p>\n }\n </div>\n </div>\n <div class=\"header-actions\">\n <button \n class=\"custom-button\"\n (click)=\"clearConversation()\"\n [disabled]=\"isExecuting || conversationMessages.length === 0\"\n title=\"Clear conversation\">\n <i class=\"fa-solid fa-broom\"></i>\n Clear\n </button>\n <button \n class=\"custom-button\"\n (click)=\"saveConversation()\"\n [disabled]=\"conversationMessages.length === 0\"\n title=\"Save conversation\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n <button \n class=\"custom-button\"\n (click)=\"exportConversation()\"\n [disabled]=\"conversationMessages.length === 0\"\n title=\"Export conversation\">\n <i class=\"fa-solid fa-download\"></i>\n Export\n </button>\n <button \n class=\"custom-button\"\n (click)=\"importConversation()\"\n title=\"Import conversation\">\n <i class=\"fa-solid fa-upload\"></i>\n Import\n </button>\n <input \n #fileInput\n type=\"file\"\n accept=\".json\"\n style=\"display: none\"\n (change)=\"onFileSelected($event)\">\n <button \n class=\"custom-button icon-only\"\n (click)=\"toggleSidebar()\"\n title=\"Toggle sidebar\">\n <i class=\"fa-solid\" [class.fa-chevron-right]=\"!showSidebar\" [class.fa-chevron-left]=\"showSidebar\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Main Content Area with Splitter -->\n <div class=\"harness-body\">\n @if (showSidebar) {\n <kendo-splitter \n orientation=\"horizontal\"\n style=\"height: 100%; width: 100%;\">\n \n <!-- Chat Area (Left Pane) -->\n <kendo-splitter-pane \n [collapsible]=\"false\"\n [resizable]=\"true\"\n [min]=\"'400px'\">\n <div class=\"chat-area\">\n <!-- Messages Container -->\n <div class=\"messages-container\" #messagesContainer>\n @if (conversationMessages.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-comments\"></i>\n <h4>Start a conversation</h4>\n <p>Send a message to begin testing the AI agent</p>\n </div>\n }\n\n @for (message of conversationMessages; track message.id) {\n <div [ngClass]=\"getMessageClass(message)\">\n <div class=\"message-header\">\n <span class=\"message-role\">\n @if (message.role === 'user') {\n <i class=\"fa-solid fa-user\"></i> You\n } @else {\n <i class=\"fa-solid fa-robot\"></i> {{ getEntityName() || 'Assistant' }}\n }\n </span>\n <span class=\"message-time\">{{ formatTimestamp(message.timestamp) }}</span>\n @if (message.isStreaming && message.elapsedTime !== undefined) {\n <span class=\"execution-time streaming\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i> {{ formatElapsedTime(message.elapsedTime) }}\n </span>\n } @else if (message.executionTime) {\n <span class=\"execution-time\" [title]=\"getExecutionSummary(message)\">{{ formatExecutionTime(message.executionTime) }}</span>\n }\n @if (showRawToggle(message)) {\n <button class=\"raw-toggle\" (click)=\"showRawJsonDialog(message)\" title=\"View raw JSON response\">\n <i class=\"fa-solid fa-code\"></i>\n </button>\n }\n </div>\n <div class=\"message-content\">\n <button class=\"copy-button-in-bubble\" (click)=\"copyMessage(message)\" title=\"Copy message\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (message.isStreaming) {\n <div class=\"streaming-wrapper\">\n <span class=\"streaming-content\">{{ message.streamingContent }}<span class=\"cursor\">|</span></span>\n </div>\n } @else {\n <div [innerHTML]=\"getFormattedContent(message)\"></div>\n }\n @if (message.error) {\n <div class=\"error-details\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n {{ message.error }}\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Input Area -->\n <div class=\"input-area\">\n <div class=\"input-container\">\n <kendo-textarea\n #messageInput\n [(ngModel)]=\"currentUserMessage\"\n [rows]=\"3\"\n placeholder=\"Type your message here... (Press Enter to send, Shift+Enter for new line)\"\n (keypress)=\"handleKeyPress($event)\"\n [disabled]=\"isExecuting\"\n class=\"message-input\">\n </kendo-textarea>\n <button\n class=\"send-button\"\n (click)=\"sendMessage()\"\n [disabled]=\"isExecuting || !currentUserMessage.trim()\"\n title=\"Send message\">\n @if (isExecuting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\"></i>\n }\n </button>\n </div>\n </div>\n </div>\n </kendo-splitter-pane>\n \n <!-- Sidebar (Right Pane) -->\n <kendo-splitter-pane \n [collapsible]=\"false\"\n [resizable]=\"true\"\n [size]=\"'30%'\"\n [min]=\"'300px'\">\n <div class=\"sidebar\">\n <!-- Tab Navigation -->\n <div class=\"sidebar-tabs\">\n @if (mode === 'agent') {\n <button \n class=\"tab-button\"\n [class.active]=\"activeTab === 'agentVariables'\"\n [disabled]=\"executionMonitorMode === 'live' && activeTab !== 'agentVariables'\"\n (click)=\"selectTab('agentVariables')\">\n <i class=\"fa-solid fa-code\"></i>\n Agent Variables\n </button>\n <button \n class=\"tab-button\"\n [class.active]=\"activeTab === 'executionMonitor'\"\n (click)=\"selectTab('executionMonitor')\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n Execution Monitor\n </button>\n } @else {\n <button \n class=\"tab-button\"\n [class.active]=\"activeTab === 'templateVariables'\"\n (click)=\"selectTab('templateVariables')\">\n <i class=\"fa-solid fa-code\"></i>\n Template Variables\n </button>\n <button \n class=\"tab-button\"\n [class.active]=\"activeTab === 'modelSettings'\"\n (click)=\"selectTab('modelSettings')\">\n <i class=\"fa-solid fa-sliders\"></i>\n Model Settings\n </button>\n }\n <button \n class=\"tab-button\"\n [class.active]=\"activeTab === 'savedConversations'\"\n [disabled]=\"executionMonitorMode === 'live' && activeTab !== 'savedConversations'\"\n (click)=\"selectTab('savedConversations')\">\n <i class=\"fa-solid fa-history\"></i>\n Saved\n @if (savedConversations.length > 0) {\n <span class=\"badge\">{{ savedConversations.length }}</span>\n }\n </button>\n </div>\n\n <!-- Tab Content -->\n <div class=\"sidebar-content\">\n <!-- Agent Variables Tab (Unified) -->\n @if (activeTab === 'agentVariables') {\n <div class=\"tab-content\">\n <div class=\"tab-header\">\n <h4>Agent Variables</h4>\n <p>Variables available during agent execution and template rendering</p>\n </div>\n \n <div class=\"variables-list\">\n @if (agentVariables.length === 0) {\n <div class=\"empty-variables\">\n <p>No variables defined. Click \"Add Variable\" to get started.</p>\n </div>\n }\n \n @for (variable of agentVariables; track $index) {\n <div class=\"variable-item\">\n <kendo-textbox \n [(ngModel)]=\"variable.name\" \n placeholder=\"Variable name\"\n [disabled]=\"isExecuting\"\n class=\"variable-name\">\n </kendo-textbox>\n \n <kendo-dropdownlist \n [(ngModel)]=\"variable.type\"\n [data]=\"[\n {text: 'String', value: 'string'}, \n {text: 'Number', value: 'number'}, \n {text: 'Boolean', value: 'boolean'}, \n {text: 'Object', value: 'object'}\n ]\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [disabled]=\"isExecuting\"\n class=\"variable-type\">\n </kendo-dropdownlist>\n \n <kendo-textbox \n [(ngModel)]=\"variable.value\" \n [placeholder]=\"variable.type === 'object' ? 'JSON value' : 'Value'\"\n [disabled]=\"isExecuting\"\n class=\"variable-value\">\n </kendo-textbox>\n \n <button \n class=\"remove-button\"\n (click)=\"removeAgentVariable($index)\"\n [disabled]=\"isExecuting\"\n title=\"Remove variable\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n </div>\n \n <button \n class=\"add-button\"\n (click)=\"addAgentVariable()\"\n [disabled]=\"isExecuting\">\n <i class=\"fa-solid fa-plus\"></i> Add Variable\n </button>\n </div>\n }\n\n <!-- Execution Monitor Tab -->\n @if (activeTab === 'executionMonitor') {\n <div class=\"tab-content\" style=\"padding: 0; height: 100%;\">\n <mj-agent-execution-monitor\n [mode]=\"executionMonitorMode\"\n [executionData]=\"currentExecutionData\"\n [autoExpand]=\"true\">\n </mj-agent-execution-monitor>\n </div>\n }\n\n <!-- Template Variables Tab (Prompt Mode) -->\n @if (activeTab === 'templateVariables') {\n <div class=\"tab-content\">\n <div class=\"tab-header\">\n <h4>Template Variables</h4>\n <p>Variables for prompt template rendering</p>\n </div>\n \n <div class=\"variables-list\">\n @for (variable of templateVariables; track $index) {\n <div class=\"variable-item\">\n <kendo-textbox \n [(ngModel)]=\"variable.name\" \n placeholder=\"Variable name\"\n [disabled]=\"isExecuting\"\n class=\"variable-name\">\n </kendo-textbox>\n \n <kendo-dropdownlist \n [(ngModel)]=\"variable.type\"\n [data]=\"[\n {text: 'String', value: 'string'}, \n {text: 'Number', value: 'number'}, \n {text: 'Boolean', value: 'boolean'}, \n {text: 'Object', value: 'object'}\n ]\"\n textField=\"text\"\n valueField=\"value\"\n [valuePrimitive]=\"true\"\n [disabled]=\"isExecuting\"\n class=\"variable-type\">\n </kendo-dropdownlist>\n \n <kendo-textbox \n [(ngModel)]=\"variable.value\" \n [placeholder]=\"variable.type === 'object' ? 'JSON value' : 'Value'\"\n [disabled]=\"isExecuting\"\n class=\"variable-value\">\n </kendo-textbox>\n \n <button \n class=\"remove-button\"\n (click)=\"removeTemplateVariable($index)\"\n title=\"Remove variable\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n }\n </div>\n \n <button \n class=\"add-button\"\n (click)=\"addTemplateVariable()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Variable\n </button>\n </div>\n }\n\n <!-- Model Settings Tab (Prompt Mode) -->\n @if (activeTab === 'modelSettings') {\n <div class=\"tab-content\" style=\"overflow-y: auto;\">\n <div class=\"tab-header\">\n <h4>Model Settings</h4>\n <p>Configure AI model parameters</p>\n <button class=\"custom-button icon-only\" (click)=\"resetToPromptDefaults()\" title=\"Reset to Prompt Settings\">\n <i class=\"fa-solid fa-undo\"></i>\n </button>\n </div>\n \n <div class=\"model-settings\">\n <div class=\"setting-group\">\n <label>AI Model</label>\n <kendo-dropdownlist \n [(ngModel)]=\"selectedModelId\"\n (ngModelChange)=\"onModelSelectionChange()\"\n [data]=\"availableModels\"\n textField=\"Name\"\n valueField=\"ID\"\n [valuePrimitive]=\"true\"\n placeholder=\"Select a model...\"\n class=\"model-selector\">\n </kendo-dropdownlist>\n </div>\n \n <div class=\"setting-group\">\n <label>AI Vendor</label>\n <kendo-dropdownlist \n [(ngModel)]=\"selectedVendorId\"\n [data]=\"availableVendors\"\n textField=\"Name\"\n valueField=\"ID\"\n [valuePrimitive]=\"true\"\n [disabled]=\"availableVendors.length <= 1 || !selectedModelId\"\n placeholder=\"Select a vendor...\"\n class=\"vendor-selector\">\n </kendo-dropdownlist>\n <div class=\"setting-help\">\n @if (!selectedModelId) {\n Using default vendor for the default model\n } @else if (availableVendors.length <= 1) {\n Only one vendor available for this model\n } @else {\n Select the inference provider for this model\n }\n </div>\n </div>\n \n <div class=\"setting-group\">\n <label>Response Format</label>\n <kendo-dropdownlist \n [(ngModel)]=\"selectedResponseFormat\"\n [data]=\"responseFormatOptions\"\n textField=\"text\"\n valueField=\"value\"\n placeholder=\"Select format...\"\n class=\"format-selector\">\n </kendo-dropdownlist>\n <div class=\"setting-help\">\n Expected format for AI response (defaults to prompt setting)\n </div>\n </div>\n \n <div class=\"setting-group\">\n <label>Max Tokens</label>\n <kendo-numerictextbox \n [(ngModel)]=\"maxTokens\"\n [min]=\"100\"\n [max]=\"8000\"\n [step]=\"100\"\n placeholder=\"Max tokens...\"\n class=\"max-tokens-input\">\n </kendo-numerictextbox>\n <div class=\"setting-help\">\n Maximum length of generated response\n </div>\n </div>\n \n <div class=\"setting-group\">\n <label class=\"checkbox-label\">\n <input type=\"checkbox\" \n [(ngModel)]=\"skipValidation\"\n class=\"skip-validation-checkbox\">\n Skip Validation\n </label>\n <div class=\"setting-help\">\n Skip output validation during testing\n </div>\n </div>\n \n <!-- Advanced Parameters Expansion Panel -->\n <kendo-expansionpanel \n [expanded]=\"advancedParamsExpanded\"\n (action)=\"toggleAdvancedParams()\"\n title=\"Advanced Parameters\"\n class=\"advanced-params-panel\">\n <div class=\"advanced-settings\">\n <div class=\"setting-group\">\n <label title=\"Controls randomness: 0 = more focused and deterministic, 2 = more random and creative\">\n Temperature\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.temperature\"\n [min]=\"0\"\n [max]=\"2\"\n [step]=\"0.1\"\n [format]=\"'n1'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Nucleus sampling: only consider tokens with cumulative probability up to this value. 1 = consider all tokens\">\n Top P\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.topP\"\n [min]=\"0\"\n [max]=\"1\"\n [step]=\"0.01\"\n [format]=\"'n2'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Only sample from the top K tokens. Lower values reduce randomness\">\n Top K\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.topK\"\n [min]=\"1\"\n [max]=\"100\"\n [step]=\"1\"\n [format]=\"'n0'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Minimum probability for token selection. Filters out low-probability tokens\">\n Min P\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.minP\"\n [min]=\"0\"\n [max]=\"1\"\n [step]=\"0.01\"\n [format]=\"'n2'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Penalizes tokens based on their frequency in the text. Positive values reduce repetition\">\n Frequency Penalty\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.frequencyPenalty\"\n [min]=\"-2\"\n [max]=\"2\"\n [step]=\"0.1\"\n [format]=\"'n1'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Penalizes tokens that have appeared in the text. Positive values increase topic diversity\">\n Presence Penalty\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.presencePenalty\"\n [min]=\"-2\"\n [max]=\"2\"\n [step]=\"0.1\"\n [format]=\"'n1'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Random seed for deterministic generation. Same seed produces same output\">\n Seed\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.seed\"\n [format]=\"'n0'\"\n placeholder=\"None\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Sequences that will stop generation when encountered. Enter comma-separated values\">\n Stop Sequences\n </label>\n <kendo-textarea \n [(ngModel)]=\"stopSequencesText\"\n (ngModelChange)=\"updateStopSequences()\"\n [rows]=\"2\"\n placeholder=\"Enter comma-separated sequences\"\n class=\"advanced-param-input\">\n </kendo-textarea>\n <div class=\"setting-help\">\n Separate multiple sequences with commas\n </div>\n </div>\n \n <div class=\"setting-group\">\n <label title=\"Include log probabilities of the output tokens in the response\">\n Include Log Probs\n </label>\n <kendo-switch \n [(ngModel)]=\"advancedParams.includeLogProbs\"\n class=\"advanced-param-switch\">\n </kendo-switch>\n </div>\n \n @if (advancedParams.includeLogProbs) {\n <div class=\"setting-group\">\n <label title=\"Number of top log probabilities to include for each token\">\n Top Log Probs\n </label>\n <kendo-numerictextbox \n [(ngModel)]=\"advancedParams.topLogProbs\"\n [min]=\"2\"\n [max]=\"20\"\n [step]=\"1\"\n [format]=\"'n0'\"\n placeholder=\"2\"\n class=\"advanced-param-input\">\n </kendo-numerictextbox>\n </div>\n }\n </div>\n </kendo-expansionpanel>\n </div>\n </div>\n }\n\n <!-- Saved Conversations Tab -->\n @if (activeTab === 'savedConversations') {\n <div class=\"tab-content\">\n <div class=\"tab-header\">\n <h4>Saved Conversations</h4>\n <p>Previously saved test sessions</p>\n @if (currentConversationId) {\n <button class=\"custom-button small primary\" (click)=\"newConversation()\" title=\"Start a new conversation\">\n <i class=\"fa-solid fa-plus\"></i>\n New\n </button>\n }\n </div>\n \n <div class=\"saved-list\">\n @if (savedConversations.length === 0) {\n <div class=\"empty-saved\">\n <i class=\"fa-solid fa-folder-open\"></i>\n <p>No saved conversations</p>\n </div>\n }\n \n @for (conv of savedConversations; track conv.id) {\n <div class=\"saved-item\" \n [class.current]=\"conv.id === currentConversationId\"\n (click)=\"loadConversation(conv)\">\n <div class=\"saved-info\">\n <h5>{{ conv.name }}</h5>\n <p>{{ conv.messages.length }} messages</p>\n <span class=\"saved-date\">{{ conv.updatedAt | date:'short' }}</span>\n </div>\n <button \n class=\"delete-button\"\n (click)=\"deleteConversation(conv, $event)\"\n title=\"Delete conversation\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </kendo-splitter-pane>\n </kendo-splitter>\n } @else {\n <!-- No sidebar - full width chat -->\n <div class=\"chat-area\">\n <!-- Messages Container -->\n <div class=\"messages-container\" #messagesContainer>\n @if (conversationMessages.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-comments\"></i>\n <h4>Start a conversation</h4>\n <p>Send a message to begin testing the AI agent</p>\n </div>\n }\n\n @for (message of conversationMessages; track message.id) {\n <div [ngClass]=\"getMessageClass(message)\">\n <div class=\"message-header\">\n <span class=\"message-role\">\n @if (message.role === 'user') {\n <i class=\"fa-solid fa-user\"></i> You\n } @else {\n <i class=\"fa-solid fa-robot\"></i> {{ getEntityName() || 'Assistant' }}\n }\n </span>\n <span class=\"message-time\">{{ formatTimestamp(message.timestamp) }}</span>\n @if (message.isStreaming && message.elapsedTime !== undefined) {\n <span class=\"execution-time streaming\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i> {{ formatElapsedTime(message.elapsedTime) }}\n </span>\n } @else if (message.executionTime) {\n <span class=\"execution-time\" [title]=\"getExecutionSummary(message)\">{{ formatExecutionTime(message.executionTime) }}</span>\n }\n @if (showRawToggle(message)) {\n <button class=\"raw-toggle\" (click)=\"showRawJsonDialog(message)\" title=\"View raw JSON response\">\n <i class=\"fa-solid fa-code\"></i>\n </button>\n }\n @if (message.executionData && mode === 'agent' && message.role === 'assistant') {\n <button class=\"execution-history-toggle\" (click)=\"showMessageExecutionHistory(message)\" title=\"View execution details\">\n <i class=\"fa-solid fa-diagram-project\"></i>\n </button>\n }\n </div>\n <div class=\"message-content\">\n <button class=\"copy-button-in-bubble\" (click)=\"copyMessage(message)\" title=\"Copy message\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n @if (message.isStreaming) {\n <div class=\"streaming-wrapper\">\n <span class=\"streaming-content\">{{ message.streamingContent }}<span class=\"cursor\">|</span></span>\n </div>\n } @else {\n <div [innerHTML]=\"getFormattedContent(message)\"></div>\n }\n @if (message.error) {\n <div class=\"error-details\">\n <i class=\"fa-solid fa-exclamation-triangle\"></i>\n {{ message.error }}\n </div>\n }\n </div>\n </div>\n }\n </div>\n\n <!-- Input Area -->\n <div class=\"input-area\">\n <div class=\"input-container\">\n <kendo-textarea\n #messageInput\n [(ngModel)]=\"currentUserMessage\"\n [rows]=\"3\"\n placeholder=\"Type your message here... (Press Enter to send, Shift+Enter for new line)\"\n (keypress)=\"handleKeyPress($event)\"\n [disabled]=\"isExecuting\"\n class=\"message-input\">\n </kendo-textarea>\n <button\n class=\"send-button\"\n (click)=\"sendMessage()\"\n [disabled]=\"isExecuting || !currentUserMessage.trim()\"\n title=\"Send message\">\n @if (isExecuting) {\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\"></i>\n }\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Save Conversation Dialog -->\n @if (showSaveDialog) {\n <kendo-dialog \n [title]=\"'Save Conversation'\" \n [width]=\"400\"\n (close)=\"cancelSaveDialog()\"\n class=\"save-dialog\">\n <div class=\"dialog-content\">\n <p>Enter a name for this conversation:</p>\n <input \n #saveDialogInput\n type=\"text\"\n (keyup)=\"updateTempConversation()\"\n placeholder=\"Conversation name\"\n class=\"k-textbox k-input k-input-md k-rounded-md k-input-solid full-width\" />\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelSaveDialog()\">Cancel</button>\n <button kendoButton \n [primary]=\"true\" \n (click)=\"confirmSaveConversation()\"\n [disabled]=\"tempConversationName === ''\">\n Save\n </button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n \n <!-- Load Confirmation Dialog -->\n @if (showLoadConfirmDialog) {\n <kendo-dialog \n [title]=\"'Replace Current Conversation?'\" \n [width]=\"400\"\n (close)=\"cancelLoadConversation()\"\n class=\"confirm-dialog\">\n <div class=\"dialog-content\">\n <p>Loading this conversation will replace the current one. Any unsaved changes will be lost.</p>\n <p>Do you want to continue?</p>\n </div>\n <kendo-dialog-actions>\n <button kendoButton (click)=\"cancelLoadConversation()\">Cancel</button>\n <button kendoButton \n [primary]=\"true\" \n (click)=\"confirmLoadConversation()\">\n Load Conversation\n </button>\n </kendo-dialog-actions>\n </kendo-dialog>\n }\n\n", styles: ["/* Container Styles */\n.harness-container {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: #f8f9fa;\n}\n\n/* Custom Button Styles */\n.custom-button {\n background: white;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n color: #495057;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.custom-button:hover:not(:disabled) {\n background: #f8f9fa;\n border-color: #adb5bd;\n color: #212529;\n}\n\n.custom-button:active:not(:disabled) {\n background: #e9ecef;\n border-color: #adb5bd;\n}\n\n.custom-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.custom-button.icon-only {\n padding: 6px 8px;\n}\n\n.custom-button i {\n font-size: 14px;\n}\n\n/* Send Button - Primary Style */\n.send-button {\n background: #0d6efd;\n border: 1px solid #0d6efd;\n color: white;\n border-radius: 4px;\n padding: 8px 16px;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 44px;\n font-family: inherit;\n}\n\n.send-button:hover:not(:disabled) {\n background: #0b5ed7;\n border-color: #0a58ca;\n}\n\n.send-button:active:not(:disabled) {\n background: #0a58ca;\n border-color: #0a58ca;\n}\n\n.send-button:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.send-button i {\n font-size: 16px;\n}\n\n/* Add Button */\n.add-button {\n background: white;\n border: 1px solid #0d6efd;\n color: #0d6efd;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.add-button:hover {\n background: #0d6efd;\n color: white;\n}\n\n/* Remove/Delete Buttons */\n.remove-button,\n.delete-button {\n background: transparent;\n border: none;\n color: #dc3545;\n padding: 4px 8px;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n border-radius: 4px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.remove-button:hover,\n.delete-button:hover {\n background: rgba(220, 53, 69, 0.1);\n color: #bd2130;\n}\n\n/* Copy Button in Message Bubble */\n.copy-button-in-bubble {\n position: absolute;\n top: 8px;\n right: 8px;\n background: transparent;\n border: none;\n padding: 6px;\n font-size: 14px;\n color: #adb5bd;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n opacity: 0;\n z-index: 10; /* Ensure it's above content */\n}\n\n.message-content:hover .copy-button-in-bubble {\n opacity: 1;\n}\n\n.copy-button-in-bubble:hover {\n background: rgba(0, 0, 0, 0.08);\n color: #6c757d;\n}\n\n.copy-button-in-bubble i {\n font-size: 14px;\n}\n\n/* Model Settings Styles (Prompt Mode) */\n.model-settings {\n padding: 16px;\n}\n\n.setting-group {\n margin-bottom: 24px;\n}\n\n.setting-group label {\n display: block;\n margin-bottom: 8px;\n font-weight: 600;\n color: #495057;\n font-size: 14px;\n}\n\n.model-selector,\n.vendor-selector {\n width: 100%;\n}\n\n.temperature-slider {\n width: 100%;\n margin-top: 8px;\n}\n\n.max-tokens-input {\n width: 100%;\n}\n\n.setting-help {\n margin-top: 6px;\n font-size: 12px;\n color: #6c757d;\n font-style: italic;\n}\n\n/* Checkbox Styles */\n.checkbox-label {\n display: flex;\n align-items: center;\n font-weight: 600;\n color: #495057;\n font-size: 14px;\n cursor: pointer;\n}\n\n.skip-validation-checkbox {\n margin-right: 8px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n}\n\n/* Header Styles */\n.harness-header {\n background: white;\n border-bottom: 1px solid #dee2e6;\n padding: 16px 20px;\n}\n\n.agent-info {\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.agent-details {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.agent-details i {\n font-size: 28px;\n color: #17a2b8;\n}\n\n.agent-details h3 {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n color: #212529;\n}\n\n.agent-details p {\n margin: 4px 0 0 0;\n font-size: 14px;\n color: #6c757d;\n}\n\n.header-actions {\n display: flex;\n gap: 8px;\n}\n\n/* Body Layout */\n.harness-body {\n flex: 1;\n display: flex;\n overflow: hidden;\n}\n\n/* Chat Area */\n.chat-area {\n flex: 1;\n display: flex;\n flex-direction: column;\n background: white;\n border-right: 1px solid #dee2e6;\n height: 100%;\n min-height: 0; /* Important for flexbox children */\n}\n\n/* Messages Container */\n.messages-container {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n scroll-behavior: smooth;\n}\n\n.empty-state {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n text-align: center;\n color: #6c757d;\n}\n\n.empty-state i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.5;\n}\n\n.empty-state h4 {\n margin: 0 0 8px 0;\n font-size: 18px;\n font-weight: 500;\n}\n\n.empty-state p {\n margin: 0;\n font-size: 14px;\n}\n\n/* Message Styles */\n.message {\n margin-bottom: 20px;\n animation: fadeIn 0.3s ease-in;\n}\n\n@keyframes fadeIn {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.message-user {\n margin-left: auto;\n max-width: 70%;\n}\n\n.message-assistant {\n margin-right: auto;\n max-width: 70%;\n}\n\n.message-header {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 8px;\n font-size: 13px;\n}\n\n.message-role {\n font-weight: 600;\n color: #495057;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.message-role i {\n font-size: 14px;\n}\n\n.message-user .message-role {\n color: #6f42c1;\n}\n\n.message-assistant .message-role {\n color: #17a2b8;\n}\n\n.message-time {\n color: #6c757d;\n font-size: 12px;\n}\n\n.execution-time {\n background: #e9ecef;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 11px;\n color: #495057;\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.execution-time.streaming {\n background: #fff3cd;\n color: #856404;\n border: 1px solid #ffeaa7;\n}\n\n.raw-toggle {\n background: transparent;\n border: none;\n padding: 4px 8px;\n font-size: 12px;\n color: #6c757d;\n cursor: pointer;\n transition: all 0.2s;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n}\n\n.raw-toggle:hover {\n background: rgba(0, 0, 0, 0.05);\n color: #495057;\n}\n\n.raw-toggle i {\n font-size: 12px;\n}\n\n/* Message content styles are now defined above in enhanced content formatting section */\n\n.message-user .message-content {\n background: #e7f3ff;\n border: 1px solid #b8daff;\n}\n\n.message-assistant .message-content {\n background: #f8f9fa;\n border: 1px solid #e9ecef;\n}\n\n.message.streaming .message-content {\n background: #fff3cd;\n border: 1px solid #ffeaa7;\n}\n\n.streaming-content {\n display: inline;\n}\n\n.streaming-wrapper {\n position: relative;\n}\n\n.cursor {\n animation: blink 1s infinite;\n font-weight: 100;\n color: #495057;\n}\n\n@keyframes blink {\n 0%, 50% { opacity: 1; }\n 51%, 100% { opacity: 0; }\n}\n\n.error-details {\n margin-top: 8px;\n padding: 8px 12px;\n background: #f8d7da;\n border: 1px solid #f5c6cb;\n border-radius: 6px;\n color: #721c24;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.error-details i {\n color: #dc3545;\n}\n\n/* Dialog styles */\n.save-dialog,\n.confirm-dialog {\n .dialog-content {\n padding: 20px;\n \n p {\n margin: 0 0 16px 0;\n color: #333;\n font-size: 14px;\n line-height: 1.5;\n \n &:last-child {\n margin-bottom: 0;\n }\n }\n \n .full-width {\n width: 100%;\n }\n \n input.k-textbox {\n padding: 8px 12px;\n font-size: 14px;\n line-height: 1.5;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n \n &:focus {\n outline: none;\n border-color: #ff6358;\n box-shadow: 0 0 0 0.25rem rgba(255, 99, 88, 0.25);\n }\n }\n }\n}\n\n/* Enhanced content formatting styles */\n.markdown-content {\n line-height: 1.6;\n word-wrap: break-word;\n overflow-wrap: break-word;\n max-width: 100%;\n}\n\n/* Inline JSON editor styles */\n.inline-json-editor {\n margin: 8px 0;\n \n .json-editor-container {\n background: #f8f9fa;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n position: relative;\n max-height: 400px;\n \n pre {\n margin: 0;\n padding: 12px;\n font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;\n font-size: 13px;\n line-height: 1.5;\n overflow: auto;\n height: 100%;\n background: transparent;\n color: #333;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n }\n}\n\n.markdown-paragraph {\n margin: 8px 0;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.markdown-h1 {\n font-size: 1.5em;\n font-weight: 600;\n margin: 16px 0 8px 0;\n color: #212529;\n border-bottom: 2px solid #e9ecef;\n padding-bottom: 4px;\n}\n\n.markdown-h2 {\n font-size: 1.3em;\n font-weight: 600;\n margin: 14px 0 6px 0;\n color: #212529;\n border-bottom: 1px solid #e9ecef;\n padding-bottom: 2px;\n}\n\n.markdown-h3 {\n font-size: 1.1em;\n font-weight: 600;\n margin: 12px 0 4px 0;\n color: #212529;\n}\n\n.code-block {\n background: #f8f9fa;\n border: 1px solid #e9ecef;\n border-radius: 6px;\n padding: 16px;\n margin: 12px 0;\n overflow-x: auto;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 14px;\n line-height: 1.5;\n color: #212529;\n white-space: pre;\n word-wrap: normal;\n max-width: 100%;\n}\n\n.code-block code {\n background: none;\n border: none;\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre;\n word-wrap: normal;\n}\n\n.language-json {\n color: #d73e48;\n}\n\n.inline-code {\n background: #f1f3f4;\n border: 1px solid #dadce0;\n border-radius: 4px;\n padding: 2px 6px;\n font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;\n font-size: 0.9em;\n color: #d73e48;\n white-space: nowrap;\n}\n\n.markdown-list {\n margin: 8px 0;\n padding-left: 24px;\n}\n\n.markdown-list li {\n margin: 4px 0;\n line-height: 1.5;\n}\n\n.markdown-link {\n color: #0d6efd;\n text-decoration: underline;\n transition: color 0.2s;\n}\n\n.markdown-link:hover {\n color: #0a58ca;\n text-decoration: none;\n}\n\n/* Ensure proper text wrapping in message content */\n.message-content {\n background: #f8f9fa;\n border-radius: 12px;\n padding: 12px 40px 12px 16px; /* Extra right padding for copy button */\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n position: relative;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n overflow-x: hidden;\n}\n\n/* Enhanced JSON content display */\n.json-content-wrapper {\n margin: 8px 0;\n}\n\n.json-main-content {\n margin-bottom: 12px;\n}\n\n.json-raw-section {\n margin-top: 12px;\n}\n\n.json-toggle-button {\n background: white;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 12px;\n color: #495057;\n cursor: pointer;\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: inherit;\n}\n\n.json-toggle-button:hover {\n background: #f8f9fa;\n border-color: #adb5bd;\n color: #212529;\n}\n\n.json-toggle-button i {\n font-size: 12px;\n}\n\n/* Compact code block for JSON in collapsible sections */\n.json-raw-section .code-block {\n margin: 8px 0 0 0;\n font-size: 12px;\n max-height: 300px;\n overflow-y: auto;\n}\n\n/* Better mobile responsiveness for JSON content */\n@media (max-width: 768px) {\n .json-raw-section .code-block {\n font-size: 11px;\n max-height: 200px;\n }\n}\n\n/* JSON raw section styling - simplified since we use code editor in dialog */\n.json-raw-section {\n margin-top: 12px;\n}\n\n/* Input Area */\n.input-area {\n padding: 16px;\n background: white;\n border-top: 1px solid #dee2e6;\n flex-shrink: 0; /* Prevent input area from shrinking */\n margin-top: auto; /* Push to bottom */\n}\n\n.input-container {\n display: flex;\n gap: 12px;\n align-items: center;\n}\n\n.message-input {\n flex: 1;\n}\n\n.message-input textarea {\n resize: none;\n font-size: 14px;\n}\n\n.send-button {\n margin-bottom: 1px;\n}\n\n/* Sidebar */\n.sidebar {\n width: 100%; /* Changed from fixed 360px to 100% */\n background: white;\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n/* Kendo Splitter Specific Styles */\n.harness-body kendo-splitter {\n height: 100%;\n}\n\n.harness-body kendo-splitter-pane {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n/* Sidebar Tabs */\n.sidebar-tabs {\n display: flex;\n background: #f8f9fa;\n border-bottom: 1px solid #dee2e6;\n}\n\n.tab-button {\n flex: 1;\n padding: 12px 8px;\n background: none;\n border: none;\n border-bottom: 2px solid transparent;\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n color: #6c757d;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n transition: all 0.2s;\n position: relative;\n}\n\n.tab-button:hover {\n background: #e9ecef;\n color: #495057;\n}\n\n.tab-button.active {\n color: #0d6efd;\n border-bottom-color: #0d6efd;\n background: white;\n}\n\n.tab-button i {\n font-size: 14px;\n}\n\n.tab-button .badge {\n background: #6c757d;\n color: white;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 10px;\n margin-left: 4px;\n}\n\n.tab-button.active .badge {\n background: #0d6efd;\n}\n\n.tab-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* Sidebar Content */\n.sidebar-content {\n flex: 1;\n overflow-y: auto;\n width: 100%; /* Ensure content takes full width */\n}\n\n.tab-content {\n padding: 20px;\n width: 100%; /* Ensure content takes full width */\n box-sizing: border-box; /* Include padding in width calculation */\n}\n\n.tab-header {\n margin-bottom: 20px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n position: relative;\n}\n\n.tab-header .custom-button.icon-only {\n position: absolute;\n top: 0;\n right: 0;\n}\n\n.tab-header h4 {\n margin: 0 0 4px 0;\n font-size: 16px;\n font-weight: 600;\n color: #212529;\n}\n\n.tab-header p {\n margin: 0;\n font-size: 13px;\n color: #6c757d;\n}\n\n.tab-header .custom-button.small {\n align-self: flex-start;\n padding: 6px 12px;\n font-size: 13px;\n}\n\n.tab-header .custom-button.small i {\n margin-right: 4px;\n}\n\n/* Variables */\n.variables-list {\n margin-bottom: 16px;\n width: 100%;\n}\n\n.variable-item {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n align-items: center;\n width: 100%;\n}\n\n.variable-name {\n width: 30%; /* Changed from fixed width to percentage */\n min-width: 100px;\n}\n\n.variable-type {\n width: 25%; /* Changed from fixed width to percentage */\n min-width: 80px;\n}\n\n.variable-value {\n flex: 1;\n min-width: 0; /* Allow flex item to shrink below content size */\n}\n\n.remove-button {\n width: 32px;\n height: 32px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.add-variable-button {\n width: 100%;\n}\n\n.empty-variables {\n text-align: center;\n padding: 40px 20px;\n color: #6c757d;\n}\n\n/* Saved Conversations */\n.saved-list {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.empty-saved {\n text-align: center;\n padding: 40px 20px;\n color: #6c757d;\n}\n\n.empty-saved i {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.saved-item {\n border: 1px solid #dee2e6;\n border-radius: 8px;\n padding: 12px;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n\n.saved-item:hover {\n background: #f8f9fa;\n border-color: #adb5bd;\n}\n\n.saved-item.current {\n background: #e7f3ff;\n border-color: #0d6efd;\n}\n\n.saved-info h5 {\n margin: 0 0 4px 0;\n font-size: 14px;\n font-weight: 600;\n color: #212529;\n}\n\n.saved-info p {\n margin: 0 0 4px 0;\n font-size: 12px;\n color: #6c757d;\n}\n\n.saved-date {\n font-size: 11px;\n color: #6c757d;\n}\n\n/* Responsive adjustments */\n@media (max-width: 1200px) {\n .sidebar {\n width: 320px;\n }\n}\n\n@media (max-width: 992px) {\n .message-user,\n .message-assistant {\n max-width: 85%;\n }\n \n .sidebar {\n width: 280px;\n }\n}\n\n/* Custom scrollbar */\n.messages-container::-webkit-scrollbar,\n.sidebar-content::-webkit-scrollbar {\n width: 8px;\n}\n\n.messages-container::-webkit-scrollbar-track,\n.sidebar-content::-webkit-scrollbar-track {\n background: #f1f1f1;\n}\n\n.messages-container::-webkit-scrollbar-thumb,\n.sidebar-content::-webkit-scrollbar-thumb {\n background: #c1c1c1;\n border-radius: 4px;\n}\n\n.messages-container::-webkit-scrollbar-thumb:hover,\n.sidebar-content::-webkit-scrollbar-thumb:hover {\n background: #a1a1a1;\n}\n\n/* Dialog customization */\n::ng-deep .ai-agent-test-harness .k-dialog-wrapper {\n border-radius: 8px;\n overflow: hidden;\n}\n\n::ng-deep .ai-agent-test-harness .k-dialog-titlebar {\n background: #343a40;\n color: white;\n}\n\n::ng-deep .ai-agent-test-harness .k-dialog-title {\n font-weight: 600;\n}\n\n::ng-deep .ai-agent-test-harness .k-dialog-titlebar-actions .k-button {\n color: white;\n}\n\n::ng-deep .ai-agent-test-harness .k-dialog-content {\n padding: 0;\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n/* JSON Dialog Styles - removed since we're using WindowService */\n\n/* Advanced Parameters Styles */\n.advanced-params-panel {\n margin-top: 24px;\n border: 1px solid #dee2e6;\n border-radius: 4px;\n}\n\n.advanced-params-panel ::ng-deep .k-expander-title {\n font-weight: 600;\n color: #495057;\n}\n\n.advanced-settings {\n padding: 16px;\n}\n\n.advanced-param-input {\n width: 100%;\n}\n\n.advanced-param-switch {\n margin-top: 8px;\n}\n\n/* Format selector dropdown */\n.format-selector {\n width: 100%;\n}"] }]
3116
+ }], () => [{ type: i1.DomSanitizer }, { type: i2.WindowService }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }], { mode: [{
3117
+ type: Input
3118
+ }], entity: [{
3119
+ type: Input
3120
+ }], aiAgent: [{
3121
+ type: Input
3122
+ }], isVisible: [{
3123
+ type: Input
3124
+ }], visibilityChange: [{
3125
+ type: Output
3126
+ }], messagesContainer: [{
3127
+ type: ViewChild,
3128
+ args: ['messagesContainer']
3129
+ }], fileInput: [{
3130
+ type: ViewChild,
3131
+ args: ['fileInput']
3132
+ }], messageInput: [{
3133
+ type: ViewChild,
3134
+ args: ['messageInput']
3135
+ }], saveDialogInput: [{
3136
+ type: ViewChild,
3137
+ args: ['saveDialogInput']
3138
+ }] }); })();
3139
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AITestHarnessComponent, { className: "AITestHarnessComponent", filePath: "src/lib/custom/ai-test-harness/ai-test-harness.component.ts", lineNumber: 144 }); })();
3140
+ //# sourceMappingURL=ai-test-harness.component.js.map