@memberjunction/ng-conversations 5.37.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 (56) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts +47 -0
  3. package/dist/lib/components/conversation/conversation-agent-picker.component.d.ts.map +1 -0
  4. package/dist/lib/components/conversation/conversation-agent-picker.component.js +253 -0
  5. package/dist/lib/components/conversation/conversation-agent-picker.component.js.map +1 -0
  6. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +148 -11
  7. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  8. package/dist/lib/components/conversation/conversation-chat-area.component.js +498 -261
  9. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  10. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +2 -1
  11. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
  12. package/dist/lib/components/conversation/conversation-empty-state.component.js +6 -3
  13. package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
  14. package/dist/lib/components/conversation/conversation-mode-picker.component.d.ts +57 -0
  15. package/dist/lib/components/conversation/conversation-mode-picker.component.d.ts.map +1 -0
  16. package/dist/lib/components/conversation/conversation-mode-picker.component.js +252 -0
  17. package/dist/lib/components/conversation/conversation-mode-picker.component.js.map +1 -0
  18. package/dist/lib/components/message/message-input.component.d.ts +51 -3
  19. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  20. package/dist/lib/components/message/message-input.component.js +107 -18
  21. package/dist/lib/components/message/message-input.component.js.map +1 -1
  22. package/dist/lib/components/message/message-item.component.d.ts +0 -13
  23. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  24. package/dist/lib/components/message/message-item.component.js +124 -150
  25. package/dist/lib/components/message/message-item.component.js.map +1 -1
  26. package/dist/lib/components/overlay/chat-overlay.component.d.ts +15 -0
  27. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  28. package/dist/lib/components/overlay/chat-overlay.component.js +123 -46
  29. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  30. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -0
  31. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  32. package/dist/lib/components/workspace/conversation-workspace.component.js +13 -3
  33. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  34. package/dist/lib/conversations.module.d.ts +59 -58
  35. package/dist/lib/conversations.module.d.ts.map +1 -1
  36. package/dist/lib/conversations.module.js +8 -4
  37. package/dist/lib/conversations.module.js.map +1 -1
  38. package/dist/lib/models/conversation-state.model.d.ts +0 -27
  39. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  40. package/dist/lib/models/conversation-state.model.js.map +1 -1
  41. package/dist/lib/services/conversation-agent.service.d.ts +1 -9
  42. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  43. package/dist/lib/services/conversation-agent.service.js +22 -121
  44. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  45. package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
  46. package/dist/lib/services/conversation-attachment.service.js +5 -26
  47. package/dist/lib/services/conversation-attachment.service.js.map +1 -1
  48. package/dist/lib/services/conversation-bridge.service.d.ts +13 -0
  49. package/dist/lib/services/conversation-bridge.service.d.ts.map +1 -1
  50. package/dist/lib/services/conversation-bridge.service.js +15 -0
  51. package/dist/lib/services/conversation-bridge.service.js.map +1 -1
  52. package/package.json +23 -22
  53. package/dist/lib/components/message/suggested-responses.component.d.ts +0 -55
  54. package/dist/lib/components/message/suggested-responses.component.d.ts.map +0 -1
  55. package/dist/lib/components/message/suggested-responses.component.js +0 -207
  56. package/dist/lib/components/message/suggested-responses.component.js.map +0 -1
package/README.md CHANGED
@@ -113,9 +113,9 @@ A floating chat panel (bottom-right corner) that wraps the chat area for persist
113
113
  The overlay is generic — it raises events for navigation and tool execution. The consuming application (e.g., MJExplorer) handles those events with app-specific logic like `NavigationService.OpenEntityRecord()`.
114
114
 
115
115
  **Related packages:**
116
- - [`@memberjunction/ai-agent-client`](../../AI/AgentsClient/README.md) — Core agent SDK (framework-agnostic, GraphQL transport, tool registry)
116
+ - [`@memberjunction/ai-agent-client`](../../../AI/AgentsClient/README.md) — Core agent SDK (framework-agnostic, GraphQL transport, tool registry)
117
117
  - [`@memberjunction/ng-agent-client`](../agent-client/README.md) — Angular wrapper for the agent SDK
118
- - [`@memberjunction/core-entities`](../../../MJCoreEntities/README.md) — ConversationEngine for centralized conversation data
118
+ - [`@memberjunction/core-entities`](../../../MJCoreEntities/readme.md) — ConversationEngine for centralized conversation data
119
119
 
120
120
  ### Message Components
121
121
 
