@memberjunction/ng-dashboards 5.36.0 → 5.38.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 (92) hide show
  1. package/README.md +32 -0
  2. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts +14 -0
  3. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.d.ts.map +1 -1
  4. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js +450 -292
  5. package/dist/AI/components/autotagging/autotagging-pipeline-resource.component.js.map +1 -1
  6. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts +73 -1
  7. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  8. package/dist/ComponentStudio/component-studio-dashboard.component.js +512 -127
  9. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  10. package/dist/ComponentStudio/component-studio-resource.component.d.ts +22 -0
  11. package/dist/ComponentStudio/component-studio-resource.component.d.ts.map +1 -0
  12. package/dist/ComponentStudio/component-studio-resource.component.js +55 -0
  13. package/dist/ComponentStudio/component-studio-resource.component.js.map +1 -0
  14. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +104 -45
  15. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
  16. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +234 -331
  17. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
  18. package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts +54 -0
  19. package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.d.ts.map +1 -0
  20. package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js +339 -0
  21. package/dist/ComponentStudio/components/form-builder/form-builder-canvas.component.js.map +1 -0
  22. package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts +65 -0
  23. package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.d.ts.map +1 -0
  24. package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js +492 -0
  25. package/dist/ComponentStudio/components/form-builder/form-builder-right-panel.component.js.map +1 -0
  26. package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts +88 -0
  27. package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.d.ts.map +1 -0
  28. package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js +457 -0
  29. package/dist/ComponentStudio/components/form-builder/form-builder-tab.component.js.map +1 -0
  30. package/dist/ComponentStudio/components/form-override-dialog.component.d.ts +106 -0
  31. package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -0
  32. package/dist/ComponentStudio/components/form-override-dialog.component.js +478 -0
  33. package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -0
  34. package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts +54 -0
  35. package/dist/ComponentStudio/components/workspace/component-preview.component.d.ts.map +1 -1
  36. package/dist/ComponentStudio/components/workspace/component-preview.component.js +361 -50
  37. package/dist/ComponentStudio/components/workspace/component-preview.component.js.map +1 -1
  38. package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts +10 -0
  39. package/dist/ComponentStudio/components/workspace/editor-tabs.component.d.ts.map +1 -1
  40. package/dist/ComponentStudio/components/workspace/editor-tabs.component.js +114 -45
  41. package/dist/ComponentStudio/components/workspace/editor-tabs.component.js.map +1 -1
  42. package/dist/ComponentStudio/services/canvas-to-code.d.ts +32 -0
  43. package/dist/ComponentStudio/services/canvas-to-code.d.ts.map +1 -0
  44. package/dist/ComponentStudio/services/canvas-to-code.js +347 -0
  45. package/dist/ComponentStudio/services/canvas-to-code.js.map +1 -0
  46. package/dist/ComponentStudio/services/code-to-canvas.d.ts +32 -0
  47. package/dist/ComponentStudio/services/code-to-canvas.d.ts.map +1 -0
  48. package/dist/ComponentStudio/services/code-to-canvas.js +92 -0
  49. package/dist/ComponentStudio/services/code-to-canvas.js.map +1 -0
  50. package/dist/ComponentStudio/services/component-studio-state.service.d.ts +29 -0
  51. package/dist/ComponentStudio/services/component-studio-state.service.d.ts.map +1 -1
  52. package/dist/ComponentStudio/services/component-studio-state.service.js +76 -0
  53. package/dist/ComponentStudio/services/component-studio-state.service.js.map +1 -1
  54. package/dist/ComponentStudio/services/entity-form-override.service.d.ts +86 -0
  55. package/dist/ComponentStudio/services/entity-form-override.service.d.ts.map +1 -0
  56. package/dist/ComponentStudio/services/entity-form-override.service.js +246 -0
  57. package/dist/ComponentStudio/services/entity-form-override.service.js.map +1 -0
  58. package/dist/ComponentStudio/services/field-binding-scanner.d.ts +29 -0
  59. package/dist/ComponentStudio/services/field-binding-scanner.d.ts.map +1 -0
  60. package/dist/ComponentStudio/services/field-binding-scanner.js +110 -0
  61. package/dist/ComponentStudio/services/field-binding-scanner.js.map +1 -0
  62. package/dist/ComponentStudio/services/form-canvas-model.d.ts +56 -0
  63. package/dist/ComponentStudio/services/form-canvas-model.d.ts.map +1 -0
  64. package/dist/ComponentStudio/services/form-canvas-model.js +35 -0
  65. package/dist/ComponentStudio/services/form-canvas-model.js.map +1 -0
  66. package/dist/ComponentStudio/services/form-host-props-fixture.d.ts +10 -0
  67. package/dist/ComponentStudio/services/form-host-props-fixture.d.ts.map +1 -0
  68. package/dist/ComponentStudio/services/form-host-props-fixture.js +10 -0
  69. package/dist/ComponentStudio/services/form-host-props-fixture.js.map +1 -0
  70. package/dist/DataExplorer/data-explorer-dashboard.component.js +2 -2
  71. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  72. package/dist/FormBuilder/form-builder-resource.component.d.ts +964 -0
  73. package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -0
  74. package/dist/FormBuilder/form-builder-resource.component.js +4487 -0
  75. package/dist/FormBuilder/form-builder-resource.component.js.map +1 -0
  76. package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts +55 -0
  77. package/dist/FormBuilder/form-builder-version-rail.helpers.d.ts.map +1 -0
  78. package/dist/FormBuilder/form-builder-version-rail.helpers.js +73 -0
  79. package/dist/FormBuilder/form-builder-version-rail.helpers.js.map +1 -0
  80. package/dist/Home/home-application.d.ts +21 -1
  81. package/dist/Home/home-application.d.ts.map +1 -1
  82. package/dist/Home/home-application.js +60 -8
  83. package/dist/Home/home-application.js.map +1 -1
  84. package/dist/QueryBrowser/query-browser-resource.component.d.ts +14 -14
  85. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  86. package/dist/QueryBrowser/query-browser-resource.component.js +11 -10
  87. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  88. package/dist/component-studio-dashboards.module.d.ts +34 -22
  89. package/dist/component-studio-dashboards.module.d.ts.map +1 -1
  90. package/dist/component-studio-dashboards.module.js +65 -9
  91. package/dist/component-studio-dashboards.module.js.map +1 -1
  92. package/package.json +54 -53
@@ -1,402 +1,305 @@
1
- import { Component, ViewChild } from '@angular/core';
2
- import { Subject } from 'rxjs';
3
- import { takeUntil } from 'rxjs/operators';
4
- import { RunView } from '@memberjunction/core';
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, } from '@angular/core';
2
+ import { CompositeKey, LogError } from '@memberjunction/core';
3
+ import { UUIDsEqual } from '@memberjunction/global';
4
+ import { AIEngineBase } from '@memberjunction/ai-engine-base';
5
5
  import { BaseAngularComponent } from '@memberjunction/ng-base-types';
6
+ import { Subject, takeUntil } from 'rxjs';
7
+ import { MJEnvironmentEntityExtended } from '@memberjunction/core-entities';
8
+ import { NavigationService } from '@memberjunction/ng-shared';
6
9
  import * as i0 from "@angular/core";
7
10
  import * as i1 from "../../services/component-studio-state.service";
8
- import * as i2 from "@angular/forms";
9
- const _c0 = ["chatThread"];
10
- const _c1 = ["chatInput"];
11
+ import * as i2 from "@memberjunction/ng-conversations";
11
12
  const _forTrack0 = ($index, $item) => $item.Label;
