@memberjunction/ng-core-entity-forms 2.53.0 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +1 -206
  2. package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts +28 -5
  3. package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts.map +1 -1
  4. package/dist/lib/custom/AIAgents/ai-agent-form.component.js +364 -234
  5. package/dist/lib/custom/AIAgents/ai-agent-form.component.js.map +1 -1
  6. package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts +36 -0
  7. package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts.map +1 -0
  8. package/dist/lib/custom/AIAgents/new-agent-dialog.component.js +325 -0
  9. package/dist/lib/custom/AIAgents/new-agent-dialog.component.js.map +1 -0
  10. package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts +41 -0
  11. package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts.map +1 -0
  12. package/dist/lib/custom/AIAgents/new-agent-dialog.service.js +87 -0
  13. package/dist/lib/custom/AIAgents/new-agent-dialog.service.js.map +1 -0
  14. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js +5 -7
  15. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js.map +1 -1
  16. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts +5 -2
  17. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts.map +1 -1
  18. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js +501 -485
  19. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js.map +1 -1
  20. package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts +2 -0
  21. package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts.map +1 -1
  22. package/dist/lib/custom/Actions/action-execution-log-form.component.js +216 -293
  23. package/dist/lib/custom/Actions/action-execution-log-form.component.js.map +1 -1
  24. package/dist/lib/custom/Actions/action-form.component.js +6 -8
  25. package/dist/lib/custom/Actions/action-form.component.js.map +1 -1
  26. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.d.ts +25 -0
  27. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.d.ts.map +1 -0
  28. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.js +261 -0
  29. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.js.map +1 -0
  30. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.d.ts +73 -0
  31. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.d.ts.map +1 -0
  32. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.js +510 -0
  33. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.js.map +1 -0
  34. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +41 -0
  35. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -0
  36. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +568 -0
  37. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -0
  38. package/dist/lib/custom/custom-forms.module.d.ts +31 -31
  39. package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
  40. package/dist/lib/custom/custom-forms.module.js +37 -34
  41. package/dist/lib/custom/custom-forms.module.js.map +1 -1
  42. package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js +16 -17
  43. package/dist/lib/generated/Entities/AIAgent/aiagent.form.component.js.map +1 -1
  44. package/dist/lib/generated/Entities/AIAgentRun/aiagentrun.form.component.js +11 -14
  45. package/dist/lib/generated/Entities/AIAgentRun/aiagentrun.form.component.js.map +1 -1
  46. package/dist/lib/generated/Entities/AIAgentRunStep/aiagentrunstep.form.component.js +3 -5
  47. package/dist/lib/generated/Entities/AIAgentRunStep/aiagentrunstep.form.component.js.map +1 -1
  48. package/dist/lib/generated/Entities/AIAgentRunStep/sections/details.component.d.ts.map +1 -1
  49. package/dist/lib/generated/Entities/AIAgentRunStep/sections/details.component.js +13 -4
  50. package/dist/lib/generated/Entities/AIAgentRunStep/sections/details.component.js.map +1 -1
  51. package/dist/lib/generated/Entities/AIAgentType/aiagenttype.form.component.js +5 -7
  52. package/dist/lib/generated/Entities/AIAgentType/aiagenttype.form.component.js.map +1 -1
  53. package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js +7 -8
  54. package/dist/lib/generated/Entities/AIConfiguration/aiconfiguration.form.component.js.map +1 -1
  55. package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js +12 -13
  56. package/dist/lib/generated/Entities/AIModel/aimodel.form.component.js.map +1 -1
  57. package/dist/lib/generated/Entities/AIModelCost/aimodelcost.form.component.js +3 -5
  58. package/dist/lib/generated/Entities/AIModelCost/aimodelcost.form.component.js.map +1 -1
  59. package/dist/lib/generated/Entities/AIModelPriceType/aimodelpricetype.form.component.js +5 -7
  60. package/dist/lib/generated/Entities/AIModelPriceType/aimodelpricetype.form.component.js.map +1 -1
  61. package/dist/lib/generated/Entities/AIModelPriceUnitType/aimodelpriceunittype.form.component.js +5 -7
  62. package/dist/lib/generated/Entities/AIModelPriceUnitType/aimodelpriceunittype.form.component.js.map +1 -1
  63. package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js +11 -12
  64. package/dist/lib/generated/Entities/AIModelType/aimodeltype.form.component.js.map +1 -1
  65. package/dist/lib/generated/Entities/AIPrompt/aiprompt.form.component.js +19 -20
  66. package/dist/lib/generated/Entities/AIPrompt/aiprompt.form.component.js.map +1 -1
  67. package/dist/lib/generated/Entities/AIPromptRun/aipromptrun.form.component.js +11 -12
  68. package/dist/lib/generated/Entities/AIPromptRun/aipromptrun.form.component.js.map +1 -1
  69. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.d.ts.map +1 -1
  70. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js +13 -4
  71. package/dist/lib/generated/Entities/AIPromptRun/sections/details.component.js.map +1 -1
  72. package/dist/lib/generated/Entities/AIVendor/aivendor.form.component.js +15 -16
  73. package/dist/lib/generated/Entities/AIVendor/aivendor.form.component.js.map +1 -1
  74. package/dist/lib/generated/Entities/Action/action.form.component.js +5 -5
  75. package/dist/lib/generated/Entities/Action/action.form.component.js.map +1 -1
  76. package/dist/lib/generated/Entities/ActionAuthorization/actionauthorization.form.component.js +1 -1
  77. package/dist/lib/generated/Entities/ActionAuthorization/actionauthorization.form.component.js.map +1 -1
  78. package/dist/lib/generated/Entities/ActionContext/actioncontext.form.component.js +1 -1
  79. package/dist/lib/generated/Entities/ActionContext/actioncontext.form.component.js.map +1 -1
  80. package/dist/lib/generated/Entities/Conversation/conversation.form.component.js +5 -5
  81. package/dist/lib/generated/Entities/Conversation/conversation.form.component.js.map +1 -1
  82. package/dist/lib/generated/Entities/TemplateContent/templatecontent.form.component.js +4 -4
  83. package/dist/lib/generated/Entities/TemplateContent/templatecontent.form.component.js.map +1 -1
  84. package/dist/lib/generated/Entities/User/user.form.component.js +5 -5
  85. package/dist/lib/generated/Entities/User/user.form.component.js.map +1 -1
  86. package/dist/public-api.d.ts +2 -3
  87. package/dist/public-api.d.ts.map +1 -1
  88. package/dist/public-api.js +3 -16
  89. package/dist/public-api.js.map +1 -1
  90. package/package.json +13 -12
  91. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.d.ts +0 -244
  92. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.d.ts.map +0 -1
  93. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.js +0 -1327
  94. package/dist/lib/custom/ai-test-harness/agent-execution-monitor.component.js.map +0 -1
  95. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.d.ts +0 -19
  96. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.d.ts.map +0 -1
  97. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.js +0 -458
  98. package/dist/lib/custom/ai-test-harness/agent-execution-node.component.js.map +0 -1
  99. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.d.ts +0 -78
  100. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.d.ts.map +0 -1
  101. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.js +0 -157
  102. package/dist/lib/custom/ai-test-harness/ai-test-harness-dialog.component.js.map +0 -1
  103. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.d.ts +0 -598
  104. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.d.ts.map +0 -1
  105. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js +0 -3204
  106. package/dist/lib/custom/ai-test-harness/ai-test-harness.component.js.map +0 -1
  107. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts +0 -8
  108. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.d.ts.map +0 -1
  109. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js +0 -57
  110. package/dist/lib/custom/ai-test-harness/json-viewer-window.component.js.map +0 -1
  111. package/dist/lib/custom/test-harness-dialog.service.d.ts +0 -111
  112. package/dist/lib/custom/test-harness-dialog.service.d.ts.map +0 -1
  113. package/dist/lib/custom/test-harness-dialog.service.js +0 -211
  114. package/dist/lib/custom/test-harness-dialog.service.js.map +0 -1