@@ -0,0 +1,47 @@
1
+ import { EventEmitter, OnInit } from '@angular/core';
2
+ import type { UserInfo } from '@memberjunction/core';
3
+ import type { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';
4
+ import type { MJConversationEntity } from '@memberjunction/core-entities';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * Header widget that lets a user pin a default AI agent on the active
8
+ * conversation. The pinned agent (saved to
9
+ * `MJConversationEntity.DefaultAgentID`) takes precedence over the
10
+ * embedder-supplied default in `MessageInputComponent.routeMessage()` —
11
+ * non-mention messages route to it instead of Sage.
12
+ *
13
+ * Eligible agents are top-level, Active, non-Sub-Agent rows from
14
+ * `AIEngineBase.Instance.Agents`. The widget renders as a compact button
15
+ * showing the current pin (or "Auto" when nothing is pinned). Clicking
16
+ * opens an inline list with a "Clear" option to remove the pin.
17
+ *
18
+ * The host controls visibility via `<mj-conversation-chat-area>`'s
19
+ * `[showAgentPicker]` input (default true). Surfaces that don't want this
20
+ * UX (e.g. agent-less embed) set it to false.
21
+ */
22
+ export declare class ConversationAgentPickerComponent implements OnInit {
23
+ /** The conversation whose `DefaultAgentID` this widget edits. */
24
+ Conversation: MJConversationEntity | null;
25
+ /** Required for the Save() audit context. */
26
+ CurrentUser: UserInfo | null;
27
+ /** Disable the picker (e.g. read-only conversation). */
28
+ Disabled: boolean;
29
+ /** Emitted after a successful save with the new (or null) agent ID. */
30
+ AgentChanged: EventEmitter<string | null>;
31
+ IsOpen: boolean;
32
+ EligibleAgents: MJAIAgentEntityExtended[];
33
+ private readonly cdr;
34
+ private readonly notifications;
35
+ ngOnInit(): Promise<void>;
36
+ get CurrentAgentLabel(): string;
37
+ get ButtonTitle(): string;
38
+ IconClassFor(agent: MJAIAgentEntityExtended): string;
39
+ IsSelected(agent: MJAIAgentEntityExtended): boolean;
40
+ Toggle(event: MouseEvent): void;
41
+ PickAgent(agent: MJAIAgentEntityExtended | null): Promise<void>;
42
+ /** Close the menu when the user clicks outside the picker. */
43
+ OnDocumentClick(_event: MouseEvent): void;
44
+ static ɵfac: i0.ɵɵFactoryDeclaration<ConversationAgentPickerComponent, never>;
45
+ static ɵcmp: i0.ɵɵComponentDeclaration<ConversationAgentPickerComponent, "mj-conversation-agent-picker", never, { "Conversation": { "alias": "Conversation"; "required": false; }; "CurrentUser": { "alias": "CurrentUser"; "required": false; }; "Disabled": { "alias": "Disabled"; "required": false; }; }, { "AgentChanged": "AgentChanged"; }, never, never, false, never>;
46
+ }
47
+ //# sourceMappingURL=conversation-agent-picker.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-agent-picker.component.d.ts","sourceRoot":"","sources":["../../../../src/lib/components/conversation/conversation-agent-picker.component.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,YAAY,EAGZ,MAAM,EAGT,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;;AAI1E;;;;;;;;;;;;;;;GAeG;AACH,qBAyFa,gCAAiC,YAAW,MAAM;IAC3D,iEAAiE;IACxD,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAQ;IAE1D,6CAA6C;IACpC,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAQ;IAE7C,wDAAwD;IAC/C,QAAQ,EAAE,OAAO,CAAS;IAEnC,uEAAuE;IAC7D,YAAY,8BAAqC;IAEpD,MAAM,UAAS;IACf,cAAc,EAAE,uBAAuB,EAAE,CAAM;IAEtD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiC;IAElD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBtC,IAAW,iBAAiB,IAAI,MAAM,CAUrC;IAED,IAAW,WAAW,IAAI,MAAM,CAI/B;IAEM,YAAY,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM;IAIpD,UAAU,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO;IAKnD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAOzB,SAAS,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B5E,8DAA8D;IAEvD,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;yCAvGvC,gCAAgC;2CAAhC,gCAAgC;CA6G5C"}
@@ -0,0 +1,253 @@
1
+ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, Output, inject, } from '@angular/core';
2
+ import { LogError } from '@memberjunction/core';
3
+ import { AIEngineBase } from '@memberjunction/ai-engine-base';
4
+ import { MJNotificationService } from '@memberjunction/ng-notifications';
5
+ import { UUIDsEqual } from '@memberjunction/global';
6
+ import * as i0 from "@angular/core";
7
+ const _forTrack0 = ($index, $item) => $item.ID;
8
+ function ConversationAgentPickerComponent_Conditional_6_Conditional_7_Template(rf, ctx) { if (rf & 1) {
9
+ i0.ɵɵelementStart(0, "div", 10);
10
+ i0.ɵɵtext(1, "No agents available");
11
+ i0.ɵɵelementEnd();
12
+ } }
13
+ function ConversationAgentPickerComponent_Conditional_6_Conditional_8_For_1_Template(rf, ctx) { if (rf & 1) {
14
+ const _r3 = i0.ɵɵgetCurrentView();
15
+ i0.ɵɵelementStart(0, "button", 12);
16
+ i0.ɵɵlistener("click", function ConversationAgentPickerComponent_Conditional_6_Conditional_8_For_1_Template_button_click_0_listener() { const agent_r4 = i0.ɵɵrestoreView(_r3).$implicit; const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.PickAgent(agent_r4)); });
17
+ i0.ɵɵelement(1, "i");
18
+ i0.ɵɵelementStart(2, "span");
19
+ i0.ɵɵtext(3);
20
+ i0.ɵɵelementEnd()();
21
+ } if (rf & 2) {
22
+ const agent_r4 = ctx.$implicit;
23
+ const ctx_r1 = i0.ɵɵnextContext(3);
24
+ i0.ɵɵclassProp("mj-cv-agent-picker__item--selected", ctx_r1.IsSelected(agent_r4));
25
+ i0.ɵɵproperty("title", agent_r4.Description || agent_r4.Name);
26
+ i0.ɵɵadvance();
27
+ i0.ɵɵclassMap(ctx_r1.IconClassFor(agent_r4));
28
+ i0.ɵɵadvance(2);
29
+ i0.ɵɵtextInterpolate(agent_r4.Name);
30
+ } }
31
+ function ConversationAgentPickerComponent_Conditional_6_Conditional_8_Template(rf, ctx) { if (rf & 1) {
32
+ i0.ɵɵrepeaterCreate(0, ConversationAgentPickerComponent_Conditional_6_Conditional_8_For_1_Template, 4, 6, "button", 11, _forTrack0);
33
+ } if (rf & 2) {
34
+ const ctx_r1 = i0.ɵɵnextContext(2);
35
+ i0.ɵɵrepeater(ctx_r1.EligibleAgents);
36
+ } }
37
+ function ConversationAgentPickerComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
38
+ const _r1 = i0.ɵɵgetCurrentView();
39
+ i0.ɵɵelementStart(0, "div", 6);
40
+ i0.ɵɵlistener("click", function ConversationAgentPickerComponent_Conditional_6_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); });
41
+ i0.ɵɵelementStart(1, "div", 7);
42
+ i0.ɵɵtext(2, "Default agent for this conversation");
43
+ i0.ɵɵelementEnd();
44
+ i0.ɵɵelementStart(3, "button", 8);
45
+ i0.ɵɵlistener("click", function ConversationAgentPickerComponent_Conditional_6_Template_button_click_3_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.PickAgent(null)); });
46
+ i0.ɵɵelement(4, "i", 9);
47
+ i0.ɵɵelementStart(5, "span");
48
+ i0.ɵɵtext(6, "Auto (use default)");
49
+ i0.ɵɵelementEnd()();
50
+ i0.ɵɵconditionalCreate(7, ConversationAgentPickerComponent_Conditional_6_Conditional_7_Template, 2, 0, "div", 10)(8, ConversationAgentPickerComponent_Conditional_6_Conditional_8_Template, 2, 0);
51
+ i0.ɵɵelementEnd();
52
+ } if (rf & 2) {
53
+ const ctx_r1 = i0.ɵɵnextContext();
54
+ i0.ɵɵadvance(3);
55
+ i0.ɵɵclassProp("mj-cv-agent-picker__item--selected", !(ctx_r1.Conversation == null ? null : ctx_r1.Conversation.DefaultAgentID));
56
+ i0.ɵɵadvance(4);
57
+ i0.ɵɵconditional(ctx_r1.EligibleAgents.length === 0 ? 7 : 8);
58
+ } }
59
+ /**
60
+ * Header widget that lets a user pin a default AI agent on the active
61
+ * conversation. The pinned agent (saved to
62
+ * `MJConversationEntity.DefaultAgentID`) takes precedence over the
63
+ * embedder-supplied default in `MessageInputComponent.routeMessage()` —
64
+ * non-mention messages route to it instead of Sage.
65
+ *
66
+ * Eligible agents are top-level, Active, non-Sub-Agent rows from
67
+ * `AIEngineBase.Instance.Agents`. The widget renders as a compact button
68
+ * showing the current pin (or "Auto" when nothing is pinned). Clicking
69
+ * opens an inline list with a "Clear" option to remove the pin.
70
+ *
71
+ * The host controls visibility via `<mj-conversation-chat-area>`'s
72
+ * `[showAgentPicker]` input (default true). Surfaces that don't want this
73
+ * UX (e.g. agent-less embed) set it to false.
74
+ */
75
+ export class ConversationAgentPickerComponent {
76
+ /** The conversation whose `DefaultAgentID` this widget edits. */
77
+ Conversation = null;
78
+ /** Required for the Save() audit context. */
79
+ CurrentUser = null;
80
+ /** Disable the picker (e.g. read-only conversation). */
81
+ Disabled = false;
82
+ /** Emitted after a successful save with the new (or null) agent ID. */
83
+ AgentChanged = new EventEmitter();
84
+ IsOpen = false;
85
+ EligibleAgents = [];
86
+ cdr = inject(ChangeDetectorRef);
87
+ notifications = inject(MJNotificationService);
88
+ async ngOnInit() {
89
+ try {
90
+ // AIEngineBase is the live, cached agent catalog. Ensure it's
91
+ // loaded — most app shells warm it up at boot, but the picker
92
+ // could mount before that completes.
93
+ await AIEngineBase.Instance.Config(false);
94
+ this.EligibleAgents = (AIEngineBase.Instance.Agents ?? [])
95
+ .filter(a => !a.ParentID &&
96
+ a.Status === 'Active' &&
97
+ a.InvocationMode !== 'Sub-Agent')
98
+ .sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''));
99
+ this.cdr.markForCheck();
100
+ }
101
+ catch (err) {
102
+ LogError(`ConversationAgentPicker.ngOnInit: ${err instanceof Error ? err.message : String(err)}`);
103
+ }
104
+ }
105
+ get CurrentAgentLabel() {
106
+ const id = this.Conversation?.DefaultAgentID;
107
+ if (!id)
108
+ return 'Auto';
109
+ const match = this.EligibleAgents.find(a => UUIDsEqual(a.ID, id));
110
+ // If the pinned agent is no longer eligible (deactivated, demoted to
111
+ // sub-agent), still surface its name from the cache so the user can
112
+ // see what was pinned even though they can't re-pin it.
113
+ const fromCache = match
114
+ ?? (AIEngineBase.Instance.Agents ?? []).find(a => UUIDsEqual(a.ID, id));
115
+ return fromCache?.Name ?? 'Pinned agent';
116
+ }
117
+ get ButtonTitle() {
118
+ return this.Conversation?.DefaultAgentID
119
+ ? `This conversation routes to ${this.CurrentAgentLabel}. Click to change.`
120
+ : 'No agent pinned — messages route via the standard rules. Click to pin an agent.';
121
+ }
122
+ IconClassFor(agent) {
123
+ return agent.IconClass?.trim() || 'fa-solid fa-robot';
124
+ }
125
+ IsSelected(agent) {
126
+ return !!this.Conversation?.DefaultAgentID
127
+ && UUIDsEqual(agent.ID, this.Conversation.DefaultAgentID);
128
+ }
129
+ Toggle(event) {
130
+ event.stopPropagation();
131
+ if (this.Disabled || !this.Conversation)
132
+ return;
133
+ this.IsOpen = !this.IsOpen;
134
+ this.cdr.markForCheck();
135
+ }
136
+ async PickAgent(agent) {
137
+ this.IsOpen = false;
138
+ const conv = this.Conversation;
139
+ if (!conv)
140
+ return;
141
+ const newId = agent?.ID ?? null;
142
+ if (conv.DefaultAgentID === newId) {
143
+ this.cdr.markForCheck();
144
+ return;
145
+ }
146
+ const prev = conv.DefaultAgentID;
147
+ conv.DefaultAgentID = newId;
148
+ try {
149
+ const saved = await conv.Save();
150
+ if (!saved) {
151
+ conv.DefaultAgentID = prev;
152
+ this.notifications.CreateSimpleNotification(conv.LatestResult?.CompleteMessage ?? 'Failed to update conversation default agent.', 'error', 4500);
153
+ return;
154
+ }
155
+ this.AgentChanged.emit(newId);
156
+ }
157
+ catch (err) {
158
+ conv.DefaultAgentID = prev;
159
+ LogError(`ConversationAgentPicker.PickAgent: ${err instanceof Error ? err.message : String(err)}`);
160
+ }
161
+ finally {
162
+ this.cdr.markForCheck();
163
+ }
164
+ }
165
+ /** Close the menu when the user clicks outside the picker. */
166
+ OnDocumentClick(_event) {
167
+ if (this.IsOpen) {
168
+ this.IsOpen = false;
169
+ this.cdr.markForCheck();
170
+ }
171
+ }
172
+ static ɵfac = function ConversationAgentPickerComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ConversationAgentPickerComponent)(); };
173
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ConversationAgentPickerComponent, selectors: [["mj-conversation-agent-picker"]], hostBindings: function ConversationAgentPickerComponent_HostBindings(rf, ctx) { if (rf & 1) {
174
+ i0.ɵɵlistener("click", function ConversationAgentPickerComponent_click_HostBindingHandler($event) { return ctx.OnDocumentClick($event); }, i0.ɵɵresolveDocument);
175
+ } }, inputs: { Conversation: "Conversation", CurrentUser: "CurrentUser", Disabled: "Disabled" }, outputs: { AgentChanged: "AgentChanged" }, standalone: false, decls: 7, vars: 6, consts: [[1, "mj-cv-agent-picker"], ["type", "button", 1, "mj-cv-agent-picker__trigger", 3, "click", "title", "disabled"], [1, "fa-solid", "fa-robot"], [1, "mj-cv-agent-picker__label"], [1, "fa-solid", "fa-caret-down", "mj-cv-agent-picker__caret"], [1, "mj-cv-agent-picker__menu"], [1, "mj-cv-agent-picker__menu", 3, "click"], [1, "mj-cv-agent-picker__menu-header"], ["type", "button", 1, "mj-cv-agent-picker__item", 3, "click"], [1, "fa-solid", "fa-wand-magic-sparkles"], [1, "mj-cv-agent-picker__empty"], ["type", "button", 1, "mj-cv-agent-picker__item", 3, "mj-cv-agent-picker__item--selected", "title"], ["type", "button", 1, "mj-cv-agent-picker__item", 3, "click", "title"]], template: function ConversationAgentPickerComponent_Template(rf, ctx) { if (rf & 1) {
176
+ i0.ɵɵelementStart(0, "div", 0)(1, "button", 1);
177
+ i0.ɵɵlistener("click", function ConversationAgentPickerComponent_Template_button_click_1_listener($event) { return ctx.Toggle($event); });
178
+ i0.ɵɵelement(2, "i", 2);
179
+ i0.ɵɵelementStart(3, "span", 3);
180
+ i0.ɵɵtext(4);
181
+ i0.ɵɵelementEnd();
182
+ i0.ɵɵelement(5, "i", 4);
183
+ i0.ɵɵelementEnd();
184
+ i0.ɵɵconditionalCreate(6, ConversationAgentPickerComponent_Conditional_6_Template, 9, 3, "div", 5);
185
+ i0.ɵɵelementEnd();
186
+ } if (rf & 2) {
187
+ i0.ɵɵclassProp("mj-cv-agent-picker--open", ctx.IsOpen);
188
+ i0.ɵɵadvance();
189
+ i0.ɵɵproperty("title", ctx.ButtonTitle)("disabled", ctx.Disabled || !ctx.Conversation);
190
+ i0.ɵɵadvance(3);
191
+ i0.ɵɵtextInterpolate(ctx.CurrentAgentLabel);
192
+ i0.ɵɵadvance(2);
193
+ i0.ɵɵconditional(ctx.IsOpen ? 6 : -1);
194
+ } }, styles: ["\n\n\n\n .mj-cv-agent-picker[_ngcontent-%COMP%] { position: relative; display: inline-flex; align-items: center; }\n .mj-cv-agent-picker__trigger[_ngcontent-%COMP%] {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 6px 12px; min-height: 32px; box-sizing: border-box;\n background: var(--mj-bg-surface-card); border: 1px solid var(--mj-border-default);\n border-radius: 4px; cursor: pointer;\n color: var(--mj-text-primary); font-size: 0.8125rem; line-height: 1.5;\n transition: background 120ms, border-color 120ms;\n }\n .mj-cv-agent-picker__trigger[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover); border-color: var(--mj-border-strong);\n }\n .mj-cv-agent-picker__trigger[_ngcontent-%COMP%]:disabled { opacity: 0.5; cursor: not-allowed; }\n .mj-cv-agent-picker--open[_ngcontent-%COMP%] .mj-cv-agent-picker__trigger[_ngcontent-%COMP%] { border-color: var(--mj-border-focus); }\n .mj-cv-agent-picker__label[_ngcontent-%COMP%] { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .mj-cv-agent-picker__caret[_ngcontent-%COMP%] { font-size: 10px; opacity: 0.7; }\n .mj-cv-agent-picker__menu[_ngcontent-%COMP%] {\n position: absolute; top: calc(100% + 4px); right: 0; z-index: 50;\n min-width: 220px; max-height: 320px; overflow-y: auto;\n background: var(--mj-bg-surface-elevated); border: 1px solid var(--mj-border-default);\n border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n padding: 4px 0;\n }\n .mj-cv-agent-picker__menu-header[_ngcontent-%COMP%] {\n padding: 6px 12px 4px; font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.04em; color: var(--mj-text-muted);\n }\n .mj-cv-agent-picker__item[_ngcontent-%COMP%] {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 6px 12px; background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n }\n .mj-cv-agent-picker__item[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { width: 14px; text-align: center; color: var(--mj-text-secondary); }\n .mj-cv-agent-picker__item[_ngcontent-%COMP%]:hover { background: var(--mj-bg-surface-hover); }\n .mj-cv-agent-picker__item--selected[_ngcontent-%COMP%] { background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent); }\n .mj-cv-agent-picker__item--selected[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-brand-primary); }\n .mj-cv-agent-picker__empty[_ngcontent-%COMP%] { padding: 8px 12px; font-size: 12px; color: var(--mj-text-muted); }"], changeDetection: 0 });
195
+ }
196
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ConversationAgentPickerComponent, [{
197
+ type: Component,
198
+ args: [{ standalone: false, selector: 'mj-conversation-agent-picker', changeDetection: ChangeDetectionStrategy.OnPush, template: `
199
+ <div class="mj-cv-agent-picker" [class.mj-cv-agent-picker--open]="IsOpen">
200
+ <button
201
+ type="button"
202
+ class="mj-cv-agent-picker__trigger"
203
+ [title]="ButtonTitle"
204
+ [disabled]="Disabled || !Conversation"
205
+ (click)="Toggle($event)">
206
+ <i class="fa-solid fa-robot"></i>
207
+ <span class="mj-cv-agent-picker__label">{{ CurrentAgentLabel }}</span>
208
+ <i class="fa-solid fa-caret-down mj-cv-agent-picker__caret"></i>
209
+ </button>
210
+ @if (IsOpen) {
211
+ <div class="mj-cv-agent-picker__menu" (click)="$event.stopPropagation()">
212
+ <div class="mj-cv-agent-picker__menu-header">Default agent for this conversation</div>
213
+ <button
214
+ type="button"
215
+ class="mj-cv-agent-picker__item"
216
+ [class.mj-cv-agent-picker__item--selected]="!Conversation?.DefaultAgentID"
217
+ (click)="PickAgent(null)">
218
+ <i class="fa-solid fa-wand-magic-sparkles"></i>
219
+ <span>Auto (use default)</span>
220
+ </button>
221
+ @if (EligibleAgents.length === 0) {
222
+ <div class="mj-cv-agent-picker__empty">No agents available</div>
223
+ } @else {
224
+ @for (agent of EligibleAgents; track agent.ID) {
225
+ <button
226
+ type="button"
227
+ class="mj-cv-agent-picker__item"
228
+ [class.mj-cv-agent-picker__item--selected]="IsSelected(agent)"
229
+ [title]="agent.Description || agent.Name"
230
+ (click)="PickAgent(agent)">
231
+ <i [class]="IconClassFor(agent)"></i>
232
+ <span>{{ agent.Name }}</span>
233
+ </button>
234
+ }
235
+ }
236
+ </div>
237
+ }
238
+ </div>
239
+ `, styles: ["\n /* Match mjButton size=\"sm\" so the trigger lines up with neighboring\n Export / Share buttons: min-height 32px, 6px/12px padding,\n 0.8125rem font. Same border-radius as the other chrome buttons. */\n .mj-cv-agent-picker { position: relative; display: inline-flex; align-items: center; }\n .mj-cv-agent-picker__trigger {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 6px 12px; min-height: 32px; box-sizing: border-box;\n background: var(--mj-bg-surface-card); border: 1px solid var(--mj-border-default);\n border-radius: 4px; cursor: pointer;\n color: var(--mj-text-primary); font-size: 0.8125rem; line-height: 1.5;\n transition: background 120ms, border-color 120ms;\n }\n .mj-cv-agent-picker__trigger:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover); border-color: var(--mj-border-strong);\n }\n .mj-cv-agent-picker__trigger:disabled { opacity: 0.5; cursor: not-allowed; }\n .mj-cv-agent-picker--open .mj-cv-agent-picker__trigger { border-color: var(--mj-border-focus); }\n .mj-cv-agent-picker__label { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .mj-cv-agent-picker__caret { font-size: 10px; opacity: 0.7; }\n .mj-cv-agent-picker__menu {\n position: absolute; top: calc(100% + 4px); right: 0; z-index: 50;\n min-width: 220px; max-height: 320px; overflow-y: auto;\n background: var(--mj-bg-surface-elevated); border: 1px solid var(--mj-border-default);\n border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n padding: 4px 0;\n }\n .mj-cv-agent-picker__menu-header {\n padding: 6px 12px 4px; font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.04em; color: var(--mj-text-muted);\n }\n .mj-cv-agent-picker__item {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 6px 12px; background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n }\n .mj-cv-agent-picker__item i { width: 14px; text-align: center; color: var(--mj-text-secondary); }\n .mj-cv-agent-picker__item:hover { background: var(--mj-bg-surface-hover); }\n .mj-cv-agent-picker__item--selected { background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent); }\n .mj-cv-agent-picker__item--selected i { color: var(--mj-brand-primary); }\n .mj-cv-agent-picker__empty { padding: 8px 12px; font-size: 12px; color: var(--mj-text-muted); }\n "] }]
240
+ }], null, { Conversation: [{
241
+ type: Input
242
+ }], CurrentUser: [{
243
+ type: Input
244
+ }], Disabled: [{
245
+ type: Input
246
+ }], AgentChanged: [{
247
+ type: Output
248
+ }], OnDocumentClick: [{
249
+ type: HostListener,
250
+ args: ['document:click', ['$event']]
251
+ }] }); })();
252
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(ConversationAgentPickerComponent, { className: "ConversationAgentPickerComponent", filePath: "src/lib/components/conversation/conversation-agent-picker.component.ts", lineNumber: 125 }); })();
253
+ //# sourceMappingURL=conversation-agent-picker.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-agent-picker.component.js","sourceRoot":"","sources":["../../../../src/lib/components/conversation/conversation-agent-picker.component.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n HostListener,\n Input,\n OnInit,\n Output,\n inject,\n} from '@angular/core';\nimport { LogError } from '@memberjunction/core';\nimport type { UserInfo } from '@memberjunction/core';\nimport { AIEngineBase } from '@memberjunction/ai-engine-base';\nimport type { MJAIAgentEntityExtended } from '@memberjunction/ai-core-plus';\nimport type { MJConversationEntity } from '@memberjunction/core-entities';\nimport { MJNotificationService } from '@memberjunction/ng-notifications';\nimport { UUIDsEqual } from '@memberjunction/global';\n\n/**\n * Header widget that lets a user pin a default AI agent on the active\n * conversation. The pinned agent (saved to\n * `MJConversationEntity.DefaultAgentID`) takes precedence over the\n * embedder-supplied default in `MessageInputComponent.routeMessage()` —\n * non-mention messages route to it instead of Sage.\n *\n * Eligible agents are top-level, Active, non-Sub-Agent rows from\n * `AIEngineBase.Instance.Agents`. The widget renders as a compact button\n * showing the current pin (or \"Auto\" when nothing is pinned). Clicking\n * opens an inline list with a \"Clear\" option to remove the pin.\n *\n * The host controls visibility via `<mj-conversation-chat-area>`'s\n * `[showAgentPicker]` input (default true). Surfaces that don't want this\n * UX (e.g. agent-less embed) set it to false.\n */\n@Component({\n standalone: false,\n selector: 'mj-conversation-agent-picker',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <div class=\"mj-cv-agent-picker\" [class.mj-cv-agent-picker--open]=\"IsOpen\">\n <button\n type=\"button\"\n class=\"mj-cv-agent-picker__trigger\"\n [title]=\"ButtonTitle\"\n [disabled]=\"Disabled || !Conversation\"\n (click)=\"Toggle($event)\">\n <i class=\"fa-solid fa-robot\"></i>\n <span class=\"mj-cv-agent-picker__label\">{{ CurrentAgentLabel }}</span>\n <i class=\"fa-solid fa-caret-down mj-cv-agent-picker__caret\"></i>\n </button>\n @if (IsOpen) {\n <div class=\"mj-cv-agent-picker__menu\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-cv-agent-picker__menu-header\">Default agent for this conversation</div>\n <button\n type=\"button\"\n class=\"mj-cv-agent-picker__item\"\n [class.mj-cv-agent-picker__item--selected]=\"!Conversation?.DefaultAgentID\"\n (click)=\"PickAgent(null)\">\n <i class=\"fa-solid fa-wand-magic-sparkles\"></i>\n <span>Auto (use default)</span>\n </button>\n @if (EligibleAgents.length === 0) {\n <div class=\"mj-cv-agent-picker__empty\">No agents available</div>\n } @else {\n @for (agent of EligibleAgents; track agent.ID) {\n <button\n type=\"button\"\n class=\"mj-cv-agent-picker__item\"\n [class.mj-cv-agent-picker__item--selected]=\"IsSelected(agent)\"\n [title]=\"agent.Description || agent.Name\"\n (click)=\"PickAgent(agent)\">\n <i [class]=\"IconClassFor(agent)\"></i>\n <span>{{ agent.Name }}</span>\n </button>\n }\n }\n </div>\n }\n </div>\n `,\n styles: [`\n /* Match mjButton size=\"sm\" so the trigger lines up with neighboring\n Export / Share buttons: min-height 32px, 6px/12px padding,\n 0.8125rem font. Same border-radius as the other chrome buttons. */\n .mj-cv-agent-picker { position: relative; display: inline-flex; align-items: center; }\n .mj-cv-agent-picker__trigger {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 6px 12px; min-height: 32px; box-sizing: border-box;\n background: var(--mj-bg-surface-card); border: 1px solid var(--mj-border-default);\n border-radius: 4px; cursor: pointer;\n color: var(--mj-text-primary); font-size: 0.8125rem; line-height: 1.5;\n transition: background 120ms, border-color 120ms;\n }\n .mj-cv-agent-picker__trigger:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover); border-color: var(--mj-border-strong);\n }\n .mj-cv-agent-picker__trigger:disabled { opacity: 0.5; cursor: not-allowed; }\n .mj-cv-agent-picker--open .mj-cv-agent-picker__trigger { border-color: var(--mj-border-focus); }\n .mj-cv-agent-picker__label { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .mj-cv-agent-picker__caret { font-size: 10px; opacity: 0.7; }\n .mj-cv-agent-picker__menu {\n position: absolute; top: calc(100% + 4px); right: 0; z-index: 50;\n min-width: 220px; max-height: 320px; overflow-y: auto;\n background: var(--mj-bg-surface-elevated); border: 1px solid var(--mj-border-default);\n border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,0.12);\n padding: 4px 0;\n }\n .mj-cv-agent-picker__menu-header {\n padding: 6px 12px 4px; font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.04em; color: var(--mj-text-muted);\n }\n .mj-cv-agent-picker__item {\n display: flex; align-items: center; gap: 8px; width: 100%;\n padding: 6px 12px; background: transparent; border: none; cursor: pointer;\n color: var(--mj-text-primary); font-size: 13px; text-align: left;\n }\n .mj-cv-agent-picker__item i { width: 14px; text-align: center; color: var(--mj-text-secondary); }\n .mj-cv-agent-picker__item:hover { background: var(--mj-bg-surface-hover); }\n .mj-cv-agent-picker__item--selected { background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent); }\n .mj-cv-agent-picker__item--selected i { color: var(--mj-brand-primary); }\n .mj-cv-agent-picker__empty { padding: 8px 12px; font-size: 12px; color: var(--mj-text-muted); }\n `]\n})\nexport class ConversationAgentPickerComponent implements OnInit {\n /** The conversation whose `DefaultAgentID` this widget edits. */\n @Input() Conversation: MJConversationEntity | null = null;\n\n /** Required for the Save() audit context. */\n @Input() CurrentUser: UserInfo | null = null;\n\n /** Disable the picker (e.g. read-only conversation). */\n @Input() Disabled: boolean = false;\n\n /** Emitted after a successful save with the new (or null) agent ID. */\n @Output() AgentChanged = new EventEmitter<string | null>();\n\n public IsOpen = false;\n public EligibleAgents: MJAIAgentEntityExtended[] = [];\n\n private readonly cdr = inject(ChangeDetectorRef);\n private readonly notifications = inject(MJNotificationService);\n\n public async ngOnInit(): Promise<void> {\n try {\n // AIEngineBase is the live, cached agent catalog. Ensure it's\n // loaded — most app shells warm it up at boot, but the picker\n // could mount before that completes.\n await AIEngineBase.Instance.Config(false);\n this.EligibleAgents = (AIEngineBase.Instance.Agents ?? [])\n .filter(a =>\n !a.ParentID &&\n a.Status === 'Active' &&\n a.InvocationMode !== 'Sub-Agent'\n )\n .sort((a, b) => (a.Name ?? '').localeCompare(b.Name ?? ''));\n this.cdr.markForCheck();\n } catch (err) {\n LogError(`ConversationAgentPicker.ngOnInit: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n public get CurrentAgentLabel(): string {\n const id = this.Conversation?.DefaultAgentID;\n if (!id) return 'Auto';\n const match = this.EligibleAgents.find(a => UUIDsEqual(a.ID, id));\n // If the pinned agent is no longer eligible (deactivated, demoted to\n // sub-agent), still surface its name from the cache so the user can\n // see what was pinned even though they can't re-pin it.\n const fromCache = match\n ?? (AIEngineBase.Instance.Agents ?? []).find(a => UUIDsEqual(a.ID, id));\n return fromCache?.Name ?? 'Pinned agent';\n }\n\n public get ButtonTitle(): string {\n return this.Conversation?.DefaultAgentID\n ? `This conversation routes to ${this.CurrentAgentLabel}. Click to change.`\n : 'No agent pinned — messages route via the standard rules. Click to pin an agent.';\n }\n\n public IconClassFor(agent: MJAIAgentEntityExtended): string {\n return agent.IconClass?.trim() || 'fa-solid fa-robot';\n }\n\n public IsSelected(agent: MJAIAgentEntityExtended): boolean {\n return !!this.Conversation?.DefaultAgentID\n && UUIDsEqual(agent.ID, this.Conversation.DefaultAgentID);\n }\n\n public Toggle(event: MouseEvent): void {\n event.stopPropagation();\n if (this.Disabled || !this.Conversation) return;\n this.IsOpen = !this.IsOpen;\n this.cdr.markForCheck();\n }\n\n public async PickAgent(agent: MJAIAgentEntityExtended | null): Promise<void> {\n this.IsOpen = false;\n const conv = this.Conversation;\n if (!conv) return;\n const newId = agent?.ID ?? null;\n if (conv.DefaultAgentID === newId) {\n this.cdr.markForCheck();\n return;\n }\n const prev = conv.DefaultAgentID;\n conv.DefaultAgentID = newId;\n try {\n const saved = await conv.Save();\n if (!saved) {\n conv.DefaultAgentID = prev;\n this.notifications.CreateSimpleNotification(\n conv.LatestResult?.CompleteMessage ?? 'Failed to update conversation default agent.',\n 'error', 4500);\n return;\n }\n this.AgentChanged.emit(newId);\n } catch (err) {\n conv.DefaultAgentID = prev;\n LogError(`ConversationAgentPicker.PickAgent: ${err instanceof Error ? err.message : String(err)}`);\n } finally {\n this.cdr.markForCheck();\n }\n }\n\n /** Close the menu when the user clicks outside the picker. */\n @HostListener('document:click', ['$event'])\n public OnDocumentClick(_event: MouseEvent): void {\n if (this.IsOpen) {\n this.IsOpen = false;\n this.cdr.markForCheck();\n }\n }\n}\n"]}
@@ -14,7 +14,7 @@ import { MJResourcePermissionShareAdapter, ResourceShareContext } from '@memberj
14
14
  import { MessageAttachment } from '../message/message-item.component';
