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