@@ -1,3204 +0,0 @@
1
- import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
2
- import { Metadata, RunView } 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
- this.loadTemplateParameters(); // Load template parameters for pre-population
1100
- }
1101
- }
1102
- /**
1103
- * Responds to input property changes, particularly when entity is set/changed
1104
- */
1105
- ngOnChanges(changes) {
1106
- if (changes['entity']) {
1107
- if (changes['entity'].currentValue) {
1108
- this.loadSavedConversations();
1109
- }
1110
- }
1111
- if (changes['aiAgent']) {
1112
- if (!this.entity && this.aiAgent) {
1113
- this.entity = this.aiAgent;
1114
- this.mode = 'agent';
1115
- this.loadSavedConversations();
1116
- }
1117
- }
1118
- if (changes['isVisible']) {
1119
- if (changes['isVisible'].currentValue) {
1120
- // Reset focus flag when dialog becomes visible
1121
- this._hasFocused = false;
1122
- }
1123
- }
1124
- }
1125
- /**
1126
- * Component cleanup. Destroys subscriptions and clears any active intervals
1127
- * to prevent memory leaks.
1128
- */
1129
- ngOnDestroy() {
1130
- this.destroy$.next();
1131
- this.destroy$.complete();
1132
- if (this.elapsedTimeInterval) {
1133
- clearInterval(this.elapsedTimeInterval);
1134
- }
1135
- if (this._mjGlobalEventSub) {
1136
- this._mjGlobalEventSub.unsubscribe();
1137
- }
1138
- if (this._agentStreamSub) {
1139
- this._agentStreamSub.unsubscribe();
1140
- }
1141
- }
1142
- /**
1143
- * Post-view check hook that handles auto-scrolling to the bottom of the message container
1144
- * when new content is added.
1145
- */
1146
- ngAfterViewChecked() {
1147
- if (this.scrollNeeded) {
1148
- this.scrollToBottom();
1149
- this.scrollNeeded = false;
1150
- }
1151
- // Auto-focus message input when dialog first becomes visible
1152
- // Use Promise.resolve to schedule this after current change detection cycle
1153
- if (this.isVisible && !this._hasFocused && this.messageInput) {
1154
- this._hasFocused = true;
1155
- Promise.resolve().then(() => {
1156
- this.messageInput?.focus();
1157
- });
1158
- }
1159
- }
1160
- subscribeToEvents() {
1161
- // Set up direct GraphQL subscription for agent execution stream
1162
- const dataProvider = Metadata.Provider;
1163
- const _providerPushStatusSub = dataProvider.PushStatusUpdates().subscribe((status) => {
1164
- const message = JSON.parse(status.message || '{}');
1165
- if (message?.resolver === 'RunAIAgentResolver') {
1166
- // Handle different types of streaming messages
1167
- if (message?.type === 'ExecutionProgress' && message.data?.progress) {
1168
- const userMsg = message.data.progress.message;
1169
- // Update streaming message content
1170
- const streamingMessage = this.conversationMessages.find(m => m.isStreaming);
1171
- if (streamingMessage) {
1172
- // Clear any typing animation interval
1173
- const typingInterval = streamingMessage._typingInterval;
1174
- if (typingInterval) {
1175
- clearInterval(typingInterval);
1176
- delete streamingMessage._typingInterval;
1177
- }
1178
- // Update the streaming content with the status message
1179
- streamingMessage.streamingContent = userMsg;
1180
- this.scrollNeeded = true;
1181
- }
1182
- // Update execution monitor with live progress
1183
- if (!this.currentExecutionData) {
1184
- this.currentExecutionData = { liveSteps: [] };
1185
- }
1186
- this.executionMonitorMode = 'live';
1187
- // Add or update the current step in the execution tree
1188
- const stepInfo = {
1189
- step: {
1190
- ID: `temp-${Date.now()}`,
1191
- StepName: userMsg,
1192
- StepType: message.data.progress.step || 'unknown',
1193
- Status: 'Running',
1194
- StartedAt: new Date()
1195
- },
1196
- executionType: message.data.progress.step || 'unknown',
1197
- agentHierarchy: message.data.progress.agentHierarchy || [],
1198
- depth: message.data.progress.depth || 0,
1199
- startTime: new Date()
1200
- };
1201
- // For live mode, we'll append steps as they come
1202
- if (!this.currentExecutionData.liveSteps) {
1203
- this.currentExecutionData.liveSteps = [];
1204
- }
1205
- this.currentExecutionData.liveSteps.push(stepInfo);
1206
- // Trigger change detection for the monitor
1207
- this.currentExecutionData = { ...this.currentExecutionData };
1208
- }
1209
- else if (message?.type === 'StreamingContent' && message.data?.streaming) {
1210
- const streamingMessage = this.conversationMessages.find(m => m.isStreaming);
1211
- if (streamingMessage) {
1212
- // Append streaming content
1213
- if (!streamingMessage.streamingContent) {
1214
- streamingMessage.streamingContent = '';
1215
- }
1216
- streamingMessage.streamingContent = message.data.streaming.content;
1217
- this.scrollNeeded = true;
1218
- }
1219
- }
1220
- else if (message?.type === 'partial_result' && message.data?.partialResult) {
1221
- // Could update execution monitor with partial results
1222
- }
1223
- else if (message?.type === 'complete') {
1224
- // Switch execution monitor to historical mode with final data
1225
- this.executionMonitorMode = 'historical';
1226
- }
1227
- }
1228
- });
1229
- }
1230
- /**
1231
- * Loads available AI models for prompt execution.
1232
- * Only called when the harness is in prompt mode.
1233
- */
1234
- async loadAvailableModels() {
1235
- await AIEngineBase.Instance.Config(false);
1236
- // Filter models by the prompt's AIModelTypeID if it exists
1237
- let filteredModels = [];
1238
- if (this.entity && 'AIModelTypeID' in this.entity) {
1239
- const prompt = this.entity;
1240
- if (prompt.AIModelTypeID) {
1241
- filteredModels = AIEngineBase.Instance.Models.filter(model => model.AIModelTypeID === prompt.AIModelTypeID && model.IsActive);
1242
- }
1243
- else {
1244
- // No model type restriction, show all active models
1245
- filteredModels = AIEngineBase.Instance.Models.filter(model => model.IsActive);
1246
- }
1247
- // Set default response format from prompt with slight delay for Kendo dropdown
1248
- setTimeout(() => {
1249
- const format = this.responseFormatOptions.find(f => f.value.trim().toLowerCase() === prompt.ResponseFormat.trim().toLowerCase());
1250
- if (format) {
1251
- this.selectedResponseFormat = format;
1252
- }
1253
- else {
1254
- this.selectedResponseFormat = this.responseFormatOptions[0]; // Default to 'Any'
1255
- }
1256
- }, 0);
1257
- }
1258
- else {
1259
- // Not a prompt entity, show all active models
1260
- filteredModels = AIEngineBase.Instance.Models.filter(model => model.IsActive);
1261
- }
1262
- // Sort models by name
1263
- filteredModels.sort((a, b) => a.Name.localeCompare(b.Name));
1264
- // Determine the default model for this prompt
1265
- if (this.entity && 'AIModelTypeID' in this.entity) {
1266
- const prompt = this.entity;
1267
- this.defaultModelName = await this.getDefaultModelName(prompt);
1268
- }
1269
- // Add a blank option at the beginning with the default model name
1270
- this.availableModels = [
1271
- { ID: '', Name: this.defaultModelName ? `-- Default: ${this.defaultModelName} --` : '-- Use Default Model --' },
1272
- ...filteredModels
1273
- ];
1274
- // Don't auto-select a model - let the dropdown show the blank option
1275
- this.selectedModelId = '';
1276
- // If we have a default model, load its default vendor
1277
- if (this.defaultModelName) {
1278
- await this.loadDefaultVendor();
1279
- }
1280
- else {
1281
- this.selectedVendorId = '';
1282
- this.availableVendors = [];
1283
- }
1284
- }
1285
- /**
1286
- * Gets the default model name for a prompt based on its configuration
1287
- */
1288
- async getDefaultModelName(prompt) {
1289
- try {
1290
- // Get prompt-specific model associations
1291
- const promptModels = AIEngineBase.Instance.PromptModels.filter(pm => pm.PromptID === prompt.ID &&
1292
- (pm.Status === 'Active' || pm.Status === 'Preview'));
1293
- let defaultModel = null;
1294
- if (promptModels.length > 0) {
1295
- // Sort by priority (higher priority first)
1296
- promptModels.sort((a, b) => (b.Priority || 0) - (a.Priority || 0));
1297
- // Find the first active model
1298
- for (const pm of promptModels) {
1299
- const model = AIEngineBase.Instance.Models.find(m => m.ID === pm.ModelID && m.IsActive);
1300
- if (model) {
1301
- defaultModel = model;
1302
- break;
1303
- }
1304
- }
1305
- }
1306
- // If no prompt-specific model, use selection strategy
1307
- if (!defaultModel) {
1308
- const candidates = AIEngineBase.Instance.Models.filter(m => m.IsActive &&
1309
- (!prompt.AIModelTypeID || m.AIModelTypeID === prompt.AIModelTypeID));
1310
- if (candidates.length > 0) {
1311
- // Apply selection strategy
1312
- if (prompt.SelectionStrategy === 'ByPower') {
1313
- candidates.sort((a, b) => {
1314
- switch (prompt.PowerPreference) {
1315
- case 'Lowest':
1316
- return (a.PowerRank || 0) - (b.PowerRank || 0);
1317
- case 'Highest':
1318
- case 'Balanced':
1319
- default:
1320
- return (b.PowerRank || 0) - (a.PowerRank || 0);
1321
- }
1322
- });
1323
- }
1324
- defaultModel = candidates[0];
1325
- }
1326
- }
1327
- // Cache the default model object for vendor lookup
1328
- this.defaultModel = defaultModel;
1329
- return defaultModel ? defaultModel.Name : '';
1330
- }
1331
- catch (error) {
1332
- console.error('Error getting default model name:', error);
1333
- return '';
1334
- }
1335
- }
1336
- /**
1337
- * Loads the vendors for the default model
1338
- */
1339
- async loadDefaultVendor() {
1340
- try {
1341
- if (!this.defaultModel) {
1342
- this.availableVendors = [];
1343
- return;
1344
- }
1345
- // Get vendors that offer this model - same logic as loadVendorsForModel
1346
- const modelVendors = AIEngineBase.Instance.ModelVendors.filter(mv => mv.ModelID === this.defaultModel.ID &&
1347
- mv.Status === 'Active' &&
1348
- mv.Type?.trim().toLowerCase() === 'inference provider');
1349
- // Map to vendor objects with priority from ModelVendor
1350
- const vendorObjects = [];
1351
- for (const mv of modelVendors) {
1352
- const vendor = AIEngineBase.Instance.Vendors.find(v => v.ID === mv.VendorID);
1353
- if (vendor) {
1354
- vendorObjects.push({
1355
- ID: vendor.ID,
1356
- Name: vendor.Name,
1357
- Priority: mv.Priority || 999 // Use ModelVendor priority
1358
- });
1359
- }
1360
- }
1361
- // Sort by priority (lower number = higher priority)
1362
- vendorObjects.sort((a, b) => a.Priority - b.Priority);
1363
- this.availableVendors = vendorObjects;
1364
- // Select the highest priority vendor
1365
- if (vendorObjects.length > 0) {
1366
- this.selectedVendorId = vendorObjects[0].ID;
1367
- }
1368
- }
1369
- catch (error) {
1370
- console.error('Error loading default vendor:', error);
1371
- this.availableVendors = [];
1372
- }
1373
- }
1374
- /**
1375
- * Handles model selection change and loads available vendors for the selected model
1376
- */
1377
- onModelSelectionChange() {
1378
- this.selectedVendorId = '';
1379
- this.availableVendors = [];
1380
- if (!this.selectedModelId) {
1381
- // When default model is selected, load default vendor
1382
- if (this.defaultModelName) {
1383
- this.loadDefaultVendor();
1384
- }
1385
- return;
1386
- }
1387
- // Ensure AIEngineBase is configured
1388
- if (!AIEngineBase.Instance.Models || AIEngineBase.Instance.Models.length === 0) {
1389
- AIEngineBase.Instance.Config(false).then(() => {
1390
- this.loadVendorsForModel();
1391
- });
1392
- }
1393
- else {
1394
- this.loadVendorsForModel();
1395
- }
1396
- }
1397
- /**
1398
- * Loads available vendors for the selected model
1399
- */
1400
- loadVendorsForModel() {
1401
- if (!this.selectedModelId) {
1402
- return;
1403
- }
1404
- // Get model-specific vendors
1405
- const modelVendors = AIEngineBase.Instance.ModelVendors.filter(mv => mv.ModelID === this.selectedModelId &&
1406
- mv.Status === 'Active' &&
1407
- mv.Type?.trim().toLowerCase() === 'inference provider');
1408
- // Map to vendor objects with priority from ModelVendor
1409
- const vendorObjects = [];
1410
- for (const mv of modelVendors) {
1411
- const vendor = AIEngineBase.Instance.Vendors.find(v => v.ID === mv.VendorID);
1412
- if (vendor) {
1413
- // For now, include all vendors. TODO: Filter by vendor type when available
1414
- vendorObjects.push({
1415
- ID: vendor.ID,
1416
- Name: vendor.Name,
1417
- Priority: mv.Priority || 999 // Use ModelVendor priority
1418
- });
1419
- }
1420
- }
1421
- // Sort by priority (lower number = higher priority)
1422
- vendorObjects.sort((a, b) => a.Priority - b.Priority);
1423
- this.availableVendors = vendorObjects;
1424
- // Auto-select the highest priority vendor if only one or set default
1425
- if (vendorObjects.length === 1) {
1426
- this.selectedVendorId = vendorObjects[0].ID;
1427
- }
1428
- else if (vendorObjects.length > 1) {
1429
- // Select the highest priority (lowest priority number)
1430
- this.selectedVendorId = vendorObjects[0].ID;
1431
- }
1432
- }
1433
- /**
1434
- * Loads default parameter values from the AI prompt entity
1435
- */
1436
- loadPromptDefaults() {
1437
- if (this.mode === 'prompt' && this.entity && this.isPromptEntity(this.entity)) {
1438
- const prompt = this.entity;
1439
- // Load default values from prompt entity
1440
- if (prompt.Temperature != null)
1441
- this.advancedParams.temperature = prompt.Temperature;
1442
- if (prompt.TopP != null)
1443
- this.advancedParams.topP = prompt.TopP;
1444
- if (prompt.TopK != null)
1445
- this.advancedParams.topK = prompt.TopK;
1446
- if (prompt.MinP != null)
1447
- this.advancedParams.minP = prompt.MinP;
1448
- if (prompt.FrequencyPenalty != null)
1449
- this.advancedParams.frequencyPenalty = prompt.FrequencyPenalty;
1450
- if (prompt.PresencePenalty != null)
1451
- this.advancedParams.presencePenalty = prompt.PresencePenalty;
1452
- if (prompt.Seed != null)
1453
- this.advancedParams.seed = prompt.Seed;
1454
- if (prompt.StopSequences) {
1455
- this.stopSequencesText = prompt.StopSequences;
1456
- this.advancedParams.stopSequences = prompt.StopSequences.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
1457
- }
1458
- if (prompt.IncludeLogProbs != null)
1459
- this.advancedParams.includeLogProbs = prompt.IncludeLogProbs;
1460
- if (prompt.TopLogProbs != null)
1461
- this.advancedParams.topLogProbs = prompt.TopLogProbs;
1462
- }
1463
- }
1464
- /**
1465
- * Loads template parameters from the prompt's template and pre-populates
1466
- * the template variables with their default values
1467
- */
1468
- async loadTemplateParameters() {
1469
- if (this.mode === 'prompt' && this.entity && this.isPromptEntity(this.entity)) {
1470
- const prompt = this.entity;
1471
- if (!prompt.TemplateID) {
1472
- return; // No template to load parameters from
1473
- }
1474
- try {
1475
- const rv = new RunView();
1476
- const result = await rv.RunView({
1477
- EntityName: 'Template Params',
1478
- ExtraFilter: `TemplateID='${prompt.TemplateID}'`,
1479
- OrderBy: 'Name ASC',
1480
- ResultType: 'entity_object'
1481
- });
1482
- if (result.Success && result.Results && result.Results.length > 0) {
1483
- // Clear existing template variables
1484
- this.templateVariables = [];
1485
- // Add each template parameter as a variable with its default value
1486
- for (const param of result.Results) {
1487
- this.templateVariables.push({
1488
- name: param.Name,
1489
- value: param.DefaultValue || '',
1490
- type: this.getVariableTypeFromParamType(param.Type)
1491
- });
1492
- }
1493
- // If no parameters found, add one empty variable to start
1494
- if (this.templateVariables.length === 0) {
1495
- this.templateVariables.push({ name: '', value: '', type: 'string' });
1496
- }
1497
- }
1498
- }
1499
- catch (error) {
1500
- console.error('Error loading template parameters:', error);
1501
- // Add one empty variable on error
1502
- this.templateVariables = [{ name: '', value: '', type: 'string' }];
1503
- }
1504
- }
1505
- }
1506
- /**
1507
- * Maps template parameter types to variable types
1508
- */
1509
- getVariableTypeFromParamType(paramType) {
1510
- switch (paramType?.toLowerCase()) {
1511
- case 'number':
1512
- case 'integer':
1513
- case 'float':
1514
- case 'decimal':
1515
- return 'number';
1516
- case 'boolean':
1517
- case 'bool':
1518
- return 'boolean';
1519
- case 'object':
1520
- case 'json':
1521
- case 'array':
1522
- return 'object';
1523
- default:
1524
- return 'string';
1525
- }
1526
- }
1527
- /**
1528
- * Resets all model settings to the prompt defaults
1529
- */
1530
- resetToPromptDefaults() {
1531
- if (this.mode === 'prompt' && this.entity && this.isPromptEntity(this.entity)) {
1532
- const prompt = this.entity;
1533
- // Reset model selection to default
1534
- this.selectedModelId = '';
1535
- // Reset vendor - will be loaded by loadDefaultVendor
1536
- this.selectedVendorId = '';
1537
- // Reset response format to prompt's setting
1538
- const format = this.responseFormatOptions.find(f => f.value.trim().toLowerCase() === prompt.ResponseFormat.trim().toLowerCase());
1539
- this.selectedResponseFormat = format || this.responseFormatOptions[0];
1540
- // Reset max tokens
1541
- this.maxTokens = null;
1542
- // Reset skip validation
1543
- this.skipValidation = false;
1544
- // Reset advanced parameters
1545
- this.advancedParams = {
1546
- temperature: null,
1547
- topP: null,
1548
- topK: null,
1549
- minP: null,
1550
- frequencyPenalty: null,
1551
- presencePenalty: null,
1552
- seed: null,
1553
- stopSequences: [],
1554
- includeLogProbs: false,
1555
- topLogProbs: 2
1556
- };
1557
- this.stopSequencesText = '';
1558
- // Reload prompt defaults for advanced params
1559
- this.loadPromptDefaults();
1560
- // Reload default vendor for the default model
1561
- if (this.defaultModelName) {
1562
- this.loadDefaultVendor();
1563
- }
1564
- }
1565
- }
1566
- /**
1567
- * Resets the test harness to its initial state, clearing all conversations,
1568
- * variables, and UI state. Called automatically when the harness becomes visible.
1569
- */
1570
- resetHarness() {
1571
- this.conversationMessages = [];
1572
- this.currentUserMessage = '';
1573
- this.isExecuting = false;
1574
- this.agentVariables = [{ name: '', value: '', type: 'string' }];
1575
- this.templateVariables = [];
1576
- this.currentConversationId = null;
1577
- this.showSidebar = true;
1578
- this.currentExecutionData = null;
1579
- this.executionMonitorMode = 'historical';
1580
- // Reset conversation state
1581
- this.agentConversationState = null;
1582
- this.lastAgentReturnValue = null;
1583
- this.subAgentHistory = [];
1584
- // Set default tab based on mode
1585
- this.activeTab = this.mode === 'agent' ? 'agentVariables' : 'templateVariables';
1586
- // Reset advanced parameters
1587
- this.advancedParams = {
1588
- temperature: null,
1589
- topP: null,
1590
- topK: null,
1591
- minP: null,
1592
- frequencyPenalty: null,
1593
- presencePenalty: null,
1594
- seed: null,
1595
- stopSequences: [],
1596
- includeLogProbs: false,
1597
- topLogProbs: 2
1598
- };
1599
- this.stopSequencesText = '';
1600
- this.advancedParamsExpanded = false;
1601
- this.skipValidation = false;
1602
- }
1603
- /**
1604
- * Starts a new conversation
1605
- */
1606
- newConversation() {
1607
- if (this.conversationMessages.length > 0 && !this.currentConversationId) {
1608
- // Unsaved conversation exists - use our custom dialog
1609
- if (confirm('You have an unsaved conversation. Would you like to save it first?')) {
1610
- this.saveConversation();
1611
- return;
1612
- }
1613
- }
1614
- this.resetHarness();
1615
- MJNotificationService.Instance.CreateSimpleNotification('Started new conversation', 'info', 2000);
1616
- }
1617
- /**
1618
- * Closes the test harness by setting visibility to false and emitting the change event.
1619
- * Used by dialog implementations and close buttons.
1620
- */
1621
- close() {
1622
- this._isVisible = false;
1623
- this.visibilityChange.emit(false);
1624
- }
1625
- /**
1626
- * Toggles the visibility of the configuration sidebar.
1627
- * Allows users to show/hide the data context and conversation management panels.
1628
- */
1629
- toggleSidebar() {
1630
- this.showSidebar = !this.showSidebar;
1631
- }
1632
- /**
1633
- * Switches to the specified tab in the configuration sidebar.
1634
- * @param tab - The tab to activate
1635
- */
1636
- selectTab(tab) {
1637
- this.activeTab = tab;
1638
- // If switching to execution monitor tab, ensure it has the latest data
1639
- if (tab === 'executionMonitor' && this.conversationMessages.length > 0) {
1640
- const lastAssistantMessage = this.conversationMessages
1641
- .filter(m => m.role === 'assistant' && m.executionData)
1642
- .pop();
1643
- if (lastAssistantMessage && lastAssistantMessage.executionData) {
1644
- this.currentExecutionData = lastAssistantMessage.executionData;
1645
- this.executionMonitorMode = 'historical';
1646
- }
1647
- }
1648
- }
1649
- /**
1650
- * Shows the execution history for a specific message
1651
- * @param message - The message to show execution history for
1652
- */
1653
- showMessageExecutionHistory(message) {
1654
- if (message.executionData && this.mode === 'agent') {
1655
- this.currentExecutionData = message.executionData;
1656
- this.executionMonitorMode = 'historical';
1657
- // Switch to execution monitor tab if not already there
1658
- if (this.activeTab !== 'executionMonitor') {
1659
- this.selectTab('executionMonitor');
1660
- }
1661
- }
1662
- }
1663
- /**
1664
- * Extract sub-agent nodes from execution tree
1665
- */
1666
- extractSubAgentNodes(executionTree) {
1667
- const subAgentNodes = [];
1668
- const traverse = (nodes) => {
1669
- for (const node of nodes) {
1670
- if (node.executionType === 'sub-agent') {
1671
- subAgentNodes.push({
1672
- agentName: node.step?.StepName,
1673
- result: node.outputData,
1674
- timestamp: node.endTime || node.startTime
1675
- });
1676
- }
1677
- if (node.children) {
1678
- traverse(node.children);
1679
- }
1680
- }
1681
- };
1682
- traverse(executionTree);
1683
- return subAgentNodes;
1684
- }
1685
- /**
1686
- * Sends the current user message to the AI agent and initiates execution.
1687
- * Handles message validation, conversation updates, UI state management, and agent execution.
1688
- * Automatically clears the input field and triggers auto-scroll after sending.
1689
- *
1690
- * @example
1691
- * ```typescript
1692
- * // Called when user presses Enter or clicks Send button
1693
- * await this.sendMessage();
1694
- * ```
1695
- */
1696
- async sendMessage() {
1697
- if (!this.currentUserMessage.trim() || !this.entity) {
1698
- return;
1699
- }
1700
- // Add user message to conversation
1701
- const userMessage = {
1702
- id: this.generateMessageId(),
1703
- role: 'user',
1704
- content: this.currentUserMessage.trim(),
1705
- timestamp: new Date()
1706
- };
1707
- this.conversationMessages.push(userMessage);
1708
- // Clear input and update change detection
1709
- const messageToSend = this.currentUserMessage;
1710
- this.currentUserMessage = '';
1711
- this.cdr.detectChanges();
1712
- // Scroll to bottom
1713
- this.scrollNeeded = true;
1714
- // Auto-switch to execution monitor tab if in agent mode
1715
- if (this.mode === 'agent' && this.activeTab !== 'executionMonitor') {
1716
- this.selectTab('executionMonitor');
1717
- }
1718
- // Execute based on mode
1719
- if (this.mode === 'agent') {
1720
- await this.executeAgent(messageToSend);
1721
- }
1722
- else {
1723
- await this.executePrompt(messageToSend);
1724
- }
1725
- }
1726
- async executeAgent(userMessage) {
1727
- if (!this.entity || !this.isAgentEntity(this.entity))
1728
- return;
1729
- this.isExecuting = true;
1730
- // Clear previous execution data when starting a new run
1731
- this.currentExecutionData = { liveSteps: [] };
1732
- this.executionMonitorMode = 'live';
1733
- // Add placeholder assistant message for streaming
1734
- const assistantMessage = {
1735
- id: this.generateMessageId(),
1736
- role: 'assistant',
1737
- content: '',
1738
- timestamp: new Date(),
1739
- isStreaming: true,
1740
- streamingContent: this.mode === 'agent' ? 'Running agent...' : 'Running prompt...',
1741
- agentRunId: '',
1742
- streamingStartTime: Date.now(),
1743
- elapsedTime: 0
1744
- };
1745
- this.conversationMessages.push(assistantMessage);
1746
- this.scrollNeeded = true;
1747
- // Start elapsed time counter
1748
- this.startElapsedTimeCounter(assistantMessage);
1749
- // Initialize execution monitor for live mode
1750
- this.currentExecutionData = { liveSteps: [] };
1751
- this.executionMonitorMode = 'live';
1752
- try {
1753
- // Get GraphQL data provider
1754
- const dataProvider = Metadata.Provider;
1755
- // Build conversation history for context
1756
- const messages = this.conversationMessages
1757
- .filter(m => !m.isStreaming)
1758
- .map(m => ({
1759
- role: m.role,
1760
- content: m.content
1761
- }));
1762
- // Build data context - include conversation state if available
1763
- const dataContext = this.buildDataContext();
1764
- const templateData = this.buildTemplateData();
1765
- // Add conversation state to data context if we have it
1766
- if (this.agentConversationState) {
1767
- dataContext._conversationState = this.agentConversationState;
1768
- }
1769
- if (this.lastAgentReturnValue) {
1770
- dataContext._lastReturnValue = this.lastAgentReturnValue;
1771
- }
1772
- if (this.subAgentHistory.length > 0) {
1773
- dataContext._subAgentHistory = this.subAgentHistory;
1774
- }
1775
- // Generate a session ID for this execution
1776
- const sessionId = dataProvider.sessionId;
1777
- // Execute the agent
1778
- const query = `
1779
- mutation RunAIAgent($agentId: String!, $messages: String!, $sessionId: String!, $data: String, $templateData: String) {
1780
- RunAIAgent(agentId: $agentId, messages: $messages, sessionId: $sessionId, data: $data, templateData: $templateData) {
1781
- success
1782
- errorMessage
1783
- executionTimeMs
1784
- payload
1785
- }
1786
- }
1787
- `;
1788
- const variables = {
1789
- agentId: this.entity.ID,
1790
- messages: JSON.stringify(messages),
1791
- sessionId: sessionId,
1792
- data: Object.keys(dataContext).length > 0 ? JSON.stringify(dataContext) : null,
1793
- templateData: Object.keys(templateData).length > 0 ? JSON.stringify(templateData) : null
1794
- };
1795
- // Start typing animation while we wait for the first real stream
1796
- this.startTypingAnimation(assistantMessage);
1797
- let result;
1798
- try {
1799
- result = await dataProvider.ExecuteGQL(query, variables);
1800
- }
1801
- catch (gqlError) {
1802
- console.error('❌ AI Test Harness: GraphQL execution failed', {
1803
- error: gqlError,
1804
- message: gqlError?.message,
1805
- networkError: gqlError?.networkError,
1806
- graphQLErrors: gqlError?.graphQLErrors,
1807
- statusCode: gqlError?.networkError?.statusCode
1808
- });
1809
- throw gqlError;
1810
- }
1811
- const executionResult = result?.RunAIAgent;
1812
- // Stop elapsed time counter
1813
- if (this.elapsedTimeInterval) {
1814
- clearInterval(this.elapsedTimeInterval);
1815
- this.elapsedTimeInterval = null;
1816
- }
1817
- // Update assistant message with result
1818
- assistantMessage.isStreaming = false;
1819
- if (executionResult?.success) {
1820
- // Parse the payload to get the full execution result
1821
- const fullResult = JSON.parse(executionResult.payload);
1822
- // Store execution data with the message
1823
- assistantMessage.executionData = fullResult;
1824
- // Merge live steps with final result if needed
1825
- if (this.currentExecutionData && this.currentExecutionData.liveSteps && fullResult) {
1826
- // Preserve the live steps we collected during execution
1827
- fullResult.liveSteps = this.currentExecutionData.liveSteps;
1828
- }
1829
- // Update execution monitor with the new data
1830
- this.currentExecutionData = fullResult;
1831
- this.executionMonitorMode = 'historical';
1832
- // Preserve conversation state from the result
1833
- if (fullResult.returnValue) {
1834
- this.lastAgentReturnValue = fullResult.returnValue;
1835
- // Extract conversation state if present
1836
- if (fullResult.returnValue.conversationState) {
1837
- this.agentConversationState = fullResult.returnValue.conversationState;
1838
- }
1839
- // Track sub-agent executions
1840
- if (fullResult.executionTree) {
1841
- const subAgentNodes = this.extractSubAgentNodes(fullResult.executionTree);
1842
- if (subAgentNodes.length > 0) {
1843
- this.subAgentHistory.push(...subAgentNodes);
1844
- }
1845
- }
1846
- }
1847
- // Extract the user message from the nested returnValue structure
1848
- let displayContent = 'No response generated';
1849
- if (fullResult.returnValue) {
1850
- if (typeof fullResult.returnValue === 'object' &&
1851
- fullResult.returnValue.nextStep?.userMessage) {
1852
- // This is the expected structure for chat responses
1853
- displayContent = fullResult.returnValue.nextStep.userMessage;
1854
- }
1855
- else if (typeof fullResult.returnValue === 'string') {
1856
- // Simple string response
1857
- displayContent = fullResult.returnValue;
1858
- }
1859
- else {
1860
- // Other structured response - show as JSON
1861
- displayContent = JSON.stringify(fullResult.returnValue, null, 2);
1862
- }
1863
- }
1864
- assistantMessage.content = displayContent;
1865
- assistantMessage.executionTime = executionResult.executionTimeMs;
1866
- assistantMessage.agentRunId = fullResult.agentRun?.ID || assistantMessage.agentRunId;
1867
- // Store the full result as raw content for debugging/inspection
1868
- assistantMessage.rawContent = executionResult.payload;
1869
- }
1870
- else {
1871
- console.error('❌ AI Test Harness: Execution failed', {
1872
- success: executionResult?.success,
1873
- errorMessage: executionResult?.errorMessage,
1874
- hasPayload: !!executionResult?.payload
1875
- });
1876
- assistantMessage.content = 'I encountered an error processing your request.';
1877
- assistantMessage.error = executionResult?.errorMessage || 'Unknown error occurred';
1878
- // Try to parse error payload if available
1879
- if (executionResult?.payload) {
1880
- try {
1881
- JSON.parse(executionResult.payload); // Validate it's valid JSON
1882
- assistantMessage.rawContent = executionResult.payload;
1883
- }
1884
- catch {
1885
- // Ignore parse errors
1886
- }
1887
- }
1888
- }
1889
- delete assistantMessage.streamingContent;
1890
- this.scrollNeeded = true;
1891
- // Auto-save conversation
1892
- this.autoSaveConversation();
1893
- }
1894
- catch (error) {
1895
- console.error('❌ AI Test Harness: Caught error during agent execution', {
1896
- error: error,
1897
- message: error?.message,
1898
- stack: error?.stack,
1899
- type: error?.constructor?.name
1900
- });
1901
- // Update assistant message with error
1902
- const lastMessage = this.conversationMessages[this.conversationMessages.length - 1];
1903
- if (lastMessage && lastMessage.role === 'assistant') {
1904
- lastMessage.isStreaming = false;
1905
- lastMessage.content = 'I encountered an error processing your request.';
1906
- lastMessage.error = error.message;
1907
- delete lastMessage.streamingContent;
1908
- }
1909
- MJNotificationService.Instance.CreateSimpleNotification('Failed to execute agent: ' + error.message, 'error', 6000);
1910
- }
1911
- finally {
1912
- this.isExecuting = false;
1913
- if (this.elapsedTimeInterval) {
1914
- clearInterval(this.elapsedTimeInterval);
1915
- this.elapsedTimeInterval = null;
1916
- }
1917
- // Focus back on the input
1918
- this.focusMessageInput();
1919
- }
1920
- }
1921
- async executePrompt(userMessage) {
1922
- if (!this.entity || !this.isPromptEntity(this.entity))
1923
- return;
1924
- this.isExecuting = true;
1925
- // Add placeholder assistant message for streaming
1926
- const assistantMessage = {
1927
- id: this.generateMessageId(),
1928
- role: 'assistant',
1929
- content: '',
1930
- timestamp: new Date(),
1931
- isStreaming: true,
1932
- streamingContent: this.mode === 'agent' ? 'Running agent...' : 'Running prompt...',
1933
- agentRunId: '',
1934
- streamingStartTime: Date.now(),
1935
- elapsedTime: 0
1936
- };
1937
- this.conversationMessages.push(assistantMessage);
1938
- this.scrollNeeded = true;
1939
- // Start elapsed time counter
1940
- this.startElapsedTimeCounter(assistantMessage);
1941
- try {
1942
- // Get GraphQL data provider
1943
- const dataProvider = Metadata.Provider;
1944
- // Build template variables from user input
1945
- const templateVariables = this.buildTemplateVariables();
1946
- // Prepare data context (template variables + user message)
1947
- const dataContext = {
1948
- ...templateVariables,
1949
- userMessage: userMessage
1950
- };
1951
- // Build conversation messages
1952
- const messages = this.conversationMessages
1953
- .filter(m => !m.isStreaming && m.content) // Only include non-streaming messages with content
1954
- .map(m => ({
1955
- role: m.role,
1956
- content: m.content
1957
- }));
1958
- // Execute the prompt using RunAIPrompt
1959
- const query = `
1960
- 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) {
1961
- 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) {
1962
- success
1963
- output
1964
- parsedResult
1965
- error
1966
- executionTimeMs
1967
- tokensUsed
1968
- promptRunId
1969
- rawResult
1970
- validationResult
1971
- }
1972
- }
1973
- `;
1974
- const variables = {
1975
- promptId: this.entity.ID,
1976
- data: JSON.stringify(dataContext),
1977
- modelId: this.selectedModelId || undefined,
1978
- vendorId: this.selectedVendorId || undefined,
1979
- configurationId: undefined, // Use default configuration
1980
- skipValidation: this.skipValidation,
1981
- templateData: null, // Additional template context if needed
1982
- responseFormat: this.selectedResponseFormat?.value,
1983
- temperature: this.advancedParams.temperature ?? undefined,
1984
- topP: this.advancedParams.topP ?? undefined,
1985
- topK: this.advancedParams.topK ?? undefined,
1986
- minP: this.advancedParams.minP ?? undefined,
1987
- frequencyPenalty: this.advancedParams.frequencyPenalty ?? undefined,
1988
- presencePenalty: this.advancedParams.presencePenalty ?? undefined,
1989
- seed: this.advancedParams.seed ?? undefined,
1990
- stopSequences: this.advancedParams.stopSequences.length > 0 ? this.advancedParams.stopSequences : undefined,
1991
- includeLogProbs: this.advancedParams.includeLogProbs,
1992
- topLogProbs: this.advancedParams.includeLogProbs ? this.advancedParams.topLogProbs : undefined,
1993
- messages: messages.length > 0 ? JSON.stringify(messages) : undefined
1994
- };
1995
- const result = await dataProvider.ExecuteGQL(query, variables);
1996
- const executionResult = result?.RunAIPrompt;
1997
- // Stop elapsed time counter
1998
- if (this.elapsedTimeInterval) {
1999
- clearInterval(this.elapsedTimeInterval);
2000
- this.elapsedTimeInterval = null;
2001
- }
2002
- // Update assistant message with result
2003
- assistantMessage.isStreaming = false;
2004
- if (executionResult?.success) {
2005
- // Use parsedResult if available, otherwise fall back to output
2006
- assistantMessage.content = executionResult.parsedResult || executionResult.output || 'No response generated';
2007
- assistantMessage.executionTime = executionResult.executionTimeMs;
2008
- // Store the complete execution result for JSON display
2009
- assistantMessage.rawContent = JSON.stringify(executionResult, null, 2);
2010
- // Store execution metadata
2011
- if (executionResult.promptRunId) {
2012
- assistantMessage.agentRunId = executionResult.promptRunId;
2013
- }
2014
- }
2015
- else {
2016
- assistantMessage.content = 'I encountered an error processing your request.';
2017
- assistantMessage.error = executionResult?.error || 'Unknown error occurred';
2018
- }
2019
- delete assistantMessage.streamingContent;
2020
- this.scrollNeeded = true;
2021
- // Auto-save conversation
2022
- this.autoSaveConversation();
2023
- // Focus back on input
2024
- this.focusMessageInput();
2025
- }
2026
- catch (error) {
2027
- // Update assistant message with error
2028
- const lastMessage = this.conversationMessages[this.conversationMessages.length - 1];
2029
- if (lastMessage && lastMessage.role === 'assistant') {
2030
- lastMessage.isStreaming = false;
2031
- lastMessage.content = 'I encountered an error processing your request.';
2032
- lastMessage.error = error.message;
2033
- delete lastMessage.streamingContent;
2034
- }
2035
- MJNotificationService.Instance.CreateSimpleNotification('Failed to execute prompt: ' + error.message, 'error', 6000);
2036
- }
2037
- finally {
2038
- this.isExecuting = false;
2039
- if (this.elapsedTimeInterval) {
2040
- clearInterval(this.elapsedTimeInterval);
2041
- this.elapsedTimeInterval = null;
2042
- }
2043
- // Focus back on the input
2044
- this.focusMessageInput();
2045
- }
2046
- }
2047
- startTypingAnimation(message) {
2048
- const initialMessages = [
2049
- "I'm processing your request...",
2050
- "Let me think about that...",
2051
- "Working on your request...",
2052
- "Analyzing your question...",
2053
- "Processing..."
2054
- ];
2055
- const selectedMessage = initialMessages[Math.floor(Math.random() * initialMessages.length)];
2056
- let index = 0;
2057
- // Store the interval handle on the message itself so we can cancel it when real streaming starts
2058
- const typingInterval = setInterval(() => {
2059
- if (index < selectedMessage.length && message.isStreaming) {
2060
- message.streamingContent = selectedMessage.substring(0, index + 1);
2061
- index++;
2062
- this.scrollNeeded = true;
2063
- }
2064
- else {
2065
- clearInterval(typingInterval);
2066
- }
2067
- }, 50);
2068
- // Store interval reference in case we need to clear it
2069
- message._typingInterval = typingInterval;
2070
- }
2071
- buildDataContext() {
2072
- const context = {};
2073
- // Use unified agent variables
2074
- for (const variable of this.agentVariables) {
2075
- if (variable.name.trim()) {
2076
- context[variable.name] = this.convertVariableValue(variable.value, variable.type);
2077
- }
2078
- }
2079
- return context;
2080
- }
2081
- buildTemplateData() {
2082
- // For backward compatibility, return the same data as buildDataContext
2083
- // since we've unified the variables
2084
- return this.buildDataContext();
2085
- }
2086
- buildTemplateVariables() {
2087
- const variables = {};
2088
- for (const variable of this.templateVariables) {
2089
- if (variable.name.trim()) {
2090
- variables[variable.name] = this.convertVariableValue(variable.value, variable.type);
2091
- }
2092
- }
2093
- return variables;
2094
- }
2095
- convertVariableValue(value, type) {
2096
- switch (type) {
2097
- case 'number':
2098
- return parseFloat(value) || 0;
2099
- case 'boolean':
2100
- return value.toLowerCase() === 'true';
2101
- case 'object':
2102
- try {
2103
- return JSON.parse(value);
2104
- }
2105
- catch {
2106
- return value;
2107
- }
2108
- default:
2109
- return value;
2110
- }
2111
- }
2112
- /**
2113
- * Adds a new empty agent variable to the collection.
2114
- * Agent variables are passed to the agent during execution for dynamic content and template rendering.
2115
- */
2116
- addAgentVariable() {
2117
- this.agentVariables.push({
2118
- name: '',
2119
- value: '',
2120
- type: 'string'
2121
- });
2122
- }
2123
- /**
2124
- * Removes an agent variable at the specified index.
2125
- * @param index - Zero-based index of the variable to remove
2126
- */
2127
- removeAgentVariable(index) {
2128
- this.agentVariables.splice(index, 1);
2129
- }
2130
- /**
2131
- * Adds a new empty template variable to the collection (prompt mode only).
2132
- * Template variables are used for prompt template rendering.
2133
- */
2134
- addTemplateVariable() {
2135
- this.templateVariables.push({
2136
- name: '',
2137
- value: '',
2138
- type: 'string'
2139
- });
2140
- }
2141
- /**
2142
- * Removes a template variable at the specified index (prompt mode only).
2143
- * @param index - Zero-based index of the variable to remove
2144
- */
2145
- removeTemplateVariable(index) {
2146
- this.templateVariables.splice(index, 1);
2147
- }
2148
- /**
2149
- * Clears the current conversation after user confirmation.
2150
- * Resets both the message history and the current conversation ID.
2151
- */
2152
- clearConversation() {
2153
- if (this.conversationMessages.length > 0) {
2154
- if (confirm('Are you sure you want to clear the conversation?')) {
2155
- this.conversationMessages = [];
2156
- this.currentConversationId = null;
2157
- }
2158
- }
2159
- }
2160
- scrollToBottom() {
2161
- try {
2162
- if (this.messagesContainer) {
2163
- this.messagesContainer.nativeElement.scrollTop = this.messagesContainer.nativeElement.scrollHeight;
2164
- }
2165
- }
2166
- catch (err) {
2167
- // Error scrolling to bottom
2168
- }
2169
- }
2170
- generateMessageId() {
2171
- return `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2172
- }
2173
- // Saved conversations functionality
2174
- loadSavedConversations() {
2175
- try {
2176
- const storageKey = this.getStorageKey();
2177
- const saved = localStorage.getItem(storageKey);
2178
- if (saved) {
2179
- const parsedData = JSON.parse(saved);
2180
- this.savedConversations = parsedData || [];
2181
- // Convert date strings back to Date objects
2182
- this.savedConversations.forEach(conv => {
2183
- conv.createdAt = new Date(conv.createdAt);
2184
- conv.updatedAt = new Date(conv.updatedAt);
2185
- conv.messages.forEach(msg => {
2186
- msg.timestamp = new Date(msg.timestamp);
2187
- });
2188
- });
2189
- }
2190
- else {
2191
- this.savedConversations = [];
2192
- }
2193
- }
2194
- catch (error) {
2195
- // Error loading saved conversations
2196
- this.savedConversations = [];
2197
- }
2198
- }
2199
- /**
2200
- * Gets the storage key for saved conversations based on entity type and ID
2201
- */
2202
- getStorageKey() {
2203
- if (!this.entity) {
2204
- return '';
2205
- }
2206
- const entityType = this.mode === 'agent' ? 'agent' : 'prompt';
2207
- const entityId = this.entity.ID || 'unknown';
2208
- return `mj_test_harness_${entityType}_${entityId}_conversations`;
2209
- }
2210
- /**
2211
- * Saves the current conversation to localStorage with user-provided name.
2212
- * Handles both creating new conversations and updating existing ones.
2213
- * Automatically limits storage to 50 conversations to prevent excessive memory usage.
2214
- *
2215
- * @example
2216
- * ```typescript
2217
- * // User clicks save button
2218
- * this.saveConversation(); // Prompts for name and saves
2219
- * ```
2220
- */
2221
- saveConversation() {
2222
- if (this.conversationMessages.length === 0) {
2223
- MJNotificationService.Instance.CreateSimpleNotification('No messages to save', 'warning', 3000);
2224
- return;
2225
- }
2226
- // If updating existing conversation, pre-fill the name
2227
- if (this.currentConversationId) {
2228
- const currentConv = this.savedConversations.find(c => c.id === this.currentConversationId);
2229
- this.tempConversationName = currentConv ? currentConv.name : '';
2230
- }
2231
- else {
2232
- this.tempConversationName = '';
2233
- }
2234
- this.showSaveDialog = true;
2235
- // Focus on input after dialog renders
2236
- setTimeout(() => {
2237
- if (this.saveDialogInput && this.saveDialogInput.nativeElement) {
2238
- const input = this.saveDialogInput.nativeElement;
2239
- input.focus();
2240
- input.select();
2241
- }
2242
- }, 100);
2243
- }
2244
- updateTempConversation() {
2245
- this.tempConversationName = this.saveDialogInput?.nativeElement.value.trim() || '';
2246
- }
2247
- /**
2248
- * Handles the save dialog confirmation
2249
- */
2250
- confirmSaveConversation() {
2251
- // Use the temp name from the input
2252
- const convoName = this.saveDialogInput?.nativeElement.value;
2253
- const trimmedName = convoName?.trim() || '';
2254
- if (!trimmedName) {
2255
- return;
2256
- }
2257
- this.newConversationName = trimmedName;
2258
- const conversation = {
2259
- id: this.generateMessageId(),
2260
- name: this.newConversationName.trim(),
2261
- agentId: this.entity?.ID || '',
2262
- agentName: this.getEntityName() || '',
2263
- messages: [...this.conversationMessages],
2264
- dataContext: this.buildDataContext(),
2265
- templateData: this.mode === 'agent' ? this.buildTemplateData() : {},
2266
- templateVariables: this.mode === 'prompt' ? this.buildTemplateVariables() : {},
2267
- advancedParams: this.mode === 'prompt' ? this.advancedParams : undefined,
2268
- selectedModelId: this.mode === 'prompt' ? this.selectedModelId : undefined,
2269
- selectedVendorId: this.mode === 'prompt' ? this.selectedVendorId : undefined,
2270
- skipValidation: this.mode === 'prompt' ? this.skipValidation : undefined,
2271
- createdAt: new Date(),
2272
- updatedAt: new Date()
2273
- };
2274
- if (this.currentConversationId) {
2275
- // Update existing conversation
2276
- const index = this.savedConversations.findIndex(c => c.id === this.currentConversationId);
2277
- if (index >= 0) {
2278
- conversation.id = this.currentConversationId;
2279
- conversation.createdAt = this.savedConversations[index].createdAt;
2280
- conversation.name = this.newConversationName.trim(); // Use the edited name
2281
- this.savedConversations[index] = conversation;
2282
- }
2283
- else {
2284
- // Current ID not found, treat as new
2285
- this.savedConversations.unshift(conversation);
2286
- this.currentConversationId = conversation.id;
2287
- }
2288
- }
2289
- else {
2290
- // Add new conversation
2291
- this.savedConversations.unshift(conversation);
2292
- this.currentConversationId = conversation.id;
2293
- }
2294
- // Limit to 50 saved conversations
2295
- if (this.savedConversations.length > 50) {
2296
- this.savedConversations = this.savedConversations.slice(0, 50);
2297
- }
2298
- // Save to localStorage
2299
- this.saveConversationsToStorage();
2300
- MJNotificationService.Instance.CreateSimpleNotification('Conversation saved successfully', 'success', 3000);
2301
- this.showSaveDialog = false;
2302
- this.newConversationName = '';
2303
- this.tempConversationName = '';
2304
- }
2305
- /**
2306
- * Cancels the save dialog
2307
- */
2308
- cancelSaveDialog() {
2309
- this.showSaveDialog = false;
2310
- this.newConversationName = '';
2311
- this.tempConversationName = '';
2312
- }
2313
- // Removed debug methods - no longer needed with ngModel binding
2314
- /**
2315
- * Saves conversations to localStorage
2316
- */
2317
- saveConversationsToStorage() {
2318
- try {
2319
- const storageKey = this.getStorageKey();
2320
- localStorage.setItem(storageKey, JSON.stringify(this.savedConversations));
2321
- }
2322
- catch (error) {
2323
- // Error saving conversations
2324
- throw error;
2325
- }
2326
- }
2327
- autoSaveConversation() {
2328
- if (this.currentConversationId && this.conversationMessages.length > 0) {
2329
- const index = this.savedConversations.findIndex(c => c.id === this.currentConversationId);
2330
- if (index >= 0) {
2331
- this.savedConversations[index].messages = [...this.conversationMessages];
2332
- this.savedConversations[index].dataContext = this.buildDataContext();
2333
- if (this.mode === 'agent') {
2334
- this.savedConversations[index].templateData = this.buildTemplateData();
2335
- }
2336
- else {
2337
- this.savedConversations[index].templateVariables = this.buildTemplateVariables();
2338
- this.savedConversations[index].advancedParams = this.advancedParams;
2339
- this.savedConversations[index].selectedModelId = this.selectedModelId;
2340
- this.savedConversations[index].selectedVendorId = this.selectedVendorId;
2341
- this.savedConversations[index].skipValidation = this.skipValidation;
2342
- }
2343
- this.savedConversations[index].updatedAt = new Date();
2344
- // Save to localStorage
2345
- try {
2346
- this.saveConversationsToStorage();
2347
- }
2348
- catch (error) {
2349
- // Error auto-saving conversation
2350
- }
2351
- }
2352
- }
2353
- }
2354
- loadConversation(conversation) {
2355
- if (this.conversationMessages.length > 0) {
2356
- this.pendingLoadConversation = conversation;
2357
- this.showLoadConfirmDialog = true;
2358
- return;
2359
- }
2360
- this.doLoadConversation(conversation);
2361
- }
2362
- /**
2363
- * Confirms loading a conversation after dialog confirmation
2364
- */
2365
- confirmLoadConversation() {
2366
- if (this.pendingLoadConversation) {
2367
- this.doLoadConversation(this.pendingLoadConversation);
2368
- this.showLoadConfirmDialog = false;
2369
- this.pendingLoadConversation = null;
2370
- }
2371
- }
2372
- /**
2373
- * Cancels loading a conversation
2374
- */
2375
- cancelLoadConversation() {
2376
- this.showLoadConfirmDialog = false;
2377
- this.pendingLoadConversation = null;
2378
- }
2379
- /**
2380
- * Actually loads the conversation
2381
- */
2382
- doLoadConversation(conversation) {
2383
- this.conversationMessages = [...conversation.messages];
2384
- this.currentConversationId = conversation.id;
2385
- // Restore agent variables (unified from dataContext and templateData)
2386
- this.agentVariables = [];
2387
- const allVariables = { ...conversation.dataContext, ...(conversation.templateData || {}) };
2388
- for (const [key, value] of Object.entries(allVariables)) {
2389
- this.agentVariables.push({
2390
- name: key,
2391
- value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2392
- type: typeof value === 'boolean' ? 'boolean' :
2393
- typeof value === 'number' ? 'number' :
2394
- typeof value === 'object' ? 'object' : 'string'
2395
- });
2396
- }
2397
- // Restore template variables for prompt mode
2398
- if (this.mode === 'prompt') {
2399
- this.templateVariables = [];
2400
- for (const [key, value] of Object.entries(conversation.templateVariables || {})) {
2401
- this.templateVariables.push({
2402
- name: key,
2403
- value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2404
- type: typeof value === 'boolean' ? 'boolean' :
2405
- typeof value === 'number' ? 'number' :
2406
- typeof value === 'object' ? 'object' : 'string'
2407
- });
2408
- }
2409
- // Restore advanced parameters
2410
- if (conversation.advancedParams) {
2411
- this.advancedParams = { ...this.advancedParams, ...conversation.advancedParams };
2412
- // Restore stop sequences text
2413
- this.stopSequencesText = this.advancedParams.stopSequences?.join(', ') || '';
2414
- }
2415
- // Restore model and vendor selection
2416
- if (conversation.selectedModelId !== undefined) {
2417
- this.selectedModelId = conversation.selectedModelId;
2418
- // Trigger vendor loading if model is selected
2419
- if (this.selectedModelId) {
2420
- this.onModelSelectionChange();
2421
- // After vendors load, restore vendor selection
2422
- setTimeout(() => {
2423
- if (conversation.selectedVendorId !== undefined) {
2424
- this.selectedVendorId = conversation.selectedVendorId;
2425
- }
2426
- }, 100);
2427
- }
2428
- }
2429
- if (conversation.selectedVendorId !== undefined && !this.selectedModelId) {
2430
- this.selectedVendorId = conversation.selectedVendorId;
2431
- }
2432
- // Restore skip validation setting
2433
- if (conversation.skipValidation !== undefined) {
2434
- this.skipValidation = conversation.skipValidation;
2435
- }
2436
- }
2437
- // Load execution data from the last assistant message if available
2438
- const lastAssistantMessage = this.conversationMessages
2439
- .filter(m => m.role === 'assistant' && m.executionData)
2440
- .pop();
2441
- if (lastAssistantMessage && lastAssistantMessage.executionData) {
2442
- this.currentExecutionData = lastAssistantMessage.executionData;
2443
- this.executionMonitorMode = 'historical';
2444
- // If execution monitor tab is active, ensure it updates
2445
- if (this.activeTab === 'executionMonitor') {
2446
- // Force change detection
2447
- setTimeout(() => {
2448
- this.currentExecutionData = { ...lastAssistantMessage.executionData };
2449
- }, 0);
2450
- }
2451
- }
2452
- else {
2453
- // Clear execution data if no execution found
2454
- this.currentExecutionData = null;
2455
- this.executionMonitorMode = 'historical';
2456
- }
2457
- this.scrollNeeded = true;
2458
- MJNotificationService.Instance.CreateSimpleNotification('Conversation loaded', 'info', 3000);
2459
- }
2460
- async deleteConversation(conversation, event) {
2461
- event.stopPropagation();
2462
- if (confirm(`Delete conversation "${conversation.name}"?`)) {
2463
- const index = this.savedConversations.findIndex(c => c.id === conversation.id);
2464
- if (index >= 0) {
2465
- this.savedConversations.splice(index, 1);
2466
- // Save to localStorage
2467
- this.saveConversationsToStorage();
2468
- if (this.currentConversationId === conversation.id) {
2469
- this.currentConversationId = null;
2470
- }
2471
- MJNotificationService.Instance.CreateSimpleNotification('Conversation deleted', 'info', 3000);
2472
- }
2473
- }
2474
- }
2475
- exportConversation() {
2476
- if (this.conversationMessages.length === 0) {
2477
- MJNotificationService.Instance.CreateSimpleNotification('No messages to export', 'warning', 3000);
2478
- return;
2479
- }
2480
- const exportData = {
2481
- entity: {
2482
- id: this.entity?.ID,
2483
- name: this.getEntityName(),
2484
- type: this.mode
2485
- },
2486
- messages: this.conversationMessages,
2487
- dataContext: this.buildDataContext(),
2488
- exportedAt: new Date().toISOString()
2489
- };
2490
- // Add mode-specific data
2491
- if (this.mode === 'agent') {
2492
- exportData.templateData = this.buildTemplateData();
2493
- }
2494
- else {
2495
- exportData.templateVariables = this.buildTemplateVariables();
2496
- exportData.modelSettings = {
2497
- modelId: this.selectedModelId,
2498
- vendorId: this.selectedVendorId,
2499
- maxTokens: this.maxTokens,
2500
- skipValidation: this.skipValidation
2501
- };
2502
- exportData.advancedParams = this.advancedParams;
2503
- }
2504
- const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
2505
- const url = window.URL.createObjectURL(blob);
2506
- const link = document.createElement('a');
2507
- link.href = url;
2508
- const entityName = this.getEntityName()?.replace(/[^a-zA-Z0-9]/g, '-') || 'conversation';
2509
- link.download = `${this.mode}-conversation-${entityName}-${new Date().toISOString().slice(0, 10)}.json`;
2510
- link.click();
2511
- window.URL.revokeObjectURL(url);
2512
- }
2513
- importConversation() {
2514
- this.fileInput.nativeElement.click();
2515
- }
2516
- onFileSelected(event) {
2517
- const input = event.target;
2518
- if (input.files && input.files[0]) {
2519
- const file = input.files[0];
2520
- const reader = new FileReader();
2521
- reader.onload = (e) => {
2522
- try {
2523
- const data = JSON.parse(e.target?.result);
2524
- // Validate and import
2525
- if (data.messages && Array.isArray(data.messages)) {
2526
- if (this.conversationMessages.length > 0) {
2527
- if (!confirm('Importing will replace the current conversation. Continue?')) {
2528
- return;
2529
- }
2530
- }
2531
- this.conversationMessages = data.messages.map((msg) => ({
2532
- ...msg,
2533
- timestamp: new Date(msg.timestamp)
2534
- }));
2535
- // Import agent variables (unified from dataContext and templateData)
2536
- this.agentVariables = [];
2537
- const importedVariables = { ...(data.dataContext || {}), ...(data.templateData || {}) };
2538
- for (const [key, value] of Object.entries(importedVariables)) {
2539
- this.agentVariables.push({
2540
- name: key,
2541
- value: typeof value === 'object' ? JSON.stringify(value) : String(value),
2542
- type: typeof value === 'boolean' ? 'boolean' :
2543
- typeof value === 'number' ? 'number' :
2544
- typeof value === 'object' ? 'object' : 'string'
2545
- });
2546
- }
2547
- // Template data is already imported into agentVariables above
2548
- // Import advanced parameters if in prompt mode
2549
- if (this.mode === 'prompt' && data.advancedParams) {
2550
- this.advancedParams = { ...this.advancedParams, ...data.advancedParams };
2551
- this.stopSequencesText = this.advancedParams.stopSequences?.join(', ') || '';
2552
- }
2553
- this.currentConversationId = null;
2554
- this.scrollNeeded = true;
2555
- MJNotificationService.Instance.CreateSimpleNotification('Conversation imported successfully', 'success', 3000);
2556
- }
2557
- else {
2558
- throw new Error('Invalid conversation format');
2559
- }
2560
- }
2561
- catch (error) {
2562
- MJNotificationService.Instance.CreateSimpleNotification('Failed to import conversation: ' + error.message, 'error', 5000);
2563
- }
2564
- // Reset file input
2565
- input.value = '';
2566
- };
2567
- reader.readAsText(file);
2568
- }
2569
- }
2570
- /**
2571
- * Handles keyboard input in the message textarea.
2572
- * Sends message on Enter (without Shift) and allows multi-line input with Shift+Enter.
2573
- * @param event - Keyboard event from the textarea
2574
- */
2575
- handleKeyPress(event) {
2576
- if (event.key === 'Enter' && !event.shiftKey) {
2577
- event.preventDefault();
2578
- this.sendMessage();
2579
- }
2580
- }
2581
- /**
2582
- * Generates CSS class names for message display based on message properties.
2583
- * @param message - The conversation message to generate classes for
2584
- * @returns Space-separated CSS class string for styling
2585
- */
2586
- getMessageClass(message) {
2587
- return `message message-${message.role}${message.isStreaming ? ' streaming' : ''}${message.error ? ' error' : ''}`;
2588
- }
2589
- /**
2590
- * Formats a Date object into a user-friendly time string.
2591
- * @param date - Date object to format
2592
- * @returns Formatted time string in HH:MM format
2593
- */
2594
- formatTimestamp(date) {
2595
- return date.toLocaleTimeString('en-US', {
2596
- hour: '2-digit',
2597
- minute: '2-digit'
2598
- });
2599
- }
2600
- /**
2601
- * Formats execution time from milliseconds into a human-readable string.
2602
- * Automatically selects appropriate units (minutes, seconds, or milliseconds).
2603
- * @param milliseconds - Execution time in milliseconds
2604
- * @returns Formatted time string (e.g., "2m 30.5s", "1.23s", "500ms")
2605
- */
2606
- formatExecutionTime(milliseconds) {
2607
- if (milliseconds >= 60000) {
2608
- const minutes = Math.floor(milliseconds / 60000);
2609
- const seconds = ((milliseconds % 60000) / 1000).toFixed(2);
2610
- return `${minutes}m ${seconds}s`;
2611
- }
2612
- else if (milliseconds >= 1000) {
2613
- return `${(milliseconds / 1000).toFixed(2)}s`;
2614
- }
2615
- else {
2616
- return `${milliseconds}ms`;
2617
- }
2618
- }
2619
- formatElapsedTime(milliseconds) {
2620
- const seconds = Math.floor(milliseconds / 1000);
2621
- const ms = milliseconds % 1000;
2622
- if (seconds > 0) {
2623
- return `${seconds}.${Math.floor(ms / 100)}s`;
2624
- }
2625
- else {
2626
- return `${ms}ms`;
2627
- }
2628
- }
2629
- startElapsedTimeCounter(message) {
2630
- if (this.elapsedTimeInterval) {
2631
- clearInterval(this.elapsedTimeInterval);
2632
- }
2633
- this.elapsedTimeInterval = setInterval(() => {
2634
- if (message.streamingStartTime && message.isStreaming) {
2635
- message.elapsedTime = Date.now() - message.streamingStartTime;
2636
- }
2637
- else {
2638
- clearInterval(this.elapsedTimeInterval);
2639
- this.elapsedTimeInterval = null;
2640
- }
2641
- }, 100); // Update every 100ms for smooth counter
2642
- }
2643
- /**
2644
- * Shows the raw JSON dialog for a specific message
2645
- * @param message - The message to show raw JSON for
2646
- */
2647
- showRawJsonDialog(message) {
2648
- if (message.rawContent) {
2649
- // Close any existing window
2650
- if (this.jsonWindowRef) {
2651
- this.jsonWindowRef.close();
2652
- }
2653
- try {
2654
- // Try to parse and format the JSON
2655
- const parsed = JSON.parse(message.rawContent);
2656
- // If this is an agent result with execution tree, enhance the display
2657
- if (parsed.executionTree) {
2658
- this.currentJsonContent = JSON.stringify({
2659
- ...parsed,
2660
- _executionTreeSummary: this.summarizeExecutionTree(parsed.executionTree)
2661
- }, null, 2);
2662
- }
2663
- else {
2664
- this.currentJsonContent = JSON.stringify(parsed, null, 2);
2665
- }
2666
- }
2667
- catch {
2668
- // If not valid JSON, show as-is
2669
- this.currentJsonContent = message.rawContent;
2670
- }
2671
- // Import the JsonViewerWindowComponent dynamically
2672
- import('./json-viewer-window.component').then(({ JsonViewerWindowComponent }) => {
2673
- // Create the window using WindowService
2674
- // Calculate center position accounting for scroll
2675
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
2676
- const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
2677
- const centerTop = Math.max(50, (window.innerHeight - 700) / 2 + scrollTop);
2678
- const centerLeft = Math.max(50, (window.innerWidth - 900) / 2 + scrollLeft);
2679
- this.jsonWindowRef = this.windowService.open({
2680
- title: 'Raw JSON Response',
2681
- content: JsonViewerWindowComponent,
2682
- width: 900,
2683
- height: 700,
2684
- minWidth: 600,
2685
- minHeight: 400,
2686
- resizable: true,
2687
- draggable: true,
2688
- top: centerTop,
2689
- left: centerLeft
2690
- });
2691
- // Pass the JSON content to the component
2692
- const windowContent = this.jsonWindowRef.content.instance;
2693
- windowContent.jsonContent = this.currentJsonContent;
2694
- // Handle window close
2695
- this.jsonWindowRef.result.subscribe((result) => {
2696
- this.jsonWindowRef = null;
2697
- this.currentJsonContent = '';
2698
- });
2699
- });
2700
- }
2701
- }
2702
- /**
2703
- * Creates a summary of the execution tree for easier viewing
2704
- * @param executionTree - The execution tree to summarize
2705
- * @returns A human-readable summary of the execution
2706
- */
2707
- summarizeExecutionTree(executionTree) {
2708
- if (!executionTree || !Array.isArray(executionTree))
2709
- return null;
2710
- const summary = {
2711
- totalSteps: 0,
2712
- stepsByType: {},
2713
- hierarchy: []
2714
- };
2715
- const processNode = (node, level = 0) => {
2716
- summary.totalSteps++;
2717
- // Count by type
2718
- const type = node.executionType || 'unknown';
2719
- summary.stepsByType[type] = (summary.stepsByType[type] || 0) + 1;
2720
- // Create hierarchical summary
2721
- const nodeSummary = {
2722
- level,
2723
- stepName: node.step?.StepName || 'Unknown Step',
2724
- type: node.executionType,
2725
- duration: node.durationMs ? `${node.durationMs}ms` : 'N/A',
2726
- status: node.step?.Status || 'Unknown',
2727
- agentPath: node.agentHierarchy?.join(' → ') || '',
2728
- hasInputData: !!node.inputData,
2729
- hasOutputData: !!node.outputData
2730
- };
2731
- summary.hierarchy.push(nodeSummary);
2732
- // Process children recursively
2733
- if (node.children && node.children.length > 0) {
2734
- node.children.forEach((child) => processNode(child, level + 1));
2735
- }
2736
- };
2737
- executionTree.forEach(node => processNode(node));
2738
- return summary;
2739
- }
2740
- /**
2741
- * Copies the message content to clipboard.
2742
- * @param message - The message to copy
2743
- */
2744
- async copyMessage(message) {
2745
- try {
2746
- // Convert content to string if needed
2747
- const content = typeof message.content === 'string'
2748
- ? message.content
2749
- : JSON.stringify(message.content);
2750
- await navigator.clipboard.writeText(content);
2751
- MJNotificationService.Instance.CreateSimpleNotification('Message copied to clipboard', 'success', 2000);
2752
- }
2753
- catch (error) {
2754
- MJNotificationService.Instance.CreateSimpleNotification('Failed to copy message', 'error', 3000);
2755
- }
2756
- }
2757
- /**
2758
- * Focuses the message input textarea.
2759
- */
2760
- focusMessageInput() {
2761
- if (this.messageInput) {
2762
- setTimeout(() => {
2763
- this.messageInput.focus();
2764
- }, 100);
2765
- }
2766
- }
2767
- /**
2768
- * Determines if the provided content is valid JSON.
2769
- * @param content - String content to test
2770
- * @returns True if content can be parsed as JSON, false otherwise
2771
- */
2772
- isJsonContent(content) {
2773
- if (!content)
2774
- return false;
2775
- try {
2776
- JSON.parse(content);
2777
- return true;
2778
- }
2779
- catch {
2780
- return false;
2781
- }
2782
- }
2783
- /**
2784
- * Formats JSON content with proper indentation for display.
2785
- * @param content - JSON string to format
2786
- * @returns Formatted JSON string or original content if parsing fails
2787
- */
2788
- formatJson(content) {
2789
- try {
2790
- return JSON.stringify(JSON.parse(content), null, 2);
2791
- }
2792
- catch {
2793
- return content;
2794
- }
2795
- }
2796
- /**
2797
- * Automatically detects the content type of a message for appropriate rendering.
2798
- * Uses pattern matching to identify JSON, Markdown, or plain text content.
2799
- * @param content - Content string to analyze
2800
- * @returns Detected content type ('markdown', 'json', or 'text')
2801
- */
2802
- detectContentType(content) {
2803
- if (!content)
2804
- return 'text';
2805
- // Check if it's JSON
2806
- if (this.isJsonContent(content)) {
2807
- return 'json';
2808
- }
2809
- // Check for markdown indicators
2810
- const markdownPatterns = [
2811
- /^#{1,6}\s/m, // Headers
2812
- /\*\*[^*]+\*\*/, // Bold
2813
- /\*[^*]+\*/, // Italic
2814
- /\[([^\]]+)\]\(([^)]+)\)/, // Links
2815
- /```[\s\S]*?```/, // Code blocks
2816
- /`[^`]+`/, // Inline code
2817
- /^[-*+]\s/m, // Lists
2818
- /^\d+\.\s/m // Numbered lists
2819
- ];
2820
- if (markdownPatterns.some(pattern => pattern.test(content))) {
2821
- return 'markdown';
2822
- }
2823
- return 'text';
2824
- }
2825
- renderMarkdown(content) {
2826
- // Basic markdown to HTML conversion with improved formatting
2827
- let html = content;
2828
- // Escape HTML first
2829
- html = html.replace(/&/g, '&amp;')
2830
- .replace(/</g, '&lt;')
2831
- .replace(/>/g, '&gt;');
2832
- // Code blocks with language support
2833
- html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
2834
- const language = lang || '';
2835
- const className = language ? ` class="language-${language}"` : '';
2836
- return `<pre class="code-block"><code${className}>${code.trim()}</code></pre>`;
2837
- });
2838
- // Regular code blocks without language
2839
- html = html.replace(/```([\s\S]*?)```/g, '<pre class="code-block"><code>$1</code></pre>');
2840
- // Inline code
2841
- html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
2842
- // Headers
2843
- html = html.replace(/^### (.+)$/gm, '<h3 class="markdown-h3">$1</h3>');
2844
- html = html.replace(/^## (.+)$/gm, '<h2 class="markdown-h2">$1</h2>');
2845
- html = html.replace(/^# (.+)$/gm, '<h1 class="markdown-h1">$1</h1>');
2846
- // Bold
2847
- html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
2848
- // Italic
2849
- html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
2850
- // Links
2851
- html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" class="markdown-link">$1</a>');
2852
- // Unordered lists
2853
- html = html.replace(/^[*-+] (.+)$/gm, '<li>$1</li>');
2854
- html = html.replace(/((?:<li>.*<\/li>\s*)+)/g, '<ul class="markdown-list">$1</ul>');
2855
- // Numbered lists
2856
- html = html.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
2857
- html = html.replace(/((?:<li>.*<\/li>\s*)+)/g, '<ol class="markdown-list">$1</ol>');
2858
- // Paragraphs and line breaks - improved handling
2859
- html = html.replace(/\n\s*\n/g, '</p><p class="markdown-paragraph">');
2860
- html = html.replace(/\n/g, '<br>');
2861
- html = `<p class="markdown-paragraph">${html}</p>`;
2862
- // Clean up empty paragraphs
2863
- html = html.replace(/<p class="markdown-paragraph"><\/p>/g, '');
2864
- return this.sanitizer.bypassSecurityTrustHtml(`<div class="markdown-content">${html}</div>`);
2865
- }
2866
- getFormattedContent(message) {
2867
- const content = message.content;
2868
- const contentStr = typeof content === 'string' ? content : String(content);
2869
- const contentType = this.detectContentType(contentStr);
2870
- if (contentType === 'json') {
2871
- // Try to extract human-readable content from JSON
2872
- const extractedContent = this.extractHumanReadableContent(contentStr);
2873
- if (extractedContent) {
2874
- // Just render the extracted content
2875
- return this.renderMarkdown(extractedContent);
2876
- }
2877
- else {
2878
- // Fallback to inline code editor for JSON display
2879
- return this.renderJsonWithCodeEditor(contentStr);
2880
- }
2881
- }
2882
- else if (contentType === 'markdown') {
2883
- return this.renderMarkdown(contentStr);
2884
- }
2885
- else {
2886
- // Convert plain text to markdown for consistent formatting
2887
- return this.renderMarkdown(contentStr);
2888
- }
2889
- }
2890
- /**
2891
- * Renders JSON content using an inline code editor component
2892
- */
2893
- renderJsonWithCodeEditor(jsonStr) {
2894
- try {
2895
- // Format the JSON for display
2896
- const formattedJson = this.formatJson(jsonStr);
2897
- // Generate a unique ID for this editor instance
2898
- const editorId = `json-editor-${this.generateMessageId()}`;
2899
- // Create the HTML with a placeholder div that we'll replace with the code editor
2900
- const html = `
2901
- <div class="inline-json-editor" data-editor-id="${editorId}" data-json-content="${this.escapeHtmlAttribute(formattedJson)}">
2902
- <div class="json-editor-container" style="height: 300px; width: 100%; border: 1px solid #e0e0e0; border-radius: 4px; overflow: hidden;">
2903
- <pre style="margin: 0; padding: 12px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 13px; overflow: auto; height: 100%;">${this.escapeHtml(formattedJson)}</pre>
2904
- </div>
2905
- </div>
2906
- `;
2907
- // Note: In a real implementation, we would need to dynamically create the code editor component
2908
- // For now, we'll use a styled pre tag as a fallback
2909
- return this.sanitizer.bypassSecurityTrustHtml(html);
2910
- }
2911
- catch {
2912
- // If JSON parsing fails, show as plain text
2913
- 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>`;
2914
- return this.sanitizer.bypassSecurityTrustHtml(html);
2915
- }
2916
- }
2917
- /**
2918
- * Escapes HTML content for use in attributes
2919
- */
2920
- escapeHtmlAttribute(text) {
2921
- return text
2922
- .replace(/&/g, '&amp;')
2923
- .replace(/"/g, '&quot;')
2924
- .replace(/'/g, '&#39;')
2925
- .replace(/</g, '&lt;')
2926
- .replace(/>/g, '&gt;');
2927
- }
2928
- /**
2929
- * Renders JSON content with human-readable content prominently displayed
2930
- * and raw JSON in a collapsible section below with proper text wrapping.
2931
- */
2932
- /**
2933
- * Gets a summary of the execution for tooltip display
2934
- * @param message - The message to get execution summary for
2935
- * @returns A brief summary of the execution steps
2936
- */
2937
- getExecutionSummary(message) {
2938
- if (!message.rawContent)
2939
- return '';
2940
- try {
2941
- const parsed = JSON.parse(message.rawContent);
2942
- if (parsed.executionTree) {
2943
- const summary = this.summarizeExecutionTree(parsed.executionTree);
2944
- const stepCounts = Object.entries(summary.stepsByType)
2945
- .map(([type, count]) => `${count} ${type}`)
2946
- .join(', ');
2947
- return `${summary.totalSteps} steps executed (${stepCounts})`;
2948
- }
2949
- }
2950
- catch {
2951
- // Ignore parse errors
2952
- }
2953
- return '';
2954
- }
2955
- /**
2956
- * Closes the JSON dialog
2957
- */
2958
- closeJsonDialog() {
2959
- if (this.jsonWindowRef) {
2960
- this.jsonWindowRef.close();
2961
- this.jsonWindowRef = null;
2962
- }
2963
- this.showJsonDialog = false;
2964
- this.currentJsonContent = '';
2965
- }
2966
- /**
2967
- * Copies the JSON content to clipboard
2968
- */
2969
- copyJsonContent() {
2970
- if (this.currentJsonContent) {
2971
- navigator.clipboard.writeText(this.currentJsonContent).then(() => {
2972
- // Success - JSON copied
2973
- }).catch((err) => {
2974
- // Error copying
2975
- });
2976
- }
2977
- }
2978
- /**
2979
- * Extracts human-readable content from JSON responses.
2980
- * Prioritizes userMessage field first, then checks other common fields.
2981
- * @param jsonStr - JSON string to extract content from
2982
- * @returns Extracted human-readable content or null if none found
2983
- */
2984
- extractHumanReadableContent(jsonStr) {
2985
- try {
2986
- const parsed = JSON.parse(jsonStr);
2987
- // Priority 1: Always check userMessage first, regardless of taskComplete status
2988
- if (parsed.userMessage && typeof parsed.userMessage === 'string' && parsed.userMessage.trim()) {
2989
- return parsed.userMessage;
2990
- }
2991
- // Priority 2: Check for nested userMessage in nextStep or other objects
2992
- if (parsed.nextStep && parsed.nextStep.userMessage &&
2993
- typeof parsed.nextStep.userMessage === 'string' && parsed.nextStep.userMessage.trim()) {
2994
- return parsed.nextStep.userMessage;
2995
- }
2996
- // Priority 3: Other common human-readable fields
2997
- const contentFields = [
2998
- 'message', 'content', 'response', 'text', 'output',
2999
- 'result', 'answer', 'reply', 'description', 'summary'
3000
- ];
3001
- for (const field of contentFields) {
3002
- if (parsed[field] && typeof parsed[field] === 'string' && parsed[field].trim()) {
3003
- return parsed[field];
3004
- }
3005
- }
3006
- // Priority 4: Check nested objects for content
3007
- for (const key of Object.keys(parsed)) {
3008
- if (typeof parsed[key] === 'object' && parsed[key] !== null) {
3009
- for (const field of ['userMessage', 'message', 'content']) {
3010
- if (parsed[key][field] && typeof parsed[key][field] === 'string' && parsed[key][field].trim()) {
3011
- return parsed[key][field];
3012
- }
3013
- }
3014
- }
3015
- }
3016
- // Priority 5: If it's a simple string value, return it
3017
- if (typeof parsed === 'string' && parsed.trim()) {
3018
- return parsed;
3019
- }
3020
- // Priority 6: If it's an object with a single string property, consider returning it
3021
- const keys = Object.keys(parsed);
3022
- if (keys.length === 1 && typeof parsed[keys[0]] === 'string' && parsed[keys[0]].trim()) {
3023
- return parsed[keys[0]];
3024
- }
3025
- return null;
3026
- }
3027
- catch {
3028
- return null;
3029
- }
3030
- }
3031
- /**
3032
- * Checks if a message contains JSON content that has extractable human-readable content.
3033
- * Used to determine if the raw toggle should be shown.
3034
- * @param message - The conversation message to check
3035
- * @returns True if the message has extractable content different from raw JSON
3036
- */
3037
- hasExtractableContent(message) {
3038
- if (!message.content || message.role === 'user') {
3039
- return false;
3040
- }
3041
- const contentStr = typeof message.content === 'string' ? message.content : String(message.content);
3042
- if (!this.isJsonContent(contentStr)) {
3043
- return false;
3044
- }
3045
- const extractedContent = this.extractHumanReadableContent(contentStr);
3046
- return extractedContent !== null && extractedContent !== contentStr;
3047
- }
3048
- /**
3049
- * Determines whether to show the raw toggle button for a message.
3050
- * Combines the logic for raw content availability and extractable content.
3051
- * @param message - The conversation message to check
3052
- * @returns True if the raw toggle should be displayed
3053
- */
3054
- showRawToggle(message) {
3055
- return (message.rawContent && !message.isStreaming) || this.hasExtractableContent(message);
3056
- }
3057
- escapeHtml(text) {
3058
- const div = document.createElement('div');
3059
- div.textContent = text;
3060
- return div.innerHTML;
3061
- }
3062
- /**
3063
- * Formats JSON for proper display with text wrapping instead of wide code blocks.
3064
- * Creates a structured, readable format that respects container width.
3065
- */
3066
- formatJsonForDisplay(jsonStr) {
3067
- try {
3068
- const parsed = JSON.parse(jsonStr);
3069
- return this.createJsonDisplayHtml(parsed, 0);
3070
- }
3071
- catch {
3072
- // If JSON parsing fails, escape and display as-is with wrapping
3073
- return `<div class="json-fallback">${this.escapeHtml(jsonStr)}</div>`;
3074
- }
3075
- }
3076
- /**
3077
- * Creates formatted HTML for JSON display with proper indentation and wrapping.
3078
- */
3079
- createJsonDisplayHtml(obj, depth = 0) {
3080
- const indent = ' '.repeat(depth);
3081
- const nextIndent = ' '.repeat(depth + 1);
3082
- if (obj === null)
3083
- return `<span class="json-null">null</span>`;
3084
- if (typeof obj === 'boolean')
3085
- return `<span class="json-boolean">${obj}</span>`;
3086
- if (typeof obj === 'number')
3087
- return `<span class="json-number">${obj}</span>`;
3088
- if (typeof obj === 'string') {
3089
- const escaped = this.escapeHtml(obj);
3090
- return `<span class="json-string">"<span class="json-string-content">${escaped}</span>"</span>`;
3091
- }
3092
- if (Array.isArray(obj)) {
3093
- if (obj.length === 0)
3094
- return '<span class="json-bracket">[]</span>';
3095
- const items = obj.map(item => `<div class="json-array-item">${nextIndent}${this.createJsonDisplayHtml(item, depth + 1)}</div>`).join(',\n');
3096
- return `<span class="json-bracket">[</span>\n${items}\n<div class="json-indent">${indent}</div><span class="json-bracket">]</span>`;
3097
- }
3098
- if (typeof obj === 'object') {
3099
- const keys = Object.keys(obj);
3100
- if (keys.length === 0)
3101
- return '<span class="json-bracket">{}</span>';
3102
- const properties = keys.map(key => {
3103
- const escapedKey = this.escapeHtml(key);
3104
- const value = this.createJsonDisplayHtml(obj[key], depth + 1);
3105
- return `<div class="json-property">${nextIndent}<span class="json-key">"${escapedKey}"</span><span class="json-colon">:</span> ${value}</div>`;
3106
- }).join(',\n');
3107
- return `<span class="json-bracket">{</span>\n${properties}\n<div class="json-indent">${indent}</div><span class="json-bracket">}</span>`;
3108
- }
3109
- return String(obj);
3110
- }
3111
- /**
3112
- * Type guard to check if entity is an AI Agent
3113
- */
3114
- isAgentEntity(entity) {
3115
- // Check using the EntityInfo property from BaseEntity
3116
- return entity && entity.EntityInfo && entity.EntityInfo.Name === 'AI Agents';
3117
- }
3118
- /**
3119
- * Type guard to check if entity is an AI Prompt
3120
- */
3121
- isPromptEntity(entity) {
3122
- // Check using the EntityInfo property from BaseEntity
3123
- const result = entity && entity.EntityInfo && entity.EntityInfo.Name === 'AI Prompts';
3124
- return result;
3125
- }
3126
- /**
3127
- * Gets the display name of the current entity
3128
- */
3129
- getEntityName() {
3130
- if (!this.entity)
3131
- return '';
3132
- return this.entity.Name || 'Untitled';
3133
- }
3134
- /**
3135
- * Updates stop sequences from the textarea input
3136
- */
3137
- updateStopSequences() {
3138
- if (this.stopSequencesText.trim() === '') {
3139
- this.advancedParams.stopSequences = [];
3140
- }
3141
- else {
3142
- // Split by comma and trim each sequence
3143
- this.advancedParams.stopSequences = this.stopSequencesText
3144
- .split(',')
3145
- .map(s => s.trim())
3146
- .filter(s => s.length > 0);
3147
- }
3148
- }
3149
- /**
3150
- * Toggles the advanced parameters expansion panel
3151
- */
3152
- toggleAdvancedParams() {
3153
- this.advancedParamsExpanded = !this.advancedParamsExpanded;
3154
- }
3155
- 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)); }; }
3156
- static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AITestHarnessComponent, selectors: [["mj-ai-test-harness"]], viewQuery: function AITestHarnessComponent_Query(rf, ctx) { if (rf & 1) {
3157
- i0.ɵɵviewQuery(_c0, 5);
3158
- i0.ɵɵviewQuery(_c1, 5);
3159
- i0.ɵɵviewQuery(_c2, 5);
3160
- i0.ɵɵviewQuery(_c3, 5);
3161
- } if (rf & 2) {
3162
- let _t;
3163
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.messagesContainer = _t.first);
3164
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.fileInput = _t.first);
3165
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.messageInput = _t.first);
3166
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.saveDialogInput = _t.first);
3167
- } }, 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) {
3168
- 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);
3169
- } if (rf & 2) {
3170
- i0.ɵɵproperty("ngIf", ctx.isVisible);
3171
- i0.ɵɵadvance();
3172
- i0.ɵɵconditional(ctx.showSaveDialog ? 1 : -1);
3173
- i0.ɵɵadvance();
3174
- i0.ɵɵconditional(ctx.showLoadConfirmDialog ? 2 : -1);
3175
- } }, 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}"] }); }
3176
- }
3177
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AITestHarnessComponent, [{
3178
- type: Component,
3179
- 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}"] }]
3180
- }], () => [{ type: i1.DomSanitizer }, { type: i2.WindowService }, { type: i0.ViewContainerRef }, { type: i0.ChangeDetectorRef }], { mode: [{
3181
- type: Input
3182
- }], entity: [{
3183
- type: Input
3184
- }], aiAgent: [{
3185
- type: Input
3186
- }], isVisible: [{
3187
- type: Input
3188
- }], visibilityChange: [{
3189
- type: Output
3190
- }], messagesContainer: [{
3191
- type: ViewChild,
3192
- args: ['messagesContainer']
3193
- }], fileInput: [{
3194
- type: ViewChild,
3195
- args: ['fileInput']
3196
- }], messageInput: [{
3197
- type: ViewChild,
3198
- args: ['messageInput']
3199
- }], saveDialogInput: [{
3200
- type: ViewChild,
3201
- args: ['saveDialogInput']
3202
- }] }); })();
3203
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AITestHarnessComponent, { className: "AITestHarnessComponent", filePath: "src/lib/custom/ai-test-harness/ai-test-harness.component.ts", lineNumber: 144 }); })();
3204
- //# sourceMappingURL=ai-test-harness.component.js.map