15
15
  import { LazyArtifactInfo } from '../../models/lazy-artifact-info';
16
16
  import { PendingAttachment } from '../mention/mention-editor.component';
17
- import { NavigationRequest, AnalyzeArtifactService } from '@memberjunction/ng-artifacts';
17
+ import { NavigationRequest, AnalyzeArtifactService, InteractiveFormApplyService } from '@memberjunction/ng-artifacts';
18
18
  import { TestFeedbackDialogData, TestFeedbackDialogResult } from '@memberjunction/ng-testing';
19
19
  import { DialogService as ConversationsDialogService } from '../../services/dialog.service';
20
20
  import { Subject } from 'rxjs';
@@ -34,6 +34,7 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
34
34
  private bridge;
35
35
  private analyzeArtifactService;
36
36
  private uiCommandHandler;
37
+ private interactiveFormApplyService;
37
38
  environmentId: string;
38
39
  currentUser: UserInfo;
39
40
  private _conversationId;
@@ -41,6 +42,30 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
41
42
  get conversationId(): string | null;
42
43
  conversation: MJConversationEntity | null;
43
44
  threadId: string | null;
45
+ /**
46
+ * When true, render the normal message-list + message-input layout even
47
+ * before a conversation exists, instead of the centered empty-state
48
+ * welcome card. Lets host pages (e.g. Form Builder cockpit) put the chat
49
+ * header + mode picker front-and-center on first open and let the user
50
+ * pick a mode before typing. The first send still routes through
51
+ * MessageInputComponent and triggers conversationCreated as usual.
52
+ */
53
+ suppressNewConversationEmptyState: boolean;
54
+ /**
55
+ * Host-level cap for @-mention autocomplete (agents and users).
56
+ * Defaults true. Hosts addressing a single fixed agent (e.g. Form Builder
57
+ * cockpit pinned to the Form Builder agent) should set false so the user
58
+ * can't accidentally redirect a turn to a different agent.
59
+ */
60
+ allowMentions: boolean;
61
+ /**
62
+ * Host-level cap for attachments. Defaults true. When false, the host
63
+ * disables attachments regardless of agent modality support — useful for
64
+ * surfaces where attachments don't make sense (cockpit text-only flows).
65
+ * When true (default), attachment availability still depends on the
66
+ * agent's modality support, computed at runtime.
67
+ */
68
+ allowAttachments: boolean;
44
69
  private _isNewConversation;