12
- const _forTrack1 = ($index, $item) => $item.ID;
13
- function AIAssistantPanelComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
14
- i0.ɵɵelementStart(0, "div", 11);
15
- i0.ɵɵelement(1, "i", 25);
16
- i0.ɵɵelementStart(2, "span");
17
- i0.ɵɵtext(3, "Loading models...");
18
- i0.ɵɵelementEnd()();
19
- } }
20
- function AIAssistantPanelComponent_Conditional_13_For_2_Template(rf, ctx) { if (rf & 1) {
21
- i0.ɵɵelementStart(0, "option", 27);
22
- i0.ɵɵtext(1);
23
- i0.ɵɵelementEnd();
24
- } if (rf & 2) {
25
- const model_r4 = ctx.$implicit;
26
- i0.ɵɵproperty("value", model_r4.ID);
27
- i0.ɵɵadvance();
28
- i0.ɵɵtextInterpolate(model_r4.DisplayLabel);
29
- } }
30
- function AIAssistantPanelComponent_Conditional_13_Conditional_3_Template(rf, ctx) { if (rf & 1) {
31
- i0.ɵɵelementStart(0, "option", 28);
32
- i0.ɵɵtext(1, "No models available");
33
- i0.ɵɵelementEnd();
34
- } if (rf & 2) {
35
- i0.ɵɵproperty("value", null);
36
- } }
37
- function AIAssistantPanelComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
38
- const _r2 = i0.ɵɵgetCurrentView();
39
- i0.ɵɵelementStart(0, "select", 26);
40
- i0.ɵɵtwoWayListener("ngModelChange", function AIAssistantPanelComponent_Conditional_13_Template_select_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); i0.ɵɵtwoWayBindingSet(ctx_r2.SelectedModelID, $event) || (ctx_r2.SelectedModelID = $event); return i0.ɵɵresetView($event); });
41
- i0.ɵɵrepeaterCreate(1, AIAssistantPanelComponent_Conditional_13_For_2_Template, 2, 2, "option", 27, _forTrack1);
42
- i0.ɵɵconditionalCreate(3, AIAssistantPanelComponent_Conditional_13_Conditional_3_Template, 2, 1, "option", 28);
43
- i0.ɵɵelementEnd();
44
- } if (rf & 2) {
45
- const ctx_r2 = i0.ɵɵnextContext();
46
- i0.ɵɵtwoWayProperty("ngModel", ctx_r2.SelectedModelID);
47
- i0.ɵɵproperty("disabled", ctx_r2.AvailableModels.length === 0);
48
- i0.ɵɵadvance();
49
- i0.ɵɵrepeater(ctx_r2.AvailableModels);
50
- i0.ɵɵadvance(2);
51
- i0.ɵɵconditional(ctx_r2.AvailableModels.length === 0 ? 3 : -1);
52
- } }
53
- function AIAssistantPanelComponent_For_16_Template(rf, ctx) { if (rf & 1) {
54
- const _r5 = i0.ɵɵgetCurrentView();
55
- i0.ɵɵelementStart(0, "button", 29);
56
- i0.ɵɵlistener("click", function AIAssistantPanelComponent_For_16_Template_button_click_0_listener() { const action_r6 = i0.ɵɵrestoreView(_r5).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnQuickAction(action_r6)); });
13
+ function AIAssistantPanelComponent_For_10_Template(rf, ctx) { if (rf & 1) {
14
+ const _r1 = i0.ɵɵgetCurrentView();
15
+ i0.ɵɵelementStart(0, "button", 11);
16
+ i0.ɵɵlistener("click", function AIAssistantPanelComponent_For_10_Template_button_click_0_listener() { const action_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnQuickAction(action_r2)); });
57
17
  i0.ɵɵelement(1, "i");
58
18
  i0.ɵɵelementStart(2, "span");
59
19
  i0.ɵɵtext(3);
60
20
  i0.ɵɵelementEnd()();
61
21
  } if (rf & 2) {
62
- const action_r6 = ctx.$implicit;
22
+ const action_r2 = ctx.$implicit;
63
23
  const ctx_r2 = i0.ɵɵnextContext();
64
- i0.ɵɵclassProp("disabled", !ctx_r2.IsQuickActionEnabled(action_r6));
65
- i0.ɵɵproperty("disabled", !ctx_r2.IsQuickActionEnabled(action_r6) || ctx_r2.IsWaitingForResponse)("title", action_r6.Label);
24
+ i0.ɵɵclassProp("disabled", !ctx_r2.IsQuickActionEnabled(action_r2));
25
+ i0.ɵɵproperty("disabled", !ctx_r2.IsQuickActionEnabled(action_r2))("title", action_r2.Label);
66
26
  i0.ɵɵadvance();
67
- i0.ɵɵclassMap(i0.ɵɵinterpolate1("fa-solid ", action_r6.Icon));
27
+ i0.ɵɵclassMap(i0.ɵɵinterpolate1("fa-solid ", action_r2.Icon));
68
28
  i0.ɵɵadvance(2);
69
- i0.ɵɵtextInterpolate(action_r6.Label);
29
+ i0.ɵɵtextInterpolate(action_r2.Label);
70
30
  } }
71
- function AIAssistantPanelComponent_Conditional_19_Template(rf, ctx) { if (rf & 1) {
72
- i0.ɵɵelementStart(0, "div", 16);
73
- i0.ɵɵelement(1, "i", 30);
74
- i0.ɵɵelementStart(2, "p");
75
- i0.ɵɵtext(3, "No messages yet. Start a conversation or use a quick action above.");
76
- i0.ɵɵelementEnd()();
77
- } }
78
- function AIAssistantPanelComponent_For_21_Conditional_1_Template(rf, ctx) { if (rf & 1) {
79
- i0.ɵɵelementStart(0, "div", 32);
80
- i0.ɵɵelement(1, "i", 5);
81
- i0.ɵɵelementEnd();
82
- } }
83
- function AIAssistantPanelComponent_For_21_Conditional_7_Template(rf, ctx) { if (rf & 1) {
84
- i0.ɵɵelementStart(0, "div", 36);
85
- i0.ɵɵelement(1, "i", 37);
86
- i0.ɵɵelementEnd();
87
- } }
88
- function AIAssistantPanelComponent_For_21_Template(rf, ctx) { if (rf & 1) {
89
- i0.ɵɵelementStart(0, "div", 31);
90
- i0.ɵɵconditionalCreate(1, AIAssistantPanelComponent_For_21_Conditional_1_Template, 2, 0, "div", 32);
91
- i0.ɵɵelementStart(2, "div", 33)(3, "div", 34);
92
- i0.ɵɵtext(4);
93
- i0.ɵɵelementEnd();
94
- i0.ɵɵelementStart(5, "div", 35);
95
- i0.ɵɵtext(6);
96
- i0.ɵɵelementEnd()();
97
- i0.ɵɵconditionalCreate(7, AIAssistantPanelComponent_For_21_Conditional_7_Template, 2, 0, "div", 36);
31
+ function AIAssistantPanelComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
32
+ const _r4 = i0.ɵɵgetCurrentView();
33
+ i0.ɵɵelementStart(0, "mj-conversation-chat-area", 12);
34
+ i0.ɵɵlistener("conversationCreated", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_conversationCreated_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnConversationCreated($event)); })("pendingMessageConsumed", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_pendingMessageConsumed_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnPendingMessageConsumed()); })("openEntityRecord", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_openEntityRecord_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnOpenEntityRecord($event)); })("navigationRequest", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_navigationRequest_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnNavigationRequest($event)); })("taskClicked", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_taskClicked_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnTaskClicked($event)); })("artifactLinkClicked", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_artifactLinkClicked_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnArtifactLinkClicked($event)); })("conversationRenamed", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_conversationRenamed_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnConversationRenamed($event)); })("threadOpened", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_threadOpened_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnThreadOpened($event)); })("threadClosed", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_threadClosed_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnThreadClosed()); })("pendingArtifactConsumed", function AIAssistantPanelComponent_Conditional_12_Template_mj_conversation_chat_area_pendingArtifactConsumed_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnPendingArtifactConsumed()); });
98
35
  i0.ɵɵelementEnd();
99
36
  } if (rf & 2) {
100
- const message_r7 = ctx.$implicit;
101
37
  const ctx_r2 = i0.ɵɵnextContext();
102
- i0.ɵɵclassProp("user", message_r7.Role === "user")("assistant", message_r7.Role === "assistant")("system", message_r7.Role === "system");
103
- i0.ɵɵadvance();
104
- i0.ɵɵconditional(message_r7.Role === "assistant" ? 1 : -1);
105
- i0.ɵɵadvance();
106
- i0.ɵɵclassProp("user-bubble", message_r7.Role === "user")("assistant-bubble", message_r7.Role === "assistant")("system-bubble", message_r7.Role === "system");
107
- i0.ɵɵadvance(2);
108
- i0.ɵɵtextInterpolate(message_r7.Content);
109
- i0.ɵɵadvance(2);
110
- i0.ɵɵtextInterpolate(ctx_r2.FormatTimestamp(message_r7.Timestamp));
111
- i0.ɵɵadvance();
112
- i0.ɵɵconditional(message_r7.Role === "user" ? 7 : -1);
38
+ i0.ɵɵproperty("Provider", ctx_r2.Provider)("environmentId", ctx_r2.EnvironmentId)("currentUser", ctx_r2.CurrentUser)("conversationId", ctx_r2.ChatConversationId)("conversation", ctx_r2.ChatConversation)("isNewConversation", ctx_r2.ChatIsNewConversation)("pendingMessage", ctx_r2.PendingMessage)("pendingAttachments", ctx_r2.PendingAttachments)("threadId", ctx_r2.ChatThreadId)("pendingArtifactId", ctx_r2.ChatPendingArtifactId)("pendingArtifactVersionNumber", ctx_r2.ChatPendingArtifactVersionNumber)("overlayMode", true)("showExportButton", false)("showShareButton", false)("showArtifactIndicator", false)("appContext", ctx_r2.ChatAppContext)("defaultAgentId", ctx_r2.CodesmithAgentId)("applicationScope", "Application")("applicationId", ctx_r2.CockpitApplicationId)("linkedEntityId", ctx_r2.ComponentsEntityID)("linkedRecordId", ctx_r2.LinkedComponentID)("emptyStateGreeting", "How can I help with this component?");
113
39
  } }
