@memberjunction/ng-composer 0.0.0 → 5.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -28
- package/dist/lib/components/mention/mention-dropdown.component.d.ts +53 -0
- package/dist/lib/components/mention/mention-dropdown.component.d.ts.map +1 -0
- package/dist/lib/components/mention/mention-dropdown.component.js +297 -0
- package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -0
- package/dist/lib/components/mention/mention-editor.component.d.ts +294 -0
- package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -0
- package/dist/lib/components/mention/mention-editor.component.js +1342 -0
- package/dist/lib/components/mention/mention-editor.component.js.map +1 -0
- package/dist/lib/components/message/message-input-box.component.d.ts +139 -0
- package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -0
- package/dist/lib/components/message/message-input-box.component.js +340 -0
- package/dist/lib/components/message/message-input-box.component.js.map +1 -0
- package/dist/lib/composer-trigger-provider.d.ts +123 -0
- package/dist/lib/composer-trigger-provider.d.ts.map +1 -0
- package/dist/lib/composer-trigger-provider.js +91 -0
- package/dist/lib/composer-trigger-provider.js.map +1 -0
- package/dist/lib/composer.module.d.ts +32 -0
- package/dist/lib/composer.module.d.ts.map +1 -0
- package/dist/lib/composer.module.js +62 -0
- package/dist/lib/composer.module.js.map +1 -0
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +15 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +37 -7
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import * as i1 from "@angular/forms";
|
|
4
|
+
import * as i2 from "../mention/mention-editor.component";
|
|
5
|
+
const _c0 = ["mentionEditor"];
|
|
6
|
+
function MessageInputBoxComponent_Conditional_4_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
7
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
8
|
+
i0.ɵɵelementStart(0, "button", 9);
|
|
9
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Conditional_4_Conditional_1_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.planModeToggle.emit(); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
10
|
+
i0.ɵɵelement(1, "i", 10);
|
|
11
|
+
i0.ɵɵelementEnd();
|
|
12
|
+
} if (rf & 2) {
|
|
13
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
14
|
+
i0.ɵɵclassProp("plan-mode-button-icon--active", ctx_r2.planModeActive);
|
|
15
|
+
i0.ɵɵproperty("disabled", ctx_r2.disabled)("title", ctx_r2.planModeActive ? "Plan Mode ON \u2014 the agent presents a plan for your approval before it acts" : "Plan Mode: have the agent present a plan for your approval before it acts");
|
|
16
|
+
i0.ɵɵattribute("aria-pressed", ctx_r2.planModeActive);
|
|
17
|
+
} }
|
|
18
|
+
function MessageInputBoxComponent_Conditional_4_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
19
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
20
|
+
i0.ɵɵelementStart(0, "button", 11);
|
|
21
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Conditional_4_Conditional_2_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.openFilePicker(); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
22
|
+
i0.ɵɵelement(1, "i", 12);
|
|
23
|
+
i0.ɵɵelementEnd();
|
|
24
|
+
} if (rf & 2) {
|
|
25
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
26
|
+
i0.ɵɵproperty("disabled", ctx_r2.disabled);
|
|
27
|
+
} }
|
|
28
|
+
function MessageInputBoxComponent_Conditional_4_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
29
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
30
|
+
i0.ɵɵelementStart(0, "button", 13);
|
|
31
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Conditional_4_Conditional_3_Template_button_click_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.onRealtimeClick(); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
32
|
+
i0.ɵɵelement(1, "i", 14);
|
|
33
|
+
i0.ɵɵelementEnd();
|
|
34
|
+
i0.ɵɵelementStart(2, "button", 15);
|
|
35
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Conditional_4_Conditional_3_Template_button_click_2_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r2 = i0.ɵɵnextContext(2); ctx_r2.onRealtimeOptionsClick(); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
36
|
+
i0.ɵɵelement(3, "i", 16);
|
|
37
|
+
i0.ɵɵelementEnd();
|
|
38
|
+
} if (rf & 2) {
|
|
39
|
+
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
40
|
+
i0.ɵɵclassProp("voice-button-icon--active", ctx_r2.voiceActive);
|
|
41
|
+
i0.ɵɵproperty("disabled", !ctx_r2.canStartRealtime)("title", ctx_r2.voiceActive ? "Voice call active" : "Start a voice call");
|
|
42
|
+
i0.ɵɵadvance(2);
|
|
43
|
+
i0.ɵɵproperty("disabled", !ctx_r2.canStartRealtime);
|
|
44
|
+
} }
|
|
45
|
+
function MessageInputBoxComponent_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
46
|
+
i0.ɵɵelementStart(0, "div", 4);
|
|
47
|
+
i0.ɵɵconditionalCreate(1, MessageInputBoxComponent_Conditional_4_Conditional_1_Template, 2, 5, "button", 7);
|
|
48
|
+
i0.ɵɵconditionalCreate(2, MessageInputBoxComponent_Conditional_4_Conditional_2_Template, 2, 1, "button", 8);
|
|
49
|
+
i0.ɵɵconditionalCreate(3, MessageInputBoxComponent_Conditional_4_Conditional_3_Template, 4, 5);
|
|
50
|
+
i0.ɵɵelementEnd();
|
|
51
|
+
} if (rf & 2) {
|
|
52
|
+
const ctx_r2 = i0.ɵɵnextContext();
|
|
53
|
+
i0.ɵɵadvance();
|
|
54
|
+
i0.ɵɵconditional(ctx_r2.enablePlanMode ? 1 : -1);
|
|
55
|
+
i0.ɵɵadvance();
|
|
56
|
+
i0.ɵɵconditional(ctx_r2.enableAttachments ? 2 : -1);
|
|
57
|
+
i0.ɵɵadvance();
|
|
58
|
+
i0.ɵɵconditional(ctx_r2.enableRealtime ? 3 : -1);
|
|
59
|
+
} }
|
|
60
|
+
/**
|
|
61
|
+
* Reusable message input box component (presentational)
|
|
62
|
+
* Now uses MentionEditorComponent for rich @mention functionality with chips
|
|
63
|
+
*
|
|
64
|
+
* Handles:
|
|
65
|
+
* - Text input with keyboard shortcuts via MentionEditorComponent
|
|
66
|
+
* - @mention autocomplete with visual chips (contentEditable)
|
|
67
|
+
* - Send button
|
|
68
|
+
*
|
|
69
|
+
* Does NOT handle:
|
|
70
|
+
* - Saving messages to database
|
|
71
|
+
* - Agent invocation
|
|
72
|
+
* - Artifact creation
|
|
73
|
+
* - Conversation management
|
|
74
|
+
*/
|
|
75
|
+
export class MessageInputBoxComponent {
|
|
76
|
+
mentionEditor;
|
|
77
|
+
placeholder = 'Type your message to start a new conversation...';
|
|
78
|
+
disabled = false;
|
|
79
|
+
value = '';
|
|
80
|
+
showCharacterCount = false;
|
|
81
|
+
/** Master switch for all mention/command triggers (pass-through to the mention editor). */
|
|
82
|
+
enableMentions = true;
|
|
83
|
+
/** Explicit trigger-provider list (pass-through to the mention editor; explicit list wins over discovery). */
|
|
84
|
+
TriggerProviders = null;
|
|
85
|
+
/** Discovery-mode filter: provider Keys to skip (pass-through to the mention editor). */
|
|
86
|
+
ExcludedTriggerKeys = [];
|
|
87
|
+
/** Optional metadata provider scoping this composer (pass-through to the mention editor). */
|
|
88
|
+
Provider = null;
|
|
89
|
+
currentUser;
|
|
90
|
+
rows = 3;
|
|
91
|
+
// Attachment settings
|
|
92
|
+
enableAttachments = true;
|
|
93
|
+
maxAttachments = 10;
|
|
94
|
+
maxAttachmentSizeBytes = 20 * 1024 * 1024; // 20MB
|
|
95
|
+
acceptedFileTypes = 'image/*';
|
|
96
|
+
/** Shows the in-composer mic button when true. */
|
|
97
|
+
enableRealtime = false;
|
|
98
|
+
/** Whether a realtime voice session is currently active (mic renders in its active state). */
|
|
99
|
+
voiceActive = false;
|
|
100
|
+
/** Whether a voice session can be started right now (mic disabled when false). */
|
|
101
|
+
canStartRealtime = true;
|
|
102
|
+
/** Shows the in-composer Plan Mode toggle button when true. */
|
|
103
|
+
enablePlanMode = false;
|
|
104
|
+
/** Current Plan Mode toggle state (renders the button in its active state). */
|
|
105
|
+
planModeActive = false;
|
|
106
|
+
textSubmitted = new EventEmitter();
|
|
107
|
+
valueChange = new EventEmitter();
|
|
108
|
+
attachmentsChanged = new EventEmitter();
|
|
109
|
+
attachmentError = new EventEmitter();
|
|
110
|
+
attachmentClicked = new EventEmitter();
|
|
111
|
+
/** Emitted when the user clicks the mic button to start/stop a voice session. */
|
|
112
|
+
voiceRequested = new EventEmitter();
|
|
113
|
+
/**
|
|
114
|
+
* Emitted when the user clicks the small caret next to the phone button — the host opens the
|
|
115
|
+
* voice agent/model picker so call options (which agent, which voice model) stay reachable
|
|
116
|
+
* without adding friction to the plain phone click's instant-start path.
|
|
117
|
+
*/
|
|
118
|
+
voiceOptionsRequested = new EventEmitter();
|
|
119
|
+
/** Emitted when the user clicks the in-composer Plan Mode toggle button. */
|
|
120
|
+
planModeToggle = new EventEmitter();
|
|
121
|
+
onRealtimeClick() {
|
|
122
|
+
this.voiceRequested.emit();
|
|
123
|
+
}
|
|
124
|
+
onRealtimeOptionsClick() {
|
|
125
|
+
this.voiceOptionsRequested.emit();
|
|
126
|
+
}
|
|
127
|
+
get canSend() {
|
|
128
|
+
const hasText = this.value.trim().length > 0;
|
|
129
|
+
const hasAttachments = this.mentionEditor?.hasAttachments() || false;
|
|
130
|
+
return !this.disabled && (hasText || hasAttachments);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handle value changes from MentionEditorComponent
|
|
134
|
+
*/
|
|
135
|
+
onValueChange(newValue) {
|
|
136
|
+
this.value = newValue;
|
|
137
|
+
this.valueChange.emit(this.value);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Handle attachment changes from MentionEditorComponent
|
|
141
|
+
*/
|
|
142
|
+
onAttachmentsChanged(attachments) {
|
|
143
|
+
this.attachmentsChanged.emit(attachments);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Handle attachment errors from MentionEditorComponent
|
|
147
|
+
*/
|
|
148
|
+
onAttachmentError(error) {
|
|
149
|
+
this.attachmentError.emit(error);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Handle attachment click from MentionEditorComponent
|
|
153
|
+
*/
|
|
154
|
+
onAttachmentClicked(attachment) {
|
|
155
|
+
this.attachmentClicked.emit(attachment);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Handle Enter key from MentionEditorComponent
|
|
159
|
+
* Extracts plain text with JSON-encoded mentions for message submission
|
|
160
|
+
*/
|
|
161
|
+
onEnterPressed(_text) {
|
|
162
|
+
this.onSendClick();
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Handle mention selection from MentionEditorComponent
|
|
166
|
+
*/
|
|
167
|
+
onMentionSelected(suggestion) {
|
|
168
|
+
// MentionEditorComponent already inserts the mention chip
|
|
169
|
+
// This is just for additional tracking/analytics if needed
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Send the message
|
|
173
|
+
* Extracts plain text with JSON-encoded mentions for proper persistence
|
|
174
|
+
*/
|
|
175
|
+
onSendClick() {
|
|
176
|
+
if (this.canSend) {
|
|
177
|
+
// Get plain text with JSON-encoded mentions (preserves configuration info)
|
|
178
|
+
const textToSend = this.mentionEditor?.getPlainTextWithJsonMentions() || this.value.trim();
|
|
179
|
+
this.textSubmitted.emit(textToSend);
|
|
180
|
+
this.value = ''; // Clear input after sending
|
|
181
|
+
// Clear the editor content
|
|
182
|
+
if (this.mentionEditor) {
|
|
183
|
+
this.mentionEditor.clear();
|
|
184
|
+
}
|
|
185
|
+
this.valueChange.emit(this.value);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Handle clicks on the container - focus the mention editor
|
|
190
|
+
* Only moves cursor to end if clicking outside the contentEditable area
|
|
191
|
+
*/
|
|
192
|
+
onContainerClick(event) {
|
|
193
|
+
const target = event.target;
|
|
194
|
+
// Don't handle clicks on the send button
|
|
195
|
+
if (target.closest('.send-button-icon')) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const editor = this.mentionEditor?.editorRef?.nativeElement;
|
|
199
|
+
if (!editor)
|
|
200
|
+
return;
|
|
201
|
+
// If clicking directly on the editor or its children, let the browser handle cursor placement
|
|
202
|
+
if (target === editor || editor.contains(target)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// Only if clicking on the container (empty space), focus and move cursor to end
|
|
206
|
+
editor.focus();
|
|
207
|
+
const selection = window.getSelection();
|
|
208
|
+
const range = document.createRange();
|
|
209
|
+
if (selection) {
|
|
210
|
+
range.selectNodeContents(editor);
|
|
211
|
+
range.collapse(false); // Collapse to end
|
|
212
|
+
selection.removeAllRanges();
|
|
213
|
+
selection.addRange(range);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Public method to focus the input programmatically
|
|
218
|
+
*/
|
|
219
|
+
focus() {
|
|
220
|
+
const editor = this.mentionEditor?.editorRef?.nativeElement;
|
|
221
|
+
if (editor) {
|
|
222
|
+
editor.focus();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get mention chip data including configuration presets
|
|
227
|
+
*/
|
|
228
|
+
getMentionChipsData() {
|
|
229
|
+
return this.mentionEditor?.getMentionChipsData() || [];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Get pending attachments from the editor
|
|
233
|
+
*/
|
|
234
|
+
getPendingAttachments() {
|
|
235
|
+
return this.mentionEditor?.getPendingAttachments() || [];
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Open file picker programmatically
|
|
239
|
+
*/
|
|
240
|
+
openFilePicker() {
|
|
241
|
+
this.mentionEditor?.openFilePicker();
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Add an artifact as a pending attachment (called by parent after artifact selection)
|
|
245
|
+
*/
|
|
246
|
+
AddArtifactAttachment(artifact) {
|
|
247
|
+
return this.mentionEditor?.AddArtifactAttachment(artifact);
|
|
248
|
+
}
|
|
249
|
+
static ɵfac = function MessageInputBoxComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MessageInputBoxComponent)(); };
|
|
250
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MessageInputBoxComponent, selectors: [["mj-message-input-box"]], viewQuery: function MessageInputBoxComponent_Query(rf, ctx) { if (rf & 1) {
|
|
251
|
+
i0.ɵɵviewQuery(_c0, 5);
|
|
252
|
+
} if (rf & 2) {
|
|
253
|
+
let _t;
|
|
254
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.mentionEditor = _t.first);
|
|
255
|
+
} }, inputs: { placeholder: "placeholder", disabled: "disabled", value: "value", showCharacterCount: "showCharacterCount", enableMentions: "enableMentions", TriggerProviders: "TriggerProviders", ExcludedTriggerKeys: "ExcludedTriggerKeys", Provider: "Provider", currentUser: "currentUser", rows: "rows", enableAttachments: "enableAttachments", maxAttachments: "maxAttachments", maxAttachmentSizeBytes: "maxAttachmentSizeBytes", acceptedFileTypes: "acceptedFileTypes", enableRealtime: "enableRealtime", voiceActive: "voiceActive", canStartRealtime: "canStartRealtime", enablePlanMode: "enablePlanMode", planModeActive: "planModeActive" }, outputs: { textSubmitted: "textSubmitted", valueChange: "valueChange", attachmentsChanged: "attachmentsChanged", attachmentError: "attachmentError", attachmentClicked: "attachmentClicked", voiceRequested: "voiceRequested", voiceOptionsRequested: "voiceOptionsRequested", planModeToggle: "planModeToggle" }, standalone: false, decls: 7, vars: 14, consts: [["mentionEditor", ""], [1, "message-input-box-container", 3, "click"], [1, "input-wrapper"], [3, "ngModelChange", "valueChange", "enterPressed", "mentionSelected", "attachmentsChanged", "attachmentError", "attachmentClicked", "placeholder", "disabled", "currentUser", "enableMentions", "TriggerProviders", "ExcludedTriggerKeys", "Provider", "enableAttachments", "maxAttachments", "maxAttachmentSizeBytes", "acceptedFileTypes", "ngModel"], [1, "attach-buttons"], ["title", "Send message (Enter)", 1, "send-button-icon", 3, "click", "disabled"], [1, "fa-solid", "fa-paper-plane"], ["aria-label", "Toggle Plan Mode", 1, "attach-button-icon", "plan-mode-button-icon", 3, "plan-mode-button-icon--active", "disabled", "title"], ["title", "Attach file", 1, "attach-button-icon", 3, "disabled"], ["aria-label", "Toggle Plan Mode", 1, "attach-button-icon", "plan-mode-button-icon", 3, "click", "disabled", "title"], ["aria-hidden", "true", 1, "fa-solid", "fa-route"], ["title", "Attach file", 1, "attach-button-icon", 3, "click", "disabled"], [1, "fa-solid", "fa-paperclip"], ["aria-label", "Start a voice call", 1, "attach-button-icon", "voice-button-icon", 3, "click", "disabled", "title"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone"], ["title", "Voice call options (choose agent and voice model)", "aria-label", "Voice call options", 1, "attach-button-icon", "voice-options-button-icon", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-chevron-down"]], template: function MessageInputBoxComponent_Template(rf, ctx) { if (rf & 1) {
|
|
256
|
+
const _r1 = i0.ɵɵgetCurrentView();
|
|
257
|
+
i0.ɵɵelementStart(0, "div", 1);
|
|
258
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onContainerClick($event)); });
|
|
259
|
+
i0.ɵɵelementStart(1, "div", 2)(2, "mj-mention-editor", 3, 0);
|
|
260
|
+
i0.ɵɵtwoWayListener("ngModelChange", function MessageInputBoxComponent_Template_mj_mention_editor_ngModelChange_2_listener($event) { i0.ɵɵrestoreView(_r1); i0.ɵɵtwoWayBindingSet(ctx.value, $event) || (ctx.value = $event); return i0.ɵɵresetView($event); });
|
|
261
|
+
i0.ɵɵlistener("valueChange", function MessageInputBoxComponent_Template_mj_mention_editor_valueChange_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onValueChange($event)); })("enterPressed", function MessageInputBoxComponent_Template_mj_mention_editor_enterPressed_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onEnterPressed($event)); })("mentionSelected", function MessageInputBoxComponent_Template_mj_mention_editor_mentionSelected_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onMentionSelected($event)); })("attachmentsChanged", function MessageInputBoxComponent_Template_mj_mention_editor_attachmentsChanged_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onAttachmentsChanged($event)); })("attachmentError", function MessageInputBoxComponent_Template_mj_mention_editor_attachmentError_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onAttachmentError($event)); })("attachmentClicked", function MessageInputBoxComponent_Template_mj_mention_editor_attachmentClicked_2_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onAttachmentClicked($event)); });
|
|
262
|
+
i0.ɵɵelementEnd();
|
|
263
|
+
i0.ɵɵconditionalCreate(4, MessageInputBoxComponent_Conditional_4_Template, 4, 3, "div", 4);
|
|
264
|
+
i0.ɵɵelementStart(5, "button", 5);
|
|
265
|
+
i0.ɵɵlistener("click", function MessageInputBoxComponent_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.onSendClick()); });
|
|
266
|
+
i0.ɵɵelement(6, "i", 6);
|
|
267
|
+
i0.ɵɵelementEnd()()();
|
|
268
|
+
} if (rf & 2) {
|
|
269
|
+
i0.ɵɵadvance(2);
|
|
270
|
+
i0.ɵɵproperty("placeholder", ctx.placeholder)("disabled", ctx.disabled)("currentUser", ctx.currentUser)("enableMentions", ctx.enableMentions)("TriggerProviders", ctx.TriggerProviders)("ExcludedTriggerKeys", ctx.ExcludedTriggerKeys)("Provider", ctx.Provider)("enableAttachments", ctx.enableAttachments)("maxAttachments", ctx.maxAttachments)("maxAttachmentSizeBytes", ctx.maxAttachmentSizeBytes)("acceptedFileTypes", ctx.acceptedFileTypes);
|
|
271
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx.value);
|
|
272
|
+
i0.ɵɵadvance(2);
|
|
273
|
+
i0.ɵɵconditional(ctx.enableAttachments || ctx.enableRealtime || ctx.enablePlanMode ? 4 : -1);
|
|
274
|
+
i0.ɵɵadvance();
|
|
275
|
+
i0.ɵɵproperty("disabled", !ctx.canSend);
|
|
276
|
+
} }, dependencies: [i1.NgControlStatus, i1.NgModel, i2.MentionEditorComponent], styles: [".message-input-box-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: var(--mj-shadow-sm);\n transition: all 0.2s ease;\n overflow: visible;\n position: relative;\n cursor: text;\n}\n.message-input-box-container[_ngcontent-%COMP%]:focus-within {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.input-wrapper[_ngcontent-%COMP%] {\n position: relative;\n display: flex;\n align-items: flex-end;\n padding: 5px;\n}\n.input-wrapper[_ngcontent-%COMP%] mj-mention-editor[_ngcontent-%COMP%] {\n flex: 1;\n display: block;\n}\n\n.message-input-box-textarea[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 100px;\n padding: 1rem;\n padding-right: 3.5rem;\n border: 0 !important;\n outline: 0 !important;\n box-shadow: none !important;\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n font-family: inherit;\n font-size: 1rem;\n line-height: 1.5;\n resize: vertical;\n background: transparent;\n color: var(--mj-text-primary);\n border: 0 !important;\n}\n.message-input-box-textarea[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n}\n.message-input-box-textarea[_ngcontent-%COMP%]:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n background: var(--mj-bg-surface-sunken);\n}\n.message-input-box-textarea[_ngcontent-%COMP%]:focus, .message-input-box-textarea[_ngcontent-%COMP%]:active, .message-input-box-textarea[_ngcontent-%COMP%]:focus-visible {\n outline: 0 !important;\n box-shadow: none !important;\n}\n\n.send-button-icon[_ngcontent-%COMP%] {\n position: absolute;\n bottom: 0.75rem;\n right: 0.75rem;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n.send-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n.send-button-icon[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n transform: scale(1.05);\n}\n.send-button-icon[_ngcontent-%COMP%]:active:not(:disabled) {\n transform: scale(0.95);\n}\n.send-button-icon[_ngcontent-%COMP%]:disabled {\n background: var(--mj-border-default);\n color: var(--mj-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n\n\n.attach-buttons[_ngcontent-%COMP%] {\n position: absolute;\n bottom: 0.75rem;\n right: 3rem;\n display: flex;\n gap: 2px;\n}\n.attach-button-icon[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n color: var(--mj-text-secondary);\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n.attach-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 1rem;\n}\n.attach-button-icon[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n color: var(--mj-brand-primary);\n transform: scale(1.05);\n}\n.attach-button-icon[_ngcontent-%COMP%]:active:not(:disabled) {\n transform: scale(0.95);\n}\n.attach-button-icon[_ngcontent-%COMP%]:disabled {\n color: var(--mj-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n\n\n\n.plan-mode-button-icon--active[_ngcontent-%COMP%]:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n.plan-mode-button-icon--active[_ngcontent-%COMP%]:not(:disabled):hover {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n color: var(--mj-text-inverse);\n}\n\n\n\n@media (max-width: 768px) {\n .message-input-box-container[_ngcontent-%COMP%] {\n border-radius: 10px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 6%, transparent);\n }\n .message-input-box-textarea[_ngcontent-%COMP%] {\n min-height: 80px;\n padding: 0.75rem;\n padding-right: 3rem;\n font-size: 16px;\n line-height: 1.4;\n }\n .send-button-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n bottom: 0.625rem;\n right: 0.625rem;\n border-radius: 6px;\n }\n .send-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n .attach-buttons[_ngcontent-%COMP%] {\n bottom: 0.625rem;\n right: 2.75rem;\n }\n .attach-button-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n }\n .attach-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n}\n\n\n@media (max-width: 480px) {\n .message-input-box-container[_ngcontent-%COMP%] {\n border-radius: 8px;\n border-width: 1.5px;\n }\n .message-input-box-textarea[_ngcontent-%COMP%] {\n min-height: 60px;\n padding: 0.625rem;\n padding-right: 2.75rem;\n font-size: 16px;\n line-height: 1.3;\n }\n .send-button-icon[_ngcontent-%COMP%] {\n width: 36px;\n height: 36px;\n bottom: 0.5rem;\n right: 0.5rem;\n border-radius: 6px;\n }\n .send-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n .attach-buttons[_ngcontent-%COMP%] {\n bottom: 0.5rem;\n right: 2.75rem;\n }\n .attach-button-icon[_ngcontent-%COMP%] {\n width: 32px;\n height: 32px;\n }\n .attach-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.875rem;\n }\n}\n\n\n\n\n\n\n.voice-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.voice-button-icon[_ngcontent-%COMP%]:hover:not(:disabled) i[_ngcontent-%COMP%] {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--mj-brand-primary) 55%, transparent));\n}\n.voice-button-icon[_ngcontent-%COMP%]:disabled i[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n}\n.voice-button-icon--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n.voice-button-icon--active[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n\n\n\n\n\n.voice-options-button-icon[_ngcontent-%COMP%] {\n width: 20px;\n margin-left: -4px;\n}\n.voice-options-button-icon[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n.voice-options-button-icon[_ngcontent-%COMP%]:hover:not(:disabled) i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n}\n.voice-options-button-icon[_ngcontent-%COMP%]:disabled i[_ngcontent-%COMP%] {\n color: var(--mj-text-disabled);\n}"] });
|
|
277
|
+
}
|
|
278
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MessageInputBoxComponent, [{
|
|
279
|
+
type: Component,
|
|
280
|
+
args: [{ standalone: false, selector: 'mj-message-input-box', template: "<div class=\"message-input-box-container\" (click)=\"onContainerClick($event)\">\n <div class=\"input-wrapper\">\n <mj-mention-editor\n #mentionEditor\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled\"\n [currentUser]=\"currentUser\"\n [enableMentions]=\"enableMentions\"\n [TriggerProviders]=\"TriggerProviders\"\n [ExcludedTriggerKeys]=\"ExcludedTriggerKeys\"\n [Provider]=\"Provider\"\n [enableAttachments]=\"enableAttachments\"\n [maxAttachments]=\"maxAttachments\"\n [maxAttachmentSizeBytes]=\"maxAttachmentSizeBytes\"\n [acceptedFileTypes]=\"acceptedFileTypes\"\n [(ngModel)]=\"value\"\n (valueChange)=\"onValueChange($event)\"\n (enterPressed)=\"onEnterPressed($event)\"\n (mentionSelected)=\"onMentionSelected($event)\"\n (attachmentsChanged)=\"onAttachmentsChanged($event)\"\n (attachmentError)=\"onAttachmentError($event)\"\n (attachmentClicked)=\"onAttachmentClicked($event)\">\n </mj-mention-editor>\n\n <!-- Attach + voice + plan-mode buttons - Positioned over editor -->\n @if (enableAttachments || enableRealtime || enablePlanMode) {\n <div class=\"attach-buttons\">\n @if (enablePlanMode) {\n <button\n class=\"attach-button-icon plan-mode-button-icon\"\n [class.plan-mode-button-icon--active]=\"planModeActive\"\n [disabled]=\"disabled\"\n [attr.aria-pressed]=\"planModeActive\"\n (click)=\"planModeToggle.emit(); $event.stopPropagation()\"\n [title]=\"planModeActive ? 'Plan Mode ON \u2014 the agent presents a plan for your approval before it acts' : 'Plan Mode: have the agent present a plan for your approval before it acts'\"\n aria-label=\"Toggle Plan Mode\"\n >\n <i class=\"fa-solid fa-route\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (enableAttachments) {\n <button\n class=\"attach-button-icon\"\n [disabled]=\"disabled\"\n (click)=\"openFilePicker(); $event.stopPropagation()\"\n title=\"Attach file\"\n >\n <i class=\"fa-solid fa-paperclip\"></i>\n </button>\n }\n @if (enableRealtime) {\n <button\n class=\"attach-button-icon voice-button-icon\"\n [class.voice-button-icon--active]=\"voiceActive\"\n [disabled]=\"!canStartRealtime\"\n (click)=\"onRealtimeClick(); $event.stopPropagation()\"\n [title]=\"voiceActive ? 'Voice call active' : 'Start a voice call'\"\n aria-label=\"Start a voice call\"\n >\n <i class=\"fa-solid fa-phone\" aria-hidden=\"true\"></i>\n </button>\n <!-- Caret beside the phone: opens the agent/model picker so call OPTIONS stay one\n click away while the plain phone click keeps its instant-start behavior. -->\n <button\n class=\"attach-button-icon voice-options-button-icon\"\n [disabled]=\"!canStartRealtime\"\n (click)=\"onRealtimeOptionsClick(); $event.stopPropagation()\"\n title=\"Voice call options (choose agent and voice model)\"\n aria-label=\"Voice call options\"\n >\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Send Button (Icon Only) - Positioned over editor -->\n <button\n class=\"send-button-icon\"\n [disabled]=\"!canSend\"\n (click)=\"onSendClick()\"\n title=\"Send message (Enter)\"\n >\n <i class=\"fa-solid fa-paper-plane\"></i>\n </button>\n </div>\n</div>\n", styles: [".message-input-box-container {\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface-card);\n border: 2px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: var(--mj-shadow-sm);\n transition: all 0.2s ease;\n overflow: visible;\n position: relative;\n cursor: text;\n}\n.message-input-box-container:focus-within {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 4px 16px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.input-wrapper {\n position: relative;\n display: flex;\n align-items: flex-end;\n padding: 5px;\n}\n.input-wrapper mj-mention-editor {\n flex: 1;\n display: block;\n}\n\n.message-input-box-textarea {\n flex: 1;\n min-height: 100px;\n padding: 1rem;\n padding-right: 3.5rem;\n border: 0 !important;\n outline: 0 !important;\n box-shadow: none !important;\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n font-family: inherit;\n font-size: 1rem;\n line-height: 1.5;\n resize: vertical;\n background: transparent;\n color: var(--mj-text-primary);\n border: 0 !important;\n}\n.message-input-box-textarea::placeholder {\n color: var(--mj-text-muted);\n}\n.message-input-box-textarea:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n background: var(--mj-bg-surface-sunken);\n}\n.message-input-box-textarea:focus, .message-input-box-textarea:active, .message-input-box-textarea:focus-visible {\n outline: 0 !important;\n box-shadow: none !important;\n}\n\n.send-button-icon {\n position: absolute;\n bottom: 0.75rem;\n right: 0.75rem;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n.send-button-icon i {\n font-size: 1rem;\n}\n.send-button-icon:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover);\n transform: scale(1.05);\n}\n.send-button-icon:active:not(:disabled) {\n transform: scale(0.95);\n}\n.send-button-icon:disabled {\n background: var(--mj-border-default);\n color: var(--mj-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* Attach buttons container */\n.attach-buttons {\n position: absolute;\n bottom: 0.75rem;\n right: 3rem;\n display: flex;\n gap: 2px;\n}\n.attach-button-icon {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n color: var(--mj-text-secondary);\n border: none;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n.attach-button-icon i {\n font-size: 1rem;\n}\n.attach-button-icon:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n color: var(--mj-brand-primary);\n transform: scale(1.05);\n}\n.attach-button-icon:active:not(:disabled) {\n transform: scale(0.95);\n}\n.attach-button-icon:disabled {\n color: var(--mj-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n/* Plan Mode toggle \u2014 inherits .attach-button-icon; when active it fills with the brand color\n so the on/off state reads clearly at a glance (parallels the voice button's active state). */\n.plan-mode-button-icon--active:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n.plan-mode-button-icon--active:not(:disabled):hover {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n color: var(--mj-text-inverse);\n}\n\n/* Mobile adjustments: 481px - 768px */\n@media (max-width: 768px) {\n .message-input-box-container {\n border-radius: 10px;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-text-primary) 6%, transparent);\n }\n .message-input-box-textarea {\n min-height: 80px;\n padding: 0.75rem;\n padding-right: 3rem;\n font-size: 16px;\n line-height: 1.4;\n }\n .send-button-icon {\n width: 32px;\n height: 32px;\n bottom: 0.625rem;\n right: 0.625rem;\n border-radius: 6px;\n }\n .send-button-icon i {\n font-size: 0.875rem;\n }\n .attach-buttons {\n bottom: 0.625rem;\n right: 2.75rem;\n }\n .attach-button-icon {\n width: 32px;\n height: 32px;\n }\n .attach-button-icon i {\n font-size: 0.875rem;\n }\n}\n/* Small Phone adjustments: <= 480px */\n@media (max-width: 480px) {\n .message-input-box-container {\n border-radius: 8px;\n border-width: 1.5px;\n }\n .message-input-box-textarea {\n min-height: 60px;\n padding: 0.625rem;\n padding-right: 2.75rem;\n font-size: 16px;\n line-height: 1.3;\n }\n .send-button-icon {\n width: 36px;\n height: 36px;\n bottom: 0.5rem;\n right: 0.5rem;\n border-radius: 6px;\n }\n .send-button-icon i {\n font-size: 0.875rem;\n }\n .attach-buttons {\n bottom: 0.5rem;\n right: 2.75rem;\n }\n .attach-button-icon {\n width: 32px;\n height: 32px;\n }\n .attach-button-icon i {\n font-size: 0.875rem;\n }\n}\n\n/* Voice call button \u2014 a phone icon in the attach-buttons cluster, left of send.\n Inherits .attach-button-icon base. Subtle brand glow on hover; active state (call\n live) renders a brand-color tint. The orb visual now lives ONLY inside the call\n overlay's banner. */\n.voice-button-icon i {\n color: var(--mj-brand-primary);\n}\n.voice-button-icon:hover:not(:disabled) i {\n filter: drop-shadow(0 0 4px color-mix(in srgb, var(--mj-brand-primary) 55%, transparent));\n}\n.voice-button-icon:disabled i {\n color: var(--mj-text-disabled);\n}\n.voice-button-icon--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n.voice-button-icon--active:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n\n/* Voice OPTIONS caret \u2014 a slim chevron hugging the phone button. Opens the agent/model\n picker; intentionally quieter (smaller icon, narrower hit area) than the phone so the\n common instant-start click stays the visual primary. */\n.voice-options-button-icon {\n width: 20px;\n margin-left: -4px;\n}\n.voice-options-button-icon i {\n font-size: 0.7rem;\n color: var(--mj-text-muted);\n}\n.voice-options-button-icon:hover:not(:disabled) i {\n color: var(--mj-brand-primary);\n}\n.voice-options-button-icon:disabled i {\n color: var(--mj-text-disabled);\n}\n"] }]
|
|
281
|
+
}], null, { mentionEditor: [{
|
|
282
|
+
type: ViewChild,
|
|
283
|
+
args: ['mentionEditor']
|
|
284
|
+
}], placeholder: [{
|
|
285
|
+
type: Input
|
|
286
|
+
}], disabled: [{
|
|
287
|
+
type: Input
|
|
288
|
+
}], value: [{
|
|
289
|
+
type: Input
|
|
290
|
+
}], showCharacterCount: [{
|
|
291
|
+
type: Input
|
|
292
|
+
}], enableMentions: [{
|
|
293
|
+
type: Input
|
|
294
|
+
}], TriggerProviders: [{
|
|
295
|
+
type: Input
|
|
296
|
+
}], ExcludedTriggerKeys: [{
|
|
297
|
+
type: Input
|
|
298
|
+
}], Provider: [{
|
|
299
|
+
type: Input
|
|
300
|
+
}], currentUser: [{
|
|
301
|
+
type: Input
|
|
302
|
+
}], rows: [{
|
|
303
|
+
type: Input
|
|
304
|
+
}], enableAttachments: [{
|
|
305
|
+
type: Input
|
|
306
|
+
}], maxAttachments: [{
|
|
307
|
+
type: Input
|
|
308
|
+
}], maxAttachmentSizeBytes: [{
|
|
309
|
+
type: Input
|
|
310
|
+
}], acceptedFileTypes: [{
|
|
311
|
+
type: Input
|
|
312
|
+
}], enableRealtime: [{
|
|
313
|
+
type: Input
|
|
314
|
+
}], voiceActive: [{
|
|
315
|
+
type: Input
|
|
316
|
+
}], canStartRealtime: [{
|
|
317
|
+
type: Input
|
|
318
|
+
}], enablePlanMode: [{
|
|
319
|
+
type: Input
|
|
320
|
+
}], planModeActive: [{
|
|
321
|
+
type: Input
|
|
322
|
+
}], textSubmitted: [{
|
|
323
|
+
type: Output
|
|
324
|
+
}], valueChange: [{
|
|
325
|
+
type: Output
|
|
326
|
+
}], attachmentsChanged: [{
|
|
327
|
+
type: Output
|
|
328
|
+
}], attachmentError: [{
|
|
329
|
+
type: Output
|
|
330
|
+
}], attachmentClicked: [{
|
|
331
|
+
type: Output
|
|
332
|
+
}], voiceRequested: [{
|
|
333
|
+
type: Output
|
|
334
|
+
}], voiceOptionsRequested: [{
|
|
335
|
+
type: Output
|
|
336
|
+
}], planModeToggle: [{
|
|
337
|
+
type: Output
|
|
338
|
+
}] }); })();
|
|
339
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MessageInputBoxComponent, { className: "MessageInputBoxComponent", filePath: "src/lib/components/message/message-input-box.component.ts", lineNumber: 27 }); })();
|
|
340
|
+
//# sourceMappingURL=message-input-box.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-input-box.component.js","sourceRoot":"","sources":["../../../../src/lib/components/message/message-input-box.component.ts","../../../../src/lib/components/message/message-input-box.component.html"],"names":[],"mappings":"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","sourcesContent":["import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';\nimport { IMetadataProvider, UserInfo } from '@memberjunction/core';\nimport { ComposerTriggerProvider, MentionSuggestion } from '../../composer-trigger-provider';\nimport { MentionEditorComponent, PendingAttachment } from '../mention/mention-editor.component';\n\n/**\n * Reusable message input box component (presentational)\n * Now uses MentionEditorComponent for rich @mention functionality with chips\n *\n * Handles:\n * - Text input with keyboard shortcuts via MentionEditorComponent\n * - @mention autocomplete with visual chips (contentEditable)\n * - Send button\n *\n * Does NOT handle:\n * - Saving messages to database\n * - Agent invocation\n * - Artifact creation\n * - Conversation management\n */\n@Component({\n standalone: false,\n selector: 'mj-message-input-box',\n templateUrl: './message-input-box.component.html',\n styleUrls: ['./message-input-box.component.css']\n})\nexport class MessageInputBoxComponent {\n @ViewChild('mentionEditor') mentionEditor?: MentionEditorComponent;\n\n @Input() placeholder: string = 'Type your message to start a new conversation...';\n @Input() disabled: boolean = false;\n @Input() value: string = '';\n @Input() showCharacterCount: boolean = false;\n /** Master switch for all mention/command triggers (pass-through to the mention editor). */\n @Input() enableMentions: boolean = true;\n /** Explicit trigger-provider list (pass-through to the mention editor; explicit list wins over discovery). */\n @Input() TriggerProviders: ComposerTriggerProvider[] | null = null;\n /** Discovery-mode filter: provider Keys to skip (pass-through to the mention editor). */\n @Input() ExcludedTriggerKeys: string[] = [];\n /** Optional metadata provider scoping this composer (pass-through to the mention editor). */\n @Input() Provider: IMetadataProvider | null = null;\n @Input() currentUser?: UserInfo;\n @Input() rows: number = 3;\n\n // Attachment settings\n @Input() enableAttachments: boolean = true;\n @Input() maxAttachments: number = 10;\n @Input() maxAttachmentSizeBytes: number = 20 * 1024 * 1024; // 20MB\n @Input() acceptedFileTypes: string = 'image/*';\n\n /** Shows the in-composer mic button when true. */\n @Input() enableRealtime: boolean = false;\n /** Whether a realtime voice session is currently active (mic renders in its active state). */\n @Input() voiceActive: boolean = false;\n /** Whether a voice session can be started right now (mic disabled when false). */\n @Input() canStartRealtime: boolean = true;\n /** Shows the in-composer Plan Mode toggle button when true. */\n @Input() enablePlanMode: boolean = false;\n /** Current Plan Mode toggle state (renders the button in its active state). */\n @Input() planModeActive: boolean = false;\n\n @Output() textSubmitted = new EventEmitter<string>();\n @Output() valueChange = new EventEmitter<string>();\n @Output() attachmentsChanged = new EventEmitter<PendingAttachment[]>();\n @Output() attachmentError = new EventEmitter<string>();\n @Output() attachmentClicked = new EventEmitter<PendingAttachment>();\n /** Emitted when the user clicks the mic button to start/stop a voice session. */\n @Output() voiceRequested = new EventEmitter<void>();\n /**\n * Emitted when the user clicks the small caret next to the phone button — the host opens the\n * voice agent/model picker so call options (which agent, which voice model) stay reachable\n * without adding friction to the plain phone click's instant-start path.\n */\n @Output() voiceOptionsRequested = new EventEmitter<void>();\n /** Emitted when the user clicks the in-composer Plan Mode toggle button. */\n @Output() planModeToggle = new EventEmitter<void>();\n\n onRealtimeClick(): void {\n this.voiceRequested.emit();\n }\n\n onRealtimeOptionsClick(): void {\n this.voiceOptionsRequested.emit();\n }\n\n get canSend(): boolean {\n const hasText = this.value.trim().length > 0;\n const hasAttachments = this.mentionEditor?.hasAttachments() || false;\n return !this.disabled && (hasText || hasAttachments);\n }\n\n /**\n * Handle value changes from MentionEditorComponent\n */\n onValueChange(newValue: string): void {\n this.value = newValue;\n this.valueChange.emit(this.value);\n }\n\n /**\n * Handle attachment changes from MentionEditorComponent\n */\n onAttachmentsChanged(attachments: PendingAttachment[]): void {\n this.attachmentsChanged.emit(attachments);\n }\n\n /**\n * Handle attachment errors from MentionEditorComponent\n */\n onAttachmentError(error: string): void {\n this.attachmentError.emit(error);\n }\n\n /**\n * Handle attachment click from MentionEditorComponent\n */\n onAttachmentClicked(attachment: PendingAttachment): void {\n this.attachmentClicked.emit(attachment);\n }\n\n /**\n * Handle Enter key from MentionEditorComponent\n * Extracts plain text with JSON-encoded mentions for message submission\n */\n onEnterPressed(_text: string): void {\n this.onSendClick();\n }\n\n /**\n * Handle mention selection from MentionEditorComponent\n */\n onMentionSelected(suggestion: MentionSuggestion): void {\n // MentionEditorComponent already inserts the mention chip\n // This is just for additional tracking/analytics if needed\n }\n\n /**\n * Send the message\n * Extracts plain text with JSON-encoded mentions for proper persistence\n */\n onSendClick(): void {\n if (this.canSend) {\n // Get plain text with JSON-encoded mentions (preserves configuration info)\n const textToSend = this.mentionEditor?.getPlainTextWithJsonMentions() || this.value.trim();\n this.textSubmitted.emit(textToSend);\n this.value = ''; // Clear input after sending\n\n // Clear the editor content\n if (this.mentionEditor) {\n this.mentionEditor.clear();\n }\n\n this.valueChange.emit(this.value);\n }\n }\n\n /**\n * Handle clicks on the container - focus the mention editor\n * Only moves cursor to end if clicking outside the contentEditable area\n */\n onContainerClick(event: MouseEvent): void {\n const target = event.target as HTMLElement;\n\n // Don't handle clicks on the send button\n if (target.closest('.send-button-icon')) {\n return;\n }\n\n const editor = this.mentionEditor?.editorRef?.nativeElement;\n if (!editor) return;\n\n // If clicking directly on the editor or its children, let the browser handle cursor placement\n if (target === editor || editor.contains(target)) {\n return;\n }\n\n // Only if clicking on the container (empty space), focus and move cursor to end\n editor.focus();\n const selection = window.getSelection();\n const range = document.createRange();\n\n if (selection) {\n range.selectNodeContents(editor);\n range.collapse(false); // Collapse to end\n selection.removeAllRanges();\n selection.addRange(range);\n }\n }\n\n /**\n * Public method to focus the input programmatically\n */\n focus(): void {\n const editor = this.mentionEditor?.editorRef?.nativeElement;\n if (editor) {\n editor.focus();\n }\n }\n\n /**\n * Get mention chip data including configuration presets\n */\n getMentionChipsData(): Array<{ id: string; type: string; name: string; presetId?: string; presetName?: string }> {\n return this.mentionEditor?.getMentionChipsData() || [];\n }\n\n /**\n * Get pending attachments from the editor\n */\n getPendingAttachments(): PendingAttachment[] {\n return this.mentionEditor?.getPendingAttachments() || [];\n }\n\n /**\n * Open file picker programmatically\n */\n openFilePicker(): void {\n this.mentionEditor?.openFilePicker();\n }\n\n /**\n * Add an artifact as a pending attachment (called by parent after artifact selection)\n */\n AddArtifactAttachment(artifact: {\n fileID: string; fileName: string; mimeType: string;\n sizeBytes: number; artifactVersionId?: string;\n }): PendingAttachment | undefined {\n return this.mentionEditor?.AddArtifactAttachment(artifact);\n }\n}\n","<div class=\"message-input-box-container\" (click)=\"onContainerClick($event)\">\n <div class=\"input-wrapper\">\n <mj-mention-editor\n #mentionEditor\n [placeholder]=\"placeholder\"\n [disabled]=\"disabled\"\n [currentUser]=\"currentUser\"\n [enableMentions]=\"enableMentions\"\n [TriggerProviders]=\"TriggerProviders\"\n [ExcludedTriggerKeys]=\"ExcludedTriggerKeys\"\n [Provider]=\"Provider\"\n [enableAttachments]=\"enableAttachments\"\n [maxAttachments]=\"maxAttachments\"\n [maxAttachmentSizeBytes]=\"maxAttachmentSizeBytes\"\n [acceptedFileTypes]=\"acceptedFileTypes\"\n [(ngModel)]=\"value\"\n (valueChange)=\"onValueChange($event)\"\n (enterPressed)=\"onEnterPressed($event)\"\n (mentionSelected)=\"onMentionSelected($event)\"\n (attachmentsChanged)=\"onAttachmentsChanged($event)\"\n (attachmentError)=\"onAttachmentError($event)\"\n (attachmentClicked)=\"onAttachmentClicked($event)\">\n </mj-mention-editor>\n\n <!-- Attach + voice + plan-mode buttons - Positioned over editor -->\n @if (enableAttachments || enableRealtime || enablePlanMode) {\n <div class=\"attach-buttons\">\n @if (enablePlanMode) {\n <button\n class=\"attach-button-icon plan-mode-button-icon\"\n [class.plan-mode-button-icon--active]=\"planModeActive\"\n [disabled]=\"disabled\"\n [attr.aria-pressed]=\"planModeActive\"\n (click)=\"planModeToggle.emit(); $event.stopPropagation()\"\n [title]=\"planModeActive ? 'Plan Mode ON — the agent presents a plan for your approval before it acts' : 'Plan Mode: have the agent present a plan for your approval before it acts'\"\n aria-label=\"Toggle Plan Mode\"\n >\n <i class=\"fa-solid fa-route\" aria-hidden=\"true\"></i>\n </button>\n }\n @if (enableAttachments) {\n <button\n class=\"attach-button-icon\"\n [disabled]=\"disabled\"\n (click)=\"openFilePicker(); $event.stopPropagation()\"\n title=\"Attach file\"\n >\n <i class=\"fa-solid fa-paperclip\"></i>\n </button>\n }\n @if (enableRealtime) {\n <button\n class=\"attach-button-icon voice-button-icon\"\n [class.voice-button-icon--active]=\"voiceActive\"\n [disabled]=\"!canStartRealtime\"\n (click)=\"onRealtimeClick(); $event.stopPropagation()\"\n [title]=\"voiceActive ? 'Voice call active' : 'Start a voice call'\"\n aria-label=\"Start a voice call\"\n >\n <i class=\"fa-solid fa-phone\" aria-hidden=\"true\"></i>\n </button>\n <!-- Caret beside the phone: opens the agent/model picker so call OPTIONS stay one\n click away while the plain phone click keeps its instant-start behavior. -->\n <button\n class=\"attach-button-icon voice-options-button-icon\"\n [disabled]=\"!canStartRealtime\"\n (click)=\"onRealtimeOptionsClick(); $event.stopPropagation()\"\n title=\"Voice call options (choose agent and voice model)\"\n aria-label=\"Voice call options\"\n >\n <i class=\"fa-solid fa-chevron-down\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Send Button (Icon Only) - Positioned over editor -->\n <button\n class=\"send-button-icon\"\n [disabled]=\"!canSend\"\n (click)=\"onSendClick()\"\n title=\"Send message (Enter)\"\n >\n <i class=\"fa-solid fa-paper-plane\"></i>\n </button>\n </div>\n</div>\n"]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { IMetadataProvider, UserInfo } from '@memberjunction/core';
|
|
2
|
+
/**
|
|
3
|
+
* A configuration preset attached to a mention suggestion (e.g. an agent's
|
|
4
|
+
* Fast / Standard / High Power presets). When a suggestion carries 2+ presets the
|
|
5
|
+
* mention editor renders a preset picker on the inserted chip; the selected preset's
|
|
6
|
+
* ID/Name travel with the chip (`data-preset-id` / `data-preset-name`) and survive
|
|
7
|
+
* serialization via `getPlainTextWithJsonMentions()`.
|
|
8
|
+
*
|
|
9
|
+
* This is a deliberately generic shape — the composer has no knowledge of what a
|
|
10
|
+
* preset IS. Trigger providers map their domain objects (e.g. `MJ: AI Agent
|
|
11
|
+
* Configurations` rows) into this shape when building suggestions.
|
|
12
|
+
*/
|
|
13
|
+
export interface MentionSuggestionPreset {
|
|
14
|
+
/** Stable identifier stored on the chip (`data-preset-id`). */
|
|
15
|
+
ID: string;
|
|
16
|
+
/** Machine name stored on the chip (`data-preset-name`). */
|
|
17
|
+
Name: string;
|
|
18
|
+
/** Friendly label shown in the preset picker (falls back to Name). */
|
|
19
|
+
DisplayName?: string;
|
|
20
|
+
/** Optional descriptive text shown under the preset label. */
|
|
21
|
+
Description?: string;
|
|
22
|
+
/** The preset preselected on chip insert. First preset wins when none is flagged. */
|
|
23
|
+
IsDefault?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Item in the mention-autocomplete dropdown, and the payload carried by an inserted
|
|
27
|
+
* mention chip. Fully generic — `type` is an open string (providers choose their own
|
|
28
|
+
* vocabulary, e.g. 'agent' | 'user' | 'entity' | 'query' | 'skill'); the editor and
|
|
29
|
+
* dropdown only use it for cosmetic styling (chip palette, badge label) and for the
|
|
30
|
+
* plain-text serialization prefix.
|
|
31
|
+
*/
|
|
32
|
+
export interface MentionSuggestion {
|
|
33
|
+
/** Open string discriminator chosen by the provider (drives chip/badge styling). */
|
|
34
|
+
type: string;
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
/** Image rendered in the dropdown row + chip when present (e.g. an agent LogoURL). */
|
|
40
|
+
imageUrl?: string;
|
|
41
|
+
/** Font Awesome (or custom) icon class for the dropdown row + chip. */
|
|
42
|
+
icon?: string;
|
|
43
|
+
/** Accent color for the chip/badge (e.g. a skill's own Color UX metadata). */
|
|
44
|
+
color?: string;
|
|
45
|
+
/** Optional presets — 2+ presets render a preset picker on the inserted chip. */
|
|
46
|
+
presets?: MentionSuggestionPreset[];
|
|
47
|
+
/** Provider-defined extra payload; the composer never inspects it. */
|
|
48
|
+
data?: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Request passed to {@link ComposerTriggerProvider.GetSuggestions} each time the user
|
|
52
|
+
* types after an active trigger character.
|
|
53
|
+
*/
|
|
54
|
+
export interface ComposerSuggestionRequest {
|
|
55
|
+
/** The text typed after the trigger character (may be empty — show the full list). */
|
|
56
|
+
Query: string;
|
|
57
|
+
/** Maximum number of suggestions the editor will display; providers should honor it. */
|
|
58
|
+
MaxResults: number;
|
|
59
|
+
/** The current user, when the host supplied one. Providers should fail closed (return []) if they require a user and none is present. */
|
|
60
|
+
ContextUser: UserInfo | null;
|
|
61
|
+
/** The metadata provider scoping this editor instance, when the host supplied one (multi-provider support). */
|
|
62
|
+
Provider: IMetadataProvider | null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Pluggable mention/command trigger for the composer's mention editor.
|
|
66
|
+
*
|
|
67
|
+
* Each provider owns ONE trigger character (e.g. '@', '#', '/') and supplies the
|
|
68
|
+
* suggestions shown while the user types after it. The composer itself ships with
|
|
69
|
+
* ZERO providers — it is a plain text editor until providers are supplied, either:
|
|
70
|
+
*
|
|
71
|
+
* 1. **Explicitly** — bind `[TriggerProviders]` on `mj-mention-editor` /
|
|
72
|
+
* `mj-message-input-box` with provider instances (explicit list always wins), or
|
|
73
|
+
* 2. **By discovery** — register subclasses with the MJ ClassFactory via
|
|
74
|
+
* `@RegisterClass(ComposerTriggerProvider, '<Key>')`; the editor resolves them
|
|
75
|
+
* through {@link DiscoverComposerTriggerProviders} when no explicit list is bound.
|
|
76
|
+
* Hosts opt individual triggers out with `[ExcludedTriggerKeys]`.
|
|
77
|
+
*
|
|
78
|
+
* The AI-aware providers (agent mentions, entity/query record mentions, skill
|
|
79
|
+
* commands) live in `@memberjunction/ng-conversations` — see its `composer-plugins`
|
|
80
|
+
* folder and the `mj-ai-composer` wrapper component.
|
|
81
|
+
*/
|
|
82
|
+
export declare abstract class ComposerTriggerProvider {
|
|
83
|
+
/** The single character that opens this provider's autocomplete (e.g. '@'). */
|
|
84
|
+
abstract readonly TriggerChar: string;
|
|
85
|
+
/**
|
|
86
|
+
* Stable identifier for this provider (e.g. 'agent-mentions'). Used as the
|
|
87
|
+
* ClassFactory registration key and by `ExcludedTriggerKeys` filtering.
|
|
88
|
+
*/
|
|
89
|
+
abstract readonly Key: string;
|
|
90
|
+
/**
|
|
91
|
+
* Ordering weight — higher runs first. When multiple providers share a
|
|
92
|
+
* TriggerChar, results are concatenated in priority order (then Key order).
|
|
93
|
+
*/
|
|
94
|
+
readonly Priority: number;
|
|
95
|
+
/**
|
|
96
|
+
* Returns the suggestions for the current query. Called on every keystroke after
|
|
97
|
+
* an active trigger; implementations should be fast (serve from warm caches) and
|
|
98
|
+
* must never throw — return [] on failure.
|
|
99
|
+
*/
|
|
100
|
+
abstract GetSuggestions(request: ComposerSuggestionRequest): Promise<MentionSuggestion[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Optional warm-up hook, invoked once by the editor when it initializes with this
|
|
103
|
+
* provider active. Default is a no-op; override to pre-load caches so the first
|
|
104
|
+
* keystroke is fast. Must be safe to call repeatedly and concurrently.
|
|
105
|
+
*/
|
|
106
|
+
Initialize(contextUser: UserInfo | null): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolves every `@RegisterClass(ComposerTriggerProvider, ...)` registration in the
|
|
110
|
+
* MJ ClassFactory into a provider instance list.
|
|
111
|
+
*
|
|
112
|
+
* - Instantiates each winning registration once (module-level cache keyed by the
|
|
113
|
+
* registered subclass) — repeated calls are cheap.
|
|
114
|
+
* - When multiple registrations share a Key, the ClassFactory's highest-priority
|
|
115
|
+
* registration wins (standard override-by-import-order semantics).
|
|
116
|
+
* - `excludedKeys` filters providers out by their stable Key (case-insensitive).
|
|
117
|
+
* - Returns [] gracefully when nothing is registered — the editor then behaves as a
|
|
118
|
+
* plain text editor.
|
|
119
|
+
*
|
|
120
|
+
* Results are sorted by provider `Priority` (desc), then `Key` (asc) for stability.
|
|
121
|
+
*/
|
|
122
|
+
export declare function DiscoverComposerTriggerProviders(excludedKeys?: string[]): ComposerTriggerProvider[];
|
|
123
|
+
//# sourceMappingURL=composer-trigger-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-trigger-provider.d.ts","sourceRoot":"","sources":["../../src/lib/composer-trigger-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGnE;;;;;;;;;;GAUG;AACH,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACpC,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,UAAU,EAAE,MAAM,CAAC;IACnB,yIAAyI;IACzI,WAAW,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7B,+GAA+G;IAC/G,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,8BAAsB,uBAAuB;IAC3C,+EAA+E;IAC/E,kBAAyB,WAAW,EAAE,MAAM,CAAC;IAE7C;;;OAGG;IACH,kBAAyB,GAAG,EAAE,MAAM,CAAC;IAErC;;;OAGG;IACH,SAAgB,QAAQ,EAAE,MAAM,CAAK;IAErC;;;;OAIG;aACa,cAAc,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAEhG;;;;OAIG;IACU,UAAU,CAAC,WAAW,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAIrE;AASD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gCAAgC,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,uBAAuB,EAAE,CAsCnG"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable mention/command trigger for the composer's mention editor.
|
|
4
|
+
*
|
|
5
|
+
* Each provider owns ONE trigger character (e.g. '@', '#', '/') and supplies the
|
|
6
|
+
* suggestions shown while the user types after it. The composer itself ships with
|
|
7
|
+
* ZERO providers — it is a plain text editor until providers are supplied, either:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Explicitly** — bind `[TriggerProviders]` on `mj-mention-editor` /
|
|
10
|
+
* `mj-message-input-box` with provider instances (explicit list always wins), or
|
|
11
|
+
* 2. **By discovery** — register subclasses with the MJ ClassFactory via
|
|
12
|
+
* `@RegisterClass(ComposerTriggerProvider, '<Key>')`; the editor resolves them
|
|
13
|
+
* through {@link DiscoverComposerTriggerProviders} when no explicit list is bound.
|
|
14
|
+
* Hosts opt individual triggers out with `[ExcludedTriggerKeys]`.
|
|
15
|
+
*
|
|
16
|
+
* The AI-aware providers (agent mentions, entity/query record mentions, skill
|
|
17
|
+
* commands) live in `@memberjunction/ng-conversations` — see its `composer-plugins`
|
|
18
|
+
* folder and the `mj-ai-composer` wrapper component.
|
|
19
|
+
*/
|
|
20
|
+
export class ComposerTriggerProvider {
|
|
21
|
+
/**
|
|
22
|
+
* Ordering weight — higher runs first. When multiple providers share a
|
|
23
|
+
* TriggerChar, results are concatenated in priority order (then Key order).
|
|
24
|
+
*/
|
|
25
|
+
Priority = 0;
|
|
26
|
+
/**
|
|
27
|
+
* Optional warm-up hook, invoked once by the editor when it initializes with this
|
|
28
|
+
* provider active. Default is a no-op; override to pre-load caches so the first
|
|
29
|
+
* keystroke is fast. Must be safe to call repeatedly and concurrently.
|
|
30
|
+
*/
|
|
31
|
+
async Initialize(contextUser) {
|
|
32
|
+
// default no-op — providers override to warm caches
|
|
33
|
+
void contextUser;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* One instance per registered provider subclass, shared across all editors in the
|
|
38
|
+
* process — providers are expected to be stateless facades over shared engines, so a
|
|
39
|
+
* single instance avoids duplicate cache warm-ups.
|
|
40
|
+
*/
|
|
41
|
+
const discoveredProviderInstances = new Map();
|
|
42
|
+
/**
|
|
43
|
+
* Resolves every `@RegisterClass(ComposerTriggerProvider, ...)` registration in the
|
|
44
|
+
* MJ ClassFactory into a provider instance list.
|
|
45
|
+
*
|
|
46
|
+
* - Instantiates each winning registration once (module-level cache keyed by the
|
|
47
|
+
* registered subclass) — repeated calls are cheap.
|
|
48
|
+
* - When multiple registrations share a Key, the ClassFactory's highest-priority
|
|
49
|
+
* registration wins (standard override-by-import-order semantics).
|
|
50
|
+
* - `excludedKeys` filters providers out by their stable Key (case-insensitive).
|
|
51
|
+
* - Returns [] gracefully when nothing is registered — the editor then behaves as a
|
|
52
|
+
* plain text editor.
|
|
53
|
+
*
|
|
54
|
+
* Results are sorted by provider `Priority` (desc), then `Key` (asc) for stability.
|
|
55
|
+
*/
|
|
56
|
+
export function DiscoverComposerTriggerProviders(excludedKeys) {
|
|
57
|
+
const factory = MJGlobal.Instance.ClassFactory;
|
|
58
|
+
const registrations = factory.GetAllRegistrations(ComposerTriggerProvider);
|
|
59
|
+
if (!registrations || registrations.length === 0) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const excluded = new Set((excludedKeys ?? []).map((k) => k.trim().toLowerCase()));
|
|
63
|
+
const seenKeys = new Set();
|
|
64
|
+
const providers = [];
|
|
65
|
+
for (const registration of registrations) {
|
|
66
|
+
const normalizedKey = registration.Key?.trim().toLowerCase();
|
|
67
|
+
if (!normalizedKey || seenKeys.has(normalizedKey) || excluded.has(normalizedKey)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
seenKeys.add(normalizedKey);
|
|
71
|
+
// Resolve the WINNING registration for this key (there may be multiple when a
|
|
72
|
+
// consumer overrides a provider) — mirrors GetRegistration's priority semantics.
|
|
73
|
+
const winner = factory.GetRegistration(ComposerTriggerProvider, registration.Key);
|
|
74
|
+
if (!winner?.SubClass) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let instance = discoveredProviderInstances.get(winner.SubClass);
|
|
78
|
+
if (!instance) {
|
|
79
|
+
const ProviderConstructor = winner.SubClass;
|
|
80
|
+
instance = new ProviderConstructor();
|
|
81
|
+
discoveredProviderInstances.set(winner.SubClass, instance);
|
|
82
|
+
}
|
|
83
|
+
providers.push(instance);
|
|
84
|
+
}
|
|
85
|
+
return providers.sort((a, b) => {
|
|
86
|
+
if (b.Priority !== a.Priority)
|
|
87
|
+
return b.Priority - a.Priority;
|
|
88
|
+
return a.Key.localeCompare(b.Key);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=composer-trigger-provider.js.map
|