45
70
  set isNewConversation(value: boolean);
46
71
  get isNewConversation(): boolean;
@@ -62,6 +87,103 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
62
87
  showArtifactIndicator: boolean;
63
88
  /** Application context snapshot for AI agent awareness. Included in agent execution data. */
64
89
  appContext: Record<string, unknown> | null;
90
+ /**
91
+ * Optional default agent ID for the conversation. Forwarded to
92
+ * `<mj-message-input>` as its `[defaultAgentId]` so the first message
93
+ * routes directly to this agent instead of Sage. See
94
+ * `MessageInputComponent.routeMessage` priority rules — explicit
95
+ * @mention and prior-agent continuity still take precedence.
96
+ *
97
+ * Embedded chat surfaces (Form Builder cockpit, future domain chats)
98
+ * set this to the specialist agent's ID; the main Chat app leaves it
99
+ * unset to preserve the Sage-fronted UX.
100
+ */
101
+ defaultAgentId: string | null;
102
+ /**
103
+ * Scope to apply when this surface CREATES a new conversation. Forwarded
104
+ * to `ConversationEngine.CreateConversation` so the new row's
105
+ * `ApplicationScope` column is stamped correctly. Embedded surfaces
106
+ * (e.g. the Form Builder cockpit) set this to `'Application'` so their
107
+ * conversations don't pollute the main Chat app list. Main Chat leaves
108
+ * it as the default `'Global'`. Has no effect on existing conversations.
109
+ */
110
+ applicationScope: 'Global' | 'Application' | 'Both';
111
+ /**
112
+ * Application ID to bind a newly-created conversation to. REQUIRED when
113
+ * `applicationScope` is 'Application' or 'Both' (DB CHECK constraint
114
+ * enforces it). Used by embedded chat surfaces to scope their
115
+ * conversations to their owning Application.
116
+ */
117
+ applicationId: string | null;
118
+ /**
119
+ * "What is this conversation about?" — the Entity ID this conversation
120
+ * references. Forwarded to `ConversationEngine.CreateConversation` so
121
+ * the new row's `LinkedEntityID` is stamped at creation time. Paired
122
+ * with {@link linkedRecordId} (DB CHECK requires both populated or both
123
+ * null). Form Builder cockpit passes the MJ: Components entity ID;
124
+ * Component Studio's AI panel does the same. Surfaces use this to
125
+ * later list "prior conversations about THIS form/component."
126
+ * Has no effect on existing conversations.
127
+ */
128
+ linkedEntityId: string | null;
129
+ /**
130
+ * Primary key of the linked record, serialized as a string. Used with
131
+ * {@link linkedEntityId}. Form Builder cockpit passes the active
132
+ * form's ComponentID; Component Studio's AI panel passes the
133
+ * currently-selected component's ID.
134
+ */
135
+ linkedRecordId: string | null;
136
+ /**
137
+ * Whether the conversation header should render the per-conversation
138
+ * agent picker. Default true. The picker lets a user pin a default
139
+ * agent on the active conversation (saved to
140
+ * `MJConversationEntity.DefaultAgentID`), so non-mention messages route
141
+ * to that agent instead of through Sage. Surfaces with no meaningful
142
+ * agent-choice UX can set this to false to hide the widget.
143
+ */
144
+ showAgentPicker: boolean;
145
+ /**
146
+ * Whether the chat header should render the per-agent mode/quality
147
+ * picker (Draft / Standard / High, etc.). Default true. The picker
148
+ * auto-hides when the bound agent has fewer than 2 configured
149
+ * presets, so embedders rarely need to set this explicitly — turn
150
+ * off only when the surface should never expose model-tier choice
151
+ * (kiosks, specialty embeds).
152
+ */
153
+ showAgentModePicker: boolean;
154
+ /**
155
+ * The mode/preset picker's selected configuration ID, forwarded to
156
+ * `<mj-message-input>` so non-mention routes apply it on the next
157
+ * send. Past messages are NOT retroactively re-routed — the picker
158
+ * only affects subsequent requests. Updated when the user picks a
159
+ * row in the mode picker; the picker itself persists the choice
160
+ * per-user, per-agent via UserInfoEngine.
161
+ */
162
+ ActiveAgentConfigurationPresetId: string | null;
163
+ /**
164
+ * Agent the mode picker should target. Mirrors the routing precedence
165
+ * minus message-history continuity (the picker is persistent UI; it
166
+ * shouldn't flip as the user scrolls history).
167
+ *
168
+ * Order: conversation-pinned default → embedder default → Sage.
169
+ */
170
+ /**
171
+ * True when the chat header should render even before a conversation
172
+ * row exists. Currently means: the embedder has enabled the mode
173
+ * picker AND we resolved a target agent for it (so there's actually
174
+ * something to put in the header). Lets surfaces like the Form
175
+ * Builder cockpit show the mode picker on top of the empty-state
176
+ * instead of waiting for the first message to create a conversation.
177
+ */
178
+ get HasPreConversationHeader(): boolean;
179
+ get ModePickerTargetAgentId(): string | null;
180
+ /**
181
+ * Mode picker emitted a new selection. Store it; the next message's
182
+ * route picks it up via `<mj-message-input>`'s
183
+ * `[agentConfigurationPresetId]` binding. Past messages stay routed
184
+ * as they were — the change is forward-only.
185
+ */
186
+ OnAgentModePresetChanged(presetId: string | null): void;
65
187
  /** Greeting message shown in the empty state when no conversation is active */