114
- function AIAssistantPanelComponent_Conditional_22_Template(rf, ctx) { if (rf & 1) {
115
- i0.ɵɵelementStart(0, "div", 18)(1, "div", 32);
116
- i0.ɵɵelement(2, "i", 5);
40
+ function AIAssistantPanelComponent_Conditional_13_Template(rf, ctx) { if (rf & 1) {
41
+ i0.ɵɵelementStart(0, "div", 10);
42
+ i0.ɵɵtext(1, "Loading\u2026");
117
43
  i0.ɵɵelementEnd();
118
- i0.ɵɵelementStart(3, "div", 38)(4, "div", 39);
119
- i0.ɵɵelement(5, "span", 40)(6, "span", 40)(7, "span", 40);
120
- i0.ɵɵelementEnd()()();
121
44
  } }
45
+ /**
46
+ * Component Studio's right-pane AI assistant.
47
+ *
48
+ * Previously this was a 880-line bespoke chat stub that emitted "AI
49
+ * assistant coming soon" — no agent was actually called. This version
50
+ * thin-wraps `<mj-conversation-chat-area>` (the same primitive the main
51
+ * Chat app and the Form Builder cockpit use) so the assistant becomes
52
+ * fully functional with zero duplicated chat plumbing.
53
+ *
54
+ * Domain integration preserved from the old stub:
55
+ * - **Quick-actions bar** (Fix Errors / Improve / Generate / Explain)
56
+ * above the chat — clicking sets `PendingMessage` which the chat-area
57
+ * consumes on the next render, mirroring the empty-state handoff.
58
+ * - **`SendErrorToAI` channel** — when the runtime preview throws, the
59
+ * state service emits a `ComponentError`. We listen and shove a
60
+ * "Fix this error: …" message into the same pendingMessage pipe so
61
+ * the user doesn't have to copy/paste error text.
62
+ *
63
+ * Scoping:
64
+ * - `[applicationScope]="'Application'"` + Component Studio app ID →
65
+ * conversations stay out of main chat (per the migration scoping work).
66
+ * - `[defaultAgentId]` → Codesmith Agent so messages route to the code
67
+ * specialist instead of Sage by default. User can still @mention any
68
+ * agent, override via the per-conversation pin, or pick a different
69
+ * agent through the chat header's picker.
70
+ */
122
71
  export class AIAssistantPanelComponent extends BaseAngularComponent {
123
72
  State;
124
- cdr;
125
- chatThreadEl;
126
- chatInputEl;
127
- // --- Chat State ---
128
- Messages = [];
129
- InputText = '';
130
- IsWaitingForResponse = false;
131
- // --- Model Selector ---
132
- AvailableModels = [];
133
- SelectedModelID = null;
134
- IsLoadingModels = false;
135
- // --- Quick Actions ---
73
+ /** Quick actions surfaced as buttons above the embedded chat. */
136
74
  QuickActions = [
137
75
  { Label: 'Fix Errors', Icon: 'fa-bug', Prompt: 'Fix this error: ', RequiresError: true },
138
76
  { Label: 'Improve Code', Icon: 'fa-magic', Prompt: 'Review and improve the current component code. Suggest optimizations, better patterns, and cleaner structure.', RequiresError: false },
139
77
  { Label: 'Generate Code', Icon: 'fa-code', Prompt: 'Generate code for the current component based on its specification.', RequiresError: false },
140
- { Label: 'Explain', Icon: 'fa-question-circle', Prompt: 'Explain what the current component does, including its structure, data flow, and key behaviors.', RequiresError: false }
78
+ { Label: 'Explain', Icon: 'fa-question-circle', Prompt: 'Explain what the current component does, including its structure, data flow, and key behaviors.', RequiresError: false },
141
79
  ];
80
+ /** Embedded chat state — same shape the Form Builder cockpit uses. */
81
+ ChatConversation = null;
82
+ ChatConversationId = null;
83
+ ChatIsNewConversation = true;
84
+ PendingMessage = null;
85
+ PendingAttachments = null;
86
+ ChatThreadId = null;
87
+ ChatPendingArtifactId = null;
88
+ ChatPendingArtifactVersionNumber = null;
89
+ /** Snapshot from NavigationService — drives the agent's app context. */
90
+ ChatAppContext = null;
91
+ /** Codesmith Agent ID resolved from AIEngineBase cache (no RunView). */
92
+ CodesmithAgentId = null;
93
+ /** Component Studio's Application ID — resolved from Metadata cache. */
94
+ CockpitApplicationId = null;
95
+ /**
96
+ * EntityID for `MJ: Components`. Stamped on every conversation
97
+ * created from this panel as the `LinkedEntityID`, paired with the
98
+ * currently-selected Component's ID. Enables "show prior conversations
99
+ * about THIS component" later. In-memory Metadata lookup; no RunView.
100
+ */
101
+ ComponentsEntityID = null;
102
+ /**
103
+ * The DB-backed Component ID currently selected — null when the
104
+ * panel has nothing selected, or when the selection is a
105
+ * file-loaded (transient) component that has no persistent DB row
106
+ * to link conversations to. Used as `[linkedRecordId]` on the
107
+ * embedded chat-area. Pairs with {@link ComponentsEntityID}.
108
+ */
109
+ get LinkedComponentID() {
110
+ const sel = this.State.SelectedComponent;
111
+ if (!sel)
112
+ return null;
113
+ // FileLoadedComponent has `isFileLoaded === true` and a lower-case
114
+ // `id`; DbComponentSummary has the canonical `ID`. Discriminate by
115
+ // the flag rather than property presence to keep TS happy.
116
+ if ('isFileLoaded' in sel && sel.isFileLoaded === true)
117
+ return null;
118
+ return sel.ID ?? null;
119
+ }
120
+ static CODESMITH_AGENT_NAME = 'Codesmith Agent';
121
+ static COCKPIT_APP_NAME = 'Component Studio';
142
122
  destroy$ = new Subject();
143
- constructor(State, cdr) {
123
+ appContextSubscription = null;
124
+ cdr = inject(ChangeDetectorRef);
125
+ navigationService = inject(NavigationService);
126
+ constructor(State) {
144
127
  super();
145
128
  this.State = State;
146
- this.cdr = cdr;
147
129
  }
148
- async ngOnInit() {
149
- this.subscribeToErrorEvents();
150
- await this.LoadModels();
151
- this.addWelcomeMessage();
130
+ get EnvironmentId() {
131
+ return MJEnvironmentEntityExtended.DefaultEnvironmentID;
152
132
  }
153
- ngOnDestroy() {
154
- this.destroy$.next();
155
- this.destroy$.complete();
133
+ get CurrentUser() {
134
+ return this.ProviderToUse?.CurrentUser ?? null;
156
135
  }
157
- // ============================================================
158
- // MODEL LOADING
159
- // ============================================================
160
- async LoadModels() {
161
- this.IsLoadingModels = true;
136
+ async ngOnInit() {
162
137
  try {
163
- const rv = RunView.FromMetadataProvider(this.ProviderToUse);
164
- const result = await rv.RunView({
165
- EntityName: 'MJ: AI Models',
166
- ExtraFilter: `IsActive = 1 AND AIModelTypeID IN (SELECT ID FROM __mj.vwAIModelTypes WHERE Name = 'LLM')`,
167
- OrderBy: 'PowerRank DESC, Name ASC',
168
- ResultType: 'entity_object'
169
- });
170
- if (result.Success && result.Results) {
171
- this.AvailableModels = result.Results.map(model => ({
172
- ID: model.ID,
173
- Name: model.Name,
174
- Vendor: model.Vendor,
175
- DisplayLabel: model.Vendor ? `${model.Name} (${model.Vendor})` : model.Name
176
- }));
177
- if (this.AvailableModels.length > 0) {
178
- this.SelectedModelID = this.AvailableModels[0].ID;
179
- }
138
+ // Resolve default agent + cockpit app ID from in-memory caches
139
+ // (no RunView round-trips). Both fall back to null cleanly:
140
+ // missing agent → routes through Sage; missing app → safety
141
+ // guard in chat-area demotes scope to 'Global'.
142
+ await AIEngineBase.Instance.Config(false);
143
+ const codesmith = AIEngineBase.Instance.Agents
144
+ ?.find(a => a.Name?.trim().toLowerCase() === AIAssistantPanelComponent.CODESMITH_AGENT_NAME.toLowerCase());
145
+ this.CodesmithAgentId = codesmith?.ID ?? null;
146
+ if (!codesmith) {
147
+ LogError(`AIAssistantPanel: '${AIAssistantPanelComponent.CODESMITH_AGENT_NAME}' not found in AIEngineBase cache`);
180
148
  }
149
+ const md = this.ProviderToUse;
150
+ const app = md.Applications?.find(a => a.Name?.trim().toLowerCase() === AIAssistantPanelComponent.COCKPIT_APP_NAME.toLowerCase());
151
+ this.CockpitApplicationId = app?.ID ?? null;
152
+ // Resolve MJ: Components entity ID for conversation linkage.
153
+ const componentsEntity = md.EntityByName?.('MJ: Components');
154
+ if (!componentsEntity) {
155
+ LogError(`AIAssistantPanel: Entity 'MJ: Components' not found in Metadata cache — conversation linkage will be skipped.`);
156
+ }
157
+ this.ComponentsEntityID = componentsEntity?.ID ?? null;
158
+ // Subscribe to the Explorer shell's app-context publisher so
159
+ // the agent sees the same snapshot the floating overlay sees.
160
+ this.appContextSubscription = this.navigationService.AppContextSnapshot$
161
+ .subscribe(snapshot => {
162
+ this.ChatAppContext = snapshot;
163
+ this.cdr.markForCheck();
164
+ });
165
+ // Wire the SendErrorToAI channel — when the runtime preview
166
+ // hits an error, push a canned "Fix this error: …" message
167
+ // into the chat-area's pendingMessage pipe so the user
168
+ // doesn't have to manually copy the error text.
169
+ this.State.SendErrorToAI
170
+ .pipe(takeUntil(this.destroy$))
171
+ .subscribe(err => this.handleIncomingError(err));
172
+ this.cdr.markForCheck();
181
173
  }
182
- catch (error) {
183
- console.error('Error loading AI models:', error);
184
- }
185
- finally {
186
- this.IsLoadingModels = false;
187
- this.cdr.detectChanges();
174
+ catch (err) {
175
+ LogError(`AIAssistantPanel.ngOnInit: ${err instanceof Error ? err.message : String(err)}`);
188
176
  }
189
177
  }
190
- // ============================================================
191
- // ERROR SUBSCRIPTION
192
- // ============================================================
193
- subscribeToErrorEvents() {
194
- this.State.SendErrorToAI
195
- .pipe(takeUntil(this.destroy$))
196
- .subscribe((error) => {
197
- this.handleIncomingError(error);
198
- });
199
- }
200
- handleIncomingError(error) {
201
- const errorDetails = error.technicalDetails
202
- ? (typeof error.technicalDetails === 'string' ? error.technicalDetails : JSON.stringify(error.technicalDetails, null, 2))
203
- : '';
204
- const systemContent = `Error detected [${error.type}]: ${error.message}${errorDetails ? '\n\nDetails:\n' + errorDetails : ''}`;
205
- this.addMessage('system', systemContent);
206
- this.InputText = `Fix this error: ${error.type} - ${error.message}`;
207
- this.cdr.detectChanges();
208
- this.focusInput();
209
- }
210
- // ============================================================
211
- // WELCOME MESSAGE
212
- // ============================================================
213
- addWelcomeMessage() {
214
- this.addMessage('assistant', 'Welcome to the Component Studio AI Assistant. I can help you fix errors, improve code, generate components, and explain how things work. Select a component and ask me anything!');
178
+ ngOnDestroy() {
179
+ this.destroy$.next();
180
+ this.destroy$.complete();
181
+ this.appContextSubscription?.unsubscribe();
182
+ this.appContextSubscription = null;
215
183
  }
216
- // ============================================================
217
- // CHAT ACTIONS
218
- // ============================================================
219
- OnSendMessage() {
220
- const text = this.InputText.trim();
221
- if (!text || this.IsWaitingForResponse)
222
- return;
223
- this.addMessage('user', text);
224
- this.InputText = '';
225
- this.resetInputHeight();
226
- this.simulateResponse(text);
184
+ /** Collapse the right pane (delegated to the parent dashboard). */
185
+ OnCollapsePanel() {
186
+ this.State.IsAIPanelCollapsed = true;
187
+ this.State.StateChanged.emit();
227
188
  }
189
+ /** Quick-action click — prefill the chat with the canned prompt. */
228
190
  OnQuickAction(action) {
229
191
  if (action.RequiresError && !this.State.CurrentError)
230
192
  return;
193
+ let prompt = action.Prompt;
231
194
  if (action.RequiresError && this.State.CurrentError) {
232
- const errorContext = `${this.State.CurrentError.type} - ${this.State.CurrentError.message}`;
233
- this.InputText = `${action.Prompt}${errorContext}`;
234
- }
235
- else {
236
- this.InputText = action.Prompt;
195
+ prompt += `${this.State.CurrentError.type} - ${this.State.CurrentError.message}`;
237
196
  }
238
- this.cdr.detectChanges();
239
- this.focusInput();
197
+ this.PendingMessage = prompt;
198
+ this.PendingAttachments = null;
199
+ this.cdr.markForCheck();
240
200
  }
241
- OnInputKeydown(event) {
242
- if (event.key === 'Enter' && !event.shiftKey) {
243
- event.preventDefault();
244
- this.OnSendMessage();
245
- }
201
+ IsQuickActionEnabled(action) {
202
+ return !action.RequiresError || this.State.CurrentError != null;
246
203
  }
247
- OnInputChange() {
248
- this.autoGrowTextarea();
204
+ /** Runtime preview surfaced an error — auto-send "Fix this error: …". */
205
+ handleIncomingError(error) {
206
+ const details = error.technicalDetails
207
+ ? (typeof error.technicalDetails === 'string'
208
+ ? error.technicalDetails
209
+ : JSON.stringify(error.technicalDetails, null, 2))
210
+ : '';
211
+ this.PendingMessage = `Fix this error: ${error.type} - ${error.message}${details ? '\n\nDetails:\n' + details : ''}`;
212
+ this.PendingAttachments = null;
213
+ this.cdr.markForCheck();
249
214
  }
250
- OnCollapsePanel() {
251
- this.State.IsAIPanelCollapsed = true;
252
- this.State.StateChanged.emit();
215
+ // ── chat-area event wiring ───────────────────────────────────────
216
+ OnConversationCreated(event) {
217
+ this.PendingMessage = event.pendingMessage ?? null;
218
+ this.PendingAttachments = (event.pendingAttachments ?? null);
219
+ this.ChatConversation = event.conversation;
220
+ this.ChatConversationId = event.conversation.ID;
221
+ this.ChatIsNewConversation = false;
222
+ this.cdr.markForCheck();
253
223
  }
254
- IsQuickActionEnabled(action) {
255
- if (action.RequiresError) {
256
- return this.State.CurrentError != null;
224
+ OnPendingMessageConsumed() {
225
+ this.PendingMessage = null;
226
+ this.PendingAttachments = null;
227
+ this.cdr.markForCheck();
228
+ }
229
+ OnOpenEntityRecord(event) {
230
+ try {
231
+ this.navigationService.OpenEntityRecord(event.entityName, event.compositeKey);
232
+ }
233
+ catch (err) {
234
+ LogError(`AIAssistantPanel.OnOpenEntityRecord: ${err instanceof Error ? err.message : String(err)}`);
257
235
  }
258
- return true;
259
236
  }
260
- // ============================================================
261
- // MESSAGE MANAGEMENT
262
- // ============================================================
263
- addMessage(role, content) {
264
- this.Messages.push({
265
- Role: role,
266
- Content: content,
267
- Timestamp: new Date()
268
- });
269
- this.cdr.detectChanges();
270
- this.scrollToBottom();
237
+ OnNavigationRequest(event) {
238
+ void this.navigationService.OpenNavItemByName(event.navItemName, event.params, event.appId);
271
239
  }
272
- simulateResponse(userMessage) {
273
- this.IsWaitingForResponse = true;
274
- this.cdr.detectChanges();
275
- this.scrollToBottom();
276
- setTimeout(() => {
277
- this.addMessage('assistant', 'AI assistant coming soon \u2014 agent integration in progress. Your message has been received and will be processed once the AI backend is connected.');
278
- this.IsWaitingForResponse = false;
279
- this.cdr.detectChanges();
280
- }, 1000);
240
+ OnTaskClicked(task) {
241
+ try {
242
+ const key = CompositeKey.FromID(task.ID);
243
+ this.navigationService.OpenEntityRecord('MJ: Tasks', key);
244
+ }
245
+ catch (err) {
246
+ LogError(`AIAssistantPanel.OnTaskClicked: ${err instanceof Error ? err.message : String(err)}`);
247
+ }
281
248
  }
282
- // ============================================================
283
- // UI HELPERS
284
- // ============================================================
285
- scrollToBottom() {
286
- Promise.resolve().then(() => {
287
- if (this.chatThreadEl) {
288
- const el = this.chatThreadEl.nativeElement;
289
- el.scrollTop = el.scrollHeight;
290
- }
291
- });
249
+ OnArtifactLinkClicked(event) {
250
+ const navItemName = event.type === 'conversation' ? 'Conversations' : 'Collections';
251
+ const paramKey = event.type === 'conversation' ? 'conversationId' : 'collectionId';
252
+ void this.navigationService.OpenNavItemByName(navItemName, { [paramKey]: event.id });
292
253
  }
293
- focusInput() {
294
- Promise.resolve().then(() => {
295
- if (this.chatInputEl) {
296
- this.chatInputEl.nativeElement.focus();
254
+ OnConversationRenamed(event) {
255
+ if (this.ChatConversation && UUIDsEqual(this.ChatConversation.ID, event.conversationId)) {
256
+ this.ChatConversation.Name = event.name;
257
+ if (event.description !== undefined) {
258
+ this.ChatConversation.Description = event.description;
297
259
  }
298
- });
299
- }
300
- autoGrowTextarea() {
301
- if (!this.chatInputEl)
302
- return;
303
- const textarea = this.chatInputEl.nativeElement;
304
- textarea.style.height = 'auto';
305
- const lineHeight = 20;
306
- const maxLines = 4;
307
- const maxHeight = lineHeight * maxLines;
308
- textarea.style.height = Math.min(textarea.scrollHeight, maxHeight) + 'px';
260
+ this.cdr.markForCheck();
261
+ }
309
262
  }
310
- resetInputHeight() {
311
- if (!this.chatInputEl)
312
- return;
313
- this.chatInputEl.nativeElement.style.height = 'auto';
263
+ OnThreadOpened(threadId) {
264
+ this.ChatThreadId = threadId;
265
+ this.cdr.markForCheck();
314
266
  }
315
- FormatTimestamp(date) {
316
- return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
267
+ OnThreadClosed() {
268
+ this.ChatThreadId = null;
269
+ this.cdr.markForCheck();
317
270
  }
318
- TrackByTimestamp(index, message) {
319
- return message.Timestamp.getTime();
271
+ OnPendingArtifactConsumed() {
272
+ this.ChatPendingArtifactId = null;
273
+ this.ChatPendingArtifactVersionNumber = null;
274
+ this.cdr.markForCheck();
320
275
  }
321
- static ɵfac = function AIAssistantPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AIAssistantPanelComponent)(i0.ɵɵdirectiveInject(i1.ComponentStudioStateService), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
322
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AIAssistantPanelComponent, selectors: [["mj-ai-assistant-panel"]], viewQuery: function AIAssistantPanelComponent_Query(rf, ctx) { if (rf & 1) {
323
- i0.ɵɵviewQuery(_c0, 5)(_c1, 5);
324
- } if (rf & 2) {
325
- let _t;
326
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chatThreadEl = _t.first);
327
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chatInputEl = _t.first);
328
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 38, vars: 8, consts: [["chatThread", ""], ["chatInput", ""], [1, "ai-assistant-panel"], [1, "panel-header"], [1, "header-title"], [1, "fa-solid", "fa-robot"], ["title", "Collapse panel", 1, "collapse-btn", 3, "click"], [1, "fa-solid", "fa-chevron-right"], [1, "model-selector"], [1, "model-label"], [1, "model-dropdown-wrapper"], [1, "model-loading"], [1, "model-dropdown", 3, "ngModel", "disabled"], [1, "quick-actions"], [1, "quick-action-btn", 3, "disabled", "title"], [1, "chat-thread"], [1, "empty-thread"], [1, "message-wrapper", 3, "user", "assistant", "system"], [1, "message-wrapper", "assistant"], [1, "chat-input-area"], [1, "input-wrapper"], ["placeholder", "Ask the AI assistant...", "rows", "1", 1, "chat-textarea", 3, "ngModelChange", "keydown", "input", "ngModel", "disabled"], ["title", "Send message", 1, "send-btn", 3, "click", "disabled"], [1, "fa-solid", "fa-paper-plane"], [1, "input-hint"], [1, "fa-solid", "fa-spinner", "fa-spin"], [1, "model-dropdown", 3, "ngModelChange", "ngModel", "disabled"], [3, "value"], ["disabled", "", 3, "value"], [1, "quick-action-btn", 3, "click", "disabled", "title"], [1, "fa-solid", "fa-comments"], [1, "message-wrapper"], [1, "message-avatar", "assistant-avatar"], [1, "message-bubble"], [1, "message-content"], [1, "message-timestamp"], [1, "message-avatar", "user-avatar"], [1, "fa-solid", "fa-user"], [1, "message-bubble", "assistant-bubble", "typing-bubble"], [1, "typing-indicator"], [1, "dot"]], template: function AIAssistantPanelComponent_Template(rf, ctx) { if (rf & 1) {
329
- const _r1 = i0.ɵɵgetCurrentView();
330
- i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "div", 4);
331
- i0.ɵɵelement(3, "i", 5);
276
+ static ɵfac = function AIAssistantPanelComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || AIAssistantPanelComponent)(i0.ɵɵdirectiveInject(i1.ComponentStudioStateService)); };
277
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: AIAssistantPanelComponent, selectors: [["mj-ai-assistant-panel"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 14, vars: 1, consts: [[1, "ai-assistant-panel"], [1, "panel-header"], [1, "header-title"], [1, "fa-solid", "fa-robot"], ["title", "Collapse panel", 1, "collapse-btn", 3, "click"], [1, "fa-solid", "fa-chevron-right"], [1, "quick-actions"], [1, "quick-action-btn", 3, "disabled", "title"], [1, "chat-host"], [3, "Provider", "environmentId", "currentUser", "conversationId", "conversation", "isNewConversation", "pendingMessage", "pendingAttachments", "threadId", "pendingArtifactId", "pendingArtifactVersionNumber", "overlayMode", "showExportButton", "showShareButton", "showArtifactIndicator", "appContext", "defaultAgentId", "applicationScope", "applicationId", "linkedEntityId", "linkedRecordId", "emptyStateGreeting"], [1, "chat-host__empty"], [1, "quick-action-btn", 3, "click", "disabled", "title"], [3, "conversationCreated", "pendingMessageConsumed", "openEntityRecord", "navigationRequest", "taskClicked", "artifactLinkClicked", "conversationRenamed", "threadOpened", "threadClosed", "pendingArtifactConsumed", "Provider", "environmentId", "currentUser", "conversationId", "conversation", "isNewConversation", "pendingMessage", "pendingAttachments", "threadId", "pendingArtifactId", "pendingArtifactVersionNumber", "overlayMode", "showExportButton", "showShareButton", "showArtifactIndicator", "appContext", "defaultAgentId", "applicationScope", "applicationId", "linkedEntityId", "linkedRecordId", "emptyStateGreeting"]], template: function AIAssistantPanelComponent_Template(rf, ctx) { if (rf & 1) {
278
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "div", 2);
279
+ i0.ɵɵelement(3, "i", 3);
332
280
  i0.ɵɵelementStart(4, "span");
333
281
  i0.ɵɵtext(5, "AI Assistant");
334
282
  i0.ɵɵelementEnd()();
335
- i0.ɵɵelementStart(6, "button", 6);
336
- i0.ɵɵlistener("click", function AIAssistantPanelComponent_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnCollapsePanel()); });
337
- i0.ɵɵelement(7, "i", 7);
338
- i0.ɵɵelementEnd()();
339
- i0.ɵɵelementStart(8, "div", 8)(9, "label", 9);
340
- i0.ɵɵtext(10, "Model");
341
- i0.ɵɵelementEnd();
342
- i0.ɵɵelementStart(11, "div", 10);
343
- i0.ɵɵconditionalCreate(12, AIAssistantPanelComponent_Conditional_12_Template, 4, 0, "div", 11)(13, AIAssistantPanelComponent_Conditional_13_Template, 4, 3, "select", 12);
283
+ i0.ɵɵelementStart(6, "button", 4);
284
+ i0.ɵɵlistener("click", function AIAssistantPanelComponent_Template_button_click_6_listener() { return ctx.OnCollapsePanel(); });
285
+ i0.ɵɵelement(7, "i", 5);
344
286
  i0.ɵɵelementEnd()();
345
- i0.ɵɵelementStart(14, "div", 13);
346
- i0.ɵɵrepeaterCreate(15, AIAssistantPanelComponent_For_16_Template, 4, 8, "button", 14, _forTrack0);
287
+ i0.ɵɵelementStart(8, "div", 6);
288
+ i0.ɵɵrepeaterCreate(9, AIAssistantPanelComponent_For_10_Template, 4, 8, "button", 7, _forTrack0);
347
289
  i0.ɵɵelementEnd();
348
- i0.ɵɵelementStart(17, "div", 15, 0);
349
- i0.ɵɵconditionalCreate(19, AIAssistantPanelComponent_Conditional_19_Template, 4, 0, "div", 16);
350
- i0.ɵɵrepeaterCreate(20, AIAssistantPanelComponent_For_21_Template, 8, 16, "div", 17, ctx.TrackByTimestamp, true);
351
- i0.ɵɵconditionalCreate(22, AIAssistantPanelComponent_Conditional_22_Template, 8, 0, "div", 18);
352
- i0.ɵɵelementEnd();
353
- i0.ɵɵelementStart(23, "div", 19)(24, "div", 20)(25, "textarea", 21, 1);
354
- i0.ɵɵtwoWayListener("ngModelChange", function AIAssistantPanelComponent_Template_textarea_ngModelChange_25_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.InputText, $event) || (ctx.InputText = $event); return i0.ɵɵresetView($event); });
355
- i0.ɵɵlistener("keydown", function AIAssistantPanelComponent_Template_textarea_keydown_25_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnInputKeydown($event)); })("input", function AIAssistantPanelComponent_Template_textarea_input_25_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnInputChange()); });
356
- i0.ɵɵelementEnd();
357
- i0.ɵɵelementStart(27, "button", 22);
358
- i0.ɵɵlistener("click", function AIAssistantPanelComponent_Template_button_click_27_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.OnSendMessage()); });
359
- i0.ɵɵelement(28, "i", 23);
290
+ i0.ɵɵelementStart(11, "div", 8);
291
+ i0.ɵɵconditionalCreate(12, AIAssistantPanelComponent_Conditional_12_Template, 1, 22, "mj-conversation-chat-area", 9)(13, AIAssistantPanelComponent_Conditional_13_Template, 2, 0, "div", 10);
360
292
  i0.ɵɵelementEnd()();
361
- i0.ɵɵelementStart(29, "div", 24)(30, "span");
362
- i0.ɵɵtext(31, "Press ");
363
- i0.ɵɵelementStart(32, "kbd");
364
- i0.ɵɵtext(33, "Enter");
365
- i0.ɵɵelementEnd();
366
- i0.ɵɵtext(34, " to send, ");
367
- i0.ɵɵelementStart(35, "kbd");
368
- i0.ɵɵtext(36, "Shift+Enter");
369
- i0.ɵɵelementEnd();
370
- i0.ɵɵtext(37, " for new line");
371
- i0.ɵɵelementEnd()()()();
372
293
  } if (rf & 2) {
373
- i0.ɵɵadvance(12);
374
- i0.ɵɵconditional(ctx.IsLoadingModels ? 12 : 13);
375
- i0.ɵɵadvance(3);
294
+ i0.ɵɵadvance(9);
376
295
  i0.ɵɵrepeater(ctx.QuickActions);
377
- i0.ɵɵadvance(4);
378
- i0.ɵɵconditional(ctx.Messages.length === 0 ? 19 : -1);
379
- i0.ɵɵadvance();
380
- i0.ɵɵrepeater(ctx.Messages);
381
- i0.ɵɵadvance(2);
382
- i0.ɵɵconditional(ctx.IsWaitingForResponse ? 22 : -1);
383
296
  i0.ɵɵadvance(3);
384
- i0.ɵɵtwoWayProperty("ngModel", ctx.InputText);
385
- i0.ɵɵproperty("disabled", ctx.IsWaitingForResponse);
386
- i0.ɵɵadvance(2);
387
- i0.ɵɵclassProp("active", ctx.InputText.trim().length > 0 && !ctx.IsWaitingForResponse);
388
- i0.ɵɵproperty("disabled", ctx.InputText.trim().length === 0 || ctx.IsWaitingForResponse);
389
- } }, dependencies: [i2.NgSelectOption, i2.ɵNgSelectMultipleOption, i2.DefaultValueAccessor, i2.SelectControlValueAccessor, i2.NgControlStatus, i2.NgModel], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.ai-assistant-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n\n\n\n\n\n\n\n.panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.panel-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.panel-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.panel-header[_ngcontent-%COMP%] .collapse-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.panel-header[_ngcontent-%COMP%] .collapse-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n\n\n\n\n\n.model-selector[_ngcontent-%COMP%] {\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-label[_ngcontent-%COMP%] {\n display: block;\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-dropdown-wrapper[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-dropdown[_ngcontent-%COMP%] {\n width: 100%;\n padding: 7px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 12px;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n appearance: none;\n -webkit-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n padding-right: 28px;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-dropdown[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.model-selector[_ngcontent-%COMP%] .model-dropdown[_ngcontent-%COMP%]:disabled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-loading[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n padding: 7px 0;\n}\n\n.model-selector[_ngcontent-%COMP%] .model-loading[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n\n\n\n\n\n\n\n.quick-actions[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.quick-actions[_ngcontent-%COMP%] .quick-action-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 5px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 16px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.quick-actions[_ngcontent-%COMP%] .quick-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.quick-actions[_ngcontent-%COMP%] .quick-action-btn[_ngcontent-%COMP%]:hover:not(.disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.quick-actions[_ngcontent-%COMP%] .quick-action-btn.disabled[_ngcontent-%COMP%] {\n opacity: 0.4;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n\n\n\n\n\n\n\n.chat-thread[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n scroll-behavior: smooth;\n}\n\n\n\n.chat-thread[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 6px;\n}\n\n.chat-thread[_ngcontent-%COMP%]::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.chat-thread[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: var(--mj-border-default);\n border-radius: 3px;\n}\n\n.chat-thread[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-secondary);\n}\n\n.empty-thread[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex: 1;\n text-align: center;\n color: var(--mj-text-secondary);\n padding: 32px 16px;\n}\n\n.empty-thread[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.empty-thread[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n font-size: 13px;\n line-height: 1.5;\n margin: 0;\n max-width: 220px;\n}\n\n\n\n\n\n\n\n\n.message-wrapper[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n}\n\n.message-wrapper.user[_ngcontent-%COMP%] {\n justify-content: flex-end;\n}\n\n.message-wrapper.assistant[_ngcontent-%COMP%] {\n justify-content: flex-start;\n}\n\n.message-wrapper.system[_ngcontent-%COMP%] {\n justify-content: center;\n}\n\n.message-avatar[_ngcontent-%COMP%] {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n font-size: 12px;\n}\n\n.message-avatar.assistant-avatar[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.message-avatar.user-avatar[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.message-bubble[_ngcontent-%COMP%] {\n max-width: 80%;\n padding: 10px 14px;\n border-radius: 16px;\n position: relative;\n}\n\n.message-bubble.user-bubble[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-bottom-right-radius: 4px;\n}\n\n.message-bubble.user-bubble[_ngcontent-%COMP%] .message-timestamp[_ngcontent-%COMP%] {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.message-bubble.assistant-bubble[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n border-bottom-left-radius: 4px;\n}\n\n.message-bubble.assistant-bubble[_ngcontent-%COMP%] .message-timestamp[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n}\n\n.message-bubble.system-bubble[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n font-style: italic;\n border-radius: 8px;\n max-width: 90%;\n}\n\n.message-bubble.system-bubble[_ngcontent-%COMP%] .message-timestamp[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n.message-bubble.typing-bubble[_ngcontent-%COMP%] {\n padding: 14px 18px;\n}\n\n.message-content[_ngcontent-%COMP%] {\n font-size: 13px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.message-timestamp[_ngcontent-%COMP%] {\n font-size: 10px;\n margin-top: 4px;\n text-align: right;\n}\n\n\n\n\n\n\n\n\n.typing-indicator[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.typing-indicator[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%] {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-text-secondary);\n animation: _ngcontent-%COMP%_typingBounce 1.4s infinite ease-in-out both;\n}\n\n.typing-indicator[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%]:nth-child(1) {\n animation-delay: 0s;\n}\n\n.typing-indicator[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%]:nth-child(2) {\n animation-delay: 0.2s;\n}\n\n.typing-indicator[_ngcontent-%COMP%] .dot[_ngcontent-%COMP%]:nth-child(3) {\n animation-delay: 0.4s;\n}\n\n@keyframes _ngcontent-%COMP%_typingBounce {\n 0%, 60%, 100% {\n transform: translateY(0);\n opacity: 0.4;\n }\n 30% {\n transform: translateY(-6px);\n opacity: 1;\n }\n}\n\n\n\n\n\n\n\n\n.chat-input-area[_ngcontent-%COMP%] {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .input-wrapper[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n padding: 4px 4px 4px 12px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .input-wrapper[_ngcontent-%COMP%]:focus-within {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.chat-input-area[_ngcontent-%COMP%] .chat-textarea[_ngcontent-%COMP%] {\n flex: 1;\n border: none;\n outline: none;\n resize: none;\n font-size: 13px;\n line-height: 20px;\n color: var(--mj-text-primary);\n background: transparent;\n padding: 6px 0;\n max-height: 80px;\n font-family: inherit;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .chat-textarea[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.chat-input-area[_ngcontent-%COMP%] .chat-textarea[_ngcontent-%COMP%]:disabled {\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .send-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: none;\n border-radius: 8px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .send-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 13px;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .send-btn.active[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .send-btn.active[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary);\n}\n\n.chat-input-area[_ngcontent-%COMP%] .send-btn[_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .input-hint[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n margin-top: 6px;\n}\n\n.chat-input-area[_ngcontent-%COMP%] .input-hint[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n font-size: 10px;\n color: var(--mj-text-secondary);\n}\n\n.chat-input-area[_ngcontent-%COMP%] .input-hint[_ngcontent-%COMP%] kbd[_ngcontent-%COMP%] {\n display: inline-block;\n padding: 1px 4px;\n font-size: 9px;\n font-family: inherit;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 3px;\n margin: 0 2px;\n}"] });
297
+ i0.ɵɵconditional(ctx.CurrentUser ? 12 : 13);
298
+ } }, dependencies: [i2.ConversationChatAreaComponent], styles: ["[_nghost-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.ai-assistant-panel[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n\n\n\n\n\n\n\n.panel-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.panel-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.panel-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.panel-header[_ngcontent-%COMP%] .collapse-btn[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n}\n\n.panel-header[_ngcontent-%COMP%] .collapse-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n\n\n\n\n\n\n\n.quick-actions[_ngcontent-%COMP%] {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.quick-action-btn[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.quick-action-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 11px;\n}\n\n.quick-action-btn[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.quick-action-btn[_ngcontent-%COMP%]:disabled, \n.quick-action-btn.disabled[_ngcontent-%COMP%] {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n\n\n\n\n\n\n\n.chat-host[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.chat-host[_ngcontent-%COMP%] mj-conversation-chat-area[_ngcontent-%COMP%] {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.chat-host__empty[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 13px;\n color: var(--mj-text-muted);\n}"], changeDetection: 0 });
390
299
  }
391
300
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AIAssistantPanelComponent, [{
392
301
  type: Component,
393
- args: [{ standalone: false, selector: 'mj-ai-assistant-panel', template: "<div class=\"ai-assistant-panel\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-robot\"></i>\n <span>AI Assistant</span>\n </div>\n <button class=\"collapse-btn\" (click)=\"OnCollapsePanel()\" title=\"Collapse panel\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Model Selector -->\n <div class=\"model-selector\">\n <label class=\"model-label\">Model</label>\n <div class=\"model-dropdown-wrapper\">\n @if (IsLoadingModels) {\n <div class=\"model-loading\">\n <i class=\"fa-solid fa-spinner fa-spin\"></i>\n <span>Loading models...</span>\n </div>\n } @else {\n <select\n class=\"model-dropdown\"\n [(ngModel)]=\"SelectedModelID\"\n [disabled]=\"AvailableModels.length === 0\">\n @for (model of AvailableModels; track model.ID) {\n <option [value]=\"model.ID\">{{ model.DisplayLabel }}</option>\n }\n @if (AvailableModels.length === 0) {\n <option [value]=\"null\" disabled>No models available</option>\n }\n </select>\n }\n </div>\n </div>\n\n <!-- Quick Actions Bar -->\n <div class=\"quick-actions\">\n @for (action of QuickActions; track action.Label) {\n <button\n class=\"quick-action-btn\"\n [class.disabled]=\"!IsQuickActionEnabled(action)\"\n [disabled]=\"!IsQuickActionEnabled(action) || IsWaitingForResponse\"\n (click)=\"OnQuickAction(action)\"\n [title]=\"action.Label\">\n <i class=\"fa-solid {{ action.Icon }}\"></i>\n <span>{{ action.Label }}</span>\n </button>\n }\n </div>\n\n <!-- Chat Thread -->\n <div class=\"chat-thread\" #chatThread>\n @if (Messages.length === 0) {\n <div class=\"empty-thread\">\n <i class=\"fa-solid fa-comments\"></i>\n <p>No messages yet. Start a conversation or use a quick action above.</p>\n </div>\n }\n\n @for (message of Messages; track TrackByTimestamp($index, message)) {\n <div class=\"message-wrapper\" [class.user]=\"message.Role === 'user'\" [class.assistant]=\"message.Role === 'assistant'\" [class.system]=\"message.Role === 'system'\">\n @if (message.Role === 'assistant') {\n <div class=\"message-avatar assistant-avatar\">\n <i class=\"fa-solid fa-robot\"></i>\n </div>\n }\n\n <div class=\"message-bubble\" [class.user-bubble]=\"message.Role === 'user'\" [class.assistant-bubble]=\"message.Role === 'assistant'\" [class.system-bubble]=\"message.Role === 'system'\">\n <div class=\"message-content\">{{ message.Content }}</div>\n <div class=\"message-timestamp\">{{ FormatTimestamp(message.Timestamp) }}</div>\n </div>\n\n @if (message.Role === 'user') {\n <div class=\"message-avatar user-avatar\">\n <i class=\"fa-solid fa-user\"></i>\n </div>\n }\n </div>\n }\n\n <!-- Typing Indicator -->\n @if (IsWaitingForResponse) {\n <div class=\"message-wrapper assistant\">\n <div class=\"message-avatar assistant-avatar\">\n <i class=\"fa-solid fa-robot\"></i>\n </div>\n <div class=\"message-bubble assistant-bubble typing-bubble\">\n <div class=\"typing-indicator\">\n <span class=\"dot\"></span>\n <span class=\"dot\"></span>\n <span class=\"dot\"></span>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Chat Input Area -->\n <div class=\"chat-input-area\">\n <div class=\"input-wrapper\">\n <textarea\n #chatInput\n class=\"chat-textarea\"\n [(ngModel)]=\"InputText\"\n (keydown)=\"OnInputKeydown($event)\"\n (input)=\"OnInputChange()\"\n [disabled]=\"IsWaitingForResponse\"\n placeholder=\"Ask the AI assistant...\"\n rows=\"1\"></textarea>\n <button\n class=\"send-btn\"\n [class.active]=\"InputText.trim().length > 0 && !IsWaitingForResponse\"\n [disabled]=\"InputText.trim().length === 0 || IsWaitingForResponse\"\n (click)=\"OnSendMessage()\"\n title=\"Send message\">\n <i class=\"fa-solid fa-paper-plane\"></i>\n </button>\n </div>\n <div class=\"input-hint\">\n <span>Press <kbd>Enter</kbd> to send, <kbd>Shift+Enter</kbd> for new line</span>\n </div>\n </div>\n</div>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.ai-assistant-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n/* ============================================================ */\n/* HEADER */\n/* ============================================================ */\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.panel-header .header-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.panel-header .header-title i {\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.panel-header .collapse-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.panel-header .collapse-btn:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n/* ============================================================ */\n/* MODEL SELECTOR */\n/* ============================================================ */\n\n.model-selector {\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.model-selector .model-label {\n display: block;\n font-size: 11px;\n font-weight: 600;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 6px;\n}\n\n.model-selector .model-dropdown-wrapper {\n position: relative;\n}\n\n.model-selector .model-dropdown {\n width: 100%;\n padding: 7px 10px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 4px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 12px;\n cursor: pointer;\n outline: none;\n transition: border-color 0.15s ease;\n appearance: none;\n -webkit-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n padding-right: 28px;\n}\n\n.model-selector .model-dropdown:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.model-selector .model-dropdown:disabled {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n}\n\n.model-selector .model-loading {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n color: var(--mj-text-secondary);\n padding: 7px 0;\n}\n\n.model-selector .model-loading i {\n font-size: 12px;\n}\n\n/* ============================================================ */\n/* QUICK ACTIONS */\n/* ============================================================ */\n\n.quick-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n.quick-actions .quick-action-btn {\n display: flex;\n align-items: center;\n gap: 5px;\n padding: 5px 10px;\n border: 1px solid var(--mj-border-default);\n border-radius: 16px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n white-space: nowrap;\n}\n\n.quick-actions .quick-action-btn i {\n font-size: 11px;\n}\n\n.quick-actions .quick-action-btn:hover:not(.disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.quick-actions .quick-action-btn.disabled {\n opacity: 0.4;\n cursor: not-allowed;\n background: var(--mj-bg-surface-sunken);\n}\n\n/* ============================================================ */\n/* CHAT THREAD */\n/* ============================================================ */\n\n.chat-thread {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n scroll-behavior: smooth;\n}\n\n/* Custom scrollbar */\n.chat-thread::-webkit-scrollbar {\n width: 6px;\n}\n\n.chat-thread::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.chat-thread::-webkit-scrollbar-thumb {\n background: var(--mj-border-default);\n border-radius: 3px;\n}\n\n.chat-thread::-webkit-scrollbar-thumb:hover {\n background: var(--mj-text-secondary);\n}\n\n.empty-thread {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n flex: 1;\n text-align: center;\n color: var(--mj-text-secondary);\n padding: 32px 16px;\n}\n\n.empty-thread i {\n font-size: 36px;\n margin-bottom: 12px;\n opacity: 0.5;\n}\n\n.empty-thread p {\n font-size: 13px;\n line-height: 1.5;\n margin: 0;\n max-width: 220px;\n}\n\n/* ============================================================ */\n/* MESSAGE BUBBLES */\n/* ============================================================ */\n\n.message-wrapper {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n}\n\n.message-wrapper.user {\n justify-content: flex-end;\n}\n\n.message-wrapper.assistant {\n justify-content: flex-start;\n}\n\n.message-wrapper.system {\n justify-content: center;\n}\n\n.message-avatar {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n font-size: 12px;\n}\n\n.message-avatar.assistant-avatar {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.message-avatar.user-avatar {\n background: color-mix(in srgb, var(--mj-brand-primary) 20%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.message-bubble {\n max-width: 80%;\n padding: 10px 14px;\n border-radius: 16px;\n position: relative;\n}\n\n.message-bubble.user-bubble {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-bottom-right-radius: 4px;\n}\n\n.message-bubble.user-bubble .message-timestamp {\n color: rgba(255, 255, 255, 0.7);\n}\n\n.message-bubble.assistant-bubble {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n border-bottom-left-radius: 4px;\n}\n\n.message-bubble.assistant-bubble .message-timestamp {\n color: var(--mj-text-secondary);\n}\n\n.message-bubble.system-bubble {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n font-style: italic;\n border-radius: 8px;\n max-width: 90%;\n}\n\n.message-bubble.system-bubble .message-timestamp {\n color: var(--mj-status-warning);\n}\n\n.message-bubble.typing-bubble {\n padding: 14px 18px;\n}\n\n.message-content {\n font-size: 13px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n\n.message-timestamp {\n font-size: 10px;\n margin-top: 4px;\n text-align: right;\n}\n\n/* ============================================================ */\n/* TYPING INDICATOR */\n/* ============================================================ */\n\n.typing-indicator {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.typing-indicator .dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--mj-text-secondary);\n animation: typingBounce 1.4s infinite ease-in-out both;\n}\n\n.typing-indicator .dot:nth-child(1) {\n animation-delay: 0s;\n}\n\n.typing-indicator .dot:nth-child(2) {\n animation-delay: 0.2s;\n}\n\n.typing-indicator .dot:nth-child(3) {\n animation-delay: 0.4s;\n}\n\n@keyframes typingBounce {\n 0%, 60%, 100% {\n transform: translateY(0);\n opacity: 0.4;\n }\n 30% {\n transform: translateY(-6px);\n opacity: 1;\n }\n}\n\n/* ============================================================ */\n/* CHAT INPUT AREA */\n/* ============================================================ */\n\n.chat-input-area {\n padding: 12px 16px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.chat-input-area .input-wrapper {\n display: flex;\n align-items: flex-end;\n gap: 8px;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-strong);\n border-radius: 12px;\n padding: 4px 4px 4px 12px;\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n\n.chat-input-area .input-wrapper:focus-within {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.chat-input-area .chat-textarea {\n flex: 1;\n border: none;\n outline: none;\n resize: none;\n font-size: 13px;\n line-height: 20px;\n color: var(--mj-text-primary);\n background: transparent;\n padding: 6px 0;\n max-height: 80px;\n font-family: inherit;\n}\n\n.chat-input-area .chat-textarea::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.chat-input-area .chat-textarea:disabled {\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n}\n\n.chat-input-area .send-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: none;\n border-radius: 8px;\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-secondary);\n cursor: not-allowed;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n\n.chat-input-area .send-btn i {\n font-size: 13px;\n}\n\n.chat-input-area .send-btn.active {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n cursor: pointer;\n}\n\n.chat-input-area .send-btn.active:hover {\n background: var(--mj-brand-primary);\n}\n\n.chat-input-area .send-btn:disabled {\n cursor: not-allowed;\n}\n\n.chat-input-area .input-hint {\n display: flex;\n justify-content: center;\n margin-top: 6px;\n}\n\n.chat-input-area .input-hint span {\n font-size: 10px;\n color: var(--mj-text-secondary);\n}\n\n.chat-input-area .input-hint kbd {\n display: inline-block;\n padding: 1px 4px;\n font-size: 9px;\n font-family: inherit;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n border: 1px solid var(--mj-border-default);\n border-radius: 3px;\n margin: 0 2px;\n}\n"] }]
394
- }], () => [{ type: i1.ComponentStudioStateService }, { type: i0.ChangeDetectorRef }], { chatThreadEl: [{
395
- type: ViewChild,
396
- args: ['chatThread']
397
- }], chatInputEl: [{
398
- type: ViewChild,
399
- args: ['chatInput']
400
- }] }); })();
401
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AIAssistantPanelComponent, { className: "AIAssistantPanelComponent", filePath: "src/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.ts", lineNumber: 44 }); })();
302
+ args: [{ standalone: false, selector: 'mj-ai-assistant-panel', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ai-assistant-panel\">\n <!-- Header: title + collapse. Style + classes match the prior bespoke\n chat so the panel chrome lines up with the rest of the cockpit. -->\n <div class=\"panel-header\">\n <div class=\"header-title\">\n <i class=\"fa-solid fa-robot\"></i>\n <span>AI Assistant</span>\n </div>\n <button class=\"collapse-btn\" (click)=\"OnCollapsePanel()\" title=\"Collapse panel\">\n <i class=\"fa-solid fa-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Quick Actions: clicking a button stuffs the canned prompt into\n `PendingMessage`, which the chat-area below picks up on its next\n render cycle and sends as if the user typed it. Same mechanism the\n SendErrorToAI handler uses. \"Fix Errors\" disables until an error\n has actually been observed in the runtime preview. -->\n <div class=\"quick-actions\">\n @for (action of QuickActions; track action.Label) {\n <button\n class=\"quick-action-btn\"\n [class.disabled]=\"!IsQuickActionEnabled(action)\"\n [disabled]=\"!IsQuickActionEnabled(action)\"\n (click)=\"OnQuickAction(action)\"\n [title]=\"action.Label\">\n <i class=\"fa-solid {{ action.Icon }}\"></i>\n <span>{{ action.Label }}</span>\n </button>\n }\n </div>\n\n <!-- Embedded chat-area \u2014 same primitive the main Chat app and the Form\n Builder cockpit use. Conversations here are scoped to the\n Component Studio app so they don't pollute main chat; the default\n agent is Codesmith (resolved at init from the AIEngineBase cache);\n and the conversation-header agent picker stays enabled so the user\n can switch on the fly. -->\n <div class=\"chat-host\">\n @if (CurrentUser) {\n <mj-conversation-chat-area\n [Provider]=\"Provider\"\n [environmentId]=\"EnvironmentId\"\n [currentUser]=\"CurrentUser\"\n [conversationId]=\"ChatConversationId\"\n [conversation]=\"ChatConversation\"\n [isNewConversation]=\"ChatIsNewConversation\"\n [pendingMessage]=\"PendingMessage\"\n [pendingAttachments]=\"PendingAttachments\"\n [threadId]=\"ChatThreadId\"\n [pendingArtifactId]=\"ChatPendingArtifactId\"\n [pendingArtifactVersionNumber]=\"ChatPendingArtifactVersionNumber\"\n [overlayMode]=\"true\"\n [showExportButton]=\"false\"\n [showShareButton]=\"false\"\n [showArtifactIndicator]=\"false\"\n [appContext]=\"$any(ChatAppContext)\"\n [defaultAgentId]=\"CodesmithAgentId\"\n [applicationScope]=\"'Application'\"\n [applicationId]=\"CockpitApplicationId\"\n [linkedEntityId]=\"ComponentsEntityID\"\n [linkedRecordId]=\"LinkedComponentID\"\n [emptyStateGreeting]=\"'How can I help with this component?'\"\n (conversationCreated)=\"OnConversationCreated($event)\"\n (pendingMessageConsumed)=\"OnPendingMessageConsumed()\"\n (openEntityRecord)=\"OnOpenEntityRecord($event)\"\n (navigationRequest)=\"OnNavigationRequest($event)\"\n (taskClicked)=\"OnTaskClicked($event)\"\n (artifactLinkClicked)=\"OnArtifactLinkClicked($event)\"\n (conversationRenamed)=\"OnConversationRenamed($event)\"\n (threadOpened)=\"OnThreadOpened($event)\"\n (threadClosed)=\"OnThreadClosed()\"\n (pendingArtifactConsumed)=\"OnPendingArtifactConsumed()\">\n </mj-conversation-chat-area>\n } @else {\n <div class=\"chat-host__empty\">Loading\u2026</div>\n }\n </div>\n</div>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.ai-assistant-panel {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n overflow: hidden;\n}\n\n/* ============================================================ */\n/* HEADER */\n/* ============================================================ */\n\n.panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n.panel-header .header-title {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.panel-header .header-title i {\n color: var(--mj-brand-primary);\n font-size: 16px;\n}\n\n.panel-header .collapse-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n border-radius: 4px;\n cursor: pointer;\n transition: background 0.15s, color 0.15s;\n}\n\n.panel-header .collapse-btn:hover {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-primary);\n}\n\n/* ============================================================ */\n/* QUICK ACTIONS */\n/* ============================================================ */\n\n.quick-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n flex-shrink: 0;\n}\n\n.quick-action-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: 4px;\n font-size: 12px;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.quick-action-btn i {\n color: var(--mj-brand-primary);\n font-size: 11px;\n}\n\n.quick-action-btn:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n}\n\n.quick-action-btn:disabled,\n.quick-action-btn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* ============================================================ */\n/* CHAT HOST */\n/* ============================================================ */\n\n.chat-host {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.chat-host mj-conversation-chat-area {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n.chat-host__empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n font-size: 13px;\n color: var(--mj-text-muted);\n}\n"] }]
303
+ }], () => [{ type: i1.ComponentStudioStateService }], null); })();
304
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(AIAssistantPanelComponent, { className: "AIAssistantPanelComponent", filePath: "src/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.ts", lineNumber: 71 }); })();
402
305
  //# sourceMappingURL=ai-assistant-panel.component.js.map