@memberjunction/ng-core-entity-forms 2.54.0 → 2.56.0

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