66
188
  emptyStateGreeting: string;
67
189
  showSidebarToggle: boolean;
@@ -190,7 +312,17 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
190
312
  acceptedFileTypes: string;
191
313
  private conversationManagerAgent;
192
314
  private engine;
193
- constructor(agentStateService: AgentStateService, conversationAgentService: ConversationAgentService, activeTasks: ActiveTasksService, cdr: ChangeDetectorRef, mentionAutocompleteService: MentionAutocompleteService, artifactPermissionService: ArtifactPermissionService, attachmentService: ConversationAttachmentService, streamingService: ConversationStreamingService, confirmDialog: ConversationsDialogService, bridge: ConversationBridgeService, analyzeArtifactService: AnalyzeArtifactService, uiCommandHandler: UICommandHandlerService);
315
+ constructor(agentStateService: AgentStateService, conversationAgentService: ConversationAgentService, activeTasks: ActiveTasksService, cdr: ChangeDetectorRef, mentionAutocompleteService: MentionAutocompleteService, artifactPermissionService: ArtifactPermissionService, attachmentService: ConversationAttachmentService, streamingService: ConversationStreamingService, confirmDialog: ConversationsDialogService, bridge: ConversationBridgeService, analyzeArtifactService: AnalyzeArtifactService, uiCommandHandler: UICommandHandlerService, interactiveFormApplyService: InteractiveFormApplyService);
316
+ /**
317
+ * Apply a form-role artifact's spec as an EntityFormOverride for the
318
+ * current user. The service handles the Create-vs-Modify decision (based
319
+ * on whether an Active override already exists), confirms via dialog,
320
+ * and surfaces success/failure via notification.
321
+ */
322
+ OnApplyFormRequested(event: {
323
+ spec: unknown;
324
+ entityName: string;
325
+ }): Promise<void>;
194
326
  ngOnInit(): Promise<void>;
195
327
  /**
196
328
  * Initializes attachment support by checking if the conversation manager agent (Sage)
@@ -620,19 +752,24 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
620
752
  * fetches run and its `getCurrentDataState()` becomes callable via
621
753
  * `callbacks.RegisterMethod('getCurrentDataState', ...)`.
622
754
  *
623
- * `GetCurrentStateSnapshot()` returns two distinct shapes:
624
- * - **Live**: a populated DataSnapshot with `tables[]` containing rows.
755
+ * `GetCurrentStateSnapshot()` returns three distinct shapes:
756
+ * - **Live**: a populated DataSnapshot with `tables[]` whose rows are filled.
625
757
  * - **Fallback**: an empty placeholder with only `title` + `interpretation`
626
758
  * ("No live data was captured — the component either has no data-fetching
627
759
  * hooks or has not yet run its queries"). This fires when the React
628
760
  * component hasn't yet registered `getCurrentDataState()`.
761
+ * - **Schema-only**: a structured snapshot with real `tables`/`columns` and
762
+ * metadata (e.g. `totalAvailableRowCount`) but `rows: []`. This is common
763
+ * for query-backed / server-paged components whose data load hasn't
764
+ * completed (or whose visible page is empty) at the moment of capture.
629
765
  *
630
- * We must NOT accept the fallback while waitingcapturing the placeholder
631
- * defeats the entire point of the snapshot pipeline. Keep polling until we
632
- * either see a real snapshot (has `tables`) or the timeout elapses. Only
633
- * after timeout do we return the last available fallback (any data is
634
- * better than no data, but the user will see the placeholder text in the
635
- * resulting artifact).
766
+ * We must accept ONLY a snapshot that actually carries rows a schema-only
767
+ * or placeholder snapshot defeats the point of the pipeline (the analysis
768
+ * agent receives an empty table). So we key "live" on `rows.length`, not just
769
+ * `tables.length`, and keep polling so async/paged data has time to load.
770
+ * Only after timeout do we return the last available row-less snapshot (any
771
+ * structure is better than nothing, but the user will see an empty table in
772
+ * the resulting artifact).
636
773
  */
637
774
  private waitForViewerSnapshot;
638
775
  /**
@@ -703,6 +840,6 @@ export declare class ConversationChatAreaComponent extends BaseAngularComponent
703
840
  */
704
841
  onIntentCheckCompleted(): void;
705
842
  static ɵfac: i0.ɵɵFactoryDeclaration<ConversationChatAreaComponent, never>;
706
- static ɵcmp: i0.ɵɵComponentDeclaration<ConversationChatAreaComponent, "mj-conversation-chat-area", never, { "environmentId": { "alias": "environmentId"; "required": false; }; "currentUser": { "alias": "currentUser"; "required": false; }; "conversationId": { "alias": "conversationId"; "required": false; }; "conversation": { "alias": "conversation"; "required": false; }; "threadId": { "alias": "threadId"; "required": false; }; "isNewConversation": { "alias": "isNewConversation"; "required": false; }; "pendingMessage": { "alias": "pendingMessage"; "required": false; }; "pendingAttachments": { "alias": "pendingAttachments"; "required": false; }; "pendingArtifactId": { "alias": "pendingArtifactId"; "required": false; }; "pendingArtifactVersionNumber": { "alias": "pendingArtifactVersionNumber"; "required": false; }; "overlayMode": { "alias": "overlayMode"; "required": false; }; "showExportButton": { "alias": "showExportButton"; "required": false; }; "showShareButton": { "alias": "showShareButton"; "required": false; }; "showArtifactIndicator": { "alias": "showArtifactIndicator"; "required": false; }; "appContext": { "alias": "appContext"; "required": false; }; "emptyStateGreeting": { "alias": "emptyStateGreeting"; "required": false; }; "showSidebarToggle": { "alias": "showSidebarToggle"; "required": false; }; }, { "conversationRenamed": "conversationRenamed"; "openEntityRecord": "openEntityRecord"; "navigationRequest": "navigationRequest"; "taskClicked": "taskClicked"; "artifactLinkClicked": "artifactLinkClicked"; "sidebarToggleClicked": "sidebarToggleClicked"; "conversationCreated": "conversationCreated"; "threadOpened": "threadOpened"; "threadClosed": "threadClosed"; "pendingArtifactConsumed": "pendingArtifactConsumed"; "pendingMessageConsumed": "pendingMessageConsumed"; "pendingMessageRequested": "pendingMessageRequested"; }, never, never, false, never>;
843
+ static ɵcmp: i0.ɵɵComponentDeclaration<ConversationChatAreaComponent, "mj-conversation-chat-area", never, { "environmentId": { "alias": "environmentId"; "required": false; }; "currentUser": { "alias": "currentUser"; "required": false; }; "conversationId": { "alias": "conversationId"; "required": false; }; "conversation": { "alias": "conversation"; "required": false; }; "threadId": { "alias": "threadId"; "required": false; }; "suppressNewConversationEmptyState": { "alias": "suppressNewConversationEmptyState"; "required": false; }; "allowMentions": { "alias": "allowMentions"; "required": false; }; "allowAttachments": { "alias": "allowAttachments"; "required": false; }; "isNewConversation": { "alias": "isNewConversation"; "required": false; }; "pendingMessage": { "alias": "pendingMessage"; "required": false; }; "pendingAttachments": { "alias": "pendingAttachments"; "required": false; }; "pendingArtifactId": { "alias": "pendingArtifactId"; "required": false; }; "pendingArtifactVersionNumber": { "alias": "pendingArtifactVersionNumber"; "required": false; }; "overlayMode": { "alias": "overlayMode"; "required": false; }; "showExportButton": { "alias": "showExportButton"; "required": false; }; "showShareButton": { "alias": "showShareButton"; "required": false; }; "showArtifactIndicator": { "alias": "showArtifactIndicator"; "required": false; }; "appContext": { "alias": "appContext"; "required": false; }; "defaultAgentId": { "alias": "defaultAgentId"; "required": false; }; "applicationScope": { "alias": "applicationScope"; "required": false; }; "applicationId": { "alias": "applicationId"; "required": false; }; "linkedEntityId": { "alias": "linkedEntityId"; "required": false; }; "linkedRecordId": { "alias": "linkedRecordId"; "required": false; }; "showAgentPicker": { "alias": "showAgentPicker"; "required": false; }; "showAgentModePicker": { "alias": "showAgentModePicker"; "required": false; }; "emptyStateGreeting": { "alias": "emptyStateGreeting"; "required": false; }; "showSidebarToggle": { "alias": "showSidebarToggle"; "required": false; }; }, { "conversationRenamed": "conversationRenamed"; "openEntityRecord": "openEntityRecord"; "navigationRequest": "navigationRequest"; "taskClicked": "taskClicked"; "artifactLinkClicked": "artifactLinkClicked"; "sidebarToggleClicked": "sidebarToggleClicked"; "conversationCreated": "conversationCreated"; "threadOpened": "threadOpened"; "threadClosed": "threadClosed"; "pendingArtifactConsumed": "pendingArtifactConsumed"; "pendingMessageConsumed": "pendingMessageConsumed"; "pendingMessageRequested": "pendingMessageRequested"; }, never, never, false, never>;
707
844
  }
708
845
  //# sourceMappingURL=conversation-chat-area.component.d.